Compare commits
27 Commits
v9.4.0-rc.
...
v9.4.0
Author | SHA1 | Date | |
---|---|---|---|
a1c0bfda6c | |||
617dcef09d | |||
d9c406800a | |||
54b869def1 | |||
d80a583979 | |||
99bfd7379b | |||
5f257382fa | |||
e3e6847c82 | |||
4ee0823acb | |||
d466123b1c | |||
21cbc14a48 | |||
b2f2c3e386 | |||
b03340ed10 | |||
5b563d8e9b | |||
2790487fc7 | |||
ad5a368065 | |||
7c0a631a9a | |||
4a8920749a | |||
8ab118dd06 | |||
e6661cb898 | |||
1671850714 | |||
d568bafe04 | |||
43dcce8478 | |||
ad70a4cffd | |||
6d4a948dd8 | |||
839ba6a964 | |||
b5cfdb9d0a |
@ -130,6 +130,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
|
||||
# Changelog highlights
|
||||
|
||||
## 9.4
|
||||
|
||||
**Vite Support 🎉**
|
||||
|
||||
## 9.0
|
||||
|
||||
Bring back support for account themes in Keycloak v23 and up! [See issue](https://github.com/keycloakify/keycloakify/issues/389).
|
||||
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "9.4.0-rc.10",
|
||||
"version": "9.4.0",
|
||||
"description": "Create Keycloak themes using React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -119,7 +119,6 @@
|
||||
"make-fetch-happen": "^11.0.3",
|
||||
"minimal-polyfills": "^2.2.2",
|
||||
"minimist": "^1.2.6",
|
||||
"path-browserify": "^1.0.1",
|
||||
"react-markdown": "^5.0.3",
|
||||
"recast": "^0.23.3",
|
||||
"rfc4648": "^1.5.2",
|
||||
|
@ -3,7 +3,7 @@ import * as fs from "fs";
|
||||
import { join as pathJoin, relative as pathRelative, dirname as pathDirname, sep as pathSep } from "path";
|
||||
import { crawl } from "../src/bin/tools/crawl";
|
||||
import { downloadBuiltinKeycloakTheme } from "../src/bin/download-builtin-keycloak-theme";
|
||||
import { getProjectRoot } from "../src/bin/tools/getProjectRoot";
|
||||
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
|
||||
import { getLogger } from "../src/bin/tools/logger";
|
||||
|
||||
// NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
|
||||
@ -19,7 +19,9 @@ const logger = getLogger({ isSilent });
|
||||
async function main() {
|
||||
const keycloakVersion = "23.0.4";
|
||||
|
||||
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
|
||||
const thisCodebaseRootDirPath = getThisCodebaseRootDirPath();
|
||||
|
||||
const tmpDirPath = pathJoin(thisCodebaseRootDirPath, "tmp_xImOef9dOd44");
|
||||
|
||||
fs.rmSync(tmpDirPath, { "recursive": true, "force": true });
|
||||
|
||||
@ -31,7 +33,8 @@ async function main() {
|
||||
keycloakVersion,
|
||||
"destDirPath": tmpDirPath,
|
||||
"buildOptions": {
|
||||
"cacheDirPath": pathJoin(getProjectRoot(), "node_modules", ".cache", "keycloakify")
|
||||
"cacheDirPath": pathJoin(thisCodebaseRootDirPath, "node_modules", ".cache", "keycloakify"),
|
||||
"npmWorkspaceRootDirPath": thisCodebaseRootDirPath
|
||||
}
|
||||
});
|
||||
|
||||
@ -72,14 +75,13 @@ async function main() {
|
||||
return;
|
||||
}
|
||||
|
||||
const baseMessagesDirPath = pathJoin(getProjectRoot(), "src", themeType, "i18n", "baseMessages");
|
||||
const baseMessagesDirPath = pathJoin(thisCodebaseRootDirPath, "src", themeType, "i18n", "baseMessages");
|
||||
|
||||
const languages = Object.keys(recordForPageType);
|
||||
|
||||
const generatedFileHeader = [
|
||||
`//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`,
|
||||
"//PLEASE DO NOT EDIT MANUALLY",
|
||||
""
|
||||
`//This code was automatically generated by running ${pathRelative(thisCodebaseRootDirPath, __filename)}`,
|
||||
"//PLEASE DO NOT EDIT MANUALLY"
|
||||
].join("\n");
|
||||
|
||||
languages.forEach(language => {
|
||||
@ -92,6 +94,7 @@ async function main() {
|
||||
Buffer.from(
|
||||
[
|
||||
generatedFileHeader,
|
||||
"",
|
||||
"/* spell-checker: disable */",
|
||||
`const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`,
|
||||
"",
|
||||
@ -110,10 +113,15 @@ async function main() {
|
||||
Buffer.from(
|
||||
[
|
||||
generatedFileHeader,
|
||||
`import * as en from "./en";`,
|
||||
"",
|
||||
"export async function getMessages(currentLanguageTag: string) {",
|
||||
" const { default: messages } = await (() => {",
|
||||
" switch (currentLanguageTag) {",
|
||||
...languages.map(language => ` case "${language}": return import("./${language}");`),
|
||||
` case "en": return en;`,
|
||||
...languages
|
||||
.filter(language => language !== "en")
|
||||
.map(language => ` case "${language}": return import("./${language}");`),
|
||||
' default: return { "default": {} };',
|
||||
" }",
|
||||
" })();",
|
||||
|
@ -1,14 +0,0 @@
|
||||
import fs from "fs";
|
||||
import path from "path";
|
||||
import zodToJsonSchema from "zod-to-json-schema";
|
||||
import { zParsedPackageJson } from "../src/bin/keycloakify/parsedPackageJson";
|
||||
|
||||
const jsonSchemaName = "keycloakifyPackageJsonSchema";
|
||||
const jsonSchema = zodToJsonSchema(zParsedPackageJson, jsonSchemaName);
|
||||
|
||||
const baseProperties = {
|
||||
// merges package.json schema with keycloakify properties
|
||||
"allOf": [{ "$ref": "https://json.schemastore.org/package.json" }, { "$ref": jsonSchemaName }]
|
||||
};
|
||||
|
||||
fs.writeFileSync(path.join(process.cwd(), "keycloakify-json-schema.json"), JSON.stringify({ ...baseProperties, ...jsonSchema }, null, 2));
|
@ -1,11 +1,11 @@
|
||||
import { execSync } from "child_process";
|
||||
import { join as pathJoin, relative as pathRelative } from "path";
|
||||
import { getProjectRoot } from "../src/bin/tools/getProjectRoot";
|
||||
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
|
||||
import * as fs from "fs";
|
||||
|
||||
const singletonDependencies: string[] = ["react", "@types/react"];
|
||||
|
||||
const rootDirPath = getProjectRoot();
|
||||
const rootDirPath = getThisCodebaseRootDirPath();
|
||||
|
||||
//NOTE: This is only required because of: https://github.com/garronej/ts-ci/blob/c0e207b9677523d4ec97fe672ddd72ccbb3c1cc4/README.md?plain=1#L54-L58
|
||||
fs.writeFileSync(
|
||||
|
@ -1,10 +1,11 @@
|
||||
import type { I18n } from "keycloakify/account/i18n";
|
||||
import type { TemplateProps, ClassKey } from "keycloakify/account/TemplateProps";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import type { KcContext } from "keycloakify/account/kcContext";
|
||||
|
||||
export type PageProps<KcContext, I18nExtended extends I18n> = {
|
||||
export type PageProps<NarowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
|
||||
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
||||
kcContext: KcContext;
|
||||
kcContext: NarowedKcContext;
|
||||
i18n: I18nExtended;
|
||||
doUseDefaultCss: boolean;
|
||||
classes?: Partial<Record<ClassKey, string>>;
|
||||
|
@ -2,7 +2,7 @@ export const nameOfTheGlobal = "kcContext";
|
||||
export const keycloak_resources = "keycloak-resources";
|
||||
export const resources_common = "resources-common";
|
||||
export const lastKeycloakVersionWithAccountV1 = "21.1.2";
|
||||
export const resolvedViteConfigJsonBasename = ".keycloakifyViteConfig.json";
|
||||
export const resolvedViteConfigJsonBasename = "vite.json";
|
||||
export const basenameOfTheKeycloakifyResourcesDir = "build";
|
||||
|
||||
export const themeTypes = ["login", "account"] as const;
|
||||
|
@ -1,17 +1,44 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { downloadKeycloakStaticResources } from "./keycloakify/generateTheme/downloadKeycloakStaticResources";
|
||||
import { join as pathJoin } from "path";
|
||||
import { downloadKeycloakStaticResources, type BuildOptionsLike } from "./keycloakify/generateTheme/downloadKeycloakStaticResources";
|
||||
import { join as pathJoin, relative as pathRelative } from "path";
|
||||
import { readBuildOptions } from "./keycloakify/buildOptions";
|
||||
import { themeTypes, keycloak_resources, lastKeycloakVersionWithAccountV1 } from "./constants";
|
||||
import { readThisNpmProjectVersion } from "./tools/readThisNpmProjectVersion";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import * as fs from "fs";
|
||||
import { rmSync } from "./tools/fs.rmSync";
|
||||
|
||||
(async () => {
|
||||
const buildOptions = readBuildOptions({
|
||||
"processArgv": process.argv.slice(2)
|
||||
export async function copyKeycloakResourcesToPublic(params: { processArgv: string[] }) {
|
||||
const { processArgv } = params;
|
||||
|
||||
const buildOptions = readBuildOptions({ processArgv });
|
||||
|
||||
const destDirPath = pathJoin(buildOptions.publicDirPath, keycloak_resources);
|
||||
|
||||
const keycloakifyBuildinfoFilePath = pathJoin(destDirPath, "keycloakify.buildinfo");
|
||||
|
||||
const { keycloakifyBuildinfoRaw } = generateKeycloakifyBuildinfoRaw({
|
||||
destDirPath,
|
||||
"keycloakifyVersion": readThisNpmProjectVersion(),
|
||||
buildOptions
|
||||
});
|
||||
|
||||
const reservedDirPath = pathJoin(buildOptions.publicDirPath, keycloak_resources);
|
||||
skip_if_already_done: {
|
||||
if (!fs.existsSync(keycloakifyBuildinfoFilePath)) {
|
||||
break skip_if_already_done;
|
||||
}
|
||||
|
||||
const keycloakifyBuildinfoRaw_previousRun = fs.readFileSync(keycloakifyBuildinfoFilePath).toString("utf8");
|
||||
|
||||
if (keycloakifyBuildinfoRaw_previousRun !== keycloakifyBuildinfoRaw) {
|
||||
break skip_if_already_done;
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
rmSync(destDirPath, { "force": true, "recursive": true });
|
||||
|
||||
for (const themeType of themeTypes) {
|
||||
await downloadKeycloakStaticResources({
|
||||
@ -24,13 +51,13 @@ import * as fs from "fs";
|
||||
}
|
||||
})(),
|
||||
themeType,
|
||||
"themeDirPath": reservedDirPath,
|
||||
"themeDirPath": destDirPath,
|
||||
buildOptions
|
||||
});
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(reservedDirPath, "README.txt"),
|
||||
pathJoin(destDirPath, "README.txt"),
|
||||
Buffer.from(
|
||||
// prettier-ignore
|
||||
[
|
||||
@ -41,4 +68,45 @@ import * as fs from "fs";
|
||||
);
|
||||
|
||||
fs.writeFileSync(pathJoin(buildOptions.publicDirPath, keycloak_resources, ".gitignore"), Buffer.from("*", "utf8"));
|
||||
})();
|
||||
|
||||
fs.writeFileSync(keycloakifyBuildinfoFilePath, Buffer.from(keycloakifyBuildinfoRaw, "utf8"));
|
||||
}
|
||||
|
||||
export function generateKeycloakifyBuildinfoRaw(params: {
|
||||
destDirPath: string;
|
||||
keycloakifyVersion: string;
|
||||
buildOptions: BuildOptionsLike & {
|
||||
loginThemeResourcesFromKeycloakVersion: string;
|
||||
};
|
||||
}) {
|
||||
const { destDirPath, keycloakifyVersion, buildOptions } = params;
|
||||
|
||||
const { cacheDirPath, npmWorkspaceRootDirPath, loginThemeResourcesFromKeycloakVersion, ...rest } = buildOptions;
|
||||
|
||||
assert<Equals<typeof rest, {}>>(true);
|
||||
|
||||
const keycloakifyBuildinfoRaw = JSON.stringify(
|
||||
{
|
||||
keycloakifyVersion,
|
||||
"buildOptions": {
|
||||
loginThemeResourcesFromKeycloakVersion,
|
||||
"cacheDirPath": pathRelative(destDirPath, cacheDirPath),
|
||||
"npmWorkspaceRootDirPath": pathRelative(destDirPath, npmWorkspaceRootDirPath)
|
||||
}
|
||||
},
|
||||
null,
|
||||
2
|
||||
);
|
||||
|
||||
return { keycloakifyBuildinfoRaw };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
await copyKeycloakResourcesToPublic({
|
||||
"processArgv": process.argv.slice(2)
|
||||
});
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
main();
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
import { join as pathJoin } from "path";
|
||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
||||
import { downloadAndUnzip } from "./downloadAndUnzip";
|
||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||
import { getLogger } from "./tools/logger";
|
||||
import { readBuildOptions, type BuildOptions } from "./keycloakify/buildOptions";
|
||||
@ -13,6 +13,7 @@ import { transformCodebase } from "./tools/transformCodebase";
|
||||
|
||||
export type BuildOptionsLike = {
|
||||
cacheDirPath: string;
|
||||
npmWorkspaceRootDirPath: string;
|
||||
};
|
||||
|
||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||
@ -21,11 +22,10 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st
|
||||
const { keycloakVersion, destDirPath, buildOptions } = params;
|
||||
|
||||
await downloadAndUnzip({
|
||||
"doUseCache": true,
|
||||
"cacheDirPath": buildOptions.cacheDirPath,
|
||||
destDirPath,
|
||||
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
|
||||
"specificDirsToExtract": ["", "-community"].map(ext => `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`),
|
||||
buildOptions,
|
||||
"preCacheTransform": {
|
||||
"actionCacheId": "npm install and build",
|
||||
"action": async ({ destDirPath }) => {
|
||||
|
203
src/bin/downloadAndUnzip.ts
Normal file
203
src/bin/downloadAndUnzip.ts
Normal file
@ -0,0 +1,203 @@
|
||||
import { createHash } from "crypto";
|
||||
import { mkdir, writeFile, unlink } from "fs/promises";
|
||||
import fetch from "make-fetch-happen";
|
||||
import { dirname as pathDirname, join as pathJoin, basename as pathBasename } from "path";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { transformCodebase } from "./tools/transformCodebase";
|
||||
import { unzip, zip } from "./tools/unzip";
|
||||
import { rm } from "./tools/fs.rm";
|
||||
import * as child_process from "child_process";
|
||||
import { existsAsync } from "./tools/fs.existsAsync";
|
||||
import type { BuildOptions } from "./keycloakify/buildOptions";
|
||||
import { getProxyFetchOptions } from "./tools/fetchProxyOptions";
|
||||
|
||||
export type BuildOptionsLike = {
|
||||
cacheDirPath: string;
|
||||
npmWorkspaceRootDirPath: string;
|
||||
};
|
||||
|
||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||
|
||||
export async function downloadAndUnzip(params: {
|
||||
url: string;
|
||||
destDirPath: string;
|
||||
specificDirsToExtract?: string[];
|
||||
preCacheTransform?: {
|
||||
actionCacheId: string;
|
||||
action: (params: { destDirPath: string }) => Promise<void>;
|
||||
};
|
||||
buildOptions: BuildOptionsLike;
|
||||
}) {
|
||||
const { url, destDirPath, specificDirsToExtract, preCacheTransform, buildOptions } = params;
|
||||
|
||||
const { extractDirPath, zipFilePath } = (() => {
|
||||
const zipFileBasenameWithoutExt = generateFileNameFromURL({
|
||||
url,
|
||||
"preCacheTransform":
|
||||
preCacheTransform === undefined
|
||||
? undefined
|
||||
: {
|
||||
"actionCacheId": preCacheTransform.actionCacheId,
|
||||
"actionFootprint": preCacheTransform.action.toString()
|
||||
}
|
||||
});
|
||||
|
||||
const zipFilePath = pathJoin(buildOptions.cacheDirPath, `${zipFileBasenameWithoutExt}.zip`);
|
||||
const extractDirPath = pathJoin(buildOptions.cacheDirPath, `tmp_unzip_${zipFileBasenameWithoutExt}`);
|
||||
|
||||
return { zipFilePath, extractDirPath };
|
||||
})();
|
||||
|
||||
download_zip_and_transform: {
|
||||
if (await existsAsync(zipFilePath)) {
|
||||
break download_zip_and_transform;
|
||||
}
|
||||
|
||||
const { response, isFromRemoteCache } = await (async () => {
|
||||
const proxyFetchOptions = await getProxyFetchOptions({
|
||||
"npmWorkspaceRootDirPath": buildOptions.npmWorkspaceRootDirPath
|
||||
});
|
||||
|
||||
const response = await fetch(
|
||||
`https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/${pathBasename(zipFilePath)}`,
|
||||
proxyFetchOptions
|
||||
);
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
response,
|
||||
"isFromRemoteCache": true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
"response": await fetch(url, proxyFetchOptions),
|
||||
"isFromRemoteCache": false
|
||||
};
|
||||
})();
|
||||
|
||||
await mkdir(pathDirname(zipFilePath), { "recursive": true });
|
||||
|
||||
/**
|
||||
* The correct way to fix this is to upgrade node-fetch beyond 3.2.5
|
||||
* (see https://github.com/node-fetch/node-fetch/issues/1295#issuecomment-1144061991.)
|
||||
* Unfortunately, octokit (a dependency of keycloakify) also uses node-fetch, and
|
||||
* does not support node-fetch 3.x. So we stick around with this band-aid until
|
||||
* octokit upgrades.
|
||||
*/
|
||||
response.body?.setMaxListeners(Number.MAX_VALUE);
|
||||
assert(typeof response.body !== "undefined" && response.body != null);
|
||||
|
||||
await writeFile(zipFilePath, response.body);
|
||||
|
||||
if (isFromRemoteCache) {
|
||||
break download_zip_and_transform;
|
||||
}
|
||||
|
||||
if (specificDirsToExtract === undefined && preCacheTransform === undefined) {
|
||||
break download_zip_and_transform;
|
||||
}
|
||||
|
||||
await unzip(zipFilePath, extractDirPath, specificDirsToExtract);
|
||||
|
||||
try {
|
||||
await preCacheTransform?.action({
|
||||
"destDirPath": extractDirPath
|
||||
});
|
||||
} catch (error) {
|
||||
await Promise.all([rm(extractDirPath, { "recursive": true }), unlink(zipFilePath)]);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
await unlink(zipFilePath);
|
||||
|
||||
await zip(extractDirPath, zipFilePath);
|
||||
|
||||
await rm(extractDirPath, { "recursive": true });
|
||||
|
||||
upload_to_remot_cache_if_admin: {
|
||||
const githubToken = process.env["KEYCLOAKIFY_ADMIN_GITHUB_PERSONAL_ACCESS_TOKEN"];
|
||||
|
||||
if (githubToken === undefined) {
|
||||
break upload_to_remot_cache_if_admin;
|
||||
}
|
||||
|
||||
console.log("uploading to remote cache");
|
||||
|
||||
try {
|
||||
child_process.execSync(`which putasset`);
|
||||
} catch {
|
||||
child_process.execSync(`npm install -g putasset`);
|
||||
}
|
||||
|
||||
try {
|
||||
child_process.execFileSync("putasset", [
|
||||
"--owner",
|
||||
"keycloakify",
|
||||
"--repo",
|
||||
"keycloakify",
|
||||
"--tag",
|
||||
"v0.0.1",
|
||||
"--filename",
|
||||
zipFilePath,
|
||||
"--token",
|
||||
githubToken
|
||||
]);
|
||||
} catch {
|
||||
console.log("upload failed, asset probably already exists in remote cache");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await unzip(zipFilePath, extractDirPath);
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": extractDirPath,
|
||||
"destDirPath": destDirPath
|
||||
});
|
||||
|
||||
await rm(extractDirPath, { "recursive": true });
|
||||
}
|
||||
|
||||
function generateFileNameFromURL(params: {
|
||||
url: string;
|
||||
preCacheTransform:
|
||||
| {
|
||||
actionCacheId: string;
|
||||
actionFootprint: string;
|
||||
}
|
||||
| undefined;
|
||||
}): string {
|
||||
const { preCacheTransform } = params;
|
||||
|
||||
// Parse the URL
|
||||
const url = new URL(params.url);
|
||||
|
||||
// Extract pathname and remove leading slashes
|
||||
let fileName = url.pathname.replace(/^\//, "").replace(/\//g, "_");
|
||||
|
||||
// Optionally, add query parameters replacing special characters
|
||||
if (url.search) {
|
||||
fileName += url.search.replace(/[&=?]/g, "-");
|
||||
}
|
||||
|
||||
// Replace any characters that are not valid in filenames
|
||||
fileName = fileName.replace(/[^a-zA-Z0-9-_]/g, "");
|
||||
|
||||
// Trim or pad the fileName to a specific length
|
||||
fileName = fileName.substring(0, 50);
|
||||
|
||||
add_pre_cache_transform: {
|
||||
if (preCacheTransform === undefined) {
|
||||
break add_pre_cache_transform;
|
||||
}
|
||||
|
||||
// Sanitize actionCacheId the same way as other components
|
||||
const sanitizedActionCacheId = preCacheTransform.actionCacheId.replace(/[^a-zA-Z0-9-_]/g, "_");
|
||||
|
||||
fileName += `_${sanitizedActionCacheId}_${createHash("sha256").update(preCacheTransform.actionFootprint).digest("hex").substring(0, 5)}`;
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
import { getProjectRoot } from "./tools/getProjectRoot";
|
||||
import { getThisCodebaseRootDirPath } from "./tools/getThisCodebaseRootDirPath";
|
||||
import cliSelect from "cli-select";
|
||||
import { loginThemePageIds, accountThemePageIds, type LoginThemePageId, type AccountThemePageId } from "./keycloakify/generateFtl";
|
||||
import { capitalize } from "tsafe/capitalize";
|
||||
@ -11,11 +11,14 @@ import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
||||
import { assert, Equals } from "tsafe/assert";
|
||||
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
|
||||
import { themeTypes, type ThemeType } from "./constants";
|
||||
import { getReactAppRootDirPath } from "./keycloakify/buildOptions/getReactAppRootDirPath";
|
||||
|
||||
(async () => {
|
||||
console.log("Select a theme type");
|
||||
|
||||
const reactAppRootDirPath = process.cwd();
|
||||
const { reactAppRootDirPath } = getReactAppRootDirPath({
|
||||
"processArgv": process.argv.slice(2)
|
||||
});
|
||||
|
||||
const { value: themeType } = await cliSelect<ThemeType>({
|
||||
"values": [...themeTypes]
|
||||
@ -55,7 +58,7 @@ import { themeTypes, type ThemeType } from "./constants";
|
||||
process.exit(-1);
|
||||
}
|
||||
|
||||
await writeFile(targetFilePath, await readFile(pathJoin(getProjectRoot(), "src", themeType, "pages", pageBasename)));
|
||||
await writeFile(targetFilePath, await readFile(pathJoin(getThisCodebaseRootDirPath(), "src", themeType, "pages", pageBasename)));
|
||||
|
||||
console.log(`${pathRelative(process.cwd(), targetFilePath)} created`);
|
||||
})();
|
||||
|
@ -4,7 +4,10 @@ import { join as pathJoin } from "path";
|
||||
import parseArgv from "minimist";
|
||||
import { getAbsoluteAndInOsFormatPath } from "../../tools/getAbsoluteAndInOsFormatPath";
|
||||
import { readResolvedViteConfig } from "./resolvedViteConfig";
|
||||
import { getKeycloakifyBuildDirPath } from "./getKeycloakifyBuildDirPath";
|
||||
import * as fs from "fs";
|
||||
import { getCacheDirPath } from "./getCacheDirPath";
|
||||
import { getReactAppRootDirPath } from "./getReactAppRootDirPath";
|
||||
import { getNpmWorkspaceRootDirPath } from "./getNpmWorkspaceRootDirPath";
|
||||
|
||||
/** Consolidated build option gathered form CLI arguments and config in package.json */
|
||||
export type BuildOptions = {
|
||||
@ -28,34 +31,24 @@ export type BuildOptions = {
|
||||
urlPathname: string | undefined;
|
||||
assetsDirPath: string;
|
||||
doBuildRetrocompatAccountTheme: boolean;
|
||||
npmWorkspaceRootDirPath: string;
|
||||
};
|
||||
|
||||
export function readBuildOptions(params: { processArgv: string[] }): BuildOptions {
|
||||
const { processArgv } = params;
|
||||
|
||||
const argv = parseArgv(processArgv);
|
||||
const { reactAppRootDirPath } = getReactAppRootDirPath({ processArgv });
|
||||
|
||||
const reactAppRootDirPath = (() => {
|
||||
const arg = argv["project"] ?? argv["p"];
|
||||
const { cacheDirPath } = getCacheDirPath({ reactAppRootDirPath });
|
||||
|
||||
if (typeof arg !== "string") {
|
||||
return process.cwd();
|
||||
}
|
||||
const { resolvedViteConfig } = readResolvedViteConfig({ cacheDirPath });
|
||||
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": arg,
|
||||
"cwd": process.cwd()
|
||||
});
|
||||
})();
|
||||
if (resolvedViteConfig === undefined && fs.existsSync(pathJoin(reactAppRootDirPath, "vite.config.ts"))) {
|
||||
throw new Error("Keycloakify's Vite plugin output not found");
|
||||
}
|
||||
|
||||
const parsedPackageJson = readParsedPackageJson({ reactAppRootDirPath });
|
||||
|
||||
const { resolvedViteConfig } =
|
||||
readResolvedViteConfig({
|
||||
"parsedPackageJson_keycloakify_keycloakifyBuildDirPath": parsedPackageJson.keycloakify?.keycloakifyBuildDirPath,
|
||||
reactAppRootDirPath
|
||||
}) ?? {};
|
||||
|
||||
const themeNames = (() => {
|
||||
if (parsedPackageJson.keycloakify?.themeName === undefined) {
|
||||
return [
|
||||
@ -73,12 +66,6 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
||||
return parsedPackageJson.keycloakify.themeName;
|
||||
})();
|
||||
|
||||
const { keycloakifyBuildDirPath } = getKeycloakifyBuildDirPath({
|
||||
"parsedPackageJson_keycloakify_keycloakifyBuildDirPath": parsedPackageJson.keycloakify?.keycloakifyBuildDirPath,
|
||||
reactAppRootDirPath,
|
||||
"bundler": resolvedViteConfig !== undefined ? "vite" : "webpack"
|
||||
});
|
||||
|
||||
const reactAppBuildDirPath = (() => {
|
||||
webpack: {
|
||||
if (resolvedViteConfig !== undefined) {
|
||||
@ -98,6 +85,10 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
||||
return pathJoin(reactAppRootDirPath, resolvedViteConfig.buildDir);
|
||||
})();
|
||||
|
||||
const argv = parseArgv(processArgv);
|
||||
|
||||
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({ reactAppRootDirPath });
|
||||
|
||||
return {
|
||||
"bundler": resolvedViteConfig !== undefined ? "vite" : "webpack",
|
||||
"isSilent": typeof argv["silent"] === "boolean" ? argv["silent"] : false,
|
||||
@ -124,7 +115,16 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
||||
"loginThemeResourcesFromKeycloakVersion": parsedPackageJson.keycloakify?.loginThemeResourcesFromKeycloakVersion ?? "11.0.3",
|
||||
reactAppRootDirPath,
|
||||
reactAppBuildDirPath,
|
||||
keycloakifyBuildDirPath,
|
||||
"keycloakifyBuildDirPath": (() => {
|
||||
if (parsedPackageJson.keycloakify?.keycloakifyBuildDirPath !== undefined) {
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": parsedPackageJson.keycloakify?.keycloakifyBuildDirPath,
|
||||
"cwd": reactAppRootDirPath
|
||||
});
|
||||
}
|
||||
|
||||
return resolvedViteConfig?.buildDir === undefined ? "build_keycloak" : `${resolvedViteConfig.buildDir}_keycloak`;
|
||||
})(),
|
||||
"publicDirPath": (() => {
|
||||
webpack: {
|
||||
if (resolvedViteConfig !== undefined) {
|
||||
@ -143,19 +143,7 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
||||
|
||||
return pathJoin(reactAppRootDirPath, resolvedViteConfig.publicDir);
|
||||
})(),
|
||||
"cacheDirPath": pathJoin(
|
||||
(() => {
|
||||
if (process.env.XDG_CACHE_HOME !== undefined) {
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": process.env.XDG_CACHE_HOME,
|
||||
"cwd": reactAppRootDirPath
|
||||
});
|
||||
}
|
||||
|
||||
return pathJoin(reactAppRootDirPath, "node_modules", ".cache");
|
||||
})(),
|
||||
"keycloakify"
|
||||
),
|
||||
cacheDirPath,
|
||||
"urlPathname": (() => {
|
||||
webpack: {
|
||||
if (resolvedViteConfig !== undefined) {
|
||||
@ -191,6 +179,7 @@ export function readBuildOptions(params: { processArgv: string[] }): BuildOption
|
||||
|
||||
return pathJoin(reactAppBuildDirPath, resolvedViteConfig.assetsDir);
|
||||
})(),
|
||||
"doBuildRetrocompatAccountTheme": parsedPackageJson.keycloakify?.doBuildRetrocompatAccountTheme ?? true
|
||||
"doBuildRetrocompatAccountTheme": parsedPackageJson.keycloakify?.doBuildRetrocompatAccountTheme ?? true,
|
||||
npmWorkspaceRootDirPath
|
||||
};
|
||||
}
|
||||
|
25
src/bin/keycloakify/buildOptions/getCacheDirPath.ts
Normal file
25
src/bin/keycloakify/buildOptions/getCacheDirPath.ts
Normal file
@ -0,0 +1,25 @@
|
||||
import { join as pathJoin } from "path";
|
||||
import { getAbsoluteAndInOsFormatPath } from "../../tools/getAbsoluteAndInOsFormatPath";
|
||||
import { getNpmWorkspaceRootDirPath } from "./getNpmWorkspaceRootDirPath";
|
||||
|
||||
export function getCacheDirPath(params: { reactAppRootDirPath: string }) {
|
||||
const { reactAppRootDirPath } = params;
|
||||
|
||||
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({ reactAppRootDirPath });
|
||||
|
||||
const cacheDirPath = pathJoin(
|
||||
(() => {
|
||||
if (process.env.XDG_CACHE_HOME !== undefined) {
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": process.env.XDG_CACHE_HOME,
|
||||
"cwd": reactAppRootDirPath
|
||||
});
|
||||
}
|
||||
|
||||
return pathJoin(npmWorkspaceRootDirPath, "node_modules", ".cache");
|
||||
})(),
|
||||
"keycloakify"
|
||||
);
|
||||
|
||||
return { cacheDirPath };
|
||||
}
|
@ -1,33 +0,0 @@
|
||||
import { getAbsoluteAndInOsFormatPath } from "../../tools/getAbsoluteAndInOsFormatPath";
|
||||
import { join as pathJoin } from "path";
|
||||
|
||||
export function getKeycloakifyBuildDirPath(params: {
|
||||
reactAppRootDirPath: string;
|
||||
parsedPackageJson_keycloakify_keycloakifyBuildDirPath: string | undefined;
|
||||
bundler: "vite" | "webpack";
|
||||
}) {
|
||||
const { reactAppRootDirPath, parsedPackageJson_keycloakify_keycloakifyBuildDirPath, bundler } = params;
|
||||
|
||||
const keycloakifyBuildDirPath = (() => {
|
||||
if (parsedPackageJson_keycloakify_keycloakifyBuildDirPath !== undefined) {
|
||||
getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": parsedPackageJson_keycloakify_keycloakifyBuildDirPath,
|
||||
"cwd": reactAppRootDirPath
|
||||
});
|
||||
}
|
||||
|
||||
return pathJoin(
|
||||
reactAppRootDirPath,
|
||||
`${(() => {
|
||||
switch (bundler) {
|
||||
case "vite":
|
||||
return "dist";
|
||||
case "webpack":
|
||||
return "build";
|
||||
}
|
||||
})()}_keycloak`
|
||||
);
|
||||
})();
|
||||
|
||||
return { keycloakifyBuildDirPath };
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
import * as child_process from "child_process";
|
||||
import { join as pathJoin, resolve as pathResolve, sep as pathSep } from "path";
|
||||
import { assert } from "tsafe/assert";
|
||||
|
||||
let cache:
|
||||
| {
|
||||
reactAppRootDirPath: string;
|
||||
npmWorkspaceRootDirPath: string;
|
||||
}
|
||||
| undefined = undefined;
|
||||
|
||||
export function getNpmWorkspaceRootDirPath(params: { reactAppRootDirPath: string }) {
|
||||
const { reactAppRootDirPath } = params;
|
||||
|
||||
use_cache: {
|
||||
if (cache === undefined || cache.reactAppRootDirPath !== reactAppRootDirPath) {
|
||||
break use_cache;
|
||||
}
|
||||
|
||||
const { npmWorkspaceRootDirPath } = cache;
|
||||
|
||||
return { npmWorkspaceRootDirPath };
|
||||
}
|
||||
|
||||
const npmWorkspaceRootDirPath = (function callee(depth: number): string {
|
||||
const cwd = pathResolve(pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")]));
|
||||
|
||||
try {
|
||||
child_process.execSync("npm config get", { cwd: cwd });
|
||||
} catch (error) {
|
||||
if (String(error).includes("ENOWORKSPACES")) {
|
||||
assert(cwd !== pathSep, "NPM workspace not found");
|
||||
|
||||
return callee(depth + 1);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return cwd;
|
||||
})(0);
|
||||
|
||||
cache = {
|
||||
reactAppRootDirPath,
|
||||
npmWorkspaceRootDirPath
|
||||
};
|
||||
|
||||
return { npmWorkspaceRootDirPath };
|
||||
}
|
23
src/bin/keycloakify/buildOptions/getReactAppRootDirPath.ts
Normal file
23
src/bin/keycloakify/buildOptions/getReactAppRootDirPath.ts
Normal file
@ -0,0 +1,23 @@
|
||||
import parseArgv from "minimist";
|
||||
import { getAbsoluteAndInOsFormatPath } from "../../tools/getAbsoluteAndInOsFormatPath";
|
||||
|
||||
export function getReactAppRootDirPath(params: { processArgv: string[] }) {
|
||||
const { processArgv } = params;
|
||||
|
||||
const argv = parseArgv(processArgv);
|
||||
|
||||
const reactAppRootDirPath = (() => {
|
||||
const arg = argv["project"] ?? argv["p"];
|
||||
|
||||
if (typeof arg !== "string") {
|
||||
return process.cwd();
|
||||
}
|
||||
|
||||
return getAbsoluteAndInOsFormatPath({
|
||||
"pathIsh": arg,
|
||||
"cwd": process.cwd()
|
||||
});
|
||||
})();
|
||||
|
||||
return { reactAppRootDirPath };
|
||||
}
|
@ -5,7 +5,6 @@ import { z } from "zod";
|
||||
import { join as pathJoin } from "path";
|
||||
import { resolvedViteConfigJsonBasename } from "../../constants";
|
||||
import type { OptionalIfCanBeUndefined } from "../../tools/OptionalIfCanBeUndefined";
|
||||
import { getKeycloakifyBuildDirPath } from "./getKeycloakifyBuildDirPath";
|
||||
|
||||
export type ResolvedViteConfig = {
|
||||
buildDir: string;
|
||||
@ -28,31 +27,18 @@ const zResolvedViteConfig = z.object({
|
||||
assert<Equals<Got, Expected>>();
|
||||
}
|
||||
|
||||
export function readResolvedViteConfig(params: {
|
||||
reactAppRootDirPath: string;
|
||||
parsedPackageJson_keycloakify_keycloakifyBuildDirPath: string | undefined;
|
||||
}):
|
||||
| {
|
||||
resolvedViteConfig: ResolvedViteConfig;
|
||||
}
|
||||
| undefined {
|
||||
const { reactAppRootDirPath, parsedPackageJson_keycloakify_keycloakifyBuildDirPath } = params;
|
||||
export function readResolvedViteConfig(params: { cacheDirPath: string }): {
|
||||
resolvedViteConfig: ResolvedViteConfig | undefined;
|
||||
} {
|
||||
const { cacheDirPath } = params;
|
||||
|
||||
const viteConfigTsFilePath = pathJoin(reactAppRootDirPath, "vite.config.ts");
|
||||
const resolvedViteConfigJsonFilePath = pathJoin(cacheDirPath, resolvedViteConfigJsonBasename);
|
||||
|
||||
if (!fs.existsSync(viteConfigTsFilePath)) {
|
||||
return undefined;
|
||||
if (!fs.existsSync(resolvedViteConfigJsonFilePath)) {
|
||||
return { "resolvedViteConfig": undefined };
|
||||
}
|
||||
|
||||
const { keycloakifyBuildDirPath } = getKeycloakifyBuildDirPath({
|
||||
reactAppRootDirPath,
|
||||
parsedPackageJson_keycloakify_keycloakifyBuildDirPath,
|
||||
"bundler": "vite"
|
||||
});
|
||||
|
||||
const resolvedViteConfig = (() => {
|
||||
const resolvedViteConfigJsonFilePath = pathJoin(keycloakifyBuildDirPath, resolvedViteConfigJsonBasename);
|
||||
|
||||
if (!fs.existsSync(resolvedViteConfigJsonFilePath)) {
|
||||
throw new Error("Missing Keycloakify Vite plugin output.");
|
||||
}
|
||||
|
@ -11,6 +11,7 @@ import { rmSync } from "../../tools/fs.rmSync";
|
||||
type BuildOptionsLike = {
|
||||
keycloakifyBuildDirPath: string;
|
||||
cacheDirPath: string;
|
||||
npmWorkspaceRootDirPath: string;
|
||||
};
|
||||
|
||||
{
|
||||
|
@ -9,6 +9,7 @@ import { rmSync } from "../../tools/fs.rmSync";
|
||||
|
||||
export type BuildOptionsLike = {
|
||||
cacheDirPath: string;
|
||||
npmWorkspaceRootDirPath: string;
|
||||
};
|
||||
|
||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||
|
@ -20,6 +20,7 @@ import { readFieldNameUsage } from "./readFieldNameUsage";
|
||||
import { readExtraPagesNames } from "./readExtraPageNames";
|
||||
import { generateMessageProperties } from "./generateMessageProperties";
|
||||
import { bringInAccountV1 } from "./bringInAccountV1";
|
||||
import { rmSync } from "../../tools/fs.rmSync";
|
||||
|
||||
export type BuildOptionsLike = {
|
||||
bundler: "vite" | "webpack";
|
||||
@ -33,6 +34,7 @@ export type BuildOptionsLike = {
|
||||
urlPathname: string | undefined;
|
||||
doBuildRetrocompatAccountTheme: boolean;
|
||||
themeNames: string[];
|
||||
npmWorkspaceRootDirPath: string;
|
||||
};
|
||||
|
||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||
@ -77,6 +79,11 @@ export async function generateTheme(params: {
|
||||
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
||||
|
||||
apply_replacers_and_move_to_theme_resources: {
|
||||
const destDirPath = pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir);
|
||||
|
||||
// NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up.
|
||||
rmSync(destDirPath, { "recursive": true, "force": true });
|
||||
|
||||
if (themeType === "account" && implementedThemeTypes.login) {
|
||||
// NOTE: We prevend doing it twice, it has been done for the login theme.
|
||||
|
||||
@ -88,7 +95,7 @@ export async function generateTheme(params: {
|
||||
"resources",
|
||||
basenameOfTheKeycloakifyResourcesDir
|
||||
),
|
||||
"destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir)
|
||||
destDirPath
|
||||
});
|
||||
|
||||
break apply_replacers_and_move_to_theme_resources;
|
||||
@ -96,7 +103,7 @@ export async function generateTheme(params: {
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": buildOptions.reactAppBuildDirPath,
|
||||
"destDirPath": pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir),
|
||||
destDirPath,
|
||||
"transformSourceCode": ({ filePath, sourceCode }) => {
|
||||
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
|
||||
// This should not happen if users follow the new instruction setup but we keep it for retrocompatibility.
|
||||
|
@ -6,9 +6,9 @@ import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTe
|
||||
import * as fs from "fs";
|
||||
import { readBuildOptions } from "./buildOptions";
|
||||
import { getLogger } from "../tools/logger";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { getThemeSrcDirPath } from "../getThemeSrcDirPath";
|
||||
import { getProjectRoot } from "../tools/getProjectRoot";
|
||||
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
|
||||
import { readThisNpmProjectVersion } from "../tools/readThisNpmProjectVersion";
|
||||
|
||||
export async function main() {
|
||||
const buildOptions = readBuildOptions({
|
||||
@ -18,23 +18,15 @@ export async function main() {
|
||||
const logger = getLogger({ "isSilent": buildOptions.isSilent });
|
||||
logger.log("🔏 Building the keycloak theme...⌚");
|
||||
|
||||
const keycloakifyDirPath = getProjectRoot();
|
||||
|
||||
const { themeSrcDirPath } = getThemeSrcDirPath({ "reactAppRootDirPath": buildOptions.reactAppRootDirPath });
|
||||
|
||||
for (const themeName of buildOptions.themeNames) {
|
||||
await generateTheme({
|
||||
themeName,
|
||||
themeSrcDirPath,
|
||||
"keycloakifySrcDirPath": pathJoin(keycloakifyDirPath, "src"),
|
||||
buildOptions,
|
||||
"keycloakifyVersion": (() => {
|
||||
const version = JSON.parse(fs.readFileSync(pathJoin(keycloakifyDirPath, "package.json")).toString("utf8"))["version"];
|
||||
|
||||
assert(typeof version === "string");
|
||||
|
||||
return version;
|
||||
})()
|
||||
"keycloakifySrcDirPath": pathJoin(getThisCodebaseRootDirPath(), "src"),
|
||||
"keycloakifyVersion": readThisNpmProjectVersion(),
|
||||
buildOptions
|
||||
});
|
||||
}
|
||||
|
||||
@ -67,7 +59,7 @@ export async function main() {
|
||||
);
|
||||
}
|
||||
|
||||
const containerKeycloakVersion = "23.0.0";
|
||||
const containerKeycloakVersion = "23.0.6";
|
||||
|
||||
generateStartKeycloakTestingContainer({
|
||||
"keycloakVersion": containerKeycloakVersion,
|
||||
|
@ -1,301 +0,0 @@
|
||||
import { exec as execCallback } from "child_process";
|
||||
import { createHash } from "crypto";
|
||||
import { mkdir, readFile, stat, writeFile, unlink } from "fs/promises";
|
||||
import fetch, { type FetchOptions } from "make-fetch-happen";
|
||||
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve, sep as pathSep, basename as pathBasename } from "path";
|
||||
import { assert } from "tsafe/assert";
|
||||
import { promisify } from "util";
|
||||
import { transformCodebase } from "./transformCodebase";
|
||||
import { unzip, zip } from "./unzip";
|
||||
import { rm } from "../tools/fs.rm";
|
||||
import * as child_process from "child_process";
|
||||
|
||||
const exec = promisify(execCallback);
|
||||
|
||||
function generateFileNameFromURL(params: {
|
||||
url: string;
|
||||
preCacheTransform:
|
||||
| {
|
||||
actionCacheId: string;
|
||||
actionFootprint: string;
|
||||
}
|
||||
| undefined;
|
||||
}): string {
|
||||
const { preCacheTransform } = params;
|
||||
|
||||
// Parse the URL
|
||||
const url = new URL(params.url);
|
||||
|
||||
// Extract pathname and remove leading slashes
|
||||
let fileName = url.pathname.replace(/^\//, "").replace(/\//g, "_");
|
||||
|
||||
// Optionally, add query parameters replacing special characters
|
||||
if (url.search) {
|
||||
fileName += url.search.replace(/[&=?]/g, "-");
|
||||
}
|
||||
|
||||
// Replace any characters that are not valid in filenames
|
||||
fileName = fileName.replace(/[^a-zA-Z0-9-_]/g, "");
|
||||
|
||||
// Trim or pad the fileName to a specific length
|
||||
fileName = fileName.substring(0, 50);
|
||||
|
||||
add_pre_cache_transform: {
|
||||
if (preCacheTransform === undefined) {
|
||||
break add_pre_cache_transform;
|
||||
}
|
||||
|
||||
// Sanitize actionCacheId the same way as other components
|
||||
const sanitizedActionCacheId = preCacheTransform.actionCacheId.replace(/[^a-zA-Z0-9-_]/g, "_");
|
||||
|
||||
fileName += `_${sanitizedActionCacheId}_${createHash("sha256").update(preCacheTransform.actionFootprint).digest("hex").substring(0, 5)}`;
|
||||
}
|
||||
|
||||
return fileName;
|
||||
}
|
||||
|
||||
async function exists(path: string) {
|
||||
try {
|
||||
await stat(path);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if ((error as Error & { code: string }).code === "ENOENT") return false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
function ensureArray<T>(arg0: T | T[]) {
|
||||
return Array.isArray(arg0) ? arg0 : typeof arg0 === "undefined" ? [] : [arg0];
|
||||
}
|
||||
|
||||
function ensureSingleOrNone<T>(arg0: T | T[]) {
|
||||
if (!Array.isArray(arg0)) return arg0;
|
||||
if (arg0.length === 0) return undefined;
|
||||
if (arg0.length === 1) return arg0[0];
|
||||
throw new Error("Illegal configuration, expected a single value but found multiple: " + arg0.map(String).join(", "));
|
||||
}
|
||||
|
||||
type NPMConfig = Record<string, string | string[]>;
|
||||
|
||||
const npmConfigReducer = (cfg: NPMConfig, [key, value]: [string, string]) =>
|
||||
key in cfg ? { ...cfg, [key]: [...ensureArray(cfg[key]), value] } : { ...cfg, [key]: value };
|
||||
|
||||
/**
|
||||
* Get npm configuration as map
|
||||
*/
|
||||
async function getNmpConfig() {
|
||||
return readNpmConfig().then(parseNpmConfig);
|
||||
}
|
||||
|
||||
function readNpmConfig(): Promise<string> {
|
||||
return (async function callee(depth: number): Promise<string> {
|
||||
const cwd = pathResolve(pathJoin(...[process.cwd(), ...Array(depth).fill("..")]));
|
||||
|
||||
let stdout: string;
|
||||
|
||||
try {
|
||||
stdout = await exec("npm config get", { "encoding": "utf8", cwd }).then(({ stdout }) => stdout);
|
||||
} catch (error) {
|
||||
if (String(error).includes("ENOWORKSPACES")) {
|
||||
assert(cwd !== pathSep);
|
||||
|
||||
return callee(depth + 1);
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
return stdout;
|
||||
})(0);
|
||||
}
|
||||
|
||||
function parseNpmConfig(stdout: string) {
|
||||
return stdout
|
||||
.split("\n")
|
||||
.filter(line => !line.startsWith(";"))
|
||||
.map(line => line.trim())
|
||||
.map(line => line.split("=", 2) as [string, string])
|
||||
.reduce(npmConfigReducer, {} as NPMConfig);
|
||||
}
|
||||
|
||||
function maybeBoolean(arg0: string | undefined) {
|
||||
return typeof arg0 === "undefined" ? undefined : Boolean(arg0);
|
||||
}
|
||||
|
||||
function chunks<T>(arr: T[], size: number = 2) {
|
||||
return arr.map((_, i) => i % size == 0 && arr.slice(i, i + size)).filter(Boolean) as T[][];
|
||||
}
|
||||
|
||||
async function readCafile(cafile: string) {
|
||||
const cafileContent = await readFile(cafile, "utf-8");
|
||||
return chunks(cafileContent.split(/(-----END CERTIFICATE-----)/), 2).map(ca => ca.join("").replace(/^\n/, "").replace(/\n/g, "\\n"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get proxy and ssl configuration from npm config files. Note that we don't care about
|
||||
* proxy config in env vars, because make-fetch-happen will do that for us.
|
||||
*
|
||||
* @returns proxy configuration
|
||||
*/
|
||||
async function getFetchOptions(): Promise<Pick<FetchOptions, "proxy" | "noProxy" | "strictSSL" | "ca" | "cert">> {
|
||||
const cfg = await getNmpConfig();
|
||||
|
||||
const proxy = ensureSingleOrNone(cfg["https-proxy"] ?? cfg["proxy"]);
|
||||
const noProxy = cfg["noproxy"] ?? cfg["no-proxy"];
|
||||
const strictSSL = maybeBoolean(ensureSingleOrNone(cfg["strict-ssl"]));
|
||||
const cert = cfg["cert"];
|
||||
const ca = ensureArray(cfg["ca"] ?? cfg["ca[]"]);
|
||||
const cafile = ensureSingleOrNone(cfg["cafile"]);
|
||||
|
||||
if (typeof cafile !== "undefined" && cafile !== "null") ca.push(...(await readCafile(cafile)));
|
||||
|
||||
return { proxy, noProxy, strictSSL, cert, ca: ca.length === 0 ? undefined : ca };
|
||||
}
|
||||
|
||||
export async function downloadAndUnzip(
|
||||
params: {
|
||||
url: string;
|
||||
destDirPath: string;
|
||||
specificDirsToExtract?: string[];
|
||||
preCacheTransform?: {
|
||||
actionCacheId: string;
|
||||
action: (params: { destDirPath: string }) => Promise<void>;
|
||||
};
|
||||
} & (
|
||||
| {
|
||||
doUseCache: true;
|
||||
cacheDirPath: string;
|
||||
}
|
||||
| {
|
||||
doUseCache: false;
|
||||
}
|
||||
)
|
||||
) {
|
||||
const { url, destDirPath, specificDirsToExtract, preCacheTransform, ...rest } = params;
|
||||
|
||||
const zipFileBasename = generateFileNameFromURL({
|
||||
url,
|
||||
"preCacheTransform":
|
||||
preCacheTransform === undefined
|
||||
? undefined
|
||||
: {
|
||||
"actionCacheId": preCacheTransform.actionCacheId,
|
||||
"actionFootprint": preCacheTransform.action.toString()
|
||||
}
|
||||
});
|
||||
|
||||
const cacheDirPath = !rest.doUseCache ? `tmp_${Math.random().toString().slice(2, 12)}` : rest.cacheDirPath;
|
||||
|
||||
const zipFilePath = pathJoin(cacheDirPath, `${zipFileBasename}.zip`);
|
||||
const extractDirPath = pathJoin(cacheDirPath, `tmp_unzip_${zipFileBasename}`);
|
||||
|
||||
download_zip_and_transform: {
|
||||
if (await exists(zipFilePath)) {
|
||||
break download_zip_and_transform;
|
||||
}
|
||||
|
||||
const opts = await getFetchOptions();
|
||||
|
||||
const { response, isFromRemoteCache } = await (async () => {
|
||||
const response = await fetch(`https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/${pathBasename(zipFilePath)}`, opts);
|
||||
|
||||
if (response.status === 200) {
|
||||
return {
|
||||
response,
|
||||
"isFromRemoteCache": true
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
"response": await fetch(url, opts),
|
||||
"isFromRemoteCache": false
|
||||
};
|
||||
})();
|
||||
|
||||
await mkdir(pathDirname(zipFilePath), { "recursive": true });
|
||||
|
||||
/**
|
||||
* The correct way to fix this is to upgrade node-fetch beyond 3.2.5
|
||||
* (see https://github.com/node-fetch/node-fetch/issues/1295#issuecomment-1144061991.)
|
||||
* Unfortunately, octokit (a dependency of keycloakify) also uses node-fetch, and
|
||||
* does not support node-fetch 3.x. So we stick around with this band-aid until
|
||||
* octokit upgrades.
|
||||
*/
|
||||
response.body?.setMaxListeners(Number.MAX_VALUE);
|
||||
assert(typeof response.body !== "undefined" && response.body != null);
|
||||
|
||||
await writeFile(zipFilePath, response.body);
|
||||
|
||||
if (isFromRemoteCache) {
|
||||
break download_zip_and_transform;
|
||||
}
|
||||
|
||||
if (specificDirsToExtract === undefined && preCacheTransform === undefined) {
|
||||
break download_zip_and_transform;
|
||||
}
|
||||
|
||||
await unzip(zipFilePath, extractDirPath, specificDirsToExtract);
|
||||
|
||||
try {
|
||||
await preCacheTransform?.action({
|
||||
"destDirPath": extractDirPath
|
||||
});
|
||||
} catch (error) {
|
||||
await Promise.all([rm(extractDirPath, { "recursive": true }), unlink(zipFilePath)]);
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
await unlink(zipFilePath);
|
||||
|
||||
await zip(extractDirPath, zipFilePath);
|
||||
|
||||
await rm(extractDirPath, { "recursive": true });
|
||||
|
||||
upload_to_remot_cache_if_admin: {
|
||||
const githubToken = process.env["KEYCLOAKIFY_ADMIN_GITHUB_PERSONAL_ACCESS_TOKEN"];
|
||||
|
||||
if (githubToken === undefined) {
|
||||
break upload_to_remot_cache_if_admin;
|
||||
}
|
||||
|
||||
console.log("uploading to remote cache");
|
||||
|
||||
try {
|
||||
child_process.execSync(`which putasset`);
|
||||
} catch {
|
||||
child_process.execSync(`npm install -g putasset`);
|
||||
}
|
||||
|
||||
try {
|
||||
child_process.execFileSync("putasset", [
|
||||
"--owner",
|
||||
"keycloakify",
|
||||
"--repo",
|
||||
"keycloakify",
|
||||
"--tag",
|
||||
"v0.0.1",
|
||||
"--filename",
|
||||
zipFilePath,
|
||||
"--token",
|
||||
githubToken
|
||||
]);
|
||||
} catch {
|
||||
console.log("upload failed, asset probably already exists in remote cache");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
await unzip(zipFilePath, extractDirPath);
|
||||
|
||||
transformCodebase({
|
||||
"srcDirPath": extractDirPath,
|
||||
"destDirPath": destDirPath
|
||||
});
|
||||
|
||||
if (!rest.doUseCache) {
|
||||
await rm(cacheDirPath, { "recursive": true });
|
||||
} else {
|
||||
await rm(extractDirPath, { "recursive": true });
|
||||
}
|
||||
}
|
73
src/bin/tools/fetchProxyOptions.ts
Normal file
73
src/bin/tools/fetchProxyOptions.ts
Normal file
@ -0,0 +1,73 @@
|
||||
import { exec as execCallback } from "child_process";
|
||||
import { readFile } from "fs/promises";
|
||||
import { type FetchOptions } from "make-fetch-happen";
|
||||
import { promisify } from "util";
|
||||
|
||||
function ensureArray<T>(arg0: T | T[]) {
|
||||
return Array.isArray(arg0) ? arg0 : typeof arg0 === "undefined" ? [] : [arg0];
|
||||
}
|
||||
|
||||
function ensureSingleOrNone<T>(arg0: T | T[]) {
|
||||
if (!Array.isArray(arg0)) return arg0;
|
||||
if (arg0.length === 0) return undefined;
|
||||
if (arg0.length === 1) return arg0[0];
|
||||
throw new Error("Illegal configuration, expected a single value but found multiple: " + arg0.map(String).join(", "));
|
||||
}
|
||||
|
||||
type NPMConfig = Record<string, string | string[]>;
|
||||
|
||||
/**
|
||||
* Get npm configuration as map
|
||||
*/
|
||||
async function getNmpConfig(params: { npmWorkspaceRootDirPath: string }) {
|
||||
const { npmWorkspaceRootDirPath } = params;
|
||||
|
||||
const exec = promisify(execCallback);
|
||||
|
||||
const stdout = await exec("npm config get", { "encoding": "utf8", "cwd": npmWorkspaceRootDirPath }).then(({ stdout }) => stdout);
|
||||
|
||||
const npmConfigReducer = (cfg: NPMConfig, [key, value]: [string, string]) =>
|
||||
key in cfg ? { ...cfg, [key]: [...ensureArray(cfg[key]), value] } : { ...cfg, [key]: value };
|
||||
|
||||
return stdout
|
||||
.split("\n")
|
||||
.filter(line => !line.startsWith(";"))
|
||||
.map(line => line.trim())
|
||||
.map(line => line.split("=", 2) as [string, string])
|
||||
.reduce(npmConfigReducer, {} as NPMConfig);
|
||||
}
|
||||
|
||||
export type ProxyFetchOptions = Pick<FetchOptions, "proxy" | "noProxy" | "strictSSL" | "cert" | "ca">;
|
||||
|
||||
export async function getProxyFetchOptions(params: { npmWorkspaceRootDirPath: string }): Promise<ProxyFetchOptions> {
|
||||
const { npmWorkspaceRootDirPath } = params;
|
||||
|
||||
const cfg = await getNmpConfig({ npmWorkspaceRootDirPath });
|
||||
|
||||
const proxy = ensureSingleOrNone(cfg["https-proxy"] ?? cfg["proxy"]);
|
||||
const noProxy = cfg["noproxy"] ?? cfg["no-proxy"];
|
||||
|
||||
function maybeBoolean(arg0: string | undefined) {
|
||||
return typeof arg0 === "undefined" ? undefined : Boolean(arg0);
|
||||
}
|
||||
|
||||
const strictSSL = maybeBoolean(ensureSingleOrNone(cfg["strict-ssl"]));
|
||||
const cert = cfg["cert"];
|
||||
const ca = ensureArray(cfg["ca"] ?? cfg["ca[]"]);
|
||||
const cafile = ensureSingleOrNone(cfg["cafile"]);
|
||||
|
||||
if (typeof cafile !== "undefined" && cafile !== "null") {
|
||||
ca.push(
|
||||
...(await (async () => {
|
||||
function chunks<T>(arr: T[], size: number = 2) {
|
||||
return arr.map((_, i) => i % size == 0 && arr.slice(i, i + size)).filter(Boolean) as T[][];
|
||||
}
|
||||
|
||||
const cafileContent = await readFile(cafile, "utf-8");
|
||||
return chunks(cafileContent.split(/(-----END CERTIFICATE-----)/), 2).map(ca => ca.join("").replace(/^\n/, "").replace(/\n/g, "\\n"));
|
||||
})())
|
||||
);
|
||||
}
|
||||
|
||||
return { proxy, noProxy, strictSSL, cert, "ca": ca.length === 0 ? undefined : ca };
|
||||
}
|
11
src/bin/tools/fs.existsAsync.ts
Normal file
11
src/bin/tools/fs.existsAsync.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import * as fs from "fs/promises";
|
||||
|
||||
export async function existsAsync(path: string) {
|
||||
try {
|
||||
await fs.stat(path);
|
||||
return true;
|
||||
} catch (error) {
|
||||
if ((error as Error & { code: string }).code === "ENOENT") return false;
|
||||
throw error;
|
||||
}
|
||||
}
|
@ -1,19 +1,19 @@
|
||||
import * as fs from "fs";
|
||||
import * as path from "path";
|
||||
|
||||
function getProjectRootRec(dirPath: string): string {
|
||||
function getThisCodebaseRootDirPath_rec(dirPath: string): string {
|
||||
if (fs.existsSync(path.join(dirPath, "package.json"))) {
|
||||
return dirPath;
|
||||
}
|
||||
return getProjectRootRec(path.join(dirPath, ".."));
|
||||
return getThisCodebaseRootDirPath_rec(path.join(dirPath, ".."));
|
||||
}
|
||||
|
||||
let result: string | undefined = undefined;
|
||||
|
||||
export function getProjectRoot(): string {
|
||||
export function getThisCodebaseRootDirPath(): string {
|
||||
if (result !== undefined) {
|
||||
return result;
|
||||
}
|
||||
|
||||
return (result = getProjectRootRec(__dirname));
|
||||
return (result = getThisCodebaseRootDirPath_rec(__dirname));
|
||||
}
|
@ -1,13 +1,15 @@
|
||||
import { getProjectRoot } from "./getProjectRoot";
|
||||
import { getThisCodebaseRootDirPath } from "./getThisCodebaseRootDirPath";
|
||||
import { join as pathJoin } from "path";
|
||||
import { constants } from "fs";
|
||||
import { chmod, stat } from "fs/promises";
|
||||
|
||||
(async () => {
|
||||
const { bin } = await import(pathJoin(getProjectRoot(), "package.json"));
|
||||
const thisCodebaseRootDirPath = getThisCodebaseRootDirPath();
|
||||
|
||||
const { bin } = await import(pathJoin(thisCodebaseRootDirPath, "package.json"));
|
||||
|
||||
const promises = Object.values<string>(bin).map(async scriptPath => {
|
||||
const fullPath = pathJoin(getProjectRoot(), scriptPath);
|
||||
const fullPath = pathJoin(thisCodebaseRootDirPath, scriptPath);
|
||||
const oldMode = (await stat(fullPath)).mode;
|
||||
const newMode = oldMode | constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH;
|
||||
await chmod(fullPath, newMode);
|
||||
|
12
src/bin/tools/readThisNpmProjectVersion.ts
Normal file
12
src/bin/tools/readThisNpmProjectVersion.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { getThisCodebaseRootDirPath } from "./getThisCodebaseRootDirPath";
|
||||
import { assert } from "tsafe/assert";
|
||||
import * as fs from "fs";
|
||||
import { join as pathJoin } from "path";
|
||||
|
||||
export function readThisNpmProjectVersion(): string {
|
||||
const version = JSON.parse(fs.readFileSync(pathJoin(getThisCodebaseRootDirPath(), "package.json")).toString("utf8"))["version"];
|
||||
|
||||
assert(typeof version === "string");
|
||||
|
||||
return version;
|
||||
}
|
@ -1,3 +1 @@
|
||||
import { BASE_URL } from "./BASE_URL";
|
||||
|
||||
export const isStorybook = BASE_URL.startsWith(".");
|
||||
export const isStorybook = typeof window === "object" && Object.keys(window).find(key => key.startsWith("__STORYBOOK")) !== undefined;
|
||||
|
@ -1,10 +1,11 @@
|
||||
import type { I18n } from "keycloakify/login/i18n";
|
||||
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
|
||||
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||
import type { KcContext } from "keycloakify/account/kcContext";
|
||||
|
||||
export type PageProps<KcContext, I18nExtended extends I18n> = {
|
||||
export type PageProps<NarowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
|
||||
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
||||
kcContext: KcContext;
|
||||
kcContext: NarowedKcContext;
|
||||
i18n: I18nExtended;
|
||||
doUseDefaultCss: boolean;
|
||||
classes?: Partial<Record<ClassKey, string>>;
|
||||
|
@ -1,232 +0,0 @@
|
||||
{
|
||||
"plugins": [
|
||||
{
|
||||
"name": "vite:build-metadata"
|
||||
},
|
||||
{
|
||||
"name": "vite:watch-package-data"
|
||||
},
|
||||
{
|
||||
"name": "vite:pre-alias"
|
||||
},
|
||||
{
|
||||
"name": "alias"
|
||||
},
|
||||
{
|
||||
"name": "vite:react-babel",
|
||||
"enforce": "pre"
|
||||
},
|
||||
{
|
||||
"name": "vite:react-refresh",
|
||||
"enforce": "pre"
|
||||
},
|
||||
{
|
||||
"name": "vite:modulepreload-polyfill"
|
||||
},
|
||||
{
|
||||
"name": "vite:resolve"
|
||||
},
|
||||
{
|
||||
"name": "vite:html-inline-proxy"
|
||||
},
|
||||
{
|
||||
"name": "vite:css"
|
||||
},
|
||||
{
|
||||
"name": "vite:esbuild"
|
||||
},
|
||||
{
|
||||
"name": "vite:json"
|
||||
},
|
||||
{
|
||||
"name": "vite:wasm-helper"
|
||||
},
|
||||
{
|
||||
"name": "vite:worker"
|
||||
},
|
||||
{
|
||||
"name": "vite:asset"
|
||||
},
|
||||
{
|
||||
"name": "vite-plugin-commonjs"
|
||||
},
|
||||
{
|
||||
"name": "keycloakify"
|
||||
},
|
||||
{
|
||||
"name": "vite:wasm-fallback"
|
||||
},
|
||||
{
|
||||
"name": "vite:define"
|
||||
},
|
||||
{
|
||||
"name": "vite:css-post"
|
||||
},
|
||||
{
|
||||
"name": "vite:build-html"
|
||||
},
|
||||
{
|
||||
"name": "vite:worker-import-meta-url"
|
||||
},
|
||||
{
|
||||
"name": "vite:asset-import-meta-url"
|
||||
},
|
||||
{
|
||||
"name": "vite:force-systemjs-wrap-complete"
|
||||
},
|
||||
{
|
||||
"name": "commonjs",
|
||||
"version": "25.0.7"
|
||||
},
|
||||
{
|
||||
"name": "vite:data-uri"
|
||||
},
|
||||
{
|
||||
"name": "vite:dynamic-import-vars"
|
||||
},
|
||||
{
|
||||
"name": "vite:import-glob"
|
||||
},
|
||||
{
|
||||
"name": "vite:build-import-analysis"
|
||||
},
|
||||
{
|
||||
"name": "vite:esbuild-transpile"
|
||||
},
|
||||
{
|
||||
"name": "vite:terser"
|
||||
},
|
||||
{
|
||||
"name": "vite:reporter"
|
||||
},
|
||||
{
|
||||
"name": "vite:load-fallback"
|
||||
}
|
||||
],
|
||||
"optimizeDeps": {
|
||||
"disabled": "build",
|
||||
"esbuildOptions": {
|
||||
"preserveSymlinks": false,
|
||||
"jsx": "automatic",
|
||||
"plugins": [
|
||||
{
|
||||
"name": "vite-plugin-commonjs:pre-bundle"
|
||||
}
|
||||
]
|
||||
},
|
||||
"include": ["react", "react/jsx-dev-runtime", "react/jsx-runtime"]
|
||||
},
|
||||
"build": {
|
||||
"target": ["es2020", "edge88", "firefox78", "chrome87", "safari14"],
|
||||
"cssTarget": ["es2020", "edge88", "firefox78", "chrome87", "safari14"],
|
||||
"outDir": "dist",
|
||||
"assetsDir": "assets",
|
||||
"assetsInlineLimit": 4096,
|
||||
"cssCodeSplit": true,
|
||||
"sourcemap": false,
|
||||
"rollupOptions": {},
|
||||
"minify": "esbuild",
|
||||
"terserOptions": {},
|
||||
"write": true,
|
||||
"emptyOutDir": null,
|
||||
"copyPublicDir": true,
|
||||
"manifest": false,
|
||||
"lib": false,
|
||||
"ssr": false,
|
||||
"ssrManifest": false,
|
||||
"ssrEmitAssets": false,
|
||||
"reportCompressedSize": true,
|
||||
"chunkSizeWarningLimit": 500,
|
||||
"watch": null,
|
||||
"commonjsOptions": {
|
||||
"include": [{}],
|
||||
"extensions": [".js", ".cjs"]
|
||||
},
|
||||
"dynamicImportVarsOptions": {
|
||||
"warnOnError": true,
|
||||
"exclude": [{}]
|
||||
},
|
||||
"modulePreload": {
|
||||
"polyfill": true
|
||||
},
|
||||
"cssMinify": true
|
||||
},
|
||||
"esbuild": {
|
||||
"jsxDev": false,
|
||||
"jsx": "automatic"
|
||||
},
|
||||
"resolve": {
|
||||
"mainFields": ["browser", "module", "jsnext:main", "jsnext"],
|
||||
"conditions": [],
|
||||
"extensions": [".mjs", ".js", ".mts", ".ts", ".jsx", ".tsx", ".json"],
|
||||
"dedupe": ["react", "react-dom"],
|
||||
"preserveSymlinks": false,
|
||||
"alias": [
|
||||
{
|
||||
"find": {},
|
||||
"replacement": "/@fs/Users/joseph/github/keycloakify-starter/node_modules/vite/dist/client/env.mjs"
|
||||
},
|
||||
{
|
||||
"find": {},
|
||||
"replacement": "/@fs/Users/joseph/github/keycloakify-starter/node_modules/vite/dist/client/client.mjs"
|
||||
}
|
||||
]
|
||||
},
|
||||
"configFile": "/Users/joseph/github/keycloakify-starter/vite.config.ts",
|
||||
"configFileDependencies": ["/Users/joseph/github/keycloakify-starter/vite.config.ts"],
|
||||
"inlineConfig": {
|
||||
"optimizeDeps": {},
|
||||
"build": {}
|
||||
},
|
||||
"root": "/Users/joseph/github/keycloakify-starter",
|
||||
"base": "/",
|
||||
"rawBase": "/",
|
||||
"publicDir": "/Users/joseph/github/keycloakify-starter/public",
|
||||
"cacheDir": "/Users/joseph/github/keycloakify-starter/node_modules/.vite",
|
||||
"command": "build",
|
||||
"mode": "production",
|
||||
"ssr": {
|
||||
"target": "node",
|
||||
"optimizeDeps": {
|
||||
"disabled": true,
|
||||
"esbuildOptions": {
|
||||
"preserveSymlinks": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"isWorker": false,
|
||||
"mainConfig": null,
|
||||
"isProduction": true,
|
||||
"css": {},
|
||||
"server": {
|
||||
"preTransformRequests": true,
|
||||
"middlewareMode": false,
|
||||
"fs": {
|
||||
"strict": true,
|
||||
"allow": ["/Users/joseph/github/keycloakify-starter"],
|
||||
"deny": [".env", ".env.*", "*.{crt,pem}"],
|
||||
"cachedChecks": false
|
||||
}
|
||||
},
|
||||
"preview": {},
|
||||
"envDir": "/Users/joseph/github/keycloakify-starter",
|
||||
"env": {
|
||||
"BASE_URL": "/",
|
||||
"MODE": "production",
|
||||
"DEV": false,
|
||||
"PROD": true
|
||||
},
|
||||
"logger": {
|
||||
"hasWarned": false
|
||||
},
|
||||
"packageCache": {},
|
||||
"worker": {
|
||||
"format": "iife",
|
||||
"rollupOptions": {}
|
||||
},
|
||||
"appType": "spa",
|
||||
"experimental": {
|
||||
"importGlobRestoreExtension": false,
|
||||
"hmrPartialAccept": false
|
||||
}
|
||||
}
|
@ -1,28 +1,39 @@
|
||||
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
|
||||
import { readParsedPackageJson } from "../bin/keycloakify/buildOptions/parsedPackageJson";
|
||||
import type { Plugin } from "vite";
|
||||
import { assert } from "tsafe/assert";
|
||||
import * as fs from "fs";
|
||||
import { resolvedViteConfigJsonBasename, nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir, keycloak_resources } from "../bin/constants";
|
||||
import type { ResolvedViteConfig } from "../bin/keycloakify/buildOptions/resolvedViteConfig";
|
||||
import { getKeycloakifyBuildDirPath } from "../bin/keycloakify/buildOptions/getKeycloakifyBuildDirPath";
|
||||
import { getCacheDirPath } from "../bin/keycloakify/buildOptions/getCacheDirPath";
|
||||
import { replaceAll } from "../bin/tools/String.prototype.replaceAll";
|
||||
import { id } from "tsafe/id";
|
||||
import { rm } from "../bin/tools/fs.rm";
|
||||
import { copyKeycloakResourcesToPublic } from "../bin/copy-keycloak-resources-to-public";
|
||||
import { assert } from "tsafe/assert";
|
||||
|
||||
export function keycloakify(): Plugin {
|
||||
export function keycloakify() {
|
||||
let reactAppRootDirPath: string | undefined = undefined;
|
||||
let urlPathname: string | undefined = undefined;
|
||||
let buildDirPath: string | undefined = undefined;
|
||||
let command: "build" | "serve" | undefined = undefined;
|
||||
|
||||
const plugin = {
|
||||
"name": "keycloakify" as const,
|
||||
"configResolved": async resolvedConfig => {
|
||||
command = resolvedConfig.command;
|
||||
|
||||
return {
|
||||
"name": "keycloakify",
|
||||
"apply": "build",
|
||||
"configResolved": resolvedConfig => {
|
||||
reactAppRootDirPath = resolvedConfig.root;
|
||||
urlPathname = (() => {
|
||||
let out = resolvedConfig.env.BASE_URL;
|
||||
|
||||
if (out.startsWith(".") && command === "build") {
|
||||
throw new Error(
|
||||
[
|
||||
`BASE_URL=${out} is not supported By Keycloakify. Use an absolute URL instead.`,
|
||||
`If this is a problem, please open an issue at https://github.com/keycloakify/keycloakify/issues/new`
|
||||
].join("\n")
|
||||
);
|
||||
}
|
||||
|
||||
if (out === undefined) {
|
||||
return undefined;
|
||||
}
|
||||
@ -40,19 +51,16 @@ export function keycloakify(): Plugin {
|
||||
|
||||
buildDirPath = pathJoin(reactAppRootDirPath, resolvedConfig.build.outDir);
|
||||
|
||||
const { keycloakifyBuildDirPath } = getKeycloakifyBuildDirPath({
|
||||
"parsedPackageJson_keycloakify_keycloakifyBuildDirPath": readParsedPackageJson({ reactAppRootDirPath }).keycloakify
|
||||
?.keycloakifyBuildDirPath,
|
||||
reactAppRootDirPath,
|
||||
"bundler": "vite"
|
||||
const { cacheDirPath } = getCacheDirPath({
|
||||
reactAppRootDirPath
|
||||
});
|
||||
|
||||
if (!fs.existsSync(keycloakifyBuildDirPath)) {
|
||||
fs.mkdirSync(keycloakifyBuildDirPath);
|
||||
if (!fs.existsSync(cacheDirPath)) {
|
||||
fs.mkdirSync(cacheDirPath, { "recursive": true });
|
||||
}
|
||||
|
||||
fs.writeFileSync(
|
||||
pathJoin(keycloakifyBuildDirPath, resolvedViteConfigJsonBasename),
|
||||
pathJoin(cacheDirPath, resolvedViteConfigJsonBasename),
|
||||
Buffer.from(
|
||||
JSON.stringify(
|
||||
id<ResolvedViteConfig>({
|
||||
@ -67,8 +75,18 @@ export function keycloakify(): Plugin {
|
||||
"utf8"
|
||||
)
|
||||
);
|
||||
|
||||
await copyKeycloakResourcesToPublic({
|
||||
"processArgv": ["--project", reactAppRootDirPath]
|
||||
});
|
||||
},
|
||||
"transform": (code, id) => {
|
||||
assert(command !== undefined);
|
||||
|
||||
if (command !== "build") {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(reactAppRootDirPath !== undefined);
|
||||
|
||||
let transformedCode: string | undefined = undefined;
|
||||
@ -82,9 +100,8 @@ export function keycloakify(): Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx");
|
||||
|
||||
{
|
||||
const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx");
|
||||
const isTypeScriptFile = id.endsWith(".ts") || id.endsWith(".tsx");
|
||||
|
||||
if (!isTypeScriptFile && !isJavascriptFile) {
|
||||
@ -92,8 +109,6 @@ export function keycloakify(): Plugin {
|
||||
}
|
||||
}
|
||||
|
||||
const windowToken = isJavascriptFile ? "window" : "(window as any)";
|
||||
|
||||
if (transformedCode === undefined) {
|
||||
transformedCode = code;
|
||||
}
|
||||
@ -103,9 +118,9 @@ export function keycloakify(): Plugin {
|
||||
"import.meta.env.BASE_URL",
|
||||
[
|
||||
`(`,
|
||||
`(${windowToken}.${nameOfTheGlobal} === undefined || import.meta.env.MODE === "development") ?`,
|
||||
` "${urlPathname ?? "/"}" :`,
|
||||
` \`\${${windowToken}.${nameOfTheGlobal}.url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/\``,
|
||||
`(window.${nameOfTheGlobal} === undefined || import.meta.env.MODE === "development")?`,
|
||||
`"${urlPathname ?? "/"}":`,
|
||||
`(window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/")`,
|
||||
`)`
|
||||
].join("")
|
||||
);
|
||||
@ -119,10 +134,18 @@ export function keycloakify(): Plugin {
|
||||
"code": transformedCode
|
||||
};
|
||||
},
|
||||
"buildEnd": async () => {
|
||||
"closeBundle": async () => {
|
||||
assert(command !== undefined);
|
||||
|
||||
if (command !== "build") {
|
||||
return;
|
||||
}
|
||||
|
||||
assert(buildDirPath !== undefined);
|
||||
|
||||
await rm(pathJoin(buildDirPath, keycloak_resources), { "recursive": true, "force": true });
|
||||
}
|
||||
};
|
||||
} satisfies Plugin;
|
||||
|
||||
return plugin as any;
|
||||
}
|
||||
|
@ -1,9 +1,16 @@
|
||||
import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip";
|
||||
import { downloadAndUnzip } from "keycloakify/bin/downloadAndUnzip";
|
||||
import { join as pathJoin } from "path";
|
||||
import { getThisCodebaseRootDirPath } from "keycloakify/bin/tools/getThisCodebaseRootDirPath";
|
||||
|
||||
export async function setupSampleReactProject(destDirPath: string) {
|
||||
const thisCodebaseRootDirPath = getThisCodebaseRootDirPath();
|
||||
|
||||
await downloadAndUnzip({
|
||||
"url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
||||
"destDirPath": destDirPath,
|
||||
"doUseCache": false
|
||||
"buildOptions": {
|
||||
"cacheDirPath": pathJoin(thisCodebaseRootDirPath, "node_modules", ".cache", "keycloakify"),
|
||||
"npmWorkspaceRootDirPath": thisCodebaseRootDirPath
|
||||
}
|
||||
});
|
||||
}
|
||||
|
Reference in New Issue
Block a user