Compare commits

...

1 Commits

Author SHA1 Message Date
Joseph Garrone
7d8ae040fd Bundle JAR WPI 2024-08-24 23:13:16 +02:00
7 changed files with 217 additions and 70 deletions

View File

@ -17,15 +17,20 @@ import { isInside } from "../../tools/isInside";
import child_process from "child_process"; import child_process from "child_process";
import { rmSync } from "../../tools/fs.rmSync"; import { rmSync } from "../../tools/fs.rmSync";
import { writeMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes"; import { writeMetaInfKeycloakThemes } from "../../shared/metaInfKeycloakThemes";
import {
bundleExtensionsIntoJar,
type BuildContextLike as BuildContextLike_bundleExtensionsIntoJar
} from "./bundleExtensionsIntoJar";
export type BuildContextLike = BuildContextLike_generatePom & { export type BuildContextLike = BuildContextLike_generatePom &
keycloakifyBuildDirPath: string; BuildContextLike_bundleExtensionsIntoJar & {
themeNames: string[]; keycloakifyBuildDirPath: string;
artifactId: string; themeNames: string[];
themeVersion: string; artifactId: string;
cacheDirPath: string; themeVersion: string;
implementedThemeTypes: BuildContext["implementedThemeTypes"]; cacheDirPath: string;
}; implementedThemeTypes: BuildContext["implementedThemeTypes"];
};
assert<BuildContext extends BuildContextLike ? true : false>(); assert<BuildContext extends BuildContextLike ? true : false>();
@ -234,12 +239,19 @@ export async function buildJar(params: {
) )
); );
const jarFilePath_generatedByMaven = pathJoin(
keycloakifyBuildCacheDirPath,
"target",
`${buildContext.artifactId}-${buildContext.themeVersion}.jar`
);
await bundleExtensionsIntoJar({
buildContext,
jarFilePath: jarFilePath_generatedByMaven
});
await fs.rename( await fs.rename(
pathJoin( jarFilePath_generatedByMaven,
keycloakifyBuildCacheDirPath,
"target",
`${buildContext.artifactId}-${buildContext.themeVersion}.jar`
),
pathJoin(buildContext.keycloakifyBuildDirPath, jarFileBasename) pathJoin(buildContext.keycloakifyBuildDirPath, jarFileBasename)
); );
} }

View File

@ -0,0 +1,137 @@
import { downloadAndExtractArchive } from "../../tools/downloadAndExtractArchive";
import { assert } from "tsafe/assert";
import type { BuildContext } from "../../shared/buildContext";
import { transformCodebase } from "../../tools/transformCodebase";
import { join as pathJoin, basename as pathBasename, sep as pathSep } from "path";
import { rm } from "../../tools/fs.rm";
import { extractArchive } from "../../tools/extractArchive";
import * as crypto from "crypto";
export type BuildContextLike = {
cacheDirPath: string;
fetchOptions: BuildContext["fetchOptions"];
extensionJars: BuildContext["extensionJars"];
};
assert<BuildContext extends BuildContextLike ? true : false>();
export async function bundleExtensionsIntoJar(params: {
jarFilePath: string;
buildContext: BuildContextLike;
}): Promise<void> {
const { jarFilePath, buildContext } = params;
if (buildContext.extensionJars.length === 0) {
return;
}
const mergeDirPath = pathJoin(
buildContext.cacheDirPath,
`merge_${pathBasename(jarFilePath).replace(/\.jar$/, "")}_${crypto
.createHash("sha256")
.update(jarFilePath)
.digest("hex")
.substring(0, 5)}`
);
await extractArchive({
archiveFilePath: jarFilePath,
onArchiveFile: async ({ relativeFilePathInArchive, writeFile }) =>
writeFile({
filePath: pathJoin(mergeDirPath, relativeFilePathInArchive)
})
});
for (const extensionJar of buildContext.extensionJars) {
const transformSourceCode = (params: {
fileRelativePath: string;
sourceCode: Buffer;
}): { modifiedSourceCode: Buffer } | undefined => {
const { fileRelativePath } = params;
if (!fileRelativePath.startsWith(`META-INF${pathSep}`)) {
for (const ext of [".DSA", ".SF", ".RSA"]) {
if (fileRelativePath.endsWith(ext)) {
return undefined;
}
}
}
return undefined;
};
switch (extensionJar.type) {
case "path":
await extractArchive({
archiveFilePath: extensionJar.path,
onArchiveFile: async ({
relativeFilePathInArchive,
writeFile,
readFile
}) => {
const transformResult = transformSourceCode({
fileRelativePath: relativeFilePathInArchive,
sourceCode: await readFile()
});
if (transformResult === undefined) {
return;
}
await writeFile({
filePath: pathJoin(mergeDirPath, relativeFilePathInArchive),
modifiedData: transformResult.modifiedSourceCode
});
}
});
break;
case "url": {
const { extractedDirPath } = await downloadAndExtractArchive({
url: extensionJar.url,
cacheDirPath: buildContext.cacheDirPath,
fetchOptions: buildContext.fetchOptions,
uniqueIdOfOnArchiveFile: "noOp",
onArchiveFile: async ({ fileRelativePath, writeFile }) =>
writeFile({ fileRelativePath })
});
transformCodebase({
srcDirPath: extractedDirPath,
destDirPath: mergeDirPath,
transformSourceCode
});
break;
}
}
/*
transformCodebase({
srcDirPath: extractedDirPath,
destDirPath: mergeDirPath,
transformSourceCode: ({ fileRelativePath, sourceCode }) => {
if (fileRelativePath === pathJoin("META-INF", "MANIFEST.MF")) {
const sourceCodeStr = sourceCode.toString("utf8");
const lines = sourceCodeStr.split(/\r?\n/);
console.log(lines);
return {
modifiedSourceCode: Buffer.concat([
sourceCode,
Buffer.from(
`Class-Path: ${pathBasename(userProvidedJarFilePathOrUrl)}\n`
)
])
};
}
}
});
*/
}
// TODO: Acctually build new jar
await rm(mergeDirPath, { recursive: true, force: true });
}

View File

@ -314,8 +314,7 @@ export async function generateResourcesForMainTheme(params: {
} }
const { extractedDirPath } = await downloadAndExtractArchive({ const { extractedDirPath } = await downloadAndExtractArchive({
urlOrPath: url: "https://repo1.maven.org/maven2/org/keycloak/keycloak-account-ui/25.0.1/keycloak-account-ui-25.0.1.jar",
"https://repo1.maven.org/maven2/org/keycloak/keycloak-account-ui/25.0.1/keycloak-account-ui-25.0.1.jar",
cacheDirPath: buildContext.cacheDirPath, cacheDirPath: buildContext.cacheDirPath,
fetchOptions: buildContext.fetchOptions, fetchOptions: buildContext.fetchOptions,
uniqueIdOfOnArchiveFile: "bring_in_account_v3_i18n_messages", uniqueIdOfOnArchiveFile: "bring_in_account_v3_i18n_messages",

View File

@ -26,6 +26,8 @@ import { type ThemeType } from "./constants";
import { id } from "tsafe/id"; import { id } from "tsafe/id";
import chalk from "chalk"; import chalk from "chalk";
import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions"; import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions";
import { removeDuplicates } from "evt/tools/reducers/removeDuplicates";
import { same } from "evt/tools/inDepth/same";
export type BuildContext = { export type BuildContext = {
themeVersion: string; themeVersion: string;
@ -61,6 +63,7 @@ export type BuildContext = {
keycloakVersionRange: KeycloakVersionRange; keycloakVersionRange: KeycloakVersionRange;
jarFileBasename: string; jarFileBasename: string;
}[]; }[];
extensionJars: ({ type: "path"; path: string } | { type: "url"; url: string })[];
startKeycloakOptions: { startKeycloakOptions: {
dockerImage: dockerImage:
| { | {
@ -88,6 +91,7 @@ export type BuildOptions = {
loginThemeResourcesFromKeycloakVersion?: string; loginThemeResourcesFromKeycloakVersion?: string;
keycloakifyBuildDirPath?: string; keycloakifyBuildDirPath?: string;
kcContextExclusionsFtl?: string; kcContextExclusionsFtl?: string;
extensionJars?: string[];
startKeycloakOptions?: { startKeycloakOptions?: {
dockerImage?: string; dockerImage?: string;
dockerExtraArgs?: string[]; dockerExtraArgs?: string[];
@ -360,6 +364,7 @@ export function getBuildContext(params: {
loginThemeResourcesFromKeycloakVersion: z.string().optional(), loginThemeResourcesFromKeycloakVersion: z.string().optional(),
keycloakifyBuildDirPath: z.string().optional(), keycloakifyBuildDirPath: z.string().optional(),
kcContextExclusionsFtl: z.string().optional(), kcContextExclusionsFtl: z.string().optional(),
extensionJars: z.array(z.string()).optional(),
startKeycloakOptions: zStartKeycloakOptions.optional() startKeycloakOptions: zStartKeycloakOptions.optional()
}), }),
zAccountThemeImplAndKeycloakVersionTargets zAccountThemeImplAndKeycloakVersionTargets
@ -520,6 +525,36 @@ export function getBuildContext(params: {
return pathJoin(projectDirPath, resolvedViteConfig.buildDir); return pathJoin(projectDirPath, resolvedViteConfig.buildDir);
})(); })();
const buildForKeycloakMajorVersionNumber = (() => {
const envValue = process.env[BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME];
if (envValue === undefined) {
return undefined;
}
const major = parseInt(envValue);
assert(!isNaN(major));
return major;
})();
function urlOrPathToDiscriminatingWrapper(
urlOrPath: string
): { type: "url"; url: string } | { type: "path"; path: string } {
if (/^https?:\/\//.test(urlOrPath)) {
return { type: "url", url: urlOrPath };
}
return {
type: "path",
path: getAbsoluteAndInOsFormatPath({
pathIsh: urlOrPath,
cwd: projectDirPath
})
};
}
return { return {
bundler, bundler,
packageJsonFilePath, packageJsonFilePath,
@ -717,21 +752,6 @@ export function getBuildContext(params: {
`keycloak-theme-for-kc-${range}.jar`; `keycloak-theme-for-kc-${range}.jar`;
build_for_specific_keycloak_major_version: { build_for_specific_keycloak_major_version: {
const buildForKeycloakMajorVersionNumber = (() => {
const envValue =
process.env[BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME];
if (envValue === undefined) {
return undefined;
}
const major = parseInt(envValue);
assert(!isNaN(major));
return major;
})();
if (buildForKeycloakMajorVersionNumber === undefined) { if (buildForKeycloakMajorVersionNumber === undefined) {
break build_for_specific_keycloak_major_version; break build_for_specific_keycloak_major_version;
} }
@ -931,6 +951,10 @@ export function getBuildContext(params: {
return jarTargets; return jarTargets;
})(), })(),
extensionJars: (buildForKeycloakMajorVersionNumber !== undefined
? []
: buildOptions.extensionJars ?? []
).map(urlOrPath => urlOrPathToDiscriminatingWrapper(urlOrPath)),
startKeycloakOptions: { startKeycloakOptions: {
dockerImage: (() => { dockerImage: (() => {
if (buildOptions.startKeycloakOptions?.dockerImage === undefined) { if (buildOptions.startKeycloakOptions?.dockerImage === undefined) {
@ -949,21 +973,14 @@ export function getBuildContext(params: {
})(), })(),
dockerExtraArgs: buildOptions.startKeycloakOptions?.dockerExtraArgs ?? [], dockerExtraArgs: buildOptions.startKeycloakOptions?.dockerExtraArgs ?? [],
keycloakExtraArgs: buildOptions.startKeycloakOptions?.keycloakExtraArgs ?? [], keycloakExtraArgs: buildOptions.startKeycloakOptions?.keycloakExtraArgs ?? [],
extensionJars: (buildOptions.startKeycloakOptions?.extensionJars ?? []).map( extensionJars: [
urlOrPath => { ...(buildForKeycloakMajorVersionNumber !== undefined
if (/^https?:\/\//.test(urlOrPath)) { ? buildOptions.extensionJars ?? []
return { type: "url", url: urlOrPath }; : []),
} ...(buildOptions.startKeycloakOptions?.extensionJars ?? [])
]
return { .map(urlOrPath => urlOrPathToDiscriminatingWrapper(urlOrPath))
type: "path", .reduce(...removeDuplicates<BuildContext["extensionJars"][number]>(same)),
path: getAbsoluteAndInOsFormatPath({
pathIsh: urlOrPath,
cwd: projectDirPath
})
};
}
),
realmJsonFilePath: realmJsonFilePath:
buildOptions.startKeycloakOptions?.realmJsonFilePath === undefined buildOptions.startKeycloakOptions?.realmJsonFilePath === undefined
? undefined ? undefined

View File

@ -21,7 +21,7 @@ export async function downloadKeycloakDefaultTheme(params: {
let kcNodeModulesKeepFilePaths_lastAccountV1: Set<string> | undefined = undefined; let kcNodeModulesKeepFilePaths_lastAccountV1: Set<string> | undefined = undefined;
const { extractedDirPath } = await downloadAndExtractArchive({ const { extractedDirPath } = await downloadAndExtractArchive({
urlOrPath: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`, url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`,
cacheDirPath: buildContext.cacheDirPath, cacheDirPath: buildContext.cacheDirPath,
fetchOptions: buildContext.fetchOptions, fetchOptions: buildContext.fetchOptions,
uniqueIdOfOnArchiveFile: "downloadKeycloakDefaultTheme", uniqueIdOfOnArchiveFile: "downloadKeycloakDefaultTheme",

View File

@ -200,7 +200,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
const { archiveFilePath } = await downloadAndExtractArchive({ const { archiveFilePath } = await downloadAndExtractArchive({
cacheDirPath: buildContext.cacheDirPath, cacheDirPath: buildContext.cacheDirPath,
fetchOptions: buildContext.fetchOptions, fetchOptions: buildContext.fetchOptions,
urlOrPath: extensionJar.url, url: extensionJar.url,
uniqueIdOfOnArchiveFile: "no extraction", uniqueIdOfOnArchiveFile: "no extraction",
onArchiveFile: async () => {} onArchiveFile: async () => {}
}); });

View File

@ -1,15 +1,14 @@
import fetch, { type FetchOptions } from "make-fetch-happen"; import fetch, { type FetchOptions } from "make-fetch-happen";
import { mkdir, unlink, writeFile, readdir, readFile } from "fs/promises"; import { mkdir, unlink, writeFile, readdir, readFile } from "fs/promises";
import { dirname as pathDirname, join as pathJoin, basename as pathBasename } from "path"; import { dirname as pathDirname, join as pathJoin } from "path";
import { assert } from "tsafe/assert"; import { assert } from "tsafe/assert";
import { extractArchive } from "./extractArchive"; import { extractArchive } from "./extractArchive";
import { existsAsync } from "./fs.existsAsync"; import { existsAsync } from "./fs.existsAsync";
import * as crypto from "crypto"; import * as crypto from "crypto";
import { rm } from "./fs.rm"; import { rm } from "./fs.rm";
import * as fsPr from "fs/promises";
export async function downloadAndExtractArchive(params: { export async function downloadAndExtractArchive(params: {
urlOrPath: string; url: string;
uniqueIdOfOnArchiveFile: string; uniqueIdOfOnArchiveFile: string;
onArchiveFile: (params: { onArchiveFile: (params: {
fileRelativePath: string; fileRelativePath: string;
@ -22,33 +21,16 @@ export async function downloadAndExtractArchive(params: {
cacheDirPath: string; cacheDirPath: string;
fetchOptions: FetchOptions | undefined; fetchOptions: FetchOptions | undefined;
}): Promise<{ extractedDirPath: string; archiveFilePath: string }> { }): Promise<{ extractedDirPath: string; archiveFilePath: string }> {
const { const { url, uniqueIdOfOnArchiveFile, onArchiveFile, cacheDirPath, fetchOptions } =
urlOrPath, params;
uniqueIdOfOnArchiveFile,
onArchiveFile,
cacheDirPath,
fetchOptions
} = params;
const isUrl = /^https?:\/\//.test(urlOrPath); const archiveFileBasename = url.split("?")[0].split("/").reverse()[0];
const archiveFileBasename = isUrl
? urlOrPath.split("?")[0].split("/").reverse()[0]
: pathBasename(urlOrPath);
const archiveFilePath = pathJoin(cacheDirPath, archiveFileBasename); const archiveFilePath = pathJoin(cacheDirPath, archiveFileBasename);
download: { download: {
await mkdir(pathDirname(archiveFilePath), { recursive: true }); await mkdir(pathDirname(archiveFilePath), { recursive: true });
if (!isUrl) {
await fsPr.copyFile(urlOrPath, archiveFilePath);
break download;
}
const url = urlOrPath;
if (await existsAsync(archiveFilePath)) { if (await existsAsync(archiveFilePath)) {
const isDownloaded = await SuccessTracker.getIsDownloaded({ const isDownloaded = await SuccessTracker.getIsDownloaded({
cacheDirPath, cacheDirPath,