From 3c2820dc31d1008b23501cd3ee8e2e9fa89aa01a Mon Sep 17 00:00:00 2001 From: garronej Date: Wed, 23 Aug 2023 08:13:09 +0200 Subject: [PATCH] Meta progaming for detecting static assets usage a build time --- .../generateTheme/readFieldNameUsage.ts | 5 +- .../generateTheme/readStaticResourcesUsage.ts | 87 ++++++++++++++++++ test/bin/readStaticResourcesUsage.spec.ts | 91 +++++++++++++++++++ 3 files changed, 179 insertions(+), 4 deletions(-) create mode 100644 src/bin/keycloakify/generateTheme/readStaticResourcesUsage.ts create mode 100644 test/bin/readStaticResourcesUsage.spec.ts diff --git a/src/bin/keycloakify/generateTheme/readFieldNameUsage.ts b/src/bin/keycloakify/generateTheme/readFieldNameUsage.ts index cf0cbae9..77aa15da 100644 --- a/src/bin/keycloakify/generateTheme/readFieldNameUsage.ts +++ b/src/bin/keycloakify/generateTheme/readFieldNameUsage.ts @@ -3,7 +3,6 @@ import { removeDuplicates } from "evt/tools/reducers/removeDuplicates"; import { join as pathJoin } from "path"; import * as fs from "fs"; import type { ThemeType } from "../generateFtl"; -import { exclude } from "tsafe/exclude"; /** Assumes the theme type exists */ export function readFieldNameUsage(params: { keycloakifySrcDirPath: string; themeSrcDirPath: string; themeType: ThemeType }): string[] { @@ -11,9 +10,7 @@ export function readFieldNameUsage(params: { keycloakifySrcDirPath: string; them const fieldNames: string[] = []; - for (const srcDirPath of ([pathJoin(keycloakifySrcDirPath, themeType), pathJoin(themeSrcDirPath, themeType)] as const).filter( - exclude(undefined) - )) { + for (const srcDirPath of [pathJoin(keycloakifySrcDirPath, themeType), pathJoin(themeSrcDirPath, themeType)]) { const filePaths = crawl({ "dirPath": srcDirPath, "returnedPathsType": "absolute" }).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath)); for (const filePath of filePaths) { diff --git a/src/bin/keycloakify/generateTheme/readStaticResourcesUsage.ts b/src/bin/keycloakify/generateTheme/readStaticResourcesUsage.ts new file mode 100644 index 00000000..646e1765 --- /dev/null +++ b/src/bin/keycloakify/generateTheme/readStaticResourcesUsage.ts @@ -0,0 +1,87 @@ +import { crawl } from "../../tools/crawl"; +import { join as pathJoin } from "path"; +import * as fs from "fs"; +import type { ThemeType } from "../generateFtl"; + +/** Assumes the theme type exists */ +export function readStaticResourcesUsage(params: { keycloakifySrcDirPath: string; themeSrcDirPath: string; themeType: ThemeType }): { + resourcesCommonFilePaths: string[]; + resourcesFilePaths: string[]; +} { + const { keycloakifySrcDirPath, themeSrcDirPath, themeType } = params; + + const resourcesCommonFilePaths = new Set(); + const resourcesFilePaths = new Set(); + + for (const srcDirPath of [pathJoin(keycloakifySrcDirPath, themeType), pathJoin(themeSrcDirPath, themeType)]) { + const filePaths = crawl({ "dirPath": srcDirPath, "returnedPathsType": "absolute" }).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath)); + + for (const filePath of filePaths) { + const rawSourceFile = fs.readFileSync(filePath).toString("utf8"); + + if (!rawSourceFile.includes("resourcesCommonPath")) { + continue; + } + + if (!rawSourceFile.includes("resourcesPath")) { + continue; + } + + const wrap = readPaths({ rawSourceFile }); + + wrap.resourcesCommonFilePaths.forEach(filePath => resourcesCommonFilePaths.add(filePath)); + wrap.resourcesFilePaths.forEach(filePath => resourcesFilePaths.add(filePath)); + } + } + + return { + "resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths), + "resourcesFilePaths": Array.from(resourcesFilePaths) + }; +} + +/** Exported for testing purpose */ +export function readPaths(params: { rawSourceFile: string }): { + resourcesCommonFilePaths: string[]; + resourcesFilePaths: string[]; +} { + const { rawSourceFile } = params; + + const resourcesCommonFilePaths = new Set(); + const resourcesFilePaths = new Set(); + + for (const isCommon of [true, false]) { + const set = isCommon ? resourcesCommonFilePaths : resourcesFilePaths; + + { + const regexp = new RegExp(`resources${isCommon ? "Common" : ""}Path\\s*}([^\`]+)\``, "g"); + + const matches = [...rawSourceFile.matchAll(regexp)]; + + for (const match of matches) { + const filePath = match[1]; + + set.add(filePath); + } + } + + { + const regexp = new RegExp(`resources${isCommon ? "Common" : ""}Path\\s*[+,]\\s*["']([^"'\`]+)["'\`]`, "g"); + + const matches = [...rawSourceFile.matchAll(regexp)]; + + for (const match of matches) { + const filePath = match[1]; + + set.add(filePath); + } + } + } + + const removePrefixSlash = (filePath: string) => (filePath.startsWith("/") ? filePath.slice(1) : filePath); + + return { + "resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths).map(removePrefixSlash), + "resourcesFilePaths": Array.from(resourcesFilePaths).map(removePrefixSlash) + }; +} diff --git a/test/bin/readStaticResourcesUsage.spec.ts b/test/bin/readStaticResourcesUsage.spec.ts new file mode 100644 index 00000000..1008b95f --- /dev/null +++ b/test/bin/readStaticResourcesUsage.spec.ts @@ -0,0 +1,91 @@ +import { readPaths } from "keycloakify/bin/keycloakify/generateTheme/readStaticResourcesUsage"; +import { same } from "evt/tools/inDepth/same"; +import { expect, it, describe } from "vitest"; + +describe("Ensure it's able to extract used Keycloak resources", () => { + const expectedPaths = { + "resourcesCommonFilePaths": [ + "node_modules/patternfly/dist/css/patternfly.min.css", + "node_modules/patternfly/dist/css/patternfly-additions.min.css", + "lib/zocial/zocial.css" + ], + "resourcesFilePaths": ["css/login.css"] + }; + + it("works with coding style n°1", () => { + const paths = readPaths({ + "rawSourceFile": ` + const { isReady } = usePrepareTemplate({ + "doFetchDefaultThemeResources": doUseDefaultCss, + "styles": [ + \`\${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css\`, + \`\${ + url.resourcesCommonPath + }/node_modules/patternfly/dist/css/patternfly-additions.min.css\`, + \`\${resourcesCommonPath }/lib/zocial/zocial.css\`, + \`\${url.resourcesPath}/css/login.css\` + ], + "htmlClassName": getClassName("kcHtmlClass"), + "bodyClassName": undefined + }); + ` + }); + + expect(same(paths, expectedPaths)).toBe(true); + }); + + it("works with coding style n°2", () => { + const paths = readPaths({ + "rawSourceFile": ` + + const { isReady } = usePrepareTemplate({ + "doFetchDefaultThemeResources": doUseDefaultCss, + "styles": [ + url.resourcesCommonPath + "/node_modules/patternfly/dist/css/patternfly.min.css", + url.resourcesCommonPath + '/node_modules/patternfly/dist/css/patternfly-additions.min.css', + url.resourcesCommonPath + + "/lib/zocial/zocial.css", + url.resourcesPath + + '/css/login.css' + ], + "htmlClassName": getClassName("kcHtmlClass"), + "bodyClassName": undefined + }); + + + ` + }); + + console.log(paths); + console.log(expectedPaths); + + expect(same(paths, expectedPaths)).toBe(true); + }); + + it("works with coding style n°3", () => { + const paths = readPaths({ + "rawSourceFile": ` + + const { isReady } = usePrepareTemplate({ + "doFetchDefaultThemeResources": doUseDefaultCss, + "styles": [ + path.join(resourcesCommonPath,"/node_modules/patternfly/dist/css/patternfly.min.css"), + path.join(url.resourcesCommonPath, '/node_modules/patternfly/dist/css/patternfly-additions.min.css'), + path.join(url.resourcesCommonPath, + "/lib/zocial/zocial.css"), + pathJoin( + url.resourcesPath, + 'css/login.css' + ) + ], + "htmlClassName": getClassName("kcHtmlClass"), + "bodyClassName": undefined + }); + + + ` + }); + + expect(same(paths, expectedPaths)).toBe(true); + }); +});