Remove --external-assets option

This commit is contained in:
garronej 2023-08-21 05:54:17 +02:00
parent 641cc38ae4
commit 1c25b69160
13 changed files with 197 additions and 576 deletions

View File

@ -15,6 +15,7 @@ export async function downloadBuiltinKeycloakTheme(params: { projectDirPath: str
console.log("Downloading Keycloak theme...", { keycloakVersion }); console.log("Downloading Keycloak theme...", { keycloakVersion });
await downloadAndUnzip({ await downloadAndUnzip({
"doUseCache": true,
projectDirPath, projectDirPath,
destDirPath, destDirPath,
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`, "url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,

View File

@ -4,15 +4,11 @@ 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 { bundlers, getParsedPackageJson, type Bundler } from "./parsedPackageJson"; import { bundlers, getParsedPackageJson, type Bundler } from "./parsedPackageJson";
import * as fs from "fs";
import { join as pathJoin, sep as pathSep } from "path"; import { join as pathJoin, sep as pathSep } from "path";
import parseArgv from "minimist"; import parseArgv from "minimist";
/** 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 = {
export namespace BuildOptions {
export type Common = {
isSilent: boolean; isSilent: boolean;
themeVersion: string; themeVersion: string;
themeName: string; themeName: string;
@ -26,84 +22,24 @@ export namespace BuildOptions {
reactAppBuildDirPath: string; reactAppBuildDirPath: string;
/** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */ /** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */
keycloakifyBuildDirPath: string; keycloakifyBuildDirPath: string;
}; /** If your app is hosted under a subpath, it's the case in CRA if you have "homepage": "https://example.com/my-app" in your package.json
* In this case the urlPathname will be "/my-app/" */
export type Standalone = Common & {
isStandalone: true;
urlPathname: string | undefined; urlPathname: string | undefined;
}; };
export type ExternalAssets = ExternalAssets.SameDomain | ExternalAssets.DifferentDomains;
export namespace ExternalAssets {
export type CommonExternalAssets = Common & {
isStandalone: false;
};
export type SameDomain = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: true;
};
export type DifferentDomains = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: false;
urlOrigin: string;
urlPathname: string | undefined;
};
}
}
export function readBuildOptions(params: { projectDirPath: string; processArgv: string[] }): BuildOptions { export function readBuildOptions(params: { projectDirPath: string; processArgv: string[] }): BuildOptions {
const { projectDirPath, processArgv } = params; const { projectDirPath, processArgv } = params;
const { isExternalAssetsCliParamProvided, isSilentCliParamProvided } = (() => { const { isSilentCliParamProvided } = (() => {
const argv = parseArgv(processArgv); const argv = parseArgv(processArgv);
return { return {
"isSilentCliParamProvided": typeof argv["silent"] === "boolean" ? argv["silent"] : false, "isSilentCliParamProvided": typeof argv["silent"] === "boolean" ? argv["silent"] : false
"isExternalAssetsCliParamProvided": typeof argv["external-assets"] === "boolean" ? argv["external-assets"] : false
}; };
})(); })();
const parsedPackageJson = getParsedPackageJson({ projectDirPath }); const parsedPackageJson = getParsedPackageJson({ projectDirPath });
const url = (() => {
const { homepage } = parsedPackageJson;
let url: URL | undefined = undefined;
if (homepage !== undefined) {
url = new URL(homepage);
}
const CNAME = (() => {
const cnameFilePath = pathJoin(projectDirPath, "public", "CNAME");
if (!fs.existsSync(cnameFilePath)) {
return undefined;
}
return fs.readFileSync(cnameFilePath).toString("utf8");
})();
if (CNAME !== undefined) {
url = new URL(`https://${CNAME.replace(/\s+$/, "")}`);
}
if (url === undefined) {
return undefined;
}
return {
"origin": url.origin,
"pathname": (() => {
const out = url.pathname.replace(/([^/])$/, "$1/");
return out === "/" ? undefined : out;
})()
};
})();
const common: BuildOptions.Common = (() => {
const { name, keycloakify = {}, version, homepage } = parsedPackageJson; const { name, keycloakify = {}, version, homepage } = parsedPackageJson;
const { extraThemeProperties, groupId, artifactId, bundler, keycloakVersionDefaultAssets, extraThemeNames = [] } = keycloakify ?? {}; const { extraThemeProperties, groupId, artifactId, bundler, keycloakVersionDefaultAssets, extraThemeNames = [] } = keycloakify ?? {};
@ -122,10 +58,7 @@ export function readBuildOptions(params: { projectDirPath: string; processArgv:
const { KEYCLOAKIFY_BUNDLER } = process.env; const { KEYCLOAKIFY_BUNDLER } = process.env;
assert( assert(
typeGuard<Bundler | undefined>( typeGuard<Bundler | undefined>(KEYCLOAKIFY_BUNDLER, [undefined, ...id<readonly string[]>(bundlers)].includes(KEYCLOAKIFY_BUNDLER)),
KEYCLOAKIFY_BUNDLER,
[undefined, ...id<readonly string[]>(bundlers)].includes(KEYCLOAKIFY_BUNDLER)
),
`${symToStr({ KEYCLOAKIFY_BUNDLER })} should be one of ${bundlers.join(", ")}` `${symToStr({ KEYCLOAKIFY_BUNDLER })} should be one of ${bundlers.join(", ")}`
); );
@ -184,48 +117,22 @@ export function readBuildOptions(params: { projectDirPath: string; processArgv:
} }
return keycloakifyBuildDirPath; return keycloakifyBuildDirPath;
})(),
"urlPathname": (() => {
const { homepage } = parsedPackageJson;
let url: URL | undefined = undefined;
if (homepage !== undefined) {
url = new URL(homepage);
}
if (url === undefined) {
return undefined;
}
const out = url.pathname.replace(/([^/])$/, "$1/");
return out === "/" ? undefined : out;
})() })()
}; };
})();
if (isExternalAssetsCliParamProvided) {
const commonExternalAssets = id<BuildOptions.ExternalAssets.CommonExternalAssets>({
...common,
"isStandalone": false
});
if (parsedPackageJson.keycloakify?.areAppAndKeycloakServerSharingSameDomain) {
return id<BuildOptions.ExternalAssets.SameDomain>({
...commonExternalAssets,
"areAppAndKeycloakServerSharingSameDomain": true
});
} else {
assert(
url !== undefined,
[
"Can't compile in external assets mode if we don't know where",
"the app will be hosted.",
"You should provide a homepage field in the package.json (or create a",
"public/CNAME file.",
"Alternatively, if your app and the Keycloak server are on the same domain, ",
"eg https://example.com is your app and https://example.com/auth is the keycloak",
'admin UI, you can set "keycloakify": { "areAppAndKeycloakServerSharingSameDomain": true }',
"in your package.json"
].join(" ")
);
return id<BuildOptions.ExternalAssets.DifferentDomains>({
...commonExternalAssets,
"areAppAndKeycloakServerSharingSameDomain": false,
"urlOrigin": url.origin,
"urlPathname": url.pathname
});
}
}
return id<BuildOptions.Standalone>({
...common,
"isStandalone": true,
"urlPathname": url?.pathname
});
} }

View File

@ -13,39 +13,11 @@ export const themeTypes = ["login", "account"] as const;
export type ThemeType = (typeof themeTypes)[number]; export type ThemeType = (typeof themeTypes)[number];
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; export type BuildOptionsLike = {
export namespace BuildOptionsLike {
export type Common = {
themeName: string; themeName: string;
themeVersion: string; themeVersion: string;
};
export type Standalone = Common & {
isStandalone: true;
urlPathname: string | undefined; urlPathname: string | undefined;
}; };
export type ExternalAssets = ExternalAssets.SameDomain | ExternalAssets.DifferentDomains;
export namespace ExternalAssets {
export type CommonExternalAssets = {
isStandalone: false;
};
export type SameDomain = Common &
CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: true;
};
export type DifferentDomains = Common &
CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: false;
urlOrigin: string;
urlPathname: string | undefined;
};
}
}
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
@ -63,22 +35,23 @@ export function generateFtlFilesCodeFactory(params: {
const $ = cheerio.load(indexHtmlCode); const $ = cheerio.load(indexHtmlCode);
fix_imports_statements: { fix_imports_statements: {
if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) {
break fix_imports_statements;
}
$("script:not([src])").each((...[, element]) => { $("script:not([src])").each((...[, element]) => {
const { fixedJsCode } = replaceImportsFromStaticInJsCode({ const jsCode = $(element).html();
"jsCode": $(element).html()!,
buildOptions assert(jsCode !== null);
});
const { fixedJsCode } = replaceImportsFromStaticInJsCode({ jsCode });
$(element).text(fixedJsCode); $(element).text(fixedJsCode);
}); });
$("style").each((...[, element]) => { $("style").each((...[, element]) => {
const cssCode = $(element).html();
assert(cssCode !== null);
const { fixedCssCode } = replaceImportsInInlineCssCode({ const { fixedCssCode } = replaceImportsInInlineCssCode({
"cssCode": $(element).html()!, cssCode,
buildOptions buildOptions
}); });
@ -100,9 +73,7 @@ export function generateFtlFilesCodeFactory(params: {
$(element).attr( $(element).attr(
attrName, attrName,
buildOptions.isStandalone href.replace(new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`), "${url.resourcesPath}/build/")
? href.replace(new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`), "${url.resourcesPath}/build/")
: href.replace(/^\//, `${buildOptions.urlOrigin}/`)
); );
}) })
); );

View File

@ -1,7 +1,6 @@
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname } from "path"; import { join as pathJoin, dirname as pathDirname } from "path";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { Reflect } from "tsafe/Reflect";
import type { BuildOptions } from "./BuildOptions"; import type { BuildOptions } from "./BuildOptions";
import type { ThemeType } from "./generateFtl"; import type { ThemeType } from "./generateFtl";
@ -13,11 +12,7 @@ export type BuildOptionsLike = {
themeVersion: string; themeVersion: string;
}; };
{ assert<BuildOptions extends BuildOptionsLike ? true : false>();
const buildOptions = Reflect<BuildOptions>();
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export function generateJavaStackFiles(params: { export function generateJavaStackFiles(params: {
keycloakThemeBuildingDirPath: string; keycloakThemeBuildingDirPath: string;

View File

@ -1,7 +1,6 @@
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { Reflect } from "tsafe/Reflect";
import type { BuildOptions } from "./BuildOptions"; import type { BuildOptions } from "./BuildOptions";
export type BuildOptionsLike = { export type BuildOptionsLike = {
@ -9,11 +8,7 @@ export type BuildOptionsLike = {
extraThemeNames: string[]; extraThemeNames: string[];
}; };
{ assert<BuildOptions extends BuildOptionsLike ? true : false>();
const buildOptions = Reflect<BuildOptions>();
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
generateStartKeycloakTestingContainer.basename = "start_keycloak_testing_container.sh"; generateStartKeycloakTestingContainer.basename = "start_keycloak_testing_container.sh";

View File

@ -13,39 +13,13 @@ import { readFieldNameUsage } from "./readFieldNameUsage";
import { readExtraPagesNames } from "./readExtraPageNames"; import { readExtraPagesNames } from "./readExtraPageNames";
import { generateMessageProperties } from "./generateMessageProperties"; import { generateMessageProperties } from "./generateMessageProperties";
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; export type BuildOptionsLike = {
export namespace BuildOptionsLike {
export type Common = {
themeName: string; themeName: string;
extraThemeProperties: string[] | undefined; extraThemeProperties: string[] | undefined;
themeVersion: string; themeVersion: string;
keycloakVersionDefaultAssets: string; keycloakVersionDefaultAssets: string;
};
export type Standalone = Common & {
isStandalone: true;
urlPathname: string | undefined; urlPathname: string | undefined;
}; };
export type ExternalAssets = ExternalAssets.SameDomain | ExternalAssets.DifferentDomains;
export namespace ExternalAssets {
export type CommonExternalAssets = Common & {
isStandalone: false;
};
export type SameDomain = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: true;
};
export type DifferentDomains = CommonExternalAssets & {
areAppAndKeycloakServerSharingSameDomain: false;
urlOrigin: string;
urlPathname: string | undefined;
};
}
}
assert<BuildOptions extends BuildOptionsLike ? true : false>(); assert<BuildOptions extends BuildOptionsLike ? true : false>();
@ -85,17 +59,16 @@ export async function generateTheme(params: {
copy_app_resources_to_theme_path: { copy_app_resources_to_theme_path: {
const isFirstPass = themeType.indexOf(themeType) === 0; const isFirstPass = themeType.indexOf(themeType) === 0;
if (!isFirstPass && !buildOptions.isStandalone) { if (!isFirstPass) {
break copy_app_resources_to_theme_path; break copy_app_resources_to_theme_path;
} }
transformCodebase({ transformCodebase({
"destDirPath": buildOptions.isStandalone ? pathJoin(themeDirPath, "resources", "build") : reactAppBuildDirPath, "destDirPath": pathJoin(themeDirPath, "resources", "build"),
"srcDirPath": reactAppBuildDirPath, "srcDirPath": reactAppBuildDirPath,
"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/
if ( if (
buildOptions.isStandalone &&
isInside({ isInside({
"dirPath": pathJoin(reactAppBuildDirPath, basenameOfKeycloakDirInPublicDir), "dirPath": pathJoin(reactAppBuildDirPath, basenameOfKeycloakDirInPublicDir),
filePath filePath
@ -105,10 +78,6 @@ export async function generateTheme(params: {
} }
if (/\.css?$/i.test(filePath)) { if (/\.css?$/i.test(filePath)) {
if (!buildOptions.isStandalone) {
return undefined;
}
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode({ const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode({
"cssCode": sourceCode.toString("utf8") "cssCode": sourceCode.toString("utf8")
}); });
@ -128,19 +97,14 @@ export async function generateTheme(params: {
} }
if (/\.js?$/i.test(filePath)) { if (/\.js?$/i.test(filePath)) {
if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) {
return undefined;
}
const { fixedJsCode } = replaceImportsFromStaticInJsCode({ const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"), "jsCode": sourceCode.toString("utf8")
buildOptions
}); });
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") }; return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
} }
return buildOptions.isStandalone ? { "modifiedSourceCode": sourceCode } : undefined; return { "modifiedSourceCode": sourceCode };
} }
}); });
} }

View File

@ -1,31 +1,6 @@
import { ftlValuesGlobalName } from "../ftlValuesGlobalName"; import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
import type { BuildOptions } from "../BuildOptions";
import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import { Reflect } from "tsafe/Reflect";
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; export function replaceImportsFromStaticInJsCode(params: { jsCode: string }): { fixedJsCode: string } {
export namespace BuildOptionsLike {
export type Standalone = {
isStandalone: true;
};
export type ExternalAssets = {
isStandalone: false;
urlOrigin: string;
};
}
{
const buildOptions = Reflect<BuildOptions>();
assert(!is<BuildOptions.ExternalAssets.CommonExternalAssets>(buildOptions));
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export function replaceImportsFromStaticInJsCode(params: { jsCode: string; buildOptions: BuildOptionsLike }): { fixedJsCode: string } {
/* /*
NOTE: NOTE:
@ -38,7 +13,7 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string; build
will always run in keycloak context. will always run in keycloak context.
*/ */
const { jsCode, buildOptions } = params; const { jsCode } = params;
const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [ const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [
new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=function\\(([a-zA-Z]+)\\){return"static\\/${language}\\/"`, "g"), new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=function\\(([a-zA-Z]+)\\){return"static\\/${language}\\/"`, "g"),
@ -46,40 +21,23 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string; build
${n}[(function(){ ${n}[(function(){
var pd= Object.getOwnPropertyDescriptor(${n}, "p"); var pd= Object.getOwnPropertyDescriptor(${n}, "p");
if( pd === undefined || pd.configurable ){ if( pd === undefined || pd.configurable ){
${
buildOptions.isStandalone
? `
Object.defineProperty(${n}, "p", { Object.defineProperty(${n}, "p", {
get: function() { return window.${ftlValuesGlobalName}.url.resourcesPath; }, get: function() { return window.${ftlValuesGlobalName}.url.resourcesPath; },
set: function (){} set: function (){}
}); });
`
: `
var p= "";
Object.defineProperty(${n}, "p", {
get: function() { return "${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : p; },
set: function (value){ p = value;}
});
`
}
} }
return "${u}"; return "${u}";
})()] = function(${e}) { return "${buildOptions.isStandalone ? "/build/" : ""}static/${language}/"` })()] = function(${e}) { return "${true ? "/build/" : ""}static/${language}/"`
]; ];
const fixedJsCode = jsCode const fixedJsCode = jsCode
.replace(...getReplaceArgs("js")) .replace(...getReplaceArgs("js"))
.replace(...getReplaceArgs("css")) .replace(...getReplaceArgs("css"))
.replace(/([a-zA-Z]+\.[a-zA-Z]+)\+"static\//g, (...[, group]) => .replace(/[a-zA-Z]+\.[a-zA-Z]+\+"static\//g, `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`)
buildOptions.isStandalone
? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
: `("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : ${group}) + "static/`
)
//TODO: Write a test case for this //TODO: Write a test case for this
.replace(/".chunk.css",([a-zA-Z])+=([a-zA-Z]+\.[a-zA-Z]+)\+([a-zA-Z]+),/, (...[, group1, group2, group3]) => .replace(
buildOptions.isStandalone /".chunk.css",([a-zA-Z])+=[a-zA-Z]+\.[a-zA-Z]+\+([a-zA-Z]+),/,
? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},` (...[, group1, group2]) => `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group2},`
: `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : ${group2}) + ${group3},`
); );
return { fixedJsCode }; return { fixedJsCode };

View File

@ -1,20 +1,12 @@
import * as crypto from "crypto"; import * as crypto from "crypto";
import type { BuildOptions } from "../BuildOptions"; import type { BuildOptions } from "../BuildOptions";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import { Reflect } from "tsafe/Reflect";
export type BuildOptionsLike = { export type BuildOptionsLike = {
urlPathname: string | undefined; urlPathname: string | undefined;
}; };
{ assert<BuildOptions extends BuildOptionsLike ? true : false>();
const buildOptions = Reflect<BuildOptions>();
assert(!is<BuildOptions.ExternalAssets.CommonExternalAssets>(buildOptions));
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export function replaceImportsInCssCode(params: { cssCode: string }): { export function replaceImportsInCssCode(params: { cssCode: string }): {
fixedCssCode: string; fixedCssCode: string;

View File

@ -1,32 +1,11 @@
import type { BuildOptions } from "../BuildOptions"; import type { BuildOptions } from "../BuildOptions";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { is } from "tsafe/is";
import { Reflect } from "tsafe/Reflect";
export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; export type BuildOptionsLike = {
export namespace BuildOptionsLike {
export type Common = {
urlPathname: string | undefined; urlPathname: string | undefined;
}; };
export type Standalone = Common & { assert<BuildOptions extends BuildOptionsLike ? true : false>();
isStandalone: true;
};
export type ExternalAssets = Common & {
isStandalone: false;
urlOrigin: string;
};
}
{
const buildOptions = Reflect<BuildOptions>();
assert(!is<BuildOptions.ExternalAssets.CommonExternalAssets>(buildOptions));
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
}
export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOptions: BuildOptionsLike }): { export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOptions: BuildOptionsLike }): {
fixedCssCode: string; fixedCssCode: string;
@ -37,10 +16,7 @@ export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOp
buildOptions.urlPathname === undefined buildOptions.urlPathname === undefined
? /url\(["']?\/([^/][^)"']+)["']?\)/g ? /url\(["']?\/([^/][^)"']+)["']?\)/g
: new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"), : new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"),
(...[, group]) => (...[, group]) => `url(\${url.resourcesPath}/build/${group})`
`url(${
buildOptions.isStandalone ? "${url.resourcesPath}/build/" + group : buildOptions.urlOrigin + (buildOptions.urlPathname ?? "/") + group
})`
); );
return { fixedCssCode }; return { fixedCssCode };

View File

@ -10,7 +10,7 @@ import { unzip, zip } from "./unzip";
const exec = promisify(execCallback); const exec = promisify(execCallback);
function hash(s: string) { function sha256(s: string) {
return createHash("sha256").update(s).digest("hex"); return createHash("sha256").update(s).digest("hex");
} }
@ -112,8 +112,8 @@ async function getFetchOptions(): Promise<Pick<FetchOptions, "proxy" | "noProxy"
return { proxy, noProxy, strictSSL, cert, ca: ca.length === 0 ? undefined : ca }; return { proxy, noProxy, strictSSL, cert, ca: ca.length === 0 ? undefined : ca };
} }
export async function downloadAndUnzip(params: { export async function downloadAndUnzip(
projectDirPath: string; params: {
url: string; url: string;
destDirPath: string; destDirPath: string;
specificDirsToExtract?: string[]; specificDirsToExtract?: string[];
@ -121,15 +121,26 @@ export async function downloadAndUnzip(params: {
actionCacheId: string; actionCacheId: string;
action: (params: { destDirPath: string }) => Promise<void>; action: (params: { destDirPath: string }) => Promise<void>;
}; };
}) { } & (
const { projectDirPath, url, destDirPath, specificDirsToExtract, preCacheTransform } = params; | {
doUseCache: true;
projectDirPath: string;
}
| {
doUseCache: false;
}
)
) {
const { url, destDirPath, specificDirsToExtract, preCacheTransform, ...rest } = params;
const downloadHash = hash( const hash = sha256(
JSON.stringify({ url }) + (preCacheTransform === undefined ? "" : `${preCacheTransform.actionCacheId}${preCacheTransform.action.toString()}`) JSON.stringify({ url }) + (preCacheTransform === undefined ? "" : `${preCacheTransform.actionCacheId}${preCacheTransform.action.toString()}`)
).substring(0, 15); ).substring(0, 15);
const cacheRoot = pathJoin(process.env.XDG_CACHE_HOME ?? pathJoin(projectDirPath, "node_modules", ".cache"), "keycloakify"); const cacheRoot = !rest.doUseCache
const zipFilePath = pathJoin(cacheRoot, `_${downloadHash}.zip`); ? `tmp_${Math.random().toString().slice(2, 12)}`
const extractDirPath = pathJoin(cacheRoot, `tmp_unzip_${downloadHash}`); : pathJoin(process.env.XDG_CACHE_HOME ?? pathJoin(rest.projectDirPath, "node_modules", ".cache"), "keycloakify");
const zipFilePath = pathJoin(cacheRoot, `_${hash}.zip`);
const extractDirPath = pathJoin(cacheRoot, `tmp_unzip_${hash}`);
if (!(await exists(zipFilePath))) { if (!(await exists(zipFilePath))) {
const opts = await getFetchOptions(); const opts = await getFetchOptions();
@ -167,4 +178,8 @@ export async function downloadAndUnzip(params: {
"srcDirPath": extractDirPath, "srcDirPath": extractDirPath,
"destDirPath": destDirPath "destDirPath": destDirPath
}); });
if (!rest.doUseCache) {
await rm(cacheRoot, { "recursive": true });
}
} }

View File

@ -35,10 +35,7 @@ describe("bin/js-transforms", () => {
`; `;
it("transforms standalone code properly", () => { it("transforms standalone code properly", () => {
const { fixedJsCode } = replaceImportsFromStaticInJsCode({ const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": jsCodeUntransformed, "jsCode": jsCodeUntransformed
"buildOptions": {
"isStandalone": true
}
}); });
const fixedJsCodeExpected = ` const fixedJsCodeExpected = `
@ -89,66 +86,6 @@ describe("bin/js-transforms", () => {
`; `;
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
});
it("transforms external app code properly", () => {
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": jsCodeUntransformed,
"buildOptions": {
"isStandalone": false,
"urlOrigin": "https://demo-app.keycloakify.dev"
}
});
const fixedJsCodeExpected = `
function f() {
return ("kcContext" in window ? "https://demo-app.keycloakify.dev/" : a.p) + "static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
function sameAsF() {
return ("kcContext" in window ? "https://demo-app.keycloakify.dev/" : a.p) + "static/js/" + ({}[e] || e) + "." + {
3: "0664cdc0"
}[e] + ".chunk.js"
}
__webpack_require__[(function (){
var pd= Object.getOwnPropertyDescriptor(__webpack_require__, "p");
if( pd === undefined || pd.configurable ){
var p= "";
Object.defineProperty(__webpack_require__, "p", {
get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev/" : p; },
set: function (value){ p = value; }
});
}
return "u";
})()] = function(e) {
return "static/js/" + e + "." + {
147: "6c5cee76",
787: "8da10fcf",
922: "be170a73"
} [e] + ".chunk.js"
}
t[(function (){
var pd= Object.getOwnPropertyDescriptor(t, "p");
if( pd === undefined || pd.configurable ){
var p= "";
Object.defineProperty(t, "p", {
get: function() { return "kcContext" in window ? "https://demo-app.keycloakify.dev/" : p; },
set: function (value){ p = value; }
});
}
return "miniCssF";
})()] = function(e) {
return "static/css/" + e + "." + {
164:"dcfd7749",
908:"67c9ed2c"
} [e] + ".chunk.css"
}
`;
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true); expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
}); });
}); });
@ -304,7 +241,6 @@ describe("bin/css-inline-transforms", () => {
const { fixedCssCode } = replaceImportsInInlineCssCode({ const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode, cssCode,
"buildOptions": { "buildOptions": {
"isStandalone": true,
"urlPathname": undefined "urlPathname": undefined
} }
}); });
@ -344,53 +280,6 @@ describe("bin/css-inline-transforms", () => {
} }
`; `;
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
});
it("transforms css for external app properly", () => {
const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode,
"buildOptions": {
"isStandalone": false,
"urlOrigin": "https://demo-app.keycloakify.dev",
"urlPathname": undefined
}
});
const fixedCssCodeExpected = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/fonts/WorkSans/worksans-regular-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/fonts/WorkSans/worksans-medium-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/fonts/WorkSans/worksans-semibold-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/fonts/WorkSans/worksans-bold-webfont.woff2)
format("woff2");
}
`;
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true); expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
}); });
}); });
@ -430,7 +319,6 @@ describe("bin/css-inline-transforms", () => {
const { fixedCssCode } = replaceImportsInInlineCssCode({ const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode, cssCode,
"buildOptions": { "buildOptions": {
"isStandalone": true,
"urlPathname": "/x/y/z/" "urlPathname": "/x/y/z/"
} }
}); });
@ -470,53 +358,6 @@ describe("bin/css-inline-transforms", () => {
} }
`; `;
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
});
it("transforms css for external app properly", () => {
const { fixedCssCode } = replaceImportsInInlineCssCode({
cssCode,
"buildOptions": {
"isStandalone": false,
"urlOrigin": "https://demo-app.keycloakify.dev",
"urlPathname": "/x/y/z/"
}
});
const fixedCssCodeExpected = `
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 400;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/x/y/z/fonts/WorkSans/worksans-regular-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 500;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/x/y/z/fonts/WorkSans/worksans-medium-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 600;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/x/y/z/fonts/WorkSans/worksans-semibold-webfont.woff2)
format("woff2");
}
@font-face {
font-family: "Work Sans";
font-style: normal;
font-weight: 700;
font-display: swap;
src: url(https://demo-app.keycloakify.dev/x/y/z/fonts/WorkSans/worksans-bold-webfont.woff2)
format("woff2");
}
`;
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true); expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
}); });
}); });

View File

@ -12,7 +12,8 @@ export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_reac
async function setupSampleReactProject(destDir: string) { async function setupSampleReactProject(destDir: string) {
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": destDir "destDirPath": destDir,
"doUseCache": false
}); });
} }
let parsedPackageJson: Record<string, unknown> = {}; let parsedPackageJson: Record<string, unknown> = {};
@ -51,17 +52,19 @@ describe("Sample Project", () => {
await setupSampleReactProject(sampleReactProjectDirPath); await setupSampleReactProject(sampleReactProjectDirPath);
await initializeEmailTheme(); await initializeEmailTheme();
const projectDirPath = process.cwd();
const destDirPath = pathJoin( const destDirPath = pathJoin(
readBuildOptions({ readBuildOptions({
"processArgv": ["--silent"], "processArgv": ["--silent"],
"projectDirPath": process.cwd() projectDirPath
}).keycloakifyBuildDirPath, }).keycloakifyBuildDirPath,
"src", "src",
"main", "main",
"resources", "resources",
"theme" "theme"
); );
await downloadBuiltinKeycloakTheme({ destDirPath, keycloakVersion: "11.0.3", "isSilent": false }); await downloadBuiltinKeycloakTheme({ destDirPath, "keycloakVersion": "11.0.3", projectDirPath });
}, },
{ timeout: 90000 } { timeout: 90000 }
); );
@ -77,17 +80,19 @@ describe("Sample Project", () => {
await setupSampleReactProject(pathJoin(sampleReactProjectDirPath, "custom_input")); await setupSampleReactProject(pathJoin(sampleReactProjectDirPath, "custom_input"));
await initializeEmailTheme(); await initializeEmailTheme();
const projectDirPath = process.cwd();
const destDirPath = pathJoin( const destDirPath = pathJoin(
readBuildOptions({ readBuildOptions({
"processArgv": ["--silent"], "processArgv": ["--silent"],
"projectDirPath": process.cwd() projectDirPath
}).keycloakifyBuildDirPath, }).keycloakifyBuildDirPath,
"src", "src",
"main", "main",
"resources", "resources",
"theme" "theme"
); );
await downloadBuiltinKeycloakTheme({ destDirPath, "keycloakVersion": "11.0.3", "isSilent": false }); await downloadBuiltinKeycloakTheme({ destDirPath, "keycloakVersion": "11.0.3", projectDirPath });
}, },
{ timeout: 90000 } { timeout: 90000 }
); );

View File

@ -3,6 +3,7 @@ import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip";
export async function setupSampleReactProject(destDirPath: string) { export async function setupSampleReactProject(destDirPath: string) {
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": destDirPath "destDirPath": destDirPath,
"doUseCache": false
}); });
} }