Building version
This commit is contained in:
@ -28,7 +28,6 @@ import * as fs from "fs";
|
|||||||
})(),
|
})(),
|
||||||
themeType,
|
themeType,
|
||||||
"themeDirPath": reservedDirPath,
|
"themeDirPath": reservedDirPath,
|
||||||
"usedResources": undefined,
|
|
||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -44,7 +43,7 @@ import * as fs from "fs";
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFileSync(pathJoin(buildOptions.publicDirPath, "keycloak-resources", ".gitignore"), Buffer.from("*", "utf8"));
|
fs.writeFileSync(pathJoin(buildOptions.publicDirPath, keycloak_resources, ".gitignore"), Buffer.from("*", "utf8"));
|
||||||
|
|
||||||
console.log(`${pathRelative(reactAppRootDirPath, reservedDirPath)} directory created.`);
|
console.log(`${pathRelative(reactAppRootDirPath, reservedDirPath)} directory created.`);
|
||||||
})();
|
})();
|
||||||
|
@ -7,6 +7,9 @@ import { readBuildOptions, type BuildOptions } from "./keycloakify/buildOptions"
|
|||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { rmSync } from "./tools/fs.rmSync";
|
||||||
|
import { lastKeycloakVersionWithAccountV1 } from "./constants";
|
||||||
|
import { transformCodebase } from "./tools/transformCodebase";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
@ -26,51 +29,6 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st
|
|||||||
"preCacheTransform": {
|
"preCacheTransform": {
|
||||||
"actionCacheId": "npm install and build",
|
"actionCacheId": "npm install and build",
|
||||||
"action": async ({ destDirPath }) => {
|
"action": async ({ destDirPath }) => {
|
||||||
fix_account_css: {
|
|
||||||
const accountCssFilePath = pathJoin(destDirPath, "keycloak", "account", "resources", "css", "account.css");
|
|
||||||
|
|
||||||
if (!fs.existsSync(accountCssFilePath)) {
|
|
||||||
break fix_account_css;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
accountCssFilePath,
|
|
||||||
Buffer.from(fs.readFileSync(accountCssFilePath).toString("utf8").replace("top: -34px;", "top: -34px !important;"), "utf8")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
fix_account_topt: {
|
|
||||||
const totpFtlFilePath = pathJoin(destDirPath, "base", "account", "totp.ftl");
|
|
||||||
|
|
||||||
if (!fs.existsSync(totpFtlFilePath)) {
|
|
||||||
break fix_account_topt;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
totpFtlFilePath,
|
|
||||||
Buffer.from(
|
|
||||||
fs
|
|
||||||
.readFileSync(totpFtlFilePath)
|
|
||||||
.toString("utf8")
|
|
||||||
.replace(
|
|
||||||
[
|
|
||||||
" <#list totp.policy.supportedApplications as app>",
|
|
||||||
" <li>${app}</li>",
|
|
||||||
" </#list>"
|
|
||||||
].join("\n"),
|
|
||||||
[
|
|
||||||
" <#if totp.policy.supportedApplications?has_content>",
|
|
||||||
" <#list totp.policy.supportedApplications as app>",
|
|
||||||
" <li>${app}</li>",
|
|
||||||
" </#list>",
|
|
||||||
" </#if>"
|
|
||||||
].join("\n")
|
|
||||||
),
|
|
||||||
"utf8"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
install_common_node_modules: {
|
install_common_node_modules: {
|
||||||
const commonResourcesDirPath = pathJoin(destDirPath, "keycloak", "common", "resources");
|
const commonResourcesDirPath = pathJoin(destDirPath, "keycloak", "common", "resources");
|
||||||
|
|
||||||
@ -128,7 +86,98 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st
|
|||||||
|
|
||||||
fs.writeFileSync(packageJsonFilePath, packageJsonRaw);
|
fs.writeFileSync(packageJsonFilePath, packageJsonRaw);
|
||||||
|
|
||||||
fs.rmSync(pathJoin(accountV2DirSrcDirPath, "node_modules"), { "recursive": true });
|
rmSync(pathJoin(accountV2DirSrcDirPath, "node_modules"), { "recursive": true });
|
||||||
|
}
|
||||||
|
|
||||||
|
last_account_v1_transformations: {
|
||||||
|
if (lastKeycloakVersionWithAccountV1 !== keycloakVersion) {
|
||||||
|
break last_account_v1_transformations;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const accountCssFilePath = pathJoin(destDirPath, "keycloak", "account", "resources", "css", "account.css");
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
accountCssFilePath,
|
||||||
|
Buffer.from(fs.readFileSync(accountCssFilePath).toString("utf8").replace("top: -34px;", "top: -34px !important;"), "utf8")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const totpFtlFilePath = pathJoin(destDirPath, "base", "account", "totp.ftl");
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
totpFtlFilePath,
|
||||||
|
Buffer.from(
|
||||||
|
fs
|
||||||
|
.readFileSync(totpFtlFilePath)
|
||||||
|
.toString("utf8")
|
||||||
|
.replace(
|
||||||
|
[
|
||||||
|
" <#list totp.policy.supportedApplications as app>",
|
||||||
|
" <li>${app}</li>",
|
||||||
|
" </#list>"
|
||||||
|
].join("\n"),
|
||||||
|
[
|
||||||
|
" <#if totp.policy.supportedApplications?has_content>",
|
||||||
|
" <#list totp.policy.supportedApplications as app>",
|
||||||
|
" <li>${app}</li>",
|
||||||
|
" </#list>",
|
||||||
|
" </#if>"
|
||||||
|
].join("\n")
|
||||||
|
),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note, this is an optimization for reducing the size of the jar
|
||||||
|
{
|
||||||
|
const defaultThemeCommonResourcesDirPath = pathJoin(destDirPath, "keycloak", "common", "resources");
|
||||||
|
|
||||||
|
const usedCommonResourceRelativeFilePaths = [
|
||||||
|
...["patternfly.min.css", "patternfly-additions.min.css", "patternfly-additions.min.css"].map(fileBasename =>
|
||||||
|
pathJoin("node_modules", "patternfly", "dist", "css", fileBasename)
|
||||||
|
),
|
||||||
|
...[
|
||||||
|
"OpenSans-Light-webfont.woff2",
|
||||||
|
"OpenSans-Regular-webfont.woff2",
|
||||||
|
"OpenSans-Bold-webfont.woff2",
|
||||||
|
"OpenSans-Semibold-webfont.woff2",
|
||||||
|
"OpenSans-Bold-webfont.woff",
|
||||||
|
"OpenSans-Light-webfont.woff",
|
||||||
|
"OpenSans-Regular-webfont.woff",
|
||||||
|
"OpenSans-Semibold-webfont.woff",
|
||||||
|
"OpenSans-Regular-webfont.ttf",
|
||||||
|
"OpenSans-Light-webfont.ttf",
|
||||||
|
"OpenSans-Semibold-webfont.ttf",
|
||||||
|
"OpenSans-Bold-webfont.ttf"
|
||||||
|
].map(fileBasename => pathJoin("node_modules", "patternfly", "dist", "fonts", fileBasename))
|
||||||
|
];
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
"srcDirPath": defaultThemeCommonResourcesDirPath,
|
||||||
|
"destDirPath": defaultThemeCommonResourcesDirPath,
|
||||||
|
"transformSourceCode": ({ sourceCode, fileRelativePath }) => {
|
||||||
|
if (!usedCommonResourceRelativeFilePaths.includes(fileRelativePath)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { "modifiedSourceCode": sourceCode };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Other optimization: Remove AngularJS
|
||||||
|
{
|
||||||
|
const nodeModuleDirPath = pathJoin(destDirPath, "keycloak", "common", "resources", "node_modules");
|
||||||
|
|
||||||
|
fs.readdirSync(nodeModuleDirPath)
|
||||||
|
.filter(basename => basename.startsWith("angular"))
|
||||||
|
.map(basename => pathJoin(nodeModuleDirPath, basename))
|
||||||
|
.filter(dirPath => fs.statSync(dirPath).isDirectory())
|
||||||
|
.forEach(dirPath => rmSync(dirPath, { "recursive": true }));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,6 +8,7 @@ import { readBuildOptions } from "./keycloakify/buildOptions";
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { getLogger } from "./tools/logger";
|
import { getLogger } from "./tools/logger";
|
||||||
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
|
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
|
||||||
|
import { rmSync } from "./tools/fs.rmSync";
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
const reactAppRootDirPath = process.cwd();
|
const reactAppRootDirPath = process.cwd();
|
||||||
@ -54,7 +55,7 @@ export async function main() {
|
|||||||
|
|
||||||
logger.log(`${pathRelative(process.cwd(), emailThemeSrcDirPath)} ready to be customized, feel free to remove every file you do not customize`);
|
logger.log(`${pathRelative(process.cwd(), emailThemeSrcDirPath)} ready to be customized, feel free to remove every file you do not customize`);
|
||||||
|
|
||||||
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
|
rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
import * as fs from "fs";
|
|
||||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import { Reflect } from "tsafe/Reflect";
|
|
||||||
import type { BuildOptions } from "../buildOptions";
|
|
||||||
import { type ThemeType, retrocompatPostfix, accountV1ThemeName } from "../../constants";
|
|
||||||
import { bringInAccountV1 } from "./bringInAccountV1";
|
|
||||||
|
|
||||||
type BuildOptionsLike = {
|
|
||||||
groupId: string;
|
|
||||||
artifactId: string;
|
|
||||||
themeVersion: string;
|
|
||||||
cacheDirPath: string;
|
|
||||||
keycloakifyBuildDirPath: string;
|
|
||||||
themeNames: string[];
|
|
||||||
doBuildRetrocompatAccountTheme: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
const buildOptions = Reflect<BuildOptions>();
|
|
||||||
|
|
||||||
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function generateJavaStackFiles(params: {
|
|
||||||
implementedThemeTypes: Record<ThemeType | "email", boolean>;
|
|
||||||
buildOptions: BuildOptionsLike;
|
|
||||||
}): Promise<{
|
|
||||||
jarFilePath: string;
|
|
||||||
}> {
|
|
||||||
const { implementedThemeTypes, buildOptions } = params;
|
|
||||||
|
|
||||||
{
|
|
||||||
const { pomFileCode } = (function generatePomFileCode(): {
|
|
||||||
pomFileCode: string;
|
|
||||||
} {
|
|
||||||
const pomFileCode = [
|
|
||||||
`<?xml version="1.0"?>`,
|
|
||||||
`<project xmlns="http://maven.apache.org/POM/4.0.0"`,
|
|
||||||
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
|
|
||||||
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">`,
|
|
||||||
` <modelVersion>4.0.0</modelVersion>`,
|
|
||||||
` <groupId>${buildOptions.groupId}</groupId>`,
|
|
||||||
` <artifactId>${buildOptions.artifactId}</artifactId>`,
|
|
||||||
` <version>${buildOptions.themeVersion}</version>`,
|
|
||||||
` <name>${buildOptions.artifactId}</name>`,
|
|
||||||
` <description />`,
|
|
||||||
` <packaging>jar</packaging>`,
|
|
||||||
` <properties>`,
|
|
||||||
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
|
|
||||||
` </properties>`,
|
|
||||||
` <build>`,
|
|
||||||
` <plugins>`,
|
|
||||||
` <plugin>`,
|
|
||||||
` <groupId>org.apache.maven.plugins</groupId>`,
|
|
||||||
` <artifactId>maven-shade-plugin</artifactId>`,
|
|
||||||
` <version>3.5.1</version>`,
|
|
||||||
` <executions>`,
|
|
||||||
` <execution>`,
|
|
||||||
` <phase>package</phase>`,
|
|
||||||
` <goals>`,
|
|
||||||
` <goal>shade</goal>`,
|
|
||||||
` </goals>`,
|
|
||||||
` </execution>`,
|
|
||||||
` </executions>`,
|
|
||||||
` </plugin>`,
|
|
||||||
` </plugins>`,
|
|
||||||
` </build>`,
|
|
||||||
` <dependencies>`,
|
|
||||||
` <dependency>`,
|
|
||||||
` <groupId>io.phasetwo.keycloak</groupId>`,
|
|
||||||
` <artifactId>keycloak-account-v1</artifactId>`,
|
|
||||||
` <version>0.1</version>`,
|
|
||||||
` </dependency>`,
|
|
||||||
` </dependencies>`,
|
|
||||||
`</project>`
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
return { pomFileCode };
|
|
||||||
})();
|
|
||||||
|
|
||||||
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (implementedThemeTypes.account) {
|
|
||||||
await bringInAccountV1({ buildOptions });
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const themeManifestFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "META-INF", "keycloak-themes.json");
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.mkdirSync(pathDirname(themeManifestFilePath));
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
themeManifestFilePath,
|
|
||||||
Buffer.from(
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
"themes": [
|
|
||||||
...(!implementedThemeTypes.account
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
"name": accountV1ThemeName,
|
|
||||||
"types": ["account"]
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
...buildOptions.themeNames
|
|
||||||
.map(themeName => [
|
|
||||||
{
|
|
||||||
"name": themeName,
|
|
||||||
"types": Object.entries(implementedThemeTypes)
|
|
||||||
.filter(([, isImplemented]) => isImplemented)
|
|
||||||
.map(([themeType]) => themeType)
|
|
||||||
},
|
|
||||||
...(!implementedThemeTypes.account || !buildOptions.doBuildRetrocompatAccountTheme
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
"name": `${themeName}${retrocompatPostfix}`,
|
|
||||||
"types": ["account"]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
])
|
|
||||||
.flat()
|
|
||||||
]
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
),
|
|
||||||
"utf8"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"jarFilePath": pathJoin(buildOptions.keycloakifyBuildDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from "./generateJavaStackFiles";
|
|
70
src/bin/keycloakify/generatePom.ts
Normal file
70
src/bin/keycloakify/generatePom.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import { Reflect } from "tsafe/Reflect";
|
||||||
|
import type { BuildOptions } from "./buildOptions";
|
||||||
|
|
||||||
|
type BuildOptionsLike = {
|
||||||
|
groupId: string;
|
||||||
|
artifactId: string;
|
||||||
|
themeVersion: string;
|
||||||
|
keycloakifyBuildDirPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
const buildOptions = Reflect<BuildOptions>();
|
||||||
|
|
||||||
|
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generatePom(params: { buildOptions: BuildOptionsLike }) {
|
||||||
|
const { buildOptions } = params;
|
||||||
|
|
||||||
|
const { pomFileCode } = (function generatePomFileCode(): {
|
||||||
|
pomFileCode: string;
|
||||||
|
} {
|
||||||
|
const pomFileCode = [
|
||||||
|
`<?xml version="1.0"?>`,
|
||||||
|
`<project xmlns="http://maven.apache.org/POM/4.0.0"`,
|
||||||
|
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
|
||||||
|
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">`,
|
||||||
|
` <modelVersion>4.0.0</modelVersion>`,
|
||||||
|
` <groupId>${buildOptions.groupId}</groupId>`,
|
||||||
|
` <artifactId>${buildOptions.artifactId}</artifactId>`,
|
||||||
|
` <version>${buildOptions.themeVersion}</version>`,
|
||||||
|
` <name>${buildOptions.artifactId}</name>`,
|
||||||
|
` <description />`,
|
||||||
|
` <packaging>jar</packaging>`,
|
||||||
|
` <properties>`,
|
||||||
|
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
|
||||||
|
` </properties>`,
|
||||||
|
` <build>`,
|
||||||
|
` <plugins>`,
|
||||||
|
` <plugin>`,
|
||||||
|
` <groupId>org.apache.maven.plugins</groupId>`,
|
||||||
|
` <artifactId>maven-shade-plugin</artifactId>`,
|
||||||
|
` <version>3.5.1</version>`,
|
||||||
|
` <executions>`,
|
||||||
|
` <execution>`,
|
||||||
|
` <phase>package</phase>`,
|
||||||
|
` <goals>`,
|
||||||
|
` <goal>shade</goal>`,
|
||||||
|
` </goals>`,
|
||||||
|
` </execution>`,
|
||||||
|
` </executions>`,
|
||||||
|
` </plugin>`,
|
||||||
|
` </plugins>`,
|
||||||
|
` </build>`,
|
||||||
|
` <dependencies>`,
|
||||||
|
` <dependency>`,
|
||||||
|
` <groupId>io.phasetwo.keycloak</groupId>`,
|
||||||
|
` <artifactId>keycloak-account-v1</artifactId>`,
|
||||||
|
` <version>0.1</version>`,
|
||||||
|
` </dependency>`,
|
||||||
|
` </dependencies>`,
|
||||||
|
`</project>`
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
return { pomFileCode };
|
||||||
|
})();
|
||||||
|
|
||||||
|
return { pomFileCode };
|
||||||
|
}
|
@ -1,11 +1,12 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { Reflect } from "tsafe/Reflect";
|
import { Reflect } from "tsafe/Reflect";
|
||||||
import type { BuildOptions } from "../buildOptions";
|
import type { BuildOptions } from "../buildOptions";
|
||||||
import { resources_common, lastKeycloakVersionWithAccountV1, accountV1ThemeName } from "../../constants";
|
import { resources_common, lastKeycloakVersionWithAccountV1, accountV1ThemeName } from "../../constants";
|
||||||
import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme";
|
import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme";
|
||||||
import { transformCodebase } from "../../tools/transformCodebase";
|
import { transformCodebase } from "../../tools/transformCodebase";
|
||||||
|
import { rmSync } from "../../tools/fs.rmSync";
|
||||||
|
|
||||||
type BuildOptionsLike = {
|
type BuildOptionsLike = {
|
||||||
keycloakifyBuildDirPath: string;
|
keycloakifyBuildDirPath: string;
|
||||||
@ -36,45 +37,17 @@ export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike
|
|||||||
"destDirPath": accountV1DirPath
|
"destDirPath": accountV1DirPath
|
||||||
});
|
});
|
||||||
|
|
||||||
const commonResourceFilePaths = [
|
transformCodebase({
|
||||||
"node_modules/patternfly/dist/css/patternfly.min.css",
|
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "account", "resources"),
|
||||||
"node_modules/patternfly/dist/css/patternfly-additions.min.css",
|
"destDirPath": pathJoin(accountV1DirPath, "resources")
|
||||||
"node_modules/patternfly/dist/css/patternfly-additions.min.css",
|
});
|
||||||
...[
|
|
||||||
"OpenSans-Light-webfont.woff2",
|
|
||||||
"OpenSans-Regular-webfont.woff2",
|
|
||||||
"OpenSans-Bold-webfont.woff2",
|
|
||||||
"OpenSans-Semibold-webfont.woff2",
|
|
||||||
"OpenSans-Bold-webfont.woff",
|
|
||||||
"OpenSans-Light-webfont.woff",
|
|
||||||
"OpenSans-Regular-webfont.woff",
|
|
||||||
"OpenSans-Semibold-webfont.woff",
|
|
||||||
"OpenSans-Regular-webfont.ttf",
|
|
||||||
"OpenSans-Light-webfont.ttf",
|
|
||||||
"OpenSans-Semibold-webfont.ttf",
|
|
||||||
"OpenSans-Bold-webfont.ttf"
|
|
||||||
].map(path => `node_modules/patternfly/dist/fonts/${path}`)
|
|
||||||
];
|
|
||||||
|
|
||||||
for (const relativeFilePath of commonResourceFilePaths.map(path => pathJoin(...path.split("/")))) {
|
transformCodebase({
|
||||||
const destFilePath = pathJoin(accountV1DirPath, "resources", resources_common, relativeFilePath);
|
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "common", "resources"),
|
||||||
|
"destDirPath": pathJoin(accountV1DirPath, "resources", resources_common)
|
||||||
|
});
|
||||||
|
|
||||||
fs.mkdirSync(pathDirname(destFilePath), { "recursive": true });
|
rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true });
|
||||||
|
|
||||||
fs.cpSync(pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "common", "resources", relativeFilePath), destFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceFilePaths = ["css/account.css", "img/icon-sidebar-active.png", "img/logo.png"];
|
|
||||||
|
|
||||||
for (const relativeFilePath of resourceFilePaths.map(path => pathJoin(...path.split("/")))) {
|
|
||||||
const destFilePath = pathJoin(accountV1DirPath, "resources", relativeFilePath);
|
|
||||||
|
|
||||||
fs.mkdirSync(pathDirname(destFilePath), { "recursive": true });
|
|
||||||
|
|
||||||
fs.cpSync(pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "account", "resources", relativeFilePath), destFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true });
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
pathJoin(accountV1DirPath, "theme.properties"),
|
pathJoin(accountV1DirPath, "theme.properties"),
|
||||||
@ -84,7 +57,15 @@ export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike
|
|||||||
"",
|
"",
|
||||||
"locales=ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
|
"locales=ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
|
||||||
"",
|
"",
|
||||||
"styles=" + [...resourceFilePaths, ...commonResourceFilePaths.map(path => `resources-common/${path}`)].join(" "),
|
"styles=" +
|
||||||
|
[
|
||||||
|
"css/account.css",
|
||||||
|
"img/icon-sidebar-active.png",
|
||||||
|
"img/logo.png",
|
||||||
|
...["patternfly.min.css", "patternfly-additions.min.css", "patternfly-additions.min.css"].map(
|
||||||
|
fileBasename => `${resources_common}/node_modules/patternfly/dist/css/${fileBasename}`
|
||||||
|
)
|
||||||
|
].join(" "),
|
||||||
"",
|
"",
|
||||||
"##### css classes for form buttons",
|
"##### css classes for form buttons",
|
||||||
"# main class used for all buttons",
|
"# main class used for all buttons",
|
@ -1,11 +1,11 @@
|
|||||||
import { transformCodebase } from "../../tools/transformCodebase";
|
import { transformCodebase } from "../../tools/transformCodebase";
|
||||||
import * as fs from "fs";
|
import { join as pathJoin } from "path";
|
||||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
|
||||||
import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme";
|
import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme";
|
||||||
import { resources_common, type ThemeType } from "../../constants";
|
import { resources_common, type ThemeType } from "../../constants";
|
||||||
import { BuildOptions } from "../buildOptions";
|
import { BuildOptions } from "../buildOptions";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
|
import { rmSync } from "../../tools/fs.rmSync";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
@ -13,45 +13,14 @@ export type BuildOptionsLike = {
|
|||||||
|
|
||||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
|
|
||||||
export async function downloadKeycloakStaticResources(
|
export async function downloadKeycloakStaticResources(params: {
|
||||||
// prettier-ignore
|
themeType: ThemeType;
|
||||||
params: {
|
themeDirPath: string;
|
||||||
themeType: ThemeType;
|
keycloakVersion: string;
|
||||||
themeDirPath: string;
|
buildOptions: BuildOptionsLike;
|
||||||
keycloakVersion: string;
|
}) {
|
||||||
usedResources: {
|
|
||||||
resourcesCommonFilePaths: string[];
|
|
||||||
} | undefined;
|
|
||||||
buildOptions: BuildOptionsLike;
|
|
||||||
}
|
|
||||||
) {
|
|
||||||
const { themeType, themeDirPath, keycloakVersion, buildOptions } = params;
|
const { themeType, themeDirPath, keycloakVersion, buildOptions } = params;
|
||||||
|
|
||||||
// NOTE: Hack for 427
|
|
||||||
const usedResources = (() => {
|
|
||||||
const { usedResources } = params;
|
|
||||||
|
|
||||||
if (usedResources === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(usedResources !== undefined);
|
|
||||||
|
|
||||||
return {
|
|
||||||
"resourcesCommonDirPaths": usedResources.resourcesCommonFilePaths.map(filePath => {
|
|
||||||
{
|
|
||||||
const splitArg = "/dist/";
|
|
||||||
|
|
||||||
if (filePath.includes(splitArg)) {
|
|
||||||
return filePath.split(splitArg)[0] + splitArg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathDirname(filePath);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
const tmpDirPath = pathJoin(
|
const tmpDirPath = pathJoin(
|
||||||
themeDirPath,
|
themeDirPath,
|
||||||
`tmp_suLeKsxId_${crypto.createHash("sha256").update(`${themeType}-${keycloakVersion}`).digest("hex").slice(0, 8)}`
|
`tmp_suLeKsxId_${crypto.createHash("sha256").update(`${themeType}-${keycloakVersion}`).digest("hex").slice(0, 8)}`
|
||||||
@ -72,18 +41,8 @@ export async function downloadKeycloakStaticResources(
|
|||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"),
|
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"),
|
||||||
"destDirPath": pathJoin(resourcesPath, resources_common),
|
"destDirPath": pathJoin(resourcesPath, resources_common)
|
||||||
"transformSourceCode":
|
|
||||||
usedResources === undefined
|
|
||||||
? undefined
|
|
||||||
: ({ fileRelativePath, sourceCode }) => {
|
|
||||||
if (usedResources.resourcesCommonDirPaths.find(dirPath => fileRelativePath.startsWith(dirPath)) === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { "modifiedSourceCode": sourceCode };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.rmSync(tmpDirPath, { "recursive": true, "force": true });
|
rmSync(tmpDirPath, { "recursive": true, "force": true });
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
import { transformCodebase } from "../../tools/transformCodebase";
|
import { transformCodebase } from "../../tools/transformCodebase";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { join as pathJoin, basename as pathBasename, resolve as pathResolve } from "path";
|
import { join as pathJoin, basename as pathBasename, resolve as pathResolve, dirname as pathDirname } from "path";
|
||||||
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
||||||
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
||||||
import { generateFtlFilesCodeFactory, loginThemePageIds, accountThemePageIds } from "../generateFtl";
|
import { generateFtlFilesCodeFactory, loginThemePageIds, accountThemePageIds } from "../generateFtl";
|
||||||
import {
|
import {
|
||||||
themeTypes,
|
|
||||||
type ThemeType,
|
type ThemeType,
|
||||||
lastKeycloakVersionWithAccountV1,
|
lastKeycloakVersionWithAccountV1,
|
||||||
keycloak_resources,
|
keycloak_resources,
|
||||||
@ -20,7 +19,7 @@ import { downloadKeycloakStaticResources } from "./downloadKeycloakStaticResourc
|
|||||||
import { readFieldNameUsage } from "./readFieldNameUsage";
|
import { readFieldNameUsage } from "./readFieldNameUsage";
|
||||||
import { readExtraPagesNames } from "./readExtraPageNames";
|
import { readExtraPagesNames } from "./readExtraPageNames";
|
||||||
import { generateMessageProperties } from "./generateMessageProperties";
|
import { generateMessageProperties } from "./generateMessageProperties";
|
||||||
import { readStaticResourcesUsage } from "./readStaticResourcesUsage";
|
import { bringInAccountV1 } from "./bringInAccountV1";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
bundler: "vite" | "webpack";
|
bundler: "vite" | "webpack";
|
||||||
@ -33,6 +32,7 @@ export type BuildOptionsLike = {
|
|||||||
assetsDirPath: string;
|
assetsDirPath: string;
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
doBuildRetrocompatAccountTheme: boolean;
|
doBuildRetrocompatAccountTheme: boolean;
|
||||||
|
themeNames: string[];
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
@ -59,27 +59,47 @@ export async function generateTheme(params: {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let allCssGlobalsToDefine: Record<string, string> = {};
|
const cssGlobalsToDefine: Record<string, string> = {};
|
||||||
|
|
||||||
for (const themeType of themeTypes) {
|
const implementedThemeTypes: Record<ThemeType | "email", boolean> = {
|
||||||
|
"login": false,
|
||||||
|
"account": false,
|
||||||
|
"email": false
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const themeType of ["login", "account"] as const) {
|
||||||
if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
|
if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementedThemeTypes[themeType] = true;
|
||||||
|
|
||||||
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
||||||
|
|
||||||
copy_app_resources_to_theme_path: {
|
apply_replacers_and_move_to_theme_resources: {
|
||||||
const isFirstPass = themeType.indexOf(themeType) === 0;
|
if (themeType === "account" && implementedThemeTypes.login) {
|
||||||
|
// NOTE: We prevend doing it twice, it has been done for the login theme.
|
||||||
|
|
||||||
if (!isFirstPass) {
|
transformCodebase({
|
||||||
break copy_app_resources_to_theme_path;
|
"srcDirPath": pathJoin(
|
||||||
|
getThemeTypeDirPath({
|
||||||
|
"themeType": "login"
|
||||||
|
}),
|
||||||
|
"resources",
|
||||||
|
basenameOfTheKeycloakifyResourcesDir
|
||||||
|
),
|
||||||
|
"destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir)
|
||||||
|
});
|
||||||
|
|
||||||
|
break apply_replacers_and_move_to_theme_resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
"destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir),
|
|
||||||
"srcDirPath": buildOptions.reactAppBuildDirPath,
|
"srcDirPath": buildOptions.reactAppBuildDirPath,
|
||||||
|
"destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir),
|
||||||
"transformSourceCode": ({ filePath, sourceCode }) => {
|
"transformSourceCode": ({ filePath, sourceCode }) => {
|
||||||
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
|
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
|
||||||
|
// This should not happen if users follow the new instruction setup but we keep it for retrocompatibility.
|
||||||
if (
|
if (
|
||||||
isInside({
|
isInside({
|
||||||
"dirPath": pathJoin(buildOptions.reactAppBuildDirPath, keycloak_resources),
|
"dirPath": pathJoin(buildOptions.reactAppBuildDirPath, keycloak_resources),
|
||||||
@ -90,20 +110,13 @@ export async function generateTheme(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (/\.css?$/i.test(filePath)) {
|
if (/\.css?$/i.test(filePath)) {
|
||||||
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode({
|
const { cssGlobalsToDefine: cssGlobalsToDefineForThisFile, fixedCssCode } = replaceImportsInCssCode({
|
||||||
"cssCode": sourceCode.toString("utf8")
|
"cssCode": sourceCode.toString("utf8")
|
||||||
});
|
});
|
||||||
|
|
||||||
register_css_variables: {
|
Object.entries(cssGlobalsToDefineForThisFile).forEach(([key, value]) => {
|
||||||
if (!isFirstPass) {
|
cssGlobalsToDefine[key] = value;
|
||||||
break register_css_variables;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
allCssGlobalsToDefine = {
|
|
||||||
...allCssGlobalsToDefine,
|
|
||||||
...cssGlobalsToDefine
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
|
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
|
||||||
}
|
}
|
||||||
@ -125,7 +138,7 @@ export async function generateTheme(params: {
|
|||||||
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
||||||
themeName,
|
themeName,
|
||||||
"indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"),
|
"indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"),
|
||||||
"cssGlobalsToDefine": allCssGlobalsToDefine,
|
cssGlobalsToDefine,
|
||||||
buildOptions,
|
buildOptions,
|
||||||
keycloakifyVersion,
|
keycloakifyVersion,
|
||||||
themeType,
|
themeType,
|
||||||
@ -181,11 +194,6 @@ export async function generateTheme(params: {
|
|||||||
})(),
|
})(),
|
||||||
"themeDirPath": pathResolve(pathJoin(themeTypeDirPath, "..")),
|
"themeDirPath": pathResolve(pathJoin(themeTypeDirPath, "..")),
|
||||||
themeType,
|
themeType,
|
||||||
"usedResources": readStaticResourcesUsage({
|
|
||||||
keycloakifySrcDirPath,
|
|
||||||
themeSrcDirPath,
|
|
||||||
themeType
|
|
||||||
}),
|
|
||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -235,9 +243,82 @@ export async function generateTheme(params: {
|
|||||||
break email;
|
break email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementedThemeTypes.email = true;
|
||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
"srcDirPath": emailThemeSrcDirPath,
|
"srcDirPath": emailThemeSrcDirPath,
|
||||||
"destDirPath": getThemeTypeDirPath({ "themeType": "email" })
|
"destDirPath": getThemeTypeDirPath({ "themeType": "email" })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parsedKeycloakThemeJson: { themes: { name: string; types: string[] }[] } = { "themes": [] };
|
||||||
|
|
||||||
|
buildOptions.themeNames.forEach(themeName =>
|
||||||
|
parsedKeycloakThemeJson.themes.push({
|
||||||
|
"name": themeName,
|
||||||
|
"types": Object.entries(implementedThemeTypes)
|
||||||
|
.filter(([, isImplemented]) => isImplemented)
|
||||||
|
.map(([themeType]) => themeType)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
account_specific_extra_work: {
|
||||||
|
if (!implementedThemeTypes.account) {
|
||||||
|
break account_specific_extra_work;
|
||||||
|
}
|
||||||
|
|
||||||
|
await bringInAccountV1({ buildOptions });
|
||||||
|
|
||||||
|
parsedKeycloakThemeJson.themes.push({
|
||||||
|
"name": accountV1ThemeName,
|
||||||
|
"types": ["account"]
|
||||||
|
});
|
||||||
|
|
||||||
|
add_retrocompat_account_theme: {
|
||||||
|
if (!buildOptions.doBuildRetrocompatAccountTheme) {
|
||||||
|
break add_retrocompat_account_theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
"srcDirPath": getThemeTypeDirPath({ "themeType": "account" }),
|
||||||
|
"destDirPath": getThemeTypeDirPath({ "themeType": "account", "isRetrocompat": true }),
|
||||||
|
"transformSourceCode": ({ filePath, sourceCode }) => {
|
||||||
|
if (pathBasename(filePath) === "theme.properties") {
|
||||||
|
return {
|
||||||
|
"modifiedSourceCode": Buffer.from(
|
||||||
|
sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { "modifiedSourceCode": sourceCode };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buildOptions.themeNames.forEach(themeName =>
|
||||||
|
parsedKeycloakThemeJson.themes.push({
|
||||||
|
"name": `${themeName}${retrocompatPostfix}`,
|
||||||
|
"types": ["account"]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const keycloakThemeJsonFilePath = pathJoin(
|
||||||
|
buildOptions.keycloakifyBuildDirPath,
|
||||||
|
"src",
|
||||||
|
"main",
|
||||||
|
"resources",
|
||||||
|
"META-INF",
|
||||||
|
"keycloak-themes.json"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(pathDirname(keycloakThemeJsonFilePath));
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
fs.writeFileSync(keycloakThemeJsonFilePath, Buffer.from(JSON.stringify(parsedKeycloakThemeJson, null, 2), "utf8"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
import { crawl } from "../../tools/crawl";
|
|
||||||
import { join as pathJoin, sep as pathSep } from "path";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import type { ThemeType } from "../../constants";
|
|
||||||
|
|
||||||
/** Assumes the theme type exists */
|
|
||||||
export function readStaticResourcesUsage(params: { keycloakifySrcDirPath: string; themeSrcDirPath: string; themeType: ThemeType }): {
|
|
||||||
resourcesCommonFilePaths: string[];
|
|
||||||
} {
|
|
||||||
const { keycloakifySrcDirPath, themeSrcDirPath, themeType } = params;
|
|
||||||
|
|
||||||
const resourcesCommonFilePaths = new Set<string>();
|
|
||||||
|
|
||||||
for (const srcDirPath of [pathJoin(keycloakifySrcDirPath, themeType), pathJoin(themeSrcDirPath, themeType)]) {
|
|
||||||
const filePaths = crawl({ "dirPath": srcDirPath, "returnedPathsType": "absolute" }).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath));
|
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
|
||||||
const rawSourceFile = fs.readFileSync(filePath).toString("utf8");
|
|
||||||
|
|
||||||
if (!rawSourceFile.includes("resourcesCommonPath") && !rawSourceFile.includes("resourcesPath")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wrap = readPaths({ rawSourceFile });
|
|
||||||
|
|
||||||
wrap.resourcesCommonFilePaths.forEach(filePath => resourcesCommonFilePaths.add(filePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Exported for testing purpose */
|
|
||||||
export function readPaths(params: { rawSourceFile: string }): {
|
|
||||||
resourcesCommonFilePaths: string[];
|
|
||||||
} {
|
|
||||||
const { rawSourceFile } = params;
|
|
||||||
|
|
||||||
const resourcesCommonFilePaths = new Set<string>();
|
|
||||||
|
|
||||||
{
|
|
||||||
const regexp = new RegExp(`resourcesCommonPath\\s*}([^\`]+)\``, "g");
|
|
||||||
|
|
||||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
|
||||||
|
|
||||||
for (const match of matches) {
|
|
||||||
const filePath = match[1];
|
|
||||||
|
|
||||||
resourcesCommonFilePaths.add(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const regexp = new RegExp(`resourcesCommonPath\\s*[+,]\\s*["']([^"'\`]+)["'\`]`, "g");
|
|
||||||
|
|
||||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
|
||||||
|
|
||||||
for (const match of matches) {
|
|
||||||
const filePath = match[1];
|
|
||||||
|
|
||||||
resourcesCommonFilePaths.add(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizePath = (filePath: string) => {
|
|
||||||
filePath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
||||||
filePath = filePath.replace(/\//g, pathSep);
|
|
||||||
return filePath;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths).map(normalizePath)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,5 +1,5 @@
|
|||||||
import { generateTheme } from "./generateTheme";
|
import { generateTheme } from "./generateTheme";
|
||||||
import { generateJavaStackFiles } from "./generateJavaStackFiles";
|
import { generatePom } from "./generatePom";
|
||||||
import { join as pathJoin, relative as pathRelative, basename as pathBasename, dirname as pathDirname, sep as pathSep } from "path";
|
import { join as pathJoin, relative as pathRelative, basename as pathBasename, dirname as pathDirname, sep as pathSep } from "path";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
|
import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
|
||||||
@ -9,7 +9,6 @@ import { getLogger } from "../tools/logger";
|
|||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { getThemeSrcDirPath } from "../getThemeSrcDirPath";
|
import { getThemeSrcDirPath } from "../getThemeSrcDirPath";
|
||||||
import { getProjectRoot } from "../tools/getProjectRoot";
|
import { getProjectRoot } from "../tools/getProjectRoot";
|
||||||
import { objectKeys } from "tsafe/objectKeys";
|
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
const reactAppRootDirPath = process.cwd();
|
const reactAppRootDirPath = process.cwd();
|
||||||
@ -42,25 +41,13 @@ export async function main() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { jarFilePath } = await generateJavaStackFiles({
|
{
|
||||||
"implementedThemeTypes": (() => {
|
const { pomFileCode } = generatePom({ buildOptions });
|
||||||
const implementedThemeTypes = {
|
|
||||||
"login": false,
|
|
||||||
"account": false,
|
|
||||||
"email": false
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const themeType of objectKeys(implementedThemeTypes)) {
|
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
|
||||||
if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
|
}
|
||||||
continue;
|
|
||||||
}
|
|
||||||
implementedThemeTypes[themeType] = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return implementedThemeTypes;
|
const jarFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`);
|
||||||
})(),
|
|
||||||
buildOptions
|
|
||||||
});
|
|
||||||
|
|
||||||
if (buildOptions.doCreateJar) {
|
if (buildOptions.doCreateJar) {
|
||||||
child_process.execSync("mvn clean install", { "cwd": buildOptions.keycloakifyBuildDirPath });
|
child_process.execSync("mvn clean install", { "cwd": buildOptions.keycloakifyBuildDirPath });
|
||||||
@ -96,48 +83,16 @@ export async function main() {
|
|||||||
"",
|
"",
|
||||||
...(!buildOptions.doCreateJar
|
...(!buildOptions.doCreateJar
|
||||||
? []
|
? []
|
||||||
: [
|
: [`✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative(reactAppRootDirPath, jarFilePath)} 🚀`]),
|
||||||
`✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative(reactAppRootDirPath, jarFilePath)} 🚀`,
|
|
||||||
`It is to be placed in "/opt/keycloak/providers" in the container running a quay.io/keycloak/keycloak Docker image.`,
|
|
||||||
""
|
|
||||||
]),
|
|
||||||
//TODO: Restore when we find a good Helm chart for Keycloak.
|
//TODO: Restore when we find a good Helm chart for Keycloak.
|
||||||
//"Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:",
|
//"Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:",
|
||||||
"",
|
"",
|
||||||
"value.yaml: ",
|
|
||||||
" extraInitContainers: |",
|
|
||||||
" - name: realm-ext-provider",
|
|
||||||
" image: curlimages/curl",
|
|
||||||
" imagePullPolicy: IfNotPresent",
|
|
||||||
" command:",
|
|
||||||
" - sh",
|
|
||||||
" args:",
|
|
||||||
" - -c",
|
|
||||||
` - curl -L -f -S -o /extensions/${pathBasename(jarFilePath)} https://AN.URL.FOR/${pathBasename(jarFilePath)}`,
|
|
||||||
" volumeMounts:",
|
|
||||||
" - name: extensions",
|
|
||||||
" mountPath: /extensions",
|
|
||||||
" ",
|
|
||||||
" extraVolumeMounts: |",
|
|
||||||
" - name: extensions",
|
|
||||||
" mountPath: /opt/keycloak/providers",
|
|
||||||
" extraEnv: |",
|
|
||||||
" - name: KEYCLOAK_USER",
|
|
||||||
" value: admin",
|
|
||||||
" - name: KEYCLOAK_PASSWORD",
|
|
||||||
" value: xxxxxxxxx",
|
|
||||||
" - name: JAVA_OPTS",
|
|
||||||
" value: -Dkeycloak.profile=preview",
|
|
||||||
"",
|
|
||||||
"",
|
|
||||||
`To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`,
|
`To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`,
|
||||||
"",
|
"",
|
||||||
`👉 $ .${pathSep}${pathRelative(
|
`👉 $ .${pathSep}${pathRelative(
|
||||||
reactAppRootDirPath,
|
reactAppRootDirPath,
|
||||||
pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename)
|
pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename)
|
||||||
)} 👈`,
|
)} 👈`,
|
||||||
"",
|
|
||||||
`Test with different Keycloak versions by editing the .sh file. see available versions here: https://quay.io/repository/keycloak/keycloak?tab=tags`,
|
|
||||||
``,
|
``,
|
||||||
`Once your container is up and running: `,
|
`Once your container is up and running: `,
|
||||||
"- Log into the admin console 👉 http://localhost:8080/admin username: admin, password: admin 👈",
|
"- Log into the admin console 👉 http://localhost:8080/admin username: admin, password: admin 👈",
|
||||||
|
@ -1,12 +1,13 @@
|
|||||||
import { exec as execCallback } from "child_process";
|
import { exec as execCallback } from "child_process";
|
||||||
import { createHash } from "crypto";
|
import { createHash } from "crypto";
|
||||||
import { mkdir, readFile, stat, writeFile, unlink, rm } from "fs/promises";
|
import { mkdir, readFile, stat, writeFile, unlink } from "fs/promises";
|
||||||
import fetch, { type FetchOptions } from "make-fetch-happen";
|
import fetch, { type FetchOptions } from "make-fetch-happen";
|
||||||
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve, sep as pathSep } from "path";
|
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve, sep as pathSep } from "path";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { promisify } from "util";
|
import { promisify } from "util";
|
||||||
import { transformCodebase } from "./transformCodebase";
|
import { transformCodebase } from "./transformCodebase";
|
||||||
import { unzip, zip } from "./unzip";
|
import { unzip, zip } from "./unzip";
|
||||||
|
import { rm } from "../tools/fs.rm";
|
||||||
|
|
||||||
const exec = promisify(execCallback);
|
const exec = promisify(execCallback);
|
||||||
|
|
||||||
|
43
src/bin/tools/fs.rm.ts
Normal file
43
src/bin/tools/fs.rm.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import * as fs from "fs/promises";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { NpmModuleVersion } from "./NpmModuleVersion";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polyfill of fs.rm(dirPath, { "recursive": true })
|
||||||
|
* For older version of Node
|
||||||
|
*/
|
||||||
|
export async function rm(dirPath: string, options: { recursive: true; force?: true }) {
|
||||||
|
if (NpmModuleVersion.compare(NpmModuleVersion.parse(process.version), NpmModuleVersion.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;
|
||||||
|
}
|
||||||
|
}
|
33
src/bin/tools/fs.rmSync.ts
Normal file
33
src/bin/tools/fs.rmSync.ts
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { NpmModuleVersion } from "./NpmModuleVersion";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polyfill of fs.rmSync(dirPath, { "recursive": true })
|
||||||
|
* For older version of Node
|
||||||
|
*/
|
||||||
|
export function rmSync(dirPath: string, options: { recursive: true; force?: true }) {
|
||||||
|
if (NpmModuleVersion.compare(NpmModuleVersion.parse(process.version), NpmModuleVersion.parse("14.14.0")) > 0) {
|
||||||
|
fs.rmSync(dirPath, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
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);
|
||||||
|
}
|
@ -2,6 +2,7 @@ import * as fs from "fs";
|
|||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { crawl } from "./crawl";
|
import { crawl } from "./crawl";
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
|
import { rmSync } from "../tools/fs.rmSync";
|
||||||
|
|
||||||
type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; fileRelativePath: string }) =>
|
type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; fileRelativePath: string }) =>
|
||||||
| {
|
| {
|
||||||
@ -10,15 +11,25 @@ type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; file
|
|||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
/** Apply a transformation function to every file of directory */
|
/**
|
||||||
|
* 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 }) {
|
export function transformCodebase(params: { srcDirPath: string; destDirPath: string; transformSourceCode?: TransformSourceCode }) {
|
||||||
const {
|
const {
|
||||||
srcDirPath,
|
srcDirPath,
|
||||||
destDirPath,
|
|
||||||
transformSourceCode = id<TransformSourceCode>(({ sourceCode }) => ({
|
transformSourceCode = id<TransformSourceCode>(({ sourceCode }) => ({
|
||||||
"modifiedSourceCode": sourceCode
|
"modifiedSourceCode": sourceCode
|
||||||
}))
|
}))
|
||||||
} = params;
|
} = params;
|
||||||
|
let { destDirPath } = params;
|
||||||
|
|
||||||
|
const isTargetSameAsSource = path.relative(srcDirPath, destDirPath) === "";
|
||||||
|
|
||||||
|
if (isTargetSameAsSource) {
|
||||||
|
destDirPath = path.join(srcDirPath, "..", "tmp_xOsPdkPsTdzPs34sOkHs");
|
||||||
|
}
|
||||||
|
|
||||||
for (const fileRelativePath of crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })) {
|
for (const fileRelativePath of crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })) {
|
||||||
const filePath = path.join(srcDirPath, fileRelativePath);
|
const filePath = path.join(srcDirPath, fileRelativePath);
|
||||||
@ -44,4 +55,10 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str
|
|||||||
modifiedSourceCode
|
modifiedSourceCode
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (isTargetSameAsSource) {
|
||||||
|
rmSync(srcDirPath, { "recursive": true });
|
||||||
|
|
||||||
|
fs.renameSync(destDirPath, srcDirPath);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Reference in New Issue
Block a user