Merge branch 'main' into fix/unzip
This commit is contained in:
commit
f49ef21fed
9
.gitignore
vendored
9
.gitignore
vendored
@ -41,14 +41,15 @@ jspm_packages
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
|
|
||||||
/dist
|
/dist
|
||||||
/dist_test
|
/keycloakify_starter_test/
|
||||||
|
/sample_custom_react_project/
|
||||||
/sample_react_project/
|
/sample_react_project/
|
||||||
/.yarn_home/
|
/.yarn_home/
|
||||||
|
|
||||||
.idea
|
.idea
|
||||||
|
|
||||||
/keycloak_email
|
|
||||||
/build_keycloak
|
|
||||||
/src/login/i18n/baseMessages/
|
/src/login/i18n/baseMessages/
|
||||||
/src/account/i18n/baseMessages/
|
/src/account/i18n/baseMessages/
|
||||||
|
|
||||||
|
# VS Code devcontainers
|
||||||
|
.devcontainer
|
@ -1,12 +1,15 @@
|
|||||||
node_modules/
|
node_modules/
|
||||||
/dist/
|
/dist/
|
||||||
/dist_test/
|
|
||||||
/CHANGELOG.md
|
/CHANGELOG.md
|
||||||
/.yarn_home/
|
/.yarn_home/
|
||||||
/src/test/apps/
|
/src/test/apps/
|
||||||
/src/tools/types/
|
/src/tools/types/
|
||||||
/sample_react_project
|
|
||||||
/build_keycloak/
|
/build_keycloak/
|
||||||
/.vscode/
|
/.vscode/
|
||||||
/src/login/i18n/baseMessages/
|
/src/login/i18n/baseMessages/
|
||||||
/src/account/i18n/baseMessages/
|
/src/account/i18n/baseMessages/
|
||||||
|
# Test Build Directories
|
||||||
|
/dist_test
|
||||||
|
/sample_react_project/
|
||||||
|
/sample_custom_react_project/
|
||||||
|
/keycloakify_starter_test/
|
@ -12,11 +12,11 @@
|
|||||||
"prepare": "yarn generate-i18n-messages",
|
"prepare": "yarn generate-i18n-messages",
|
||||||
"build": "rimraf dist/ && tsc -p src/bin && tsc -p src/tsconfig.json && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/",
|
"build": "rimraf dist/ && tsc -p src/bin && tsc -p src/tsconfig.json && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/",
|
||||||
"build:watch": "tsc -p src/tsconfig.json && (concurrently \"tsc -p src/tsconfig.json -w\" \"tsc-alias -p src/tsconfig.json\")",
|
"build:watch": "tsc -p src/tsconfig.json && (concurrently \"tsc -p src/tsconfig.json -w\" \"tsc-alias -p src/tsconfig.json\")",
|
||||||
"build:test": "rimraf dist_test/ && tsc -p test/tsconfig.json && tsc-alias -p test/tsconfig.json && yarn copy-files dist_test/src",
|
|
||||||
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
||||||
"copy-files": "copyfiles -u 1 src/**/*.ftl",
|
"copy-files": "copyfiles -u 1 src/**/*.ftl",
|
||||||
"test": "yarn build:test && node dist_test/test/bin && node dist_test/test/lib",
|
"test": "yarn test:types && vitest run",
|
||||||
"test:sample-app": "yarn build:test && node dist_test/test/bin/main.js",
|
"test:keycloakify-starter": "ts-node scripts/test-keycloakify-starter",
|
||||||
|
"test:types": "tsc -p test/tsconfig.json --noEmit",
|
||||||
"_format": "prettier '**/*.{ts,tsx,json,md}'",
|
"_format": "prettier '**/*.{ts,tsx,json,md}'",
|
||||||
"format": "yarn _format --write",
|
"format": "yarn _format --write",
|
||||||
"format:check": "yarn _format --list-different",
|
"format:check": "yarn _format --list-different",
|
||||||
@ -81,7 +81,8 @@
|
|||||||
"scripting-tools": "^0.19.13",
|
"scripting-tools": "^0.19.13",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
"tsc-alias": "^1.8.3",
|
"tsc-alias": "^1.8.3",
|
||||||
"typescript": "^5.0.1-rc"
|
"typescript": "^5.0.1-rc",
|
||||||
|
"vitest": "^0.29.8"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@octokit/rest": "^18.12.0",
|
"@octokit/rest": "^18.12.0",
|
||||||
|
29
scripts/test-keycloakify-starter.ts
Normal file
29
scripts/test-keycloakify-starter.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { execSync } from "child_process";
|
||||||
|
import { existsSync, readFileSync, rmSync, writeFileSync } from "fs";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
const testDir = "keycloakify_starter_test";
|
||||||
|
|
||||||
|
if (existsSync(path.join(process.cwd(), testDir))) {
|
||||||
|
rmSync(path.join(process.cwd(), testDir), { recursive: true });
|
||||||
|
}
|
||||||
|
// Build and link package
|
||||||
|
execSync("yarn build");
|
||||||
|
const pkgJSON = JSON.parse(readFileSync(path.join(process.cwd(), "package.json")).toString("utf8"));
|
||||||
|
pkgJSON.main = "./index.js";
|
||||||
|
pkgJSON.types = "./index.d.ts";
|
||||||
|
pkgJSON.scripts.prepare = undefined;
|
||||||
|
writeFileSync(path.join(process.cwd(), "dist", "package.json"), JSON.stringify(pkgJSON));
|
||||||
|
// Wrapped in a try/catch because unlink errors if the package isn't linked
|
||||||
|
try {
|
||||||
|
execSync("yarn unlink");
|
||||||
|
} catch {}
|
||||||
|
execSync("yarn link", { "cwd": path.join(process.cwd(), "dist") });
|
||||||
|
|
||||||
|
// Clone latest keycloakify-starter and link to keycloakify output
|
||||||
|
execSync(`git clone https://github.com/keycloakify/keycloakify-starter.git ${testDir}`);
|
||||||
|
execSync("yarn install", { "cwd": path.join(process.cwd(), testDir) });
|
||||||
|
execSync("yarn link keycloakify", { "cwd": path.join(process.cwd(), testDir) });
|
||||||
|
|
||||||
|
//Ensure keycloak theme can be built
|
||||||
|
execSync("yarn build-keycloak-theme", { "cwd": path.join(process.cwd(), testDir) });
|
@ -1,11 +1,10 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { keycloakThemeBuildingDirPath } from "./keycloakify";
|
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
||||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||||
import { getCliOptions } from "./tools/cliOptions";
|
import { getCliOptions } from "./tools/cliOptions";
|
||||||
import { getLogger } from "./tools/logger";
|
import { getLogger } from "./tools/logger";
|
||||||
|
import { getKeycloakBuildPath } from "./keycloakify/build-paths";
|
||||||
|
|
||||||
export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) {
|
export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: string; destDirPath: string; isSilent: boolean }) {
|
||||||
const { keycloakVersion, destDirPath } = params;
|
const { keycloakVersion, destDirPath } = params;
|
||||||
@ -26,7 +25,7 @@ async function main() {
|
|||||||
const logger = getLogger({ isSilent });
|
const logger = getLogger({ isSilent });
|
||||||
const { keycloakVersion } = await promptKeycloakVersion();
|
const { keycloakVersion } = await promptKeycloakVersion();
|
||||||
|
|
||||||
const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme");
|
const destDirPath = pathJoin(getKeycloakBuildPath(), "src", "main", "resources", "theme");
|
||||||
|
|
||||||
logger.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
|
logger.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
|
||||||
|
|
||||||
@ -38,5 +37,5 @@ async function main() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
main().catch(e => console.error(e));
|
main();
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@ import { existsSync } from "fs";
|
|||||||
import { join as pathJoin, relative as pathRelative } from "path";
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
||||||
import { assert, Equals } from "tsafe/assert";
|
import { assert, Equals } from "tsafe/assert";
|
||||||
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
|
import { getThemeSrcDirPath } from "./keycloakify/build-paths";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const projectRootDir = getProjectRoot();
|
const projectRootDir = getProjectRoot();
|
||||||
|
@ -1,33 +0,0 @@
|
|||||||
import { join as pathJoin } from "path";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import { crawl } from "./tools/crawl";
|
|
||||||
import { exclude } from "tsafe/exclude";
|
|
||||||
|
|
||||||
const reactProjectDirPath = process.cwd();
|
|
||||||
|
|
||||||
const themeSrcDirBasename = "keycloak-theme";
|
|
||||||
|
|
||||||
export function getThemeSrcDirPath() {
|
|
||||||
const srcDirPath = pathJoin(reactProjectDirPath, "src");
|
|
||||||
|
|
||||||
const themeSrcDirPath: string | undefined = crawl(srcDirPath)
|
|
||||||
.map(fileRelativePath => {
|
|
||||||
const split = fileRelativePath.split(themeSrcDirBasename);
|
|
||||||
|
|
||||||
if (split.length !== 2) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
|
|
||||||
})
|
|
||||||
.filter(exclude(undefined))[0];
|
|
||||||
|
|
||||||
if (themeSrcDirPath === undefined) {
|
|
||||||
if (fs.existsSync(pathJoin(srcDirPath, "login")) || fs.existsSync(pathJoin(srcDirPath, "account"))) {
|
|
||||||
return { "themeSrcDirPath": srcDirPath };
|
|
||||||
}
|
|
||||||
return { "themeSrcDirPath": undefined };
|
|
||||||
}
|
|
||||||
|
|
||||||
return { themeSrcDirPath };
|
|
||||||
}
|
|
@ -7,17 +7,9 @@ import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { getCliOptions } from "./tools/cliOptions";
|
import { getCliOptions } from "./tools/cliOptions";
|
||||||
import { getLogger } from "./tools/logger";
|
import { getLogger } from "./tools/logger";
|
||||||
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
|
import { getEmailThemeSrcDirPath } from "./keycloakify/build-paths";
|
||||||
|
|
||||||
export function getEmailThemeSrcDirPath() {
|
export async function main() {
|
||||||
const { themeSrcDirPath } = getThemeSrcDirPath();
|
|
||||||
|
|
||||||
const emailThemeSrcDirPath = themeSrcDirPath === undefined ? undefined : pathJoin(themeSrcDirPath, "email");
|
|
||||||
|
|
||||||
return { emailThemeSrcDirPath };
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
|
||||||
const { isSilent } = getCliOptions(process.argv.slice(2));
|
const { isSilent } = getCliOptions(process.argv.slice(2));
|
||||||
const logger = getLogger({ isSilent });
|
const logger = getLogger({ isSilent });
|
||||||
|
|
||||||
|
@ -1,51 +1,10 @@
|
|||||||
import { z } from "zod";
|
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { Equals } from "tsafe";
|
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import { parse as urlParse } from "url";
|
import { parse as urlParse } from "url";
|
||||||
import { typeGuard } from "tsafe/typeGuard";
|
import { typeGuard } from "tsafe/typeGuard";
|
||||||
import { symToStr } from "tsafe/symToStr";
|
import { symToStr } from "tsafe/symToStr";
|
||||||
|
import { Bundler, bundlers, getParsedPackageJson } from "./parsed-package-json";
|
||||||
const bundlers = ["mvn", "keycloakify", "none"] as const;
|
import { getAppInputPath, getKeycloakBuildPath } from "./build-paths";
|
||||||
type Bundler = (typeof bundlers)[number];
|
|
||||||
type ParsedPackageJson = {
|
|
||||||
name: string;
|
|
||||||
version: string;
|
|
||||||
homepage?: string;
|
|
||||||
keycloakify?: {
|
|
||||||
/** @deprecated: use extraLoginPages instead */
|
|
||||||
extraPages?: string[];
|
|
||||||
extraLoginPages?: string[];
|
|
||||||
extraAccountPages?: string[];
|
|
||||||
extraThemeProperties?: string[];
|
|
||||||
areAppAndKeycloakServerSharingSameDomain?: boolean;
|
|
||||||
artifactId?: string;
|
|
||||||
groupId?: string;
|
|
||||||
bundler?: Bundler;
|
|
||||||
keycloakVersionDefaultAssets?: string;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
const zParsedPackageJson = z.object({
|
|
||||||
"name": z.string(),
|
|
||||||
"version": z.string(),
|
|
||||||
"homepage": z.string().optional(),
|
|
||||||
"keycloakify": z
|
|
||||||
.object({
|
|
||||||
"extraPages": z.array(z.string()).optional(),
|
|
||||||
"extraLoginPages": z.array(z.string()).optional(),
|
|
||||||
"extraAccountPages": z.array(z.string()).optional(),
|
|
||||||
"extraThemeProperties": z.array(z.string()).optional(),
|
|
||||||
"areAppAndKeycloakServerSharingSameDomain": z.boolean().optional(),
|
|
||||||
"artifactId": z.string().optional(),
|
|
||||||
"groupId": z.string().optional(),
|
|
||||||
"bundler": z.enum(bundlers).optional(),
|
|
||||||
"keycloakVersionDefaultAssets": z.string().optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, ParsedPackageJson>>();
|
|
||||||
|
|
||||||
/** Consolidated build option gathered form CLI arguments and config in package.json */
|
/** Consolidated build option gathered form CLI arguments and config in package.json */
|
||||||
export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets;
|
export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets;
|
||||||
@ -62,6 +21,10 @@ export namespace BuildOptions {
|
|||||||
artifactId: string;
|
artifactId: string;
|
||||||
bundler: Bundler;
|
bundler: Bundler;
|
||||||
keycloakVersionDefaultAssets: string;
|
keycloakVersionDefaultAssets: string;
|
||||||
|
// Directory of your built react project. Defaults to {cwd}/build
|
||||||
|
appInputPath: string;
|
||||||
|
// Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak
|
||||||
|
keycloakBuildPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Standalone = Common & {
|
export type Standalone = Common & {
|
||||||
@ -88,15 +51,10 @@ export namespace BuildOptions {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export function readBuildOptions(params: {
|
export function readBuildOptions(params: { CNAME: string | undefined; isExternalAssetsCliParamProvided: boolean; isSilent: boolean }): BuildOptions {
|
||||||
packageJson: string;
|
const { CNAME, isExternalAssetsCliParamProvided, isSilent } = params;
|
||||||
CNAME: string | undefined;
|
|
||||||
isExternalAssetsCliParamProvided: boolean;
|
|
||||||
isSilent: boolean;
|
|
||||||
}): BuildOptions {
|
|
||||||
const { packageJson, CNAME, isExternalAssetsCliParamProvided, isSilent } = params;
|
|
||||||
|
|
||||||
const parsedPackageJson = zParsedPackageJson.parse(JSON.parse(packageJson));
|
const parsedPackageJson = getParsedPackageJson();
|
||||||
|
|
||||||
const url = (() => {
|
const url = (() => {
|
||||||
const { homepage } = parsedPackageJson;
|
const { homepage } = parsedPackageJson;
|
||||||
@ -172,7 +130,9 @@ export function readBuildOptions(params: {
|
|||||||
extraAccountPages,
|
extraAccountPages,
|
||||||
extraThemeProperties,
|
extraThemeProperties,
|
||||||
isSilent,
|
isSilent,
|
||||||
"keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3"
|
"keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3",
|
||||||
|
appInputPath: getAppInputPath(),
|
||||||
|
keycloakBuildPath: getKeycloakBuildPath()
|
||||||
};
|
};
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
72
src/bin/keycloakify/build-paths.ts
Normal file
72
src/bin/keycloakify/build-paths.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { exclude } from "tsafe";
|
||||||
|
import { crawl } from "../tools/crawl";
|
||||||
|
import { pathJoin } from "../tools/pathJoin";
|
||||||
|
import { getParsedPackageJson } from "./parsed-package-json";
|
||||||
|
|
||||||
|
const DEFAULT_APP_INPUT_PATH = "build";
|
||||||
|
|
||||||
|
const DEFAULT_KEYCLOAK_BUILD_PATH = "build_keycloak";
|
||||||
|
|
||||||
|
const THEME_SRC_DIR_BASENAME = "keycloak-theme";
|
||||||
|
|
||||||
|
export const getReactProjectDirPath = () => process.cwd();
|
||||||
|
|
||||||
|
export const getCnamePath = () => pathJoin(getReactProjectDirPath(), "public", "CNAME");
|
||||||
|
|
||||||
|
const parseAppInputPath = (path?: string) => {
|
||||||
|
if (!path) {
|
||||||
|
return pathJoin(process.cwd(), DEFAULT_APP_INPUT_PATH);
|
||||||
|
} else if (path.startsWith("./")) {
|
||||||
|
return pathJoin(process.cwd(), path.replace("./", ""));
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
|
const parseKeycloakBuildPath = (path?: string) => {
|
||||||
|
if (!path) {
|
||||||
|
return pathJoin(process.cwd(), DEFAULT_KEYCLOAK_BUILD_PATH);
|
||||||
|
} else if (path.startsWith("./")) {
|
||||||
|
return pathJoin(process.cwd(), path.replace("./", ""));
|
||||||
|
}
|
||||||
|
return path;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getAppInputPath = () => {
|
||||||
|
return parseAppInputPath(getParsedPackageJson().keycloakify?.appInputPath);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getKeycloakBuildPath = () => {
|
||||||
|
return parseKeycloakBuildPath(getParsedPackageJson().keycloakify?.keycloakBuildPath);
|
||||||
|
};
|
||||||
|
export const getThemeSrcDirPath = () => {
|
||||||
|
const srcDirPath = pathJoin(getReactProjectDirPath(), "src");
|
||||||
|
|
||||||
|
const themeSrcDirPath: string | undefined = crawl(srcDirPath)
|
||||||
|
.map(fileRelativePath => {
|
||||||
|
const split = fileRelativePath.split(THEME_SRC_DIR_BASENAME);
|
||||||
|
|
||||||
|
if (split.length !== 2) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathJoin(srcDirPath, split[0] + THEME_SRC_DIR_BASENAME);
|
||||||
|
})
|
||||||
|
.filter(exclude(undefined))[0];
|
||||||
|
if (themeSrcDirPath === undefined) {
|
||||||
|
if (fs.existsSync(pathJoin(srcDirPath, "login")) || fs.existsSync(pathJoin(srcDirPath, "account"))) {
|
||||||
|
return { "themeSrcDirPath": srcDirPath };
|
||||||
|
}
|
||||||
|
return { "themeSrcDirPath": undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
return { themeSrcDirPath };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const getEmailThemeSrcDirPath = () => {
|
||||||
|
const { themeSrcDirPath } = getThemeSrcDirPath();
|
||||||
|
|
||||||
|
const emailThemeSrcDirPath = themeSrcDirPath === undefined ? undefined : pathJoin(themeSrcDirPath, "email");
|
||||||
|
|
||||||
|
return { emailThemeSrcDirPath };
|
||||||
|
};
|
@ -4,5 +4,5 @@ export * from "./keycloakify";
|
|||||||
import { main } from "./keycloakify";
|
import { main } from "./keycloakify";
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
main().catch(e => console.error(e));
|
main();
|
||||||
}
|
}
|
||||||
|
@ -10,11 +10,8 @@ import { getCliOptions } from "../tools/cliOptions";
|
|||||||
import jar from "../tools/jar";
|
import jar from "../tools/jar";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { Equals } from "tsafe";
|
import { Equals } from "tsafe";
|
||||||
import { getEmailThemeSrcDirPath } from "../initialize-email-theme";
|
import { getEmailThemeSrcDirPath } from "./build-paths";
|
||||||
|
import { getCnamePath, getAppInputPath, getKeycloakBuildPath, getReactProjectDirPath } from "./build-paths";
|
||||||
const reactProjectDirPath = process.cwd();
|
|
||||||
|
|
||||||
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
|
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2));
|
const { isSilent, hasExternalAssets } = getCliOptions(process.argv.slice(2));
|
||||||
@ -22,9 +19,8 @@ export async function main() {
|
|||||||
logger.log("🔏 Building the keycloak theme...⌚");
|
logger.log("🔏 Building the keycloak theme...⌚");
|
||||||
|
|
||||||
const buildOptions = readBuildOptions({
|
const buildOptions = readBuildOptions({
|
||||||
"packageJson": fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8"),
|
|
||||||
"CNAME": (() => {
|
"CNAME": (() => {
|
||||||
const cnameFilePath = pathJoin(reactProjectDirPath, "public", "CNAME");
|
const cnameFilePath = getCnamePath();
|
||||||
|
|
||||||
if (!fs.existsSync(cnameFilePath)) {
|
if (!fs.existsSync(cnameFilePath)) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -37,7 +33,7 @@ export async function main() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
const { doBundlesEmailTemplate } = await generateKeycloakThemeResources({
|
const { doBundlesEmailTemplate } = await generateKeycloakThemeResources({
|
||||||
keycloakThemeBuildingDirPath,
|
keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath,
|
||||||
"emailThemeSrcDirPath": (() => {
|
"emailThemeSrcDirPath": (() => {
|
||||||
const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath();
|
const { emailThemeSrcDirPath } = getEmailThemeSrcDirPath();
|
||||||
|
|
||||||
@ -47,13 +43,13 @@ export async function main() {
|
|||||||
|
|
||||||
return emailThemeSrcDirPath;
|
return emailThemeSrcDirPath;
|
||||||
})(),
|
})(),
|
||||||
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
|
"reactAppBuildDirPath": getAppInputPath(),
|
||||||
buildOptions,
|
buildOptions,
|
||||||
"keycloakVersion": buildOptions.keycloakVersionDefaultAssets
|
"keycloakVersion": buildOptions.keycloakVersionDefaultAssets
|
||||||
});
|
});
|
||||||
|
|
||||||
const { jarFilePath } = generateJavaStackFiles({
|
const { jarFilePath } = generateJavaStackFiles({
|
||||||
keycloakThemeBuildingDirPath,
|
keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath,
|
||||||
doBundlesEmailTemplate,
|
doBundlesEmailTemplate,
|
||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
@ -65,7 +61,7 @@ export async function main() {
|
|||||||
case "keycloakify":
|
case "keycloakify":
|
||||||
logger.log("🫶 Let keycloakify do its thang");
|
logger.log("🫶 Let keycloakify do its thang");
|
||||||
await jar({
|
await jar({
|
||||||
"rootPath": pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources"),
|
"rootPath": pathJoin(buildOptions.keycloakBuildPath, "src", "main", "resources"),
|
||||||
"version": buildOptions.version,
|
"version": buildOptions.version,
|
||||||
"groupId": buildOptions.groupId,
|
"groupId": buildOptions.groupId,
|
||||||
"artifactId": buildOptions.artifactId,
|
"artifactId": buildOptions.artifactId,
|
||||||
@ -74,7 +70,7 @@ export async function main() {
|
|||||||
break;
|
break;
|
||||||
case "mvn":
|
case "mvn":
|
||||||
logger.log("🫙 Run maven to deliver a jar");
|
logger.log("🫙 Run maven to deliver a jar");
|
||||||
child_process.execSync("mvn package", { "cwd": keycloakThemeBuildingDirPath });
|
child_process.execSync("mvn package", { "cwd": buildOptions.keycloakBuildPath });
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
assert<Equals<typeof buildOptions.bundler, never>>(false);
|
assert<Equals<typeof buildOptions.bundler, never>>(false);
|
||||||
@ -84,7 +80,7 @@ export async function main() {
|
|||||||
const containerKeycloakVersion = "20.0.1";
|
const containerKeycloakVersion = "20.0.1";
|
||||||
|
|
||||||
generateStartKeycloakTestingContainer({
|
generateStartKeycloakTestingContainer({
|
||||||
keycloakThemeBuildingDirPath,
|
keycloakThemeBuildingDirPath: buildOptions.keycloakBuildPath,
|
||||||
"keycloakVersion": containerKeycloakVersion,
|
"keycloakVersion": containerKeycloakVersion,
|
||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
@ -92,7 +88,7 @@ export async function main() {
|
|||||||
logger.log(
|
logger.log(
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`,
|
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(getReactProjectDirPath(), jarFilePath)} 🚀`,
|
||||||
`It is to be placed in "/opt/keycloak/providers" in the container running a quay.io/keycloak/keycloak Docker image.`,
|
`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.
|
||||||
@ -127,8 +123,8 @@ export async function main() {
|
|||||||
`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(
|
||||||
reactProjectDirPath,
|
getReactProjectDirPath(),
|
||||||
pathJoin(keycloakThemeBuildingDirPath, generateStartKeycloakTestingContainer.basename)
|
pathJoin(getKeycloakBuildPath(), generateStartKeycloakTestingContainer.basename)
|
||||||
)} 👈`,
|
)} 👈`,
|
||||||
"",
|
"",
|
||||||
`Test with different Keycloak versions by editing the .sh file. see available versions here: https://quay.io/repository/keycloak/keycloak?tab=tags`,
|
`Test with different Keycloak versions by editing the .sh file. see available versions here: https://quay.io/repository/keycloak/keycloak?tab=tags`,
|
||||||
|
58
src/bin/keycloakify/parsed-package-json.ts
Normal file
58
src/bin/keycloakify/parsed-package-json.ts
Normal file
@ -0,0 +1,58 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { assert } from "tsafe";
|
||||||
|
import type { Equals } from "tsafe";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { pathJoin } from "../tools/pathJoin";
|
||||||
|
|
||||||
|
const reactProjectDirPath = process.cwd();
|
||||||
|
export const bundlers = ["mvn", "keycloakify", "none"] as const;
|
||||||
|
export type Bundler = (typeof bundlers)[number];
|
||||||
|
type ParsedPackageJson = {
|
||||||
|
name: string;
|
||||||
|
version: string;
|
||||||
|
homepage?: string;
|
||||||
|
keycloakify?: {
|
||||||
|
/** @deprecated: use extraLoginPages instead */
|
||||||
|
extraPages?: string[];
|
||||||
|
extraLoginPages?: string[];
|
||||||
|
extraAccountPages?: string[];
|
||||||
|
extraThemeProperties?: string[];
|
||||||
|
areAppAndKeycloakServerSharingSameDomain?: boolean;
|
||||||
|
artifactId?: string;
|
||||||
|
groupId?: string;
|
||||||
|
bundler?: Bundler;
|
||||||
|
keycloakVersionDefaultAssets?: string;
|
||||||
|
appInputPath?: string;
|
||||||
|
keycloakBuildPath?: string;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const zParsedPackageJson = z.object({
|
||||||
|
"name": z.string(),
|
||||||
|
"version": z.string(),
|
||||||
|
"homepage": z.string().optional(),
|
||||||
|
"keycloakify": z
|
||||||
|
.object({
|
||||||
|
"extraPages": z.array(z.string()).optional(),
|
||||||
|
"extraLoginPages": z.array(z.string()).optional(),
|
||||||
|
"extraAccountPages": z.array(z.string()).optional(),
|
||||||
|
"extraThemeProperties": z.array(z.string()).optional(),
|
||||||
|
"areAppAndKeycloakServerSharingSameDomain": z.boolean().optional(),
|
||||||
|
"artifactId": z.string().optional(),
|
||||||
|
"groupId": z.string().optional(),
|
||||||
|
"bundler": z.enum(bundlers).optional(),
|
||||||
|
"keycloakVersionDefaultAssets": z.string().optional(),
|
||||||
|
"appInputPath": z.string().optional(),
|
||||||
|
"keycloakBuildPath": z.string().optional()
|
||||||
|
})
|
||||||
|
.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, ParsedPackageJson>>();
|
||||||
|
|
||||||
|
let parsedPackageJson: undefined | ReturnType<(typeof zParsedPackageJson)["parse"]>;
|
||||||
|
export const getParsedPackageJson = () => {
|
||||||
|
if (parsedPackageJson) return parsedPackageJson;
|
||||||
|
parsedPackageJson = zParsedPackageJson.parse(JSON.parse(fs.readFileSync(pathJoin(reactProjectDirPath, "package.json")).toString("utf8")));
|
||||||
|
return parsedPackageJson;
|
||||||
|
};
|
@ -1,20 +0,0 @@
|
|||||||
import { join as pathJoin } from "path";
|
|
||||||
import { generateKeycloakThemeResources } from "keycloakify/bin/keycloakify/generateKeycloakThemeResources";
|
|
||||||
import { setupSampleReactProject, sampleReactProjectDirPath } from "./setupSampleReactProject";
|
|
||||||
|
|
||||||
setupSampleReactProject();
|
|
||||||
|
|
||||||
generateKeycloakThemeResources({
|
|
||||||
"reactAppBuildDirPath": pathJoin(sampleReactProjectDirPath, "build"),
|
|
||||||
"keycloakThemeBuildingDirPath": pathJoin(sampleReactProjectDirPath, "build_keycloak_theme"),
|
|
||||||
"emailThemeSrcDirPath": undefined,
|
|
||||||
"keycloakVersion": "11.0.3",
|
|
||||||
"buildOptions": {
|
|
||||||
"themeName": "keycloakify-demo-app",
|
|
||||||
"extraLoginPages": ["my-custom-page.ftl"],
|
|
||||||
"extraThemeProperties": ["env=test"],
|
|
||||||
"isStandalone": true,
|
|
||||||
"urlPathname": "/keycloakify-demo-app/",
|
|
||||||
"isSilent": false
|
|
||||||
}
|
|
||||||
});
|
|
@ -1 +0,0 @@
|
|||||||
import "./replaceImportFromStatic";
|
|
26
test/bin/jar.spec.ts
Normal file
26
test/bin/jar.spec.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import jar from "keycloakify/bin/tools/jar";
|
||||||
|
import { it, describe, vi } from "vitest";
|
||||||
|
|
||||||
|
vi.mock("fs", () => ({ promises: { mkdir: () => {} }, createWriteStream: () => {} }));
|
||||||
|
vi.mock("stream", async () => {
|
||||||
|
const readableMock = () => {
|
||||||
|
const mockDecorators = {
|
||||||
|
on: () => mockDecorators,
|
||||||
|
pipe: () => mockDecorators
|
||||||
|
};
|
||||||
|
return {
|
||||||
|
from: () => mockDecorators
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
// @ts-ignore
|
||||||
|
...(await vi.importActual("stream")),
|
||||||
|
Readable: readableMock()
|
||||||
|
};
|
||||||
|
});
|
||||||
|
describe("jar", () => {
|
||||||
|
it("creates jar artifacts without error", () => {
|
||||||
|
jar({ artifactId: "artifactId", groupId: "groupId", rootPath: "rootPath", targetPath: "targetPath", version: "1.0.0" });
|
||||||
|
});
|
||||||
|
});
|
@ -1,26 +0,0 @@
|
|||||||
import "./replaceImportFromStatic";
|
|
||||||
import { setupSampleReactProject, sampleReactProjectDirPath } from "./setupSampleReactProject";
|
|
||||||
import * as st from "scripting-tools";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import { join as pathJoin } from "path";
|
|
||||||
import { getProjectRoot } from "keycloakify/bin/tools/getProjectRoot.js";
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
fs.rmSync(sampleReactProjectDirPath, { "recursive": true });
|
|
||||||
|
|
||||||
await setupSampleReactProject();
|
|
||||||
|
|
||||||
const binDirPath = pathJoin(getProjectRoot(), "dist_test", "src", "bin");
|
|
||||||
|
|
||||||
fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "keycloak-theme"), { "recursive": true });
|
|
||||||
|
|
||||||
st.execSyncTrace(`node ${pathJoin(binDirPath, "initialize-email-theme")}`, { "cwd": sampleReactProjectDirPath });
|
|
||||||
|
|
||||||
st.execSyncTrace(`node ${pathJoin(binDirPath, "download-builtin-keycloak-theme")}`, { "cwd": sampleReactProjectDirPath });
|
|
||||||
|
|
||||||
st.execSyncTrace(
|
|
||||||
//`node ${pathJoin(binDirPath, "keycloakify")} --external-assets`,
|
|
||||||
`node ${pathJoin(binDirPath, "keycloakify")}`,
|
|
||||||
{ "cwd": sampleReactProjectDirPath }
|
|
||||||
);
|
|
||||||
})();
|
|
@ -1,11 +1,12 @@
|
|||||||
import { replaceImportsFromStaticInJsCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode";
|
import { replaceImportsFromStaticInJsCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode";
|
||||||
import { generateCssCodeToDefineGlobals, replaceImportsInCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInCssCode";
|
import { generateCssCodeToDefineGlobals, replaceImportsInCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInCssCode";
|
||||||
import { replaceImportsInInlineCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInInlineCssCode";
|
import { replaceImportsInInlineCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInInlineCssCode";
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import { same } from "evt/tools/inDepth/same";
|
import { same } from "evt/tools/inDepth/same";
|
||||||
import { assetIsSameCode } from "../tools/assertIsSameCode";
|
import { expect, it, describe } from "vitest";
|
||||||
|
|
||||||
{
|
import { isSameCode } from "../tools/isSameCode";
|
||||||
|
|
||||||
|
describe("bin/js-transforms", () => {
|
||||||
const jsCodeUntransformed = `
|
const jsCodeUntransformed = `
|
||||||
function f() {
|
function f() {
|
||||||
return a.p+"static/js/" + ({}[e] || e) + "." + {
|
return a.p+"static/js/" + ({}[e] || e) + "." + {
|
||||||
@ -32,8 +33,7 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}[e]+".chunk.css"
|
}[e]+".chunk.css"
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
it("transforms standalone code properly", () => {
|
||||||
{
|
|
||||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
||||||
"jsCode": jsCodeUntransformed,
|
"jsCode": jsCodeUntransformed,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
@ -89,10 +89,9 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
|
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(fixedJsCode, fixedJsCodeExpected);
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
}
|
});
|
||||||
|
it("transforms external app code properly", () => {
|
||||||
{
|
|
||||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
||||||
"jsCode": jsCodeUntransformed,
|
"jsCode": jsCodeUntransformed,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
@ -150,11 +149,12 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(fixedJsCode, fixedJsCodeExpected);
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
describe("bin/css-transforms", () => {
|
||||||
|
it("transforms absolute urls to css globals properly with no urlPathname", () => {
|
||||||
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
|
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
|
||||||
"cssCode": `
|
"cssCode": `
|
||||||
.my-div {
|
.my-div {
|
||||||
@ -185,14 +185,14 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
|
|
||||||
const cssGlobalsToDefineExpected = {
|
const cssGlobalsToDefineExpected = {
|
||||||
"url1f9ef5a892c104c": "url(/logo192.png) no-repeat center center",
|
"url1f9ef5a892c104c": "url(/logo192.png) no-repeat center center",
|
||||||
"urldd75cab58377c19": "url(/static/media/something.svg)"
|
"urldd75cab58377c19": "url(/static/media/something.svg)"
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(same(cssGlobalsToDefine, cssGlobalsToDefineExpected));
|
expect(same(cssGlobalsToDefine, cssGlobalsToDefineExpected)).toBe(true);
|
||||||
|
|
||||||
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
|
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
|
||||||
cssGlobalsToDefine,
|
cssGlobalsToDefine,
|
||||||
@ -208,10 +208,9 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected);
|
expect(isSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected)).toBe(true);
|
||||||
}
|
});
|
||||||
|
it("transforms absolute urls to css globals properly with custom urlPathname", () => {
|
||||||
{
|
|
||||||
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
|
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
|
||||||
"cssCode": `
|
"cssCode": `
|
||||||
.my-div {
|
.my-div {
|
||||||
@ -242,14 +241,14 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
|
|
||||||
const cssGlobalsToDefineExpected = {
|
const cssGlobalsToDefineExpected = {
|
||||||
"urlf8277cddaa2be78": "url(/x/y/z/logo192.png) no-repeat center center",
|
"urlf8277cddaa2be78": "url(/x/y/z/logo192.png) no-repeat center center",
|
||||||
"url8bdc0887b97ac9a": "url(/x/y/z/static/media/something.svg)"
|
"url8bdc0887b97ac9a": "url(/x/y/z/static/media/something.svg)"
|
||||||
};
|
};
|
||||||
|
|
||||||
assert(same(cssGlobalsToDefine, cssGlobalsToDefineExpected));
|
expect(same(cssGlobalsToDefine, cssGlobalsToDefineExpected)).toBe(true);
|
||||||
|
|
||||||
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
|
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
|
||||||
cssGlobalsToDefine,
|
cssGlobalsToDefine,
|
||||||
@ -265,10 +264,12 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected);
|
expect(isSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected)).toBe(true);
|
||||||
}
|
});
|
||||||
|
});
|
||||||
|
|
||||||
{
|
describe("bin/css-inline-transforms", () => {
|
||||||
|
describe("no url pathName", () => {
|
||||||
const cssCode = `
|
const cssCode = `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Work Sans";
|
font-family: "Work Sans";
|
||||||
@ -299,8 +300,7 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
src: url("/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
src: url("/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
it("transforms css for standalone app properly", () => {
|
||||||
{
|
|
||||||
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
||||||
cssCode,
|
cssCode,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
@ -344,10 +344,9 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
}
|
});
|
||||||
|
it("transforms css for external app properly", () => {
|
||||||
{
|
|
||||||
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
||||||
cssCode,
|
cssCode,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
@ -392,11 +391,11 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
|
|
||||||
{
|
describe("with url pathName", () => {
|
||||||
const cssCode = `
|
const cssCode = `
|
||||||
@font-face {
|
@font-face {
|
||||||
font-family: "Work Sans";
|
font-family: "Work Sans";
|
||||||
@ -427,8 +426,7 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
src: url("/x/y/z/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
src: url("/x/y/z/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
it("transforms css for standalone app properly", () => {
|
||||||
{
|
|
||||||
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
||||||
cssCode,
|
cssCode,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
@ -472,10 +470,9 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
}
|
});
|
||||||
|
it("transforms css for external app properly", () => {
|
||||||
{
|
|
||||||
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
||||||
cssCode,
|
cssCode,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
@ -520,8 +517,7 @@ import { assetIsSameCode } from "../tools/assertIsSameCode";
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
assetIsSameCode(fixedCssCode, fixedCssCodeExpected);
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
}
|
});
|
||||||
}
|
});
|
||||||
|
});
|
||||||
console.log("PASS replace import from static");
|
|
63
test/bin/setupCustomReactProject.spec.ts
Normal file
63
test/bin/setupCustomReactProject.spec.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { getProjectRoot } from "keycloakify/bin/tools/getProjectRoot.js";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip";
|
||||||
|
import { main as initializeEmailTheme } from "keycloakify/bin/initialize-email-theme";
|
||||||
|
import { it, describe, afterAll, beforeAll, beforeEach, vi } from "vitest";
|
||||||
|
import { getKeycloakBuildPath } from "keycloakify/bin/keycloakify/build-paths";
|
||||||
|
import { downloadBuiltinKeycloakTheme } from "keycloakify/bin/download-builtin-keycloak-theme";
|
||||||
|
|
||||||
|
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_custom_react_project");
|
||||||
|
|
||||||
|
async function setupSampleReactProject(destDir: string) {
|
||||||
|
await downloadAndUnzip({
|
||||||
|
"url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
||||||
|
"destDirPath": destDir
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const nativeCwd = process.cwd;
|
||||||
|
vi.mock("keycloakify/bin/keycloakify/parsed-package-json", async () => ({
|
||||||
|
...((await vi.importActual("keycloakify/bin/keycloakify/parsed-package-json")) as Record<string, unknown>),
|
||||||
|
getParsedPackageJson: () => ({
|
||||||
|
"keycloakify": {
|
||||||
|
"appInputPath": "./custom_input/build",
|
||||||
|
"keycloakBuildDir": "./custom_output"
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("keycloakify/bin/promptKeycloakVersion", async () => ({
|
||||||
|
...((await vi.importActual("keycloakify/bin/promptKeycloakVersion")) as Record<string, unknown>),
|
||||||
|
promptKeycloakVersion: () => ({ "keycloakVersion": "11.0.3" })
|
||||||
|
}));
|
||||||
|
|
||||||
|
describe("Sample Project", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
// Monkey patching the cwd to the app location for the duration of this testv
|
||||||
|
process.cwd = () => sampleReactProjectDirPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
fs.rmSync(sampleReactProjectDirPath, { "recursive": true });
|
||||||
|
process.cwd = nativeCwd;
|
||||||
|
});
|
||||||
|
beforeEach(() => {
|
||||||
|
if (fs.existsSync(sampleReactProjectDirPath)) {
|
||||||
|
fs.rmSync(sampleReactProjectDirPath, { "recursive": true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "keycloak-theme"), { "recursive": true });
|
||||||
|
fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "login"), { "recursive": true });
|
||||||
|
});
|
||||||
|
it(
|
||||||
|
"Sets up the project with a custom input and output directory without error",
|
||||||
|
async () => {
|
||||||
|
await setupSampleReactProject(pathJoin(sampleReactProjectDirPath, "custom_input"));
|
||||||
|
await initializeEmailTheme();
|
||||||
|
|
||||||
|
const destDirPath = pathJoin(getKeycloakBuildPath(), "src", "main", "resources", "theme");
|
||||||
|
await downloadBuiltinKeycloakTheme({ destDirPath, keycloakVersion: "11.0.3", isSilent: false });
|
||||||
|
},
|
||||||
|
{ timeout: 30000 }
|
||||||
|
);
|
||||||
|
});
|
60
test/bin/setupSampleReactProject.spec.ts
Normal file
60
test/bin/setupSampleReactProject.spec.ts
Normal file
@ -0,0 +1,60 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { getProjectRoot } from "keycloakify/bin/tools/getProjectRoot.js";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip";
|
||||||
|
import { main as initializeEmailTheme } from "keycloakify/bin/initialize-email-theme";
|
||||||
|
import { it, describe, afterAll, beforeAll, beforeEach, vi } from "vitest";
|
||||||
|
import { getKeycloakBuildPath } from "keycloakify/bin/keycloakify/build-paths";
|
||||||
|
import { downloadBuiltinKeycloakTheme } from "keycloakify/bin/download-builtin-keycloak-theme";
|
||||||
|
|
||||||
|
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project");
|
||||||
|
|
||||||
|
async function setupSampleReactProject(destDir: string) {
|
||||||
|
await downloadAndUnzip({
|
||||||
|
"url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
||||||
|
"destDirPath": destDir
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
vi.mock("keycloakify/bin/keycloakify/parsed-package-json", async () => ({
|
||||||
|
...((await vi.importActual("keycloakify/bin/keycloakify/parsed-package-json")) as Record<string, unknown>),
|
||||||
|
getParsedPackageJson: () => ({})
|
||||||
|
}));
|
||||||
|
|
||||||
|
vi.mock("keycloakify/bin/promptKeycloakVersion", async () => ({
|
||||||
|
...((await vi.importActual("keycloakify/bin/promptKeycloakVersion")) as Record<string, unknown>),
|
||||||
|
promptKeycloakVersion: () => ({ keycloakVersion: "11.0.3" })
|
||||||
|
}));
|
||||||
|
|
||||||
|
const nativeCwd = process.cwd;
|
||||||
|
|
||||||
|
describe("Sample Project", () => {
|
||||||
|
beforeAll(() => {
|
||||||
|
// Monkey patching the cwd to the app location for the duration of this test
|
||||||
|
process.cwd = () => sampleReactProjectDirPath;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterAll(() => {
|
||||||
|
fs.rmSync(sampleReactProjectDirPath, { "recursive": true });
|
||||||
|
process.cwd = nativeCwd;
|
||||||
|
});
|
||||||
|
beforeEach(() => {
|
||||||
|
if (fs.existsSync(sampleReactProjectDirPath)) {
|
||||||
|
fs.rmSync(sampleReactProjectDirPath, { "recursive": true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "keycloak-theme"), { "recursive": true });
|
||||||
|
fs.mkdirSync(pathJoin(sampleReactProjectDirPath, "src", "login"), { "recursive": true });
|
||||||
|
});
|
||||||
|
it(
|
||||||
|
"Sets up the project without error",
|
||||||
|
async () => {
|
||||||
|
await setupSampleReactProject(sampleReactProjectDirPath);
|
||||||
|
await initializeEmailTheme();
|
||||||
|
|
||||||
|
const destDirPath = pathJoin(getKeycloakBuildPath(), "src", "main", "resources", "theme");
|
||||||
|
await downloadBuiltinKeycloakTheme({ destDirPath, keycloakVersion: "11.0.3", isSilent: false });
|
||||||
|
},
|
||||||
|
{ timeout: 30000 }
|
||||||
|
);
|
||||||
|
});
|
@ -1,12 +1,8 @@
|
|||||||
import { getProjectRoot } from "keycloakify/bin/tools/getProjectRoot.js";
|
|
||||||
import { join as pathJoin } from "path";
|
|
||||||
import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip";
|
import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip";
|
||||||
|
|
||||||
export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_react_project");
|
export async function setupSampleReactProject(destDirPath: string) {
|
||||||
|
|
||||||
export async function setupSampleReactProject() {
|
|
||||||
await downloadAndUnzip({
|
await downloadAndUnzip({
|
||||||
"url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
"url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
||||||
"destDirPath": sampleReactProjectDirPath
|
"destDirPath": destDirPath
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { getKcContext } from "../../src/login/kcContext/getKcContext";
|
import { getKcContext } from "keycloakify/login/kcContext/getKcContext";
|
||||||
import type { ExtendKcContext } from "../../src/login/kcContext/getKcContextFromWindow";
|
import type { ExtendKcContext } from "keycloakify/login/kcContext/getKcContextFromWindow";
|
||||||
import type { KcContext } from "../../src/login/kcContext";
|
import type { KcContext } from "keycloakify/login/kcContext";
|
||||||
import { same } from "evt/tools/inDepth";
|
import { same } from "evt/tools/inDepth";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
import { kcContextMocks, kcContextCommonMock } from "../../src/login/kcContext/kcContextMocks";
|
import { kcContextMocks, kcContextCommonMock } from "keycloakify/login/kcContext/kcContextMocks";
|
||||||
import { deepClone } from "../../src/tools/deepClone";
|
import { deepClone } from "keycloakify/tools/deepClone";
|
||||||
|
import { expect, it, describe } from "vitest";
|
||||||
|
|
||||||
{
|
describe("getKcContext", () => {
|
||||||
const authorizedMailDomains = ["example.com", "another-example.com", "*.yet-another-example.com", "*.example.com", "hello-world.com"];
|
const authorizedMailDomains = ["example.com", "another-example.com", "*.yet-another-example.com", "*.example.com", "hello-world.com"];
|
||||||
|
|
||||||
const displayName = "this is an overwritten common value";
|
const displayName = "this is an overwritten common value";
|
||||||
@ -59,8 +60,7 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
|
|
||||||
return { kcContext };
|
return { kcContext };
|
||||||
};
|
};
|
||||||
|
it("has proper API for login.ftl", () => {
|
||||||
{
|
|
||||||
const pageId = "login.ftl";
|
const pageId = "login.ftl";
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
||||||
@ -69,7 +69,7 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext.Login>>();
|
assert<Equals<typeof kcContext, KcContext.Login>>();
|
||||||
|
|
||||||
assert(
|
expect(
|
||||||
same(
|
same(
|
||||||
//NOTE: deepClone for printIfExists or other functions...
|
//NOTE: deepClone for printIfExists or other functions...
|
||||||
deepClone(kcContext),
|
deepClone(kcContext),
|
||||||
@ -81,12 +81,10 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
return mock;
|
return mock;
|
||||||
})()
|
})()
|
||||||
)
|
)
|
||||||
);
|
).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
console.log(`PASS ${pageId}`);
|
it("has a proper API for info.ftl", () => {
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const pageId = "info.ftl";
|
const pageId = "info.ftl";
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
||||||
@ -104,7 +102,7 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
>
|
>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
assert(
|
expect(
|
||||||
same(
|
same(
|
||||||
deepClone(kcContext),
|
deepClone(kcContext),
|
||||||
(() => {
|
(() => {
|
||||||
@ -115,12 +113,9 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
return mock;
|
return mock;
|
||||||
})()
|
})()
|
||||||
)
|
)
|
||||||
);
|
).toBe(true);
|
||||||
|
});
|
||||||
console.log(`PASS ${pageId}`);
|
it("has a proper API for register.ftl", () => {
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const pageId = "register.ftl";
|
const pageId = "register.ftl";
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
||||||
@ -138,7 +133,7 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
>
|
>
|
||||||
>();
|
>();
|
||||||
|
|
||||||
assert(
|
expect(
|
||||||
same(
|
same(
|
||||||
deepClone(kcContext),
|
deepClone(kcContext),
|
||||||
(() => {
|
(() => {
|
||||||
@ -149,12 +144,9 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
return mock;
|
return mock;
|
||||||
})()
|
})()
|
||||||
)
|
)
|
||||||
);
|
).toBe(true);
|
||||||
|
});
|
||||||
console.log(`PASS ${pageId}`);
|
it("has a proper API for my-extra-page-2.ftl", () => {
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const pageId = "my-extra-page-2.ftl";
|
const pageId = "my-extra-page-2.ftl";
|
||||||
|
|
||||||
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
const { kcContext } = getKcContextProxy({ "mockPageId": pageId });
|
||||||
@ -173,7 +165,7 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
|
|
||||||
kcContext.aNonStandardValue2;
|
kcContext.aNonStandardValue2;
|
||||||
|
|
||||||
assert(
|
expect(
|
||||||
same(
|
same(
|
||||||
deepClone(kcContext),
|
deepClone(kcContext),
|
||||||
(() => {
|
(() => {
|
||||||
@ -184,12 +176,9 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
return mock;
|
return mock;
|
||||||
})()
|
})()
|
||||||
)
|
)
|
||||||
);
|
).toBe(true);
|
||||||
|
});
|
||||||
console.log(`PASS ${pageId}`);
|
it("has a proper API for my-extra-page-1.ftl", () => {
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const pageId = "my-extra-page-1.ftl";
|
const pageId = "my-extra-page-1.ftl";
|
||||||
|
|
||||||
console.log("We expect a warning here =>");
|
console.log("We expect a warning here =>");
|
||||||
@ -200,7 +189,7 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext.Common & { pageId: typeof pageId }>>();
|
assert<Equals<typeof kcContext, KcContext.Common & { pageId: typeof pageId }>>();
|
||||||
|
|
||||||
assert(
|
expect(
|
||||||
same(
|
same(
|
||||||
deepClone(kcContext),
|
deepClone(kcContext),
|
||||||
(() => {
|
(() => {
|
||||||
@ -211,13 +200,9 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
return mock;
|
return mock;
|
||||||
})()
|
})()
|
||||||
)
|
)
|
||||||
);
|
).toBe(true);
|
||||||
|
});
|
||||||
console.log(`PASS ${pageId}`);
|
it("returns the proper mock for login.ftl", () => {
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const pageId = "login.ftl";
|
const pageId = "login.ftl";
|
||||||
|
|
||||||
const { kcContext } = getKcContext({
|
const { kcContext } = getKcContext({
|
||||||
@ -227,16 +212,12 @@ import { deepClone } from "../../src/tools/deepClone";
|
|||||||
assert<Equals<typeof kcContext, KcContext | undefined>>();
|
assert<Equals<typeof kcContext, KcContext | undefined>>();
|
||||||
|
|
||||||
assert(same(deepClone(kcContext), deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!)));
|
assert(same(deepClone(kcContext), deepClone(kcContextMocks.find(({ pageId: pageId_i }) => pageId_i === pageId)!)));
|
||||||
|
});
|
||||||
console.log("PASS no extension");
|
it("returns the proper mock for login.ftl", () => {
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const { kcContext } = getKcContext();
|
const { kcContext } = getKcContext();
|
||||||
|
|
||||||
assert<Equals<typeof kcContext, KcContext | undefined>>();
|
assert<Equals<typeof kcContext, KcContext | undefined>>();
|
||||||
|
|
||||||
assert(kcContext === undefined);
|
assert(kcContext === undefined);
|
||||||
|
});
|
||||||
console.log("PASS no extension, no mock");
|
});
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
import "./getKcContext";
|
|
@ -1,4 +1,4 @@
|
|||||||
import { AndByDiscriminatingKey } from "../../../src/tools/AndByDiscriminatingKey";
|
import { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { Equals } from "tsafe";
|
import type { Equals } from "tsafe";
|
||||||
|
|
@ -1,7 +0,0 @@
|
|||||||
import { assert } from "tsafe/assert";
|
|
||||||
|
|
||||||
export function assetIsSameCode(code1: string, code2: string, message?: string): void {
|
|
||||||
const removeSpacesAndNewLines = (code: string) => code.replace(/\s/g, "").replace(/\n/g, "");
|
|
||||||
|
|
||||||
assert(removeSpacesAndNewLines(code1) === removeSpacesAndNewLines(code2), message);
|
|
||||||
}
|
|
5
test/tools/isSameCode.ts
Normal file
5
test/tools/isSameCode.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
export function isSameCode(code1: string, code2: string): boolean {
|
||||||
|
const removeSpacesAndNewLines = (code: string) => code.replace(/\s/g, "").replace(/\n/g, "");
|
||||||
|
|
||||||
|
return removeSpacesAndNewLines(code1) === removeSpacesAndNewLines(code2);
|
||||||
|
}
|
@ -10,7 +10,7 @@
|
|||||||
"newLine": "LF",
|
"newLine": "LF",
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"incremental": true,
|
"incremental": false,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
|
12
vitest.config.ts
Normal file
12
vitest.config.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
/// <reference types="vitest" />
|
||||||
|
import { defineConfig } from "vite";
|
||||||
|
import path from "path";
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
"test": {
|
||||||
|
"alias": {
|
||||||
|
"keycloakify": path.resolve(__dirname, "./src")
|
||||||
|
},
|
||||||
|
"watchExclude": ["**/node_modules/**", "**/dist/**", "**/sample_react_project/**", "**/sample_custom_react_project/**"]
|
||||||
|
}
|
||||||
|
});
|
Loading…
x
Reference in New Issue
Block a user