From 1c25b69160ffe39ba0e9ef382d58b4b052746c3e Mon Sep 17 00:00:00 2001 From: garronej Date: Mon, 21 Aug 2023 05:54:17 +0200 Subject: [PATCH] Remove --external-assets option --- src/bin/download-builtin-keycloak-theme.ts | 1 + src/bin/keycloakify/BuildOptions.ts | 309 ++++++------------ .../keycloakify/generateFtl/generateFtl.ts | 61 +--- src/bin/keycloakify/generateJavaStackFiles.ts | 7 +- .../generateStartKeycloakTestingContainer.ts | 7 +- .../generateTheme/generateTheme.ts | 58 +--- .../replaceImportsFromStaticInJsCode.ts | 60 +--- .../replacers/replaceImportsInCssCode.ts | 10 +- .../replaceImportsInInlineCssCode.ts | 34 +- src/bin/tools/downloadAndUnzip.ts | 47 ++- test/bin/replaceImportFromStatic.spec.ts | 161 +-------- test/bin/setupSampleReactProject.spec.ts | 15 +- test/bin/setupSampleReactProject.ts | 3 +- 13 files changed, 197 insertions(+), 576 deletions(-) diff --git a/src/bin/download-builtin-keycloak-theme.ts b/src/bin/download-builtin-keycloak-theme.ts index bd2248ed..e37480ba 100644 --- a/src/bin/download-builtin-keycloak-theme.ts +++ b/src/bin/download-builtin-keycloak-theme.ts @@ -15,6 +15,7 @@ export async function downloadBuiltinKeycloakTheme(params: { projectDirPath: str console.log("Downloading Keycloak theme...", { keycloakVersion }); await downloadAndUnzip({ + "doUseCache": true, projectDirPath, destDirPath, "url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`, diff --git a/src/bin/keycloakify/BuildOptions.ts b/src/bin/keycloakify/BuildOptions.ts index b32f9501..b48927b6 100644 --- a/src/bin/keycloakify/BuildOptions.ts +++ b/src/bin/keycloakify/BuildOptions.ts @@ -4,228 +4,135 @@ import { parse as urlParse } from "url"; import { typeGuard } from "tsafe/typeGuard"; import { symToStr } from "tsafe/symToStr"; import { bundlers, getParsedPackageJson, type Bundler } from "./parsedPackageJson"; -import * as fs from "fs"; import { join as pathJoin, sep as pathSep } from "path"; import parseArgv from "minimist"; /** Consolidated build option gathered form CLI arguments and config in package.json */ -export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets; - -export namespace BuildOptions { - export type Common = { - isSilent: boolean; - themeVersion: string; - themeName: string; - extraThemeNames: string[]; - extraThemeProperties: string[] | undefined; - groupId: string; - artifactId: string; - bundler: Bundler; - keycloakVersionDefaultAssets: string; - /** Directory of your built react project. Defaults to {cwd}/build */ - reactAppBuildDirPath: string; - /** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */ - keycloakifyBuildDirPath: string; - }; - - export type Standalone = Common & { - isStandalone: true; - 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 type BuildOptions = { + isSilent: boolean; + themeVersion: string; + themeName: string; + extraThemeNames: string[]; + extraThemeProperties: string[] | undefined; + groupId: string; + artifactId: string; + bundler: Bundler; + keycloakVersionDefaultAssets: string; + /** Directory of your built react project. Defaults to {cwd}/build */ + reactAppBuildDirPath: string; + /** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */ + 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/" */ + urlPathname: string | undefined; +}; export function readBuildOptions(params: { projectDirPath: string; processArgv: string[] }): BuildOptions { const { projectDirPath, processArgv } = params; - const { isExternalAssetsCliParamProvided, isSilentCliParamProvided } = (() => { + const { isSilentCliParamProvided } = (() => { const argv = parseArgv(processArgv); return { - "isSilentCliParamProvided": typeof argv["silent"] === "boolean" ? argv["silent"] : false, - "isExternalAssetsCliParamProvided": typeof argv["external-assets"] === "boolean" ? argv["external-assets"] : false + "isSilentCliParamProvided": typeof argv["silent"] === "boolean" ? argv["silent"] : false }; })(); const parsedPackageJson = getParsedPackageJson({ projectDirPath }); - const url = (() => { - const { homepage } = parsedPackageJson; + const { name, keycloakify = {}, version, homepage } = parsedPackageJson; - let url: URL | undefined = undefined; + const { extraThemeProperties, groupId, artifactId, bundler, keycloakVersionDefaultAssets, extraThemeNames = [] } = keycloakify ?? {}; - if (homepage !== undefined) { - url = new URL(homepage); - } + const themeName = + keycloakify.themeName ?? + name + .replace(/^@(.*)/, "$1") + .split("/") + .join("-"); - const CNAME = (() => { - const cnameFilePath = pathJoin(projectDirPath, "public", "CNAME"); + return { + themeName, + extraThemeNames, + "bundler": (() => { + const { KEYCLOAKIFY_BUNDLER } = process.env; - if (!fs.existsSync(cnameFilePath)) { + assert( + typeGuard(KEYCLOAKIFY_BUNDLER, [undefined, ...id(bundlers)].includes(KEYCLOAKIFY_BUNDLER)), + `${symToStr({ KEYCLOAKIFY_BUNDLER })} should be one of ${bundlers.join(", ")}` + ); + + return KEYCLOAKIFY_BUNDLER ?? bundler ?? "keycloakify"; + })(), + "artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? artifactId ?? `${themeName}-keycloak-theme`, + "groupId": (() => { + const fallbackGroupId = `${themeName}.keycloak`; + + return ( + process.env.KEYCLOAKIFY_GROUP_ID ?? + groupId ?? + (!homepage + ? fallbackGroupId + : urlParse(homepage) + .host?.replace(/:[0-9]+$/, "") + ?.split(".") + .reverse() + .join(".") ?? fallbackGroupId) + ".keycloak" + ); + })(), + "themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? process.env.KEYCLOAKIFY_VERSION ?? version ?? "0.0.0", + extraThemeProperties, + "isSilent": isSilentCliParamProvided, + "keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3", + "reactAppBuildDirPath": (() => { + let { reactAppBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {}; + + if (reactAppBuildDirPath === undefined) { + return pathJoin(projectDirPath, "build"); + } + + if (pathSep === "\\") { + reactAppBuildDirPath = reactAppBuildDirPath.replace(/\//g, pathSep); + } + + if (reactAppBuildDirPath.startsWith(`.${pathSep}`)) { + return pathJoin(projectDirPath, reactAppBuildDirPath); + } + + return reactAppBuildDirPath; + })(), + "keycloakifyBuildDirPath": (() => { + let { keycloakifyBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {}; + + if (keycloakifyBuildDirPath === undefined) { + return pathJoin(projectDirPath, "build_keycloak"); + } + + if (pathSep === "\\") { + keycloakifyBuildDirPath = keycloakifyBuildDirPath.replace(/\//g, pathSep); + } + + if (keycloakifyBuildDirPath.startsWith(`.${pathSep}`)) { + return pathJoin(projectDirPath, keycloakifyBuildDirPath); + } + + return keycloakifyBuildDirPath; + })(), + "urlPathname": (() => { + const { homepage } = parsedPackageJson; + + let url: URL | undefined = undefined; + + if (homepage !== undefined) { + url = new URL(homepage); + } + + if (url === undefined) { 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 { extraThemeProperties, groupId, artifactId, bundler, keycloakVersionDefaultAssets, extraThemeNames = [] } = keycloakify ?? {}; - - const themeName = - keycloakify.themeName ?? - name - .replace(/^@(.*)/, "$1") - .split("/") - .join("-"); - - return { - themeName, - extraThemeNames, - "bundler": (() => { - const { KEYCLOAKIFY_BUNDLER } = process.env; - - assert( - typeGuard( - KEYCLOAKIFY_BUNDLER, - [undefined, ...id(bundlers)].includes(KEYCLOAKIFY_BUNDLER) - ), - `${symToStr({ KEYCLOAKIFY_BUNDLER })} should be one of ${bundlers.join(", ")}` - ); - - return KEYCLOAKIFY_BUNDLER ?? bundler ?? "keycloakify"; - })(), - "artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? artifactId ?? `${themeName}-keycloak-theme`, - "groupId": (() => { - const fallbackGroupId = `${themeName}.keycloak`; - - return ( - process.env.KEYCLOAKIFY_GROUP_ID ?? - groupId ?? - (!homepage - ? fallbackGroupId - : urlParse(homepage) - .host?.replace(/:[0-9]+$/, "") - ?.split(".") - .reverse() - .join(".") ?? fallbackGroupId) + ".keycloak" - ); - })(), - "themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? process.env.KEYCLOAKIFY_VERSION ?? version ?? "0.0.0", - extraThemeProperties, - "isSilent": isSilentCliParamProvided, - "keycloakVersionDefaultAssets": keycloakVersionDefaultAssets ?? "11.0.3", - "reactAppBuildDirPath": (() => { - let { reactAppBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {}; - - if (reactAppBuildDirPath === undefined) { - return pathJoin(projectDirPath, "build"); - } - - if (pathSep === "\\") { - reactAppBuildDirPath = reactAppBuildDirPath.replace(/\//g, pathSep); - } - - if (reactAppBuildDirPath.startsWith(`.${pathSep}`)) { - return pathJoin(projectDirPath, reactAppBuildDirPath); - } - - return reactAppBuildDirPath; - })(), - "keycloakifyBuildDirPath": (() => { - let { keycloakifyBuildDirPath = undefined } = parsedPackageJson.keycloakify ?? {}; - - if (keycloakifyBuildDirPath === undefined) { - return pathJoin(projectDirPath, "build_keycloak"); - } - - if (pathSep === "\\") { - keycloakifyBuildDirPath = keycloakifyBuildDirPath.replace(/\//g, pathSep); - } - - if (keycloakifyBuildDirPath.startsWith(`.${pathSep}`)) { - return pathJoin(projectDirPath, keycloakifyBuildDirPath); - } - - return keycloakifyBuildDirPath; - })() - }; - })(); - - if (isExternalAssetsCliParamProvided) { - const commonExternalAssets = id({ - ...common, - "isStandalone": false - }); - - if (parsedPackageJson.keycloakify?.areAppAndKeycloakServerSharingSameDomain) { - return id({ - ...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({ - ...commonExternalAssets, - "areAppAndKeycloakServerSharingSameDomain": false, - "urlOrigin": url.origin, - "urlPathname": url.pathname - }); - } - } - - return id({ - ...common, - "isStandalone": true, - "urlPathname": url?.pathname - }); + const out = url.pathname.replace(/([^/])$/, "$1/"); + return out === "/" ? undefined : out; + })() + }; } diff --git a/src/bin/keycloakify/generateFtl/generateFtl.ts b/src/bin/keycloakify/generateFtl/generateFtl.ts index 81444562..fd9e704e 100644 --- a/src/bin/keycloakify/generateFtl/generateFtl.ts +++ b/src/bin/keycloakify/generateFtl/generateFtl.ts @@ -13,39 +13,11 @@ export const themeTypes = ["login", "account"] as const; export type ThemeType = (typeof themeTypes)[number]; -export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; - -export namespace BuildOptionsLike { - export type Common = { - themeName: string; - themeVersion: string; - }; - - export type Standalone = Common & { - isStandalone: true; - 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; - }; - } -} +export type BuildOptionsLike = { + themeName: string; + themeVersion: string; + urlPathname: string | undefined; +}; assert(); @@ -63,22 +35,23 @@ export function generateFtlFilesCodeFactory(params: { const $ = cheerio.load(indexHtmlCode); fix_imports_statements: { - if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) { - break fix_imports_statements; - } - $("script:not([src])").each((...[, element]) => { - const { fixedJsCode } = replaceImportsFromStaticInJsCode({ - "jsCode": $(element).html()!, - buildOptions - }); + const jsCode = $(element).html(); + + assert(jsCode !== null); + + const { fixedJsCode } = replaceImportsFromStaticInJsCode({ jsCode }); $(element).text(fixedJsCode); }); $("style").each((...[, element]) => { + const cssCode = $(element).html(); + + assert(cssCode !== null); + const { fixedCssCode } = replaceImportsInInlineCssCode({ - "cssCode": $(element).html()!, + cssCode, buildOptions }); @@ -100,9 +73,7 @@ export function generateFtlFilesCodeFactory(params: { $(element).attr( attrName, - buildOptions.isStandalone - ? href.replace(new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`), "${url.resourcesPath}/build/") - : href.replace(/^\//, `${buildOptions.urlOrigin}/`) + href.replace(new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`), "${url.resourcesPath}/build/") ); }) ); diff --git a/src/bin/keycloakify/generateJavaStackFiles.ts b/src/bin/keycloakify/generateJavaStackFiles.ts index 5808d0a1..5fa9266f 100644 --- a/src/bin/keycloakify/generateJavaStackFiles.ts +++ b/src/bin/keycloakify/generateJavaStackFiles.ts @@ -1,7 +1,6 @@ import * as fs from "fs"; import { join as pathJoin, dirname as pathDirname } from "path"; import { assert } from "tsafe/assert"; -import { Reflect } from "tsafe/Reflect"; import type { BuildOptions } from "./BuildOptions"; import type { ThemeType } from "./generateFtl"; @@ -13,11 +12,7 @@ export type BuildOptionsLike = { themeVersion: string; }; -{ - const buildOptions = Reflect(); - - assert(); -} +assert(); export function generateJavaStackFiles(params: { keycloakThemeBuildingDirPath: string; diff --git a/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts b/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts index 76622466..a5efffef 100644 --- a/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts +++ b/src/bin/keycloakify/generateStartKeycloakTestingContainer.ts @@ -1,7 +1,6 @@ import * as fs from "fs"; import { join as pathJoin } from "path"; import { assert } from "tsafe/assert"; -import { Reflect } from "tsafe/Reflect"; import type { BuildOptions } from "./BuildOptions"; export type BuildOptionsLike = { @@ -9,11 +8,7 @@ export type BuildOptionsLike = { extraThemeNames: string[]; }; -{ - const buildOptions = Reflect(); - - assert(); -} +assert(); generateStartKeycloakTestingContainer.basename = "start_keycloak_testing_container.sh"; diff --git a/src/bin/keycloakify/generateTheme/generateTheme.ts b/src/bin/keycloakify/generateTheme/generateTheme.ts index c85a7628..74ecb315 100644 --- a/src/bin/keycloakify/generateTheme/generateTheme.ts +++ b/src/bin/keycloakify/generateTheme/generateTheme.ts @@ -13,39 +13,13 @@ import { readFieldNameUsage } from "./readFieldNameUsage"; import { readExtraPagesNames } from "./readExtraPageNames"; import { generateMessageProperties } from "./generateMessageProperties"; -export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.ExternalAssets; - -export namespace BuildOptionsLike { - export type Common = { - themeName: string; - extraThemeProperties: string[] | undefined; - themeVersion: string; - keycloakVersionDefaultAssets: string; - }; - - export type Standalone = Common & { - isStandalone: true; - 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 type BuildOptionsLike = { + themeName: string; + extraThemeProperties: string[] | undefined; + themeVersion: string; + keycloakVersionDefaultAssets: string; + urlPathname: string | undefined; +}; assert(); @@ -85,17 +59,16 @@ export async function generateTheme(params: { copy_app_resources_to_theme_path: { const isFirstPass = themeType.indexOf(themeType) === 0; - if (!isFirstPass && !buildOptions.isStandalone) { + if (!isFirstPass) { break copy_app_resources_to_theme_path; } transformCodebase({ - "destDirPath": buildOptions.isStandalone ? pathJoin(themeDirPath, "resources", "build") : reactAppBuildDirPath, + "destDirPath": pathJoin(themeDirPath, "resources", "build"), "srcDirPath": reactAppBuildDirPath, "transformSourceCode": ({ filePath, sourceCode }) => { //NOTE: Prevent cycles, excludes the folder we generated for debug in public/ if ( - buildOptions.isStandalone && isInside({ "dirPath": pathJoin(reactAppBuildDirPath, basenameOfKeycloakDirInPublicDir), filePath @@ -105,10 +78,6 @@ export async function generateTheme(params: { } if (/\.css?$/i.test(filePath)) { - if (!buildOptions.isStandalone) { - return undefined; - } - const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode({ "cssCode": sourceCode.toString("utf8") }); @@ -128,19 +97,14 @@ export async function generateTheme(params: { } if (/\.js?$/i.test(filePath)) { - if (!buildOptions.isStandalone && buildOptions.areAppAndKeycloakServerSharingSameDomain) { - return undefined; - } - const { fixedJsCode } = replaceImportsFromStaticInJsCode({ - "jsCode": sourceCode.toString("utf8"), - buildOptions + "jsCode": sourceCode.toString("utf8") }); return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") }; } - return buildOptions.isStandalone ? { "modifiedSourceCode": sourceCode } : undefined; + return { "modifiedSourceCode": sourceCode }; } }); } diff --git a/src/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode.ts b/src/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode.ts index ac0ffceb..4ea29ffb 100644 --- a/src/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode.ts +++ b/src/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode.ts @@ -1,31 +1,6 @@ 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 namespace BuildOptionsLike { - export type Standalone = { - isStandalone: true; - }; - - export type ExternalAssets = { - isStandalone: false; - urlOrigin: string; - }; -} - -{ - const buildOptions = Reflect(); - - assert(!is(buildOptions)); - - assert(); -} - -export function replaceImportsFromStaticInJsCode(params: { jsCode: string; buildOptions: BuildOptionsLike }): { fixedJsCode: string } { +export function replaceImportsFromStaticInJsCode(params: { jsCode: string }): { fixedJsCode: string } { /* NOTE: @@ -38,7 +13,7 @@ export function replaceImportsFromStaticInJsCode(params: { jsCode: string; build will always run in keycloak context. */ - const { jsCode, buildOptions } = params; + const { jsCode } = params; const getReplaceArgs = (language: "js" | "css"): Parameters => [ 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(){ var pd= Object.getOwnPropertyDescriptor(${n}, "p"); if( pd === undefined || pd.configurable ){ - ${ - buildOptions.isStandalone - ? ` - Object.defineProperty(${n}, "p", { - get: function() { return window.${ftlValuesGlobalName}.url.resourcesPath; }, - set: function (){} - }); - ` - : ` - var p= ""; Object.defineProperty(${n}, "p", { - get: function() { return "${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : p; }, - set: function (value){ p = value;} + get: function() { return window.${ftlValuesGlobalName}.url.resourcesPath; }, + set: function (){} }); - ` - } } return "${u}"; - })()] = function(${e}) { return "${buildOptions.isStandalone ? "/build/" : ""}static/${language}/"` + })()] = function(${e}) { return "${true ? "/build/" : ""}static/${language}/"` ]; const fixedJsCode = jsCode .replace(...getReplaceArgs("js")) .replace(...getReplaceArgs("css")) - .replace(/([a-zA-Z]+\.[a-zA-Z]+)\+"static\//g, (...[, group]) => - buildOptions.isStandalone - ? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/` - : `("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : ${group}) + "static/` - ) + .replace(/[a-zA-Z]+\.[a-zA-Z]+\+"static\//g, `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`) //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]) => - buildOptions.isStandalone - ? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},` - : `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${buildOptions.urlOrigin}/" : ${group2}) + ${group3},` + .replace( + /".chunk.css",([a-zA-Z])+=[a-zA-Z]+\.[a-zA-Z]+\+([a-zA-Z]+),/, + (...[, group1, group2]) => `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group2},` ); return { fixedJsCode }; diff --git a/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts b/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts index 278986ba..9212b7b0 100644 --- a/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts +++ b/src/bin/keycloakify/replacers/replaceImportsInCssCode.ts @@ -1,20 +1,12 @@ import * as crypto from "crypto"; import type { BuildOptions } from "../BuildOptions"; import { assert } from "tsafe/assert"; -import { is } from "tsafe/is"; -import { Reflect } from "tsafe/Reflect"; export type BuildOptionsLike = { urlPathname: string | undefined; }; -{ - const buildOptions = Reflect(); - - assert(!is(buildOptions)); - - assert(); -} +assert(); export function replaceImportsInCssCode(params: { cssCode: string }): { fixedCssCode: string; diff --git a/src/bin/keycloakify/replacers/replaceImportsInInlineCssCode.ts b/src/bin/keycloakify/replacers/replaceImportsInInlineCssCode.ts index c7551621..88b3e0e8 100644 --- a/src/bin/keycloakify/replacers/replaceImportsInInlineCssCode.ts +++ b/src/bin/keycloakify/replacers/replaceImportsInInlineCssCode.ts @@ -1,32 +1,11 @@ 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 type BuildOptionsLike = { + urlPathname: string | undefined; +}; -export namespace BuildOptionsLike { - export type Common = { - urlPathname: string | undefined; - }; - - export type Standalone = Common & { - isStandalone: true; - }; - - export type ExternalAssets = Common & { - isStandalone: false; - urlOrigin: string; - }; -} - -{ - const buildOptions = Reflect(); - - assert(!is(buildOptions)); - - assert(); -} +assert(); export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOptions: BuildOptionsLike }): { fixedCssCode: string; @@ -37,10 +16,7 @@ export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOp buildOptions.urlPathname === undefined ? /url\(["']?\/([^/][^)"']+)["']?\)/g : new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"), - (...[, group]) => - `url(${ - buildOptions.isStandalone ? "${url.resourcesPath}/build/" + group : buildOptions.urlOrigin + (buildOptions.urlPathname ?? "/") + group - })` + (...[, group]) => `url(\${url.resourcesPath}/build/${group})` ); return { fixedCssCode }; diff --git a/src/bin/tools/downloadAndUnzip.ts b/src/bin/tools/downloadAndUnzip.ts index 60643aff..0b1eb0ed 100644 --- a/src/bin/tools/downloadAndUnzip.ts +++ b/src/bin/tools/downloadAndUnzip.ts @@ -10,7 +10,7 @@ import { unzip, zip } from "./unzip"; const exec = promisify(execCallback); -function hash(s: string) { +function sha256(s: string) { return createHash("sha256").update(s).digest("hex"); } @@ -112,24 +112,35 @@ async function getFetchOptions(): Promise Promise; - }; -}) { - const { projectDirPath, url, destDirPath, specificDirsToExtract, preCacheTransform } = params; +export async function downloadAndUnzip( + params: { + url: string; + destDirPath: string; + specificDirsToExtract?: string[]; + preCacheTransform?: { + actionCacheId: string; + action: (params: { destDirPath: string }) => Promise; + }; + } & ( + | { + 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()}`) ).substring(0, 15); - const cacheRoot = pathJoin(process.env.XDG_CACHE_HOME ?? pathJoin(projectDirPath, "node_modules", ".cache"), "keycloakify"); - const zipFilePath = pathJoin(cacheRoot, `_${downloadHash}.zip`); - const extractDirPath = pathJoin(cacheRoot, `tmp_unzip_${downloadHash}`); + const cacheRoot = !rest.doUseCache + ? `tmp_${Math.random().toString().slice(2, 12)}` + : 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))) { const opts = await getFetchOptions(); @@ -167,4 +178,8 @@ export async function downloadAndUnzip(params: { "srcDirPath": extractDirPath, "destDirPath": destDirPath }); + + if (!rest.doUseCache) { + await rm(cacheRoot, { "recursive": true }); + } } diff --git a/test/bin/replaceImportFromStatic.spec.ts b/test/bin/replaceImportFromStatic.spec.ts index c7894aca..269ca255 100644 --- a/test/bin/replaceImportFromStatic.spec.ts +++ b/test/bin/replaceImportFromStatic.spec.ts @@ -35,10 +35,7 @@ describe("bin/js-transforms", () => { `; it("transforms standalone code properly", () => { const { fixedJsCode } = replaceImportsFromStaticInJsCode({ - "jsCode": jsCodeUntransformed, - "buildOptions": { - "isStandalone": true - } + "jsCode": jsCodeUntransformed }); 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); }); }); @@ -304,7 +241,6 @@ describe("bin/css-inline-transforms", () => { const { fixedCssCode } = replaceImportsInInlineCssCode({ cssCode, "buildOptions": { - "isStandalone": true, "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); }); }); @@ -430,7 +319,6 @@ describe("bin/css-inline-transforms", () => { const { fixedCssCode } = replaceImportsInInlineCssCode({ cssCode, "buildOptions": { - "isStandalone": true, "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); }); }); diff --git a/test/bin/setupSampleReactProject.spec.ts b/test/bin/setupSampleReactProject.spec.ts index 1a2cbb85..1995b49e 100644 --- a/test/bin/setupSampleReactProject.spec.ts +++ b/test/bin/setupSampleReactProject.spec.ts @@ -12,7 +12,8 @@ export const sampleReactProjectDirPath = pathJoin(getProjectRoot(), "sample_reac 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 + "destDirPath": destDir, + "doUseCache": false }); } let parsedPackageJson: Record = {}; @@ -51,17 +52,19 @@ describe("Sample Project", () => { await setupSampleReactProject(sampleReactProjectDirPath); await initializeEmailTheme(); + const projectDirPath = process.cwd(); + const destDirPath = pathJoin( readBuildOptions({ "processArgv": ["--silent"], - "projectDirPath": process.cwd() + projectDirPath }).keycloakifyBuildDirPath, "src", "main", "resources", "theme" ); - await downloadBuiltinKeycloakTheme({ destDirPath, keycloakVersion: "11.0.3", "isSilent": false }); + await downloadBuiltinKeycloakTheme({ destDirPath, "keycloakVersion": "11.0.3", projectDirPath }); }, { timeout: 90000 } ); @@ -77,17 +80,19 @@ describe("Sample Project", () => { await setupSampleReactProject(pathJoin(sampleReactProjectDirPath, "custom_input")); await initializeEmailTheme(); + const projectDirPath = process.cwd(); + const destDirPath = pathJoin( readBuildOptions({ "processArgv": ["--silent"], - "projectDirPath": process.cwd() + projectDirPath }).keycloakifyBuildDirPath, "src", "main", "resources", "theme" ); - await downloadBuiltinKeycloakTheme({ destDirPath, "keycloakVersion": "11.0.3", "isSilent": false }); + await downloadBuiltinKeycloakTheme({ destDirPath, "keycloakVersion": "11.0.3", projectDirPath }); }, { timeout: 90000 } ); diff --git a/test/bin/setupSampleReactProject.ts b/test/bin/setupSampleReactProject.ts index 12061e68..5ea6daf1 100644 --- a/test/bin/setupSampleReactProject.ts +++ b/test/bin/setupSampleReactProject.ts @@ -3,6 +3,7 @@ import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip"; export async function setupSampleReactProject(destDirPath: string) { await downloadAndUnzip({ "url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip", - "destDirPath": destDirPath + "destDirPath": destDirPath, + "doUseCache": false }); }