Merge main, fix runtime error in scripts, fix clean build

This commit is contained in:
Joseph Garrone 2024-02-07 20:01:26 +01:00
commit dca8c9f9d7
14 changed files with 162 additions and 120 deletions

View File

@ -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)! 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 👼 ## 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. 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.

View File

@ -5,6 +5,7 @@ import { crawl } from "../src/bin/tools/crawl";
import { downloadBuiltinKeycloakTheme } from "../src/bin/download-builtin-keycloak-theme"; import { downloadBuiltinKeycloakTheme } from "../src/bin/download-builtin-keycloak-theme";
import { getProjectRoot } from "../src/bin/tools/getProjectRoot"; import { getProjectRoot } from "../src/bin/tools/getProjectRoot";
import { getLogger } from "../src/bin/tools/logger"; 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, // NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
// update the version array for generating for newer version. // update the version array for generating for newer version.
@ -23,6 +24,10 @@ async function main() {
fs.rmSync(tmpDirPath, { "recursive": true, "force": true }); fs.rmSync(tmpDirPath, { "recursive": true, "force": true });
fs.mkdirSync(tmpDirPath);
fs.writeFileSync(pathJoin(tmpDirPath, ".gitignore"), Buffer.from("/*\n!.gitignore\n", "utf8"));
await downloadBuiltinKeycloakTheme({ await downloadBuiltinKeycloakTheme({
keycloakVersion, keycloakVersion,
"destDirPath": tmpDirPath, "destDirPath": tmpDirPath,

View File

@ -23,7 +23,6 @@ export async function promptKeycloakVersion() {
const tags = [ const tags = [
...(await getLatestsSemVersionedTag({ ...(await getLatestsSemVersionedTag({
"count": 10, "count": 10,
"doIgnoreBeta": true,
"owner": "keycloak", "owner": "keycloak",
"repo": "keycloak" "repo": "keycloak"
}).then(arr => arr.map(({ tag }) => tag))), }).then(arr => arr.map(({ tag }) => tag))),

View File

@ -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";
}
}

99
src/bin/tools/SemVer.ts Normal file
View File

@ -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<SemVer, "parsedFrom"> = {
"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<SemVer, "parsedFrom">): 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";
}
}

View File

@ -1,17 +1,17 @@
import * as fs from "fs"; 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[]) => { const crawlRec = (dirPath: string, filePaths: string[]) => {
for (const file_name of fs.readdirSync(dir_path)) { for (const basename of fs.readdirSync(dirPath)) {
const file_path = path.join(dir_path, file_name); const fileOrDirPath = pathJoin(dirPath, basename);
if (fs.lstatSync(file_path).isDirectory()) { if (fs.lstatSync(fileOrDirPath).isDirectory()) {
crawlRec(file_path, paths); crawlRec(fileOrDirPath, filePaths);
continue; continue;
} }
paths.push(file_path); filePaths.push(fileOrDirPath);
} }
}; };
@ -27,6 +27,6 @@ export function crawl(params: { dirPath: string; returnedPathsType: "absolute" |
case "absolute": case "absolute":
return filePaths; return filePaths;
case "relative to dirPath": case "relative to dirPath":
return filePaths.map(filePath => path.relative(dirPath, filePath)); return filePaths.map(filePath => pathRelative(dirPath, filePath));
} }
} }

View File

@ -206,9 +206,15 @@ export async function downloadAndUnzip(
if (specificDirsToExtract !== undefined || preCacheTransform !== undefined) { if (specificDirsToExtract !== undefined || preCacheTransform !== undefined) {
await unzip(zipFilePath, extractDirPath, specificDirsToExtract); await unzip(zipFilePath, extractDirPath, specificDirsToExtract);
try {
await preCacheTransform?.action({ await preCacheTransform?.action({
"destDirPath": extractDirPath "destDirPath": extractDirPath
}); });
} catch (error) {
await Promise.all([rm(extractDirPath, { "recursive": true }), unlink(zipFilePath)]);
throw error;
}
await unlink(zipFilePath); await unlink(zipFilePath);

View File

@ -1,13 +1,13 @@
import * as fs from "fs/promises"; import * as fs from "fs/promises";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { NpmModuleVersion } from "./NpmModuleVersion"; import { SemVer } from "./SemVer";
/** /**
* Polyfill of fs.rm(dirPath, { "recursive": true }) * Polyfill of fs.rm(dirPath, { "recursive": true })
* For older version of Node * For older version of Node
*/ */
export async function rm(dirPath: string, options: { recursive: true; force?: true }) { 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); return fs.rm(dirPath, options);
} }

View File

@ -1,14 +1,15 @@
import * as fs from "fs"; import * as fs from "fs";
import { join as pathJoin } from "path"; import { join as pathJoin } from "path";
import { NpmModuleVersion } from "./NpmModuleVersion"; import { SemVer } from "./SemVer";
/** /**
* Polyfill of fs.rmSync(dirPath, { "recursive": true }) * Polyfill of fs.rmSync(dirPath, { "recursive": true })
* For older version of Node * For older version of Node
*/ */
export function rmSync(dirPath: string, options: { recursive: true; force?: true }) { 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); fs.rmSync(dirPath, options);
return;
} }
const { force = true } = options; const { force = true } = options;

View File

@ -1,39 +1,39 @@
import { listTagsFactory } from "./listTags"; import { listTagsFactory } from "./listTags";
import type { Octokit } from "@octokit/rest"; import type { Octokit } from "@octokit/rest";
import { NpmModuleVersion } from "../NpmModuleVersion"; import { SemVer } from "../SemVer";
export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) { export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
const { octokit } = params; 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; 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 }); const { listTags } = listTagsFactory({ octokit });
for await (const tag of listTags({ owner, repo })) { for await (const tag of listTags({ owner, repo })) {
let version: NpmModuleVersion; let version: SemVer;
try { try {
version = NpmModuleVersion.parse(tag.replace(/^[vV]?/, "")); version = SemVer.parse(tag.replace(/^[vV]?/, ""));
} catch { } catch {
continue; continue;
} }
if (doIgnoreBeta && version.betaPreRelease !== undefined) { if (version.rc !== undefined) {
continue; continue;
} }
semVersionedTags.push({ tag, version }); 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 }; return { getLatestsSemVersionedTag };

View File

@ -1,7 +1,6 @@
import * as fs from "fs"; import * as fs from "fs";
import * as path from "path"; import * as path from "path";
import { crawl } from "./crawl"; import { crawl } from "./crawl";
import { id } from "tsafe/id";
import { rmSync } from "../tools/fs.rmSync"; import { rmSync } from "../tools/fs.rmSync";
type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; fileRelativePath: string }) => 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. * like filtering out some files or modifying them.
* */ * */
export function transformCodebase(params: { srcDirPath: string; destDirPath: string; transformSourceCode?: TransformSourceCode }) { export function transformCodebase(params: { srcDirPath: string; destDirPath: string; transformSourceCode?: TransformSourceCode }) {
const { const { srcDirPath, transformSourceCode } = params;
srcDirPath,
transformSourceCode = id<TransformSourceCode>(({ sourceCode }) => ({
"modifiedSourceCode": sourceCode
}))
} = params;
let { destDirPath } = params; let { destDirPath } = params;
const isTargetSameAsSource = path.relative(srcDirPath, destDirPath) === ""; 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" })) { for (const fileRelativePath of crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })) {
const filePath = path.join(srcDirPath, fileRelativePath); 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({ const transformSourceCodeResult = transformSourceCode({
"sourceCode": fs.readFileSync(filePath), "sourceCode": fs.readFileSync(filePath),
@ -44,16 +51,13 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str
continue; continue;
} }
fs.mkdirSync(path.dirname(path.join(destDirPath, fileRelativePath)), { fs.mkdirSync(path.dirname(destFilePath), {
"recursive": true "recursive": true
}); });
const { newFileName, modifiedSourceCode } = transformSourceCodeResult; const { newFileName, modifiedSourceCode } = transformSourceCodeResult;
fs.writeFileSync( fs.writeFileSync(path.join(path.dirname(destFilePath), newFileName ?? path.basename(destFilePath)), modifiedSourceCode);
path.join(path.dirname(path.join(destDirPath, fileRelativePath)), newFileName ?? path.basename(fileRelativePath)),
modifiedSourceCode
);
} }
if (isTargetSameAsSource) { if (isTargetSameAsSource) {

View File

@ -7,6 +7,7 @@
"lib": ["es2019", "es2020.bigint", "es2020.string", "es2020.symbol.wellknown"], "lib": ["es2019", "es2020.bigint", "es2020.string", "es2020.symbol.wellknown"],
"outDir": "../../dist/vite-plugin", "outDir": "../../dist/vite-plugin",
"rootDir": ".", "rootDir": ".",
// https://github.com/vitejs/vite/issues/15112#issuecomment-1823908010
"skipLibCheck": true "skipLibCheck": true
}, },
"references": [ "references": [

View File

@ -4,20 +4,17 @@
"target": "es5", "target": "es5",
"lib": ["es2015", "DOM", "ES2019.Object"], "lib": ["es2015", "DOM", "ES2019.Object"],
"esModuleInterop": true, "esModuleInterop": true,
"declaration": true,
"outDir": "../dist_test",
"sourceMap": true,
"newLine": "LF",
"noUnusedLocals": true, "noUnusedLocals": true,
"noUnusedParameters": true, "noUnusedParameters": true,
"incremental": false,
"strict": true, "strict": true,
"downlevelIteration": true, "downlevelIteration": true,
"jsx": "react-jsx", "jsx": "react-jsx",
"noFallthroughCasesInSwitch": true, "noFallthroughCasesInSwitch": true,
"paths": { "paths": {
"keycloakify/*": ["../src/*"] "keycloakify/*": ["../src/*"]
} },
// https://github.com/vitejs/vite/issues/15112#issuecomment-1823908010
"skipLibCheck": true
}, },
"include": ["../src", "."] "include": ["../src", "."]
} }

View File

@ -1,11 +1,10 @@
/// <reference types="vitest" /> import { defineConfig } from "vitest/config";
import { defineConfig } from "vite"; import { resolve as pathResolve } from "path";
import path from "path";
export default defineConfig({ export default defineConfig({
"test": { "test": {
"alias": { "alias": {
"keycloakify": path.resolve(__dirname, "./src") "keycloakify": pathResolve(__dirname, "./src")
}, },
"watchExclude": ["**/node_modules/**", "**/dist/**", "**/sample_react_project/**"] "watchExclude": ["**/node_modules/**", "**/dist/**", "**/sample_react_project/**"]
} }