Compare commits
17 Commits
Author | SHA1 | Date | |
---|---|---|---|
556ce60b27 | |||
12857e3027 | |||
57b056b388 | |||
9058e9ac9d | |||
ad3de8bff5 | |||
476b100b04 | |||
b2c7c86609 | |||
f8a8ec2e4d | |||
393a5ba125 | |||
466c2d3eb4 | |||
b325b3537f | |||
e429127313 | |||
2d05521789 | |||
feaf34c124 | |||
c1e0563eba | |||
1c66f35337 | |||
4a7dd64982 |
91
keycloakify-json-schema.json
Normal file
91
keycloakify-json-schema.json
Normal file
@ -0,0 +1,91 @@
|
||||
{
|
||||
"allOf": [
|
||||
{
|
||||
"$ref": "https://json.schemastore.org/package.json"
|
||||
},
|
||||
{
|
||||
"$ref": "keycloakifyPackageJsonSchema"
|
||||
}
|
||||
],
|
||||
"$ref": "#/definitions/keycloakifyPackageJsonSchema",
|
||||
"definitions": {
|
||||
"keycloakifyPackageJsonSchema": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"version": {
|
||||
"type": "string"
|
||||
},
|
||||
"homepage": {
|
||||
"type": "string"
|
||||
},
|
||||
"keycloakify": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"extraPages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extraLoginPages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extraAccountPages": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"extraThemeProperties": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"areAppAndKeycloakServerSharingSameDomain": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"artifactId": {
|
||||
"type": "string"
|
||||
},
|
||||
"groupId": {
|
||||
"type": "string"
|
||||
},
|
||||
"bundler": {
|
||||
"type": "string",
|
||||
"enum": ["mvn", "keycloakify", "none"]
|
||||
},
|
||||
"keycloakVersionDefaultAssets": {
|
||||
"type": "string"
|
||||
},
|
||||
"reactAppBuildDirPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"keycloakifyBuildDirPath": {
|
||||
"type": "string"
|
||||
},
|
||||
"customUserAttributes": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"themeName": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"required": ["name", "version"],
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"$schema": "http://json-schema.org/draft-07/schema#"
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "7.6.4",
|
||||
"version": "7.6.7",
|
||||
"description": "Create Keycloak themes using React",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -12,6 +12,7 @@
|
||||
"prepare": "yarn generate-i18n-messages",
|
||||
"build": "rimraf dist/ && tsc -p src/bin && tsc -p src/tsconfig.json && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/",
|
||||
"build:watch": "tsc -p src/tsconfig.json && (concurrently \"tsc -p src/tsconfig.json -w\" \"tsc-alias -p src/tsconfig.json\")",
|
||||
"generate:json-schema": "ts-node scripts/generate-json-schema.ts",
|
||||
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
||||
"copy-files": "copyfiles -u 1 src/**/*.ftl",
|
||||
"test": "yarn test:types && vitest run",
|
||||
@ -82,7 +83,8 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"tsc-alias": "^1.8.3",
|
||||
"typescript": "^5.0.1-rc",
|
||||
"vitest": "^0.29.8"
|
||||
"vitest": "^0.29.8",
|
||||
"zod-to-json-schema": "^3.20.4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@octokit/rest": "^18.12.0",
|
||||
|
14
scripts/generate-json-schema.ts
Normal file
14
scripts/generate-json-schema.ts
Normal file
@ -0,0 +1,14 @@
|
||||
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));
|
@ -124,7 +124,6 @@ export default function LogoutConfirm(props: PageProps<Extract<KcContext, { page
|
||||
>
|
||||
{msg("doCancel")}
|
||||
</button>
|
||||
I
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
@ -13,7 +13,7 @@ export type BuildOptions = BuildOptions.Standalone | BuildOptions.ExternalAssets
|
||||
export namespace BuildOptions {
|
||||
export type Common = {
|
||||
isSilent: boolean;
|
||||
version: string;
|
||||
themeVersion: string;
|
||||
themeName: string;
|
||||
extraLoginPages: string[] | undefined;
|
||||
extraAccountPages: string[] | undefined;
|
||||
@ -139,7 +139,7 @@ export function readBuildOptions(params: { projectDirPath: string; isExternalAss
|
||||
.join(".") ?? fallbackGroupId) + ".keycloak"
|
||||
);
|
||||
})(),
|
||||
"version": process.env.KEYCLOAKIFY_VERSION ?? version,
|
||||
"themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? process.env.KEYCLOAKIFY_VERSION ?? version ?? "0.0.0",
|
||||
"extraLoginPages": [...(extraPages ?? []), ...(extraLoginPages ?? [])],
|
||||
extraAccountPages,
|
||||
extraThemeProperties,
|
||||
|
@ -1,4 +1,5 @@
|
||||
<script>const _=
|
||||
<#assign pageId="PAGE_ID_xIgLsPgGId9D8e">
|
||||
(()=>{
|
||||
|
||||
const out = ${ftl_object_to_js_code_declaring_an_object(.data_model, [])?no_esc};
|
||||
@ -118,8 +119,9 @@
|
||||
};
|
||||
</#if>
|
||||
|
||||
out["pageId"] = "PAGE_ID_xIgLsPgGId9D8e";
|
||||
out["keycloakifyVersion"] = "KEYCLOAKIFY_VERSION_xEdKd3xEdr";
|
||||
out["themeVersion"] = "KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx";
|
||||
out["pageId"] = "${pageId}";
|
||||
|
||||
return out;
|
||||
|
||||
|
@ -18,6 +18,7 @@ export type BuildOptionsLike = BuildOptionsLike.Standalone | BuildOptionsLike.Ex
|
||||
export namespace BuildOptionsLike {
|
||||
export type Common = {
|
||||
customUserAttributes: string[];
|
||||
themeVersion: string;
|
||||
};
|
||||
|
||||
export type Standalone = Common & {
|
||||
@ -130,7 +131,8 @@ export function generateFtlFilesCodeFactory(params: {
|
||||
"CUSTOM_USER_ATTRIBUTES_eKsIY4ZsZ4xeM",
|
||||
buildOptions.customUserAttributes.length === 0 ? "" : ", " + buildOptions.customUserAttributes.map(name => `"${name}"`).join(", ")
|
||||
)
|
||||
.replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion),
|
||||
.replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion)
|
||||
.replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion),
|
||||
"<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
|
||||
"<#if scripts??>",
|
||||
" <#list scripts as script>",
|
||||
|
@ -9,7 +9,7 @@ export type BuildOptionsLike = {
|
||||
themeName: string;
|
||||
groupId: string;
|
||||
artifactId?: string;
|
||||
version: string;
|
||||
themeVersion: string;
|
||||
};
|
||||
|
||||
{
|
||||
@ -26,7 +26,7 @@ export function generateJavaStackFiles(params: {
|
||||
jarFilePath: string;
|
||||
} {
|
||||
const {
|
||||
buildOptions: { groupId, themeName, version, artifactId },
|
||||
buildOptions: { groupId, themeName, themeVersion, artifactId },
|
||||
keycloakThemeBuildingDirPath,
|
||||
doBundlesEmailTemplate
|
||||
} = params;
|
||||
@ -43,7 +43,7 @@ export function generateJavaStackFiles(params: {
|
||||
` <modelVersion>4.0.0</modelVersion>`,
|
||||
` <groupId>${groupId}</groupId>`,
|
||||
` <artifactId>${artifactId}</artifactId>`,
|
||||
` <version>${version}</version>`,
|
||||
` <version>${themeVersion}</version>`,
|
||||
` <name>${artifactId}</name>`,
|
||||
` <description />`,
|
||||
`</project>`
|
||||
@ -83,6 +83,6 @@ export function generateJavaStackFiles(params: {
|
||||
}
|
||||
|
||||
return {
|
||||
"jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${artifactId}-${version}.jar`)
|
||||
"jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${artifactId}-${themeVersion}.jar`)
|
||||
};
|
||||
}
|
||||
|
@ -20,6 +20,7 @@ export namespace BuildOptionsLike {
|
||||
extraThemeProperties?: string[];
|
||||
isSilent: boolean;
|
||||
customUserAttributes: string[];
|
||||
themeVersion: string;
|
||||
};
|
||||
|
||||
export type Standalone = Common & {
|
||||
|
@ -63,7 +63,7 @@ export async function main() {
|
||||
logger.log("🫶 Let keycloakify do its thang");
|
||||
await jar({
|
||||
"rootPath": pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources"),
|
||||
"version": buildOptions.version,
|
||||
"version": buildOptions.themeVersion,
|
||||
"groupId": buildOptions.groupId,
|
||||
"artifactId": buildOptions.artifactId,
|
||||
"targetPath": jarFilePath
|
||||
|
@ -8,7 +8,7 @@ export const bundlers = ["mvn", "keycloakify", "none"] as const;
|
||||
export type Bundler = (typeof bundlers)[number];
|
||||
export type ParsedPackageJson = {
|
||||
name: string;
|
||||
version: string;
|
||||
version?: string;
|
||||
homepage?: string;
|
||||
keycloakify?: {
|
||||
/** @deprecated: use extraLoginPages instead */
|
||||
@ -28,9 +28,9 @@ export type ParsedPackageJson = {
|
||||
};
|
||||
};
|
||||
|
||||
const zParsedPackageJson = z.object({
|
||||
export const zParsedPackageJson = z.object({
|
||||
"name": z.string(),
|
||||
"version": z.string(),
|
||||
"version": z.string().optional(),
|
||||
"homepage": z.string().optional(),
|
||||
"keycloakify": z
|
||||
.object({
|
||||
|
@ -48,7 +48,7 @@ export async function jarStream({ groupId, artifactId, version, asyncPathGenerat
|
||||
for await (const entry of asyncPathGeneratorFn()) {
|
||||
if ("buffer" in entry) {
|
||||
zipFile.addBuffer(entry.buffer, entry.zipPath);
|
||||
} else if ("fsPath" in entry && entry.fsPath.endsWith(sep)) {
|
||||
} else if ("fsPath" in entry && !entry.fsPath.endsWith(sep)) {
|
||||
zipFile.addFile(entry.fsPath, entry.zipPath);
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,9 @@
|
||||
import { readdir } from "fs/promises";
|
||||
import { resolve } from "path";
|
||||
import { resolve, sep } from "path";
|
||||
|
||||
/**
|
||||
* Asynchronously and recursively walk a directory tree, yielding every file and directory
|
||||
* found
|
||||
* found. Directory paths will _always_ end with a path separator.
|
||||
*
|
||||
* @param root the starting directory
|
||||
* @returns AsyncGenerator
|
||||
@ -12,8 +12,8 @@ export default async function* walk(root: string): AsyncGenerator<string, void,
|
||||
for (const entry of await readdir(root, { withFileTypes: true })) {
|
||||
const absolutePath = resolve(root, entry.name);
|
||||
if (entry.isDirectory()) {
|
||||
yield absolutePath;
|
||||
yield absolutePath.endsWith(sep) ? absolutePath : absolutePath + sep;
|
||||
yield* walk(absolutePath);
|
||||
} else yield absolutePath;
|
||||
} else yield absolutePath.endsWith(sep) ? absolutePath.substring(0, absolutePath.length - 1) : absolutePath;
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,12 @@
|
||||
import { jarStream, type ZipEntryGenerator } from "keycloakify/bin/tools/jar";
|
||||
import jar, { jarStream, type ZipEntryGenerator } from "keycloakify/bin/tools/jar";
|
||||
import { fromBuffer, Entry, ZipFile } from "yauzl";
|
||||
import { it, describe, assert } from "vitest";
|
||||
import { it, describe, assert, afterAll } from "vitest";
|
||||
import { Readable } from "stream";
|
||||
import { tmpdir } from "os";
|
||||
import { mkdtemp, cp, mkdir, rm } from "fs/promises";
|
||||
import path from "path";
|
||||
import { createReadStream } from "fs";
|
||||
import walk from "keycloakify/bin/tools/walk";
|
||||
|
||||
type AsyncIterable<T> = {
|
||||
[Symbol.asyncIterator](): AsyncIterableIterator<T>;
|
||||
@ -17,7 +22,7 @@ async function readToBuffer(stream: NodeJS.ReadableStream) {
|
||||
return Buffer.concat(await arrayFromAsync(stream as AsyncIterable<Buffer>));
|
||||
}
|
||||
|
||||
function unzip(buffer: Buffer) {
|
||||
function unzipBuffer(buffer: Buffer) {
|
||||
return new Promise<ZipFile>((resolve, reject) =>
|
||||
fromBuffer(buffer, { lazyEntries: true }, (err, zipFile) => {
|
||||
if (err !== null) {
|
||||
@ -57,15 +62,22 @@ function readAll(zipFile: ZipFile): Promise<Map<string, Buffer>> {
|
||||
}
|
||||
|
||||
describe("jar", () => {
|
||||
const coords = { artifactId: "someArtifactId", groupId: "someGroupId", version: "1.2.3" };
|
||||
|
||||
const tmpDirs: string[] = [];
|
||||
|
||||
afterAll(async () => {
|
||||
await Promise.all(tmpDirs.map(dir => rm(dir, { force: true, recursive: true })));
|
||||
});
|
||||
|
||||
it("creates jar artifacts without error", async () => {
|
||||
async function* mockFiles(): ZipEntryGenerator {
|
||||
yield { zipPath: "foo", buffer: Buffer.from("foo") };
|
||||
}
|
||||
|
||||
const opts = { artifactId: "someArtifactId", groupId: "someGroupId", version: "1.2.3", asyncPathGeneratorFn: mockFiles };
|
||||
const zipped = await jarStream(opts);
|
||||
const zipped = await jarStream({ ...coords, asyncPathGeneratorFn: mockFiles });
|
||||
const buffered = await readToBuffer(zipped.outputStream);
|
||||
const unzipped = await unzip(buffered);
|
||||
const unzipped = await unzipBuffer(buffered);
|
||||
const entries = await readAll(unzipped);
|
||||
|
||||
assert.equal(entries.size, 3);
|
||||
@ -83,4 +95,31 @@ describe("jar", () => {
|
||||
assert.isOk(pomProperties?.includes("someGroupId"));
|
||||
assert.isOk(pomProperties?.includes("someArtifactId"));
|
||||
});
|
||||
|
||||
it("creates a jar from _real_ files without error", async () => {
|
||||
const tmp = await mkdtemp(path.join(tmpdir(), "kc-jar-test-"));
|
||||
tmpDirs.push(tmp);
|
||||
const rootPath = path.join(tmp, "src");
|
||||
const targetPath = path.join(tmp, "jar.jar");
|
||||
await mkdir(rootPath);
|
||||
|
||||
await cp(path.dirname(__dirname), rootPath, { recursive: true });
|
||||
|
||||
await jar({ ...coords, rootPath, targetPath });
|
||||
|
||||
const buffered = await readToBuffer(createReadStream(targetPath));
|
||||
const unzipped = await unzipBuffer(buffered);
|
||||
const entries = await readAll(unzipped);
|
||||
const zipPaths = Array.from(entries.keys());
|
||||
|
||||
assert.isOk(entries.has("META-INF/MANIFEST.MF"));
|
||||
assert.isOk(entries.has("META-INF/maven/someGroupId/someArtifactId/pom.properties"));
|
||||
|
||||
for await (const fsPath of walk(rootPath)) {
|
||||
if (!fsPath.endsWith(path.sep)) {
|
||||
const rel = path.relative(rootPath, fsPath).replace(path.sep === "/" ? /\//g : /\\/g, "/");
|
||||
assert.isOk(zipPaths.includes(rel), `missing ${rel} (${rel}, ${zipPaths.join(", ")})`);
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -3242,6 +3242,11 @@ yocto-queue@^1.0.0:
|
||||
resolved "https://registry.yarnpkg.com/yocto-queue/-/yocto-queue-1.0.0.tgz#7f816433fb2cbc511ec8bf7d263c3b58a1a3c251"
|
||||
integrity sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==
|
||||
|
||||
zod-to-json-schema@^3.20.4:
|
||||
version "3.20.4"
|
||||
resolved "https://registry.yarnpkg.com/zod-to-json-schema/-/zod-to-json-schema-3.20.4.tgz#155f687c5a059fdc0f1bb3ff32d6e9200036b6f4"
|
||||
integrity sha512-Un9+kInJ2Zt63n6Z7mLqBifzzPcOyX+b+Exuzf7L1+xqck9Q2EPByyTRduV3kmSPaXaRer1JCsucubpgL1fipg==
|
||||
|
||||
zod@^3.17.10:
|
||||
version "3.21.4"
|
||||
resolved "https://registry.yarnpkg.com/zod/-/zod-3.21.4.tgz#10882231d992519f0a10b5dd58a38c9dabbb64db"
|
||||
|
Reference in New Issue
Block a user