diff --git a/README.md b/README.md index fcd5d23b..f441dcf1 100644 --- a/README.md +++ b/README.md @@ -43,6 +43,10 @@ Keycloakify is fully compatible with Keycloak 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, [~~22~~](https://github.com/keycloakify/keycloakify/issues/389#issuecomment-1822509763), **23** [and up](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)! +> 📣 I've observed that a few people have unstarred the project recently. +> I'm concerned that I may have inadvertently introduced some misinformation in the documentation, leading to frustration. +> If you're having a negative experience, [please let me know so I can resolve the issue](https://github.com/keycloakify/keycloakify/discussions/507). + ## Sponsor 👼 We are exclusively sponsored by [Cloud IAM](https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github), a French company offering Keycloak as a service. diff --git a/scripts/generate-i18n-messages.ts b/scripts/generate-i18n-messages.ts index 660551bc..6775835b 100644 --- a/scripts/generate-i18n-messages.ts +++ b/scripts/generate-i18n-messages.ts @@ -5,6 +5,7 @@ import { crawl } from "../src/bin/tools/crawl"; import { downloadBuiltinKeycloakTheme } from "../src/bin/download-builtin-keycloak-theme"; import { getProjectRoot } from "../src/bin/tools/getProjectRoot"; import { getLogger } from "../src/bin/tools/logger"; +import { rmSync } from "../src/bin/tools/fs.rmSync"; // NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files, // update the version array for generating for newer version. @@ -23,6 +24,10 @@ async function main() { fs.rmSync(tmpDirPath, { "recursive": true, "force": true }); + fs.mkdirSync(tmpDirPath); + + fs.writeFileSync(pathJoin(tmpDirPath, ".gitignore"), Buffer.from("/*\n!.gitignore\n", "utf8")); + await downloadBuiltinKeycloakTheme({ keycloakVersion, "destDirPath": tmpDirPath, diff --git a/src/bin/promptKeycloakVersion.ts b/src/bin/promptKeycloakVersion.ts index 63e7b25c..52e14191 100644 --- a/src/bin/promptKeycloakVersion.ts +++ b/src/bin/promptKeycloakVersion.ts @@ -23,7 +23,6 @@ export async function promptKeycloakVersion() { const tags = [ ...(await getLatestsSemVersionedTag({ "count": 10, - "doIgnoreBeta": true, "owner": "keycloak", "repo": "keycloak" }).then(arr => arr.map(({ tag }) => tag))), diff --git a/src/bin/tools/NpmModuleVersion.ts b/src/bin/tools/NpmModuleVersion.ts deleted file mode 100644 index e517d4c8..00000000 --- a/src/bin/tools/NpmModuleVersion.ts +++ /dev/null @@ -1,73 +0,0 @@ -export type NpmModuleVersion = { - major: number; - minor: number; - patch: number; - betaPreRelease?: number; -}; - -export namespace NpmModuleVersion { - export function parse(versionStr: string): NpmModuleVersion { - const match = versionStr.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-beta.([0-9]+))?/); - - if (!match) { - throw new Error(`${versionStr} is not a valid NPM version`); - } - - return { - "major": parseInt(match[1]), - "minor": parseInt(match[2]), - "patch": parseInt(match[3]), - ...(() => { - const str = match[4]; - return str === undefined ? {} : { "betaPreRelease": parseInt(str) }; - })() - }; - } - - export function stringify(v: NpmModuleVersion) { - return `${v.major}.${v.minor}.${v.patch}${v.betaPreRelease === undefined ? "" : `-beta.${v.betaPreRelease}`}`; - } - - /** - * - * v1 < v2 => -1 - * v1 === v2 => 0 - * v1 > v2 => 1 - * - */ - export function compare(v1: NpmModuleVersion, v2: NpmModuleVersion): -1 | 0 | 1 { - const sign = (diff: number): -1 | 0 | 1 => (diff === 0 ? 0 : diff < 0 ? -1 : 1); - const noUndefined = (n: number | undefined) => n ?? Infinity; - - for (const level of ["major", "minor", "patch", "betaPreRelease"] as const) { - if (noUndefined(v1[level]) !== noUndefined(v2[level])) { - return sign(noUndefined(v1[level]) - noUndefined(v2[level])); - } - } - - return 0; - } - - /* - console.log(compare(parse("3.0.0-beta.3"), parse("3.0.0")) === -1 ) - console.log(compare(parse("3.0.0-beta.3"), parse("3.0.0-beta.4")) === -1 ) - console.log(compare(parse("3.0.0-beta.3"), parse("4.0.0")) === -1 ) - */ - - export function bumpType(params: { versionBehindStr: string; versionAheadStr: string }): "major" | "minor" | "patch" | "betaPreRelease" | "same" { - const versionAhead = parse(params.versionAheadStr); - const versionBehind = parse(params.versionBehindStr); - - if (compare(versionBehind, versionAhead) === 1) { - throw new Error(`Version regression ${versionBehind} -> ${versionAhead}`); - } - - for (const level of ["major", "minor", "patch", "betaPreRelease"] as const) { - if (versionBehind[level] !== versionAhead[level]) { - return level; - } - } - - return "same"; - } -} diff --git a/src/bin/tools/SemVer.ts b/src/bin/tools/SemVer.ts new file mode 100644 index 00000000..f8db9c3a --- /dev/null +++ b/src/bin/tools/SemVer.ts @@ -0,0 +1,99 @@ +export type SemVer = { + major: number; + minor: number; + patch: number; + rc?: number; + parsedFrom: string; +}; + +export namespace SemVer { + const bumpTypes = ["major", "minor", "patch", "rc", "no bump"] as const; + + export type BumpType = (typeof bumpTypes)[number]; + + export function parse(versionStr: string): SemVer { + const match = versionStr.match(/^v?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-rc.([0-9]+))?$/); + + if (!match) { + throw new Error(`${versionStr} is not a valid semantic version`); + } + + const semVer: Omit = { + "major": parseInt(match[1]), + "minor": parseInt(match[2]), + "patch": (() => { + const str = match[3]; + + return str === undefined ? 0 : parseInt(str); + })(), + ...(() => { + const str = match[4]; + return str === undefined ? {} : { "rc": parseInt(str) }; + })() + }; + + const initialStr = stringify(semVer); + + Object.defineProperty(semVer, "parsedFrom", { + "enumerable": true, + "get": function () { + const currentStr = stringify(this); + + if (currentStr !== initialStr) { + throw new Error(`SemVer.parsedFrom can't be read anymore, the version have been modified from ${initialStr} to ${currentStr}`); + } + + return versionStr; + } + }); + + return semVer as any; + } + + export function stringify(v: Omit): string { + return `${v.major}.${v.minor}.${v.patch}${v.rc === undefined ? "" : `-rc.${v.rc}`}`; + } + + /** + * + * v1 < v2 => -1 + * v1 === v2 => 0 + * v1 > v2 => 1 + * + */ + export function compare(v1: SemVer, v2: SemVer): -1 | 0 | 1 { + const sign = (diff: number): -1 | 0 | 1 => (diff === 0 ? 0 : diff < 0 ? -1 : 1); + const noUndefined = (n: number | undefined) => n ?? Infinity; + + for (const level of ["major", "minor", "patch", "rc"] as const) { + if (noUndefined(v1[level]) !== noUndefined(v2[level])) { + return sign(noUndefined(v1[level]) - noUndefined(v2[level])); + } + } + + return 0; + } + + /* + console.log(compare(parse("3.0.0-rc.3"), parse("3.0.0")) === -1 ) + console.log(compare(parse("3.0.0-rc.3"), parse("3.0.0-rc.4")) === -1 ) + console.log(compare(parse("3.0.0-rc.3"), parse("4.0.0")) === -1 ) + */ + + export function bumpType(params: { versionBehind: string | SemVer; versionAhead: string | SemVer }): BumpType | "no bump" { + const versionAhead = typeof params.versionAhead === "string" ? parse(params.versionAhead) : params.versionAhead; + const versionBehind = typeof params.versionBehind === "string" ? parse(params.versionBehind) : params.versionBehind; + + if (compare(versionBehind, versionAhead) === 1) { + throw new Error(`Version regression ${stringify(versionBehind)} -> ${stringify(versionAhead)}`); + } + + for (const level of ["major", "minor", "patch", "rc"] as const) { + if (versionBehind[level] !== versionAhead[level]) { + return level; + } + } + + return "no bump"; + } +} diff --git a/src/bin/tools/crawl.ts b/src/bin/tools/crawl.ts index dd53a6d9..c83a6e54 100644 --- a/src/bin/tools/crawl.ts +++ b/src/bin/tools/crawl.ts @@ -1,17 +1,17 @@ import * as fs from "fs"; -import * as path from "path"; +import { join as pathJoin, relative as pathRelative } from "path"; -const crawlRec = (dir_path: string, paths: string[]) => { - for (const file_name of fs.readdirSync(dir_path)) { - const file_path = path.join(dir_path, file_name); +const crawlRec = (dirPath: string, filePaths: string[]) => { + for (const basename of fs.readdirSync(dirPath)) { + const fileOrDirPath = pathJoin(dirPath, basename); - if (fs.lstatSync(file_path).isDirectory()) { - crawlRec(file_path, paths); + if (fs.lstatSync(fileOrDirPath).isDirectory()) { + crawlRec(fileOrDirPath, filePaths); continue; } - paths.push(file_path); + filePaths.push(fileOrDirPath); } }; @@ -27,6 +27,6 @@ export function crawl(params: { dirPath: string; returnedPathsType: "absolute" | case "absolute": return filePaths; case "relative to dirPath": - return filePaths.map(filePath => path.relative(dirPath, filePath)); + return filePaths.map(filePath => pathRelative(dirPath, filePath)); } } diff --git a/src/bin/tools/downloadAndUnzip.ts b/src/bin/tools/downloadAndUnzip.ts index d5c82015..7f131454 100644 --- a/src/bin/tools/downloadAndUnzip.ts +++ b/src/bin/tools/downloadAndUnzip.ts @@ -206,9 +206,15 @@ export async function downloadAndUnzip( if (specificDirsToExtract !== undefined || preCacheTransform !== undefined) { await unzip(zipFilePath, extractDirPath, specificDirsToExtract); - await preCacheTransform?.action({ - "destDirPath": extractDirPath - }); + try { + await preCacheTransform?.action({ + "destDirPath": extractDirPath + }); + } catch (error) { + await Promise.all([rm(extractDirPath, { "recursive": true }), unlink(zipFilePath)]); + + throw error; + } await unlink(zipFilePath); diff --git a/src/bin/tools/fs.rm.ts b/src/bin/tools/fs.rm.ts index d7d41f50..4dd28c2b 100644 --- a/src/bin/tools/fs.rm.ts +++ b/src/bin/tools/fs.rm.ts @@ -1,13 +1,13 @@ import * as fs from "fs/promises"; import { join as pathJoin } from "path"; -import { NpmModuleVersion } from "./NpmModuleVersion"; +import { SemVer } from "./SemVer"; /** * Polyfill of fs.rm(dirPath, { "recursive": true }) * For older version of Node */ export async function rm(dirPath: string, options: { recursive: true; force?: true }) { - if (NpmModuleVersion.compare(NpmModuleVersion.parse(process.version), NpmModuleVersion.parse("14.14.0")) > 0) { + if (SemVer.compare(SemVer.parse(process.version), SemVer.parse("14.14.0")) > 0) { return fs.rm(dirPath, options); } diff --git a/src/bin/tools/fs.rmSync.ts b/src/bin/tools/fs.rmSync.ts index ff7f5ff8..2e9cd237 100644 --- a/src/bin/tools/fs.rmSync.ts +++ b/src/bin/tools/fs.rmSync.ts @@ -1,14 +1,15 @@ import * as fs from "fs"; import { join as pathJoin } from "path"; -import { NpmModuleVersion } from "./NpmModuleVersion"; +import { SemVer } from "./SemVer"; /** * Polyfill of fs.rmSync(dirPath, { "recursive": true }) * For older version of Node */ export function rmSync(dirPath: string, options: { recursive: true; force?: true }) { - if (NpmModuleVersion.compare(NpmModuleVersion.parse(process.version), NpmModuleVersion.parse("14.14.0")) > 0) { + if (SemVer.compare(SemVer.parse(process.version), SemVer.parse("14.14.0")) > 0) { fs.rmSync(dirPath, options); + return; } const { force = true } = options; diff --git a/src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts b/src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts index f3fb505b..92c7ffb6 100644 --- a/src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts +++ b/src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts @@ -1,39 +1,39 @@ import { listTagsFactory } from "./listTags"; import type { Octokit } from "@octokit/rest"; -import { NpmModuleVersion } from "../NpmModuleVersion"; +import { SemVer } from "../SemVer"; export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) { const { octokit } = params; - async function getLatestsSemVersionedTag(params: { owner: string; repo: string; doIgnoreBeta: boolean; count: number }): Promise< + async function getLatestsSemVersionedTag(params: { owner: string; repo: string; count: number }): Promise< { tag: string; - version: NpmModuleVersion; + version: SemVer; }[] > { - const { owner, repo, doIgnoreBeta, count } = params; + const { owner, repo, count } = params; - const semVersionedTags: { tag: string; version: NpmModuleVersion }[] = []; + const semVersionedTags: { tag: string; version: SemVer }[] = []; const { listTags } = listTagsFactory({ octokit }); for await (const tag of listTags({ owner, repo })) { - let version: NpmModuleVersion; + let version: SemVer; try { - version = NpmModuleVersion.parse(tag.replace(/^[vV]?/, "")); + version = SemVer.parse(tag.replace(/^[vV]?/, "")); } catch { continue; } - if (doIgnoreBeta && version.betaPreRelease !== undefined) { + if (version.rc !== undefined) { continue; } semVersionedTags.push({ tag, version }); } - return semVersionedTags.sort(({ version: vX }, { version: vY }) => NpmModuleVersion.compare(vY, vX)).slice(0, count); + return semVersionedTags.sort(({ version: vX }, { version: vY }) => SemVer.compare(vY, vX)).slice(0, count); } return { getLatestsSemVersionedTag }; diff --git a/src/bin/tools/transformCodebase.ts b/src/bin/tools/transformCodebase.ts index 5b59978e..fad6c983 100644 --- a/src/bin/tools/transformCodebase.ts +++ b/src/bin/tools/transformCodebase.ts @@ -1,7 +1,6 @@ import * as fs from "fs"; import * as path from "path"; import { crawl } from "./crawl"; -import { id } from "tsafe/id"; import { rmSync } from "../tools/fs.rmSync"; type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; fileRelativePath: string }) => @@ -17,12 +16,7 @@ type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; file * like filtering out some files or modifying them. * */ export function transformCodebase(params: { srcDirPath: string; destDirPath: string; transformSourceCode?: TransformSourceCode }) { - const { - srcDirPath, - transformSourceCode = id(({ sourceCode }) => ({ - "modifiedSourceCode": sourceCode - })) - } = params; + const { srcDirPath, transformSourceCode } = params; let { destDirPath } = params; const isTargetSameAsSource = path.relative(srcDirPath, destDirPath) === ""; @@ -33,6 +27,19 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str for (const fileRelativePath of crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })) { const filePath = path.join(srcDirPath, fileRelativePath); + const destFilePath = path.join(destDirPath, fileRelativePath); + + // NOTE: Optimization, if we don't need to transform the file, just copy + // it using the lower level implementation. + if (transformSourceCode === undefined) { + fs.mkdirSync(path.dirname(destFilePath), { + "recursive": true + }); + + fs.copyFileSync(filePath, destFilePath); + + continue; + } const transformSourceCodeResult = transformSourceCode({ "sourceCode": fs.readFileSync(filePath), @@ -44,16 +51,13 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str continue; } - fs.mkdirSync(path.dirname(path.join(destDirPath, fileRelativePath)), { + fs.mkdirSync(path.dirname(destFilePath), { "recursive": true }); const { newFileName, modifiedSourceCode } = transformSourceCodeResult; - fs.writeFileSync( - path.join(path.dirname(path.join(destDirPath, fileRelativePath)), newFileName ?? path.basename(fileRelativePath)), - modifiedSourceCode - ); + fs.writeFileSync(path.join(path.dirname(destFilePath), newFileName ?? path.basename(destFilePath)), modifiedSourceCode); } if (isTargetSameAsSource) { diff --git a/src/vite-plugin/tsconfig.json b/src/vite-plugin/tsconfig.json index cb7242ca..3fd7aee3 100644 --- a/src/vite-plugin/tsconfig.json +++ b/src/vite-plugin/tsconfig.json @@ -7,6 +7,7 @@ "lib": ["es2019", "es2020.bigint", "es2020.string", "es2020.symbol.wellknown"], "outDir": "../../dist/vite-plugin", "rootDir": ".", + // https://github.com/vitejs/vite/issues/15112#issuecomment-1823908010 "skipLibCheck": true }, "references": [ diff --git a/test/tsconfig.json b/test/tsconfig.json index 8c6c685d..3c10636a 100644 --- a/test/tsconfig.json +++ b/test/tsconfig.json @@ -4,20 +4,17 @@ "target": "es5", "lib": ["es2015", "DOM", "ES2019.Object"], "esModuleInterop": true, - "declaration": true, - "outDir": "../dist_test", - "sourceMap": true, - "newLine": "LF", "noUnusedLocals": true, "noUnusedParameters": true, - "incremental": false, "strict": true, "downlevelIteration": true, "jsx": "react-jsx", "noFallthroughCasesInSwitch": true, "paths": { "keycloakify/*": ["../src/*"] - } + }, + // https://github.com/vitejs/vite/issues/15112#issuecomment-1823908010 + "skipLibCheck": true }, "include": ["../src", "."] } diff --git a/vitest.config.ts b/vitest.config.ts index d3941fb6..bad5ed77 100644 --- a/vitest.config.ts +++ b/vitest.config.ts @@ -1,11 +1,10 @@ -/// -import { defineConfig } from "vite"; -import path from "path"; +import { defineConfig } from "vitest/config"; +import { resolve as pathResolve } from "path"; export default defineConfig({ "test": { "alias": { - "keycloakify": path.resolve(__dirname, "./src") + "keycloakify": pathResolve(__dirname, "./src") }, "watchExclude": ["**/node_modules/**", "**/dist/**", "**/sample_react_project/**"] }