keycloakify 6.10.1 → 6.11.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -0
- package/bin/keycloakify/BuildOptions.d.ts +5 -0
- package/bin/keycloakify/BuildOptions.js +47 -8
- package/bin/keycloakify/BuildOptions.js.map +1 -1
- package/bin/keycloakify/generateJavaStackFiles.d.ts +2 -1
- package/bin/keycloakify/generateJavaStackFiles.js +4 -5
- package/bin/keycloakify/generateJavaStackFiles.js.map +1 -1
- package/bin/keycloakify/keycloakify.js +39 -8
- package/bin/keycloakify/keycloakify.js.map +1 -1
- package/bin/tools/crc32.d.ts +9 -0
- package/bin/tools/crc32.js +63 -0
- package/bin/tools/crc32.js.map +1 -0
- package/bin/tools/deflate.d.ts +24 -0
- package/bin/tools/deflate.js +155 -0
- package/bin/tools/deflate.js.map +1 -0
- package/bin/tools/jar.d.ts +14 -0
- package/bin/tools/jar.js +124 -0
- package/bin/tools/jar.js.map +1 -0
- package/bin/tools/tee.d.ts +3 -0
- package/bin/tools/tee.js +36 -0
- package/bin/tools/tee.js.map +1 -0
- package/bin/tools/walk.d.ts +8 -0
- package/bin/tools/walk.js +125 -0
- package/bin/tools/walk.js.map +1 -0
- package/bin/tools/zip.d.ts +29 -0
- package/bin/tools/zip.js +329 -0
- package/bin/tools/zip.js.map +1 -0
- package/bin/tsconfig.tsbuildinfo +1 -1
- package/package.json +25 -1
- package/src/bin/keycloakify/BuildOptions.ts +31 -4
- package/src/bin/keycloakify/generateJavaStackFiles.ts +6 -8
- package/src/bin/keycloakify/keycloakify.ts +25 -5
- package/src/bin/tools/crc32.ts +54 -0
- package/src/bin/tools/deflate.ts +61 -0
- package/src/bin/tools/downloadAndUnzip.ts +1 -1
- package/src/bin/tools/jar.ts +100 -0
- package/src/bin/tools/tee.ts +37 -0
- package/src/bin/tools/walk.ts +19 -0
- package/src/bin/tools/zip.ts +245 -0
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "keycloakify",
|
3
|
-
"version": "6.
|
3
|
+
"version": "6.11.1",
|
4
4
|
"description": "Keycloak theme generator for Reacts app",
|
5
5
|
"repository": {
|
6
6
|
"type": "git",
|
@@ -48,15 +48,21 @@
|
|
48
48
|
"src/bin/tools/NpmModuleVersion.ts",
|
49
49
|
"src/bin/tools/cliOptions.ts",
|
50
50
|
"src/bin/tools/crawl.ts",
|
51
|
+
"src/bin/tools/crc32.ts",
|
52
|
+
"src/bin/tools/deflate.ts",
|
51
53
|
"src/bin/tools/downloadAndUnzip.ts",
|
52
54
|
"src/bin/tools/getProjectRoot.ts",
|
53
55
|
"src/bin/tools/grant-exec-perms.ts",
|
54
56
|
"src/bin/tools/isInside.ts",
|
57
|
+
"src/bin/tools/jar.ts",
|
55
58
|
"src/bin/tools/logger.ts",
|
56
59
|
"src/bin/tools/octokit-addons/getLatestsSemVersionedTag.ts",
|
57
60
|
"src/bin/tools/octokit-addons/listTags.ts",
|
58
61
|
"src/bin/tools/pathJoin.ts",
|
62
|
+
"src/bin/tools/tee.ts",
|
59
63
|
"src/bin/tools/transformCodebase.ts",
|
64
|
+
"src/bin/tools/walk.ts",
|
65
|
+
"src/bin/tools/zip.ts",
|
60
66
|
"src/bin/tsconfig.json",
|
61
67
|
"src/lib/components/Error.tsx",
|
62
68
|
"src/lib/components/IdpReviewUserProfile.tsx",
|
@@ -406,6 +412,12 @@
|
|
406
412
|
"bin/tools/crawl.d.ts",
|
407
413
|
"bin/tools/crawl.js",
|
408
414
|
"bin/tools/crawl.js.map",
|
415
|
+
"bin/tools/crc32.d.ts",
|
416
|
+
"bin/tools/crc32.js",
|
417
|
+
"bin/tools/crc32.js.map",
|
418
|
+
"bin/tools/deflate.d.ts",
|
419
|
+
"bin/tools/deflate.js",
|
420
|
+
"bin/tools/deflate.js.map",
|
409
421
|
"bin/tools/downloadAndUnzip.d.ts",
|
410
422
|
"bin/tools/downloadAndUnzip.js",
|
411
423
|
"bin/tools/downloadAndUnzip.js.map",
|
@@ -418,6 +430,9 @@
|
|
418
430
|
"bin/tools/isInside.d.ts",
|
419
431
|
"bin/tools/isInside.js",
|
420
432
|
"bin/tools/isInside.js.map",
|
433
|
+
"bin/tools/jar.d.ts",
|
434
|
+
"bin/tools/jar.js",
|
435
|
+
"bin/tools/jar.js.map",
|
421
436
|
"bin/tools/logger.d.ts",
|
422
437
|
"bin/tools/logger.js",
|
423
438
|
"bin/tools/logger.js.map",
|
@@ -430,9 +445,18 @@
|
|
430
445
|
"bin/tools/pathJoin.d.ts",
|
431
446
|
"bin/tools/pathJoin.js",
|
432
447
|
"bin/tools/pathJoin.js.map",
|
448
|
+
"bin/tools/tee.d.ts",
|
449
|
+
"bin/tools/tee.js",
|
450
|
+
"bin/tools/tee.js.map",
|
433
451
|
"bin/tools/transformCodebase.d.ts",
|
434
452
|
"bin/tools/transformCodebase.js",
|
435
453
|
"bin/tools/transformCodebase.js.map",
|
454
|
+
"bin/tools/walk.d.ts",
|
455
|
+
"bin/tools/walk.js",
|
456
|
+
"bin/tools/walk.js.map",
|
457
|
+
"bin/tools/zip.d.ts",
|
458
|
+
"bin/tools/zip.js",
|
459
|
+
"bin/tools/zip.js.map",
|
436
460
|
"bin/tsconfig.tsbuildinfo",
|
437
461
|
"lib/components/Error.d.ts",
|
438
462
|
"lib/components/Error.js",
|
@@ -3,7 +3,11 @@ import { assert } from "tsafe/assert";
|
|
3
3
|
import type { Equals } from "tsafe";
|
4
4
|
import { id } from "tsafe/id";
|
5
5
|
import { parse as urlParse } from "url";
|
6
|
+
import { typeGuard } from "tsafe/typeGuard";
|
7
|
+
import { symToStr } from "tsafe/symToStr";
|
6
8
|
|
9
|
+
const bundlers = ["mvn", "keycloakify", "none"] as const;
|
10
|
+
type Bundler = typeof bundlers[number];
|
7
11
|
type ParsedPackageJson = {
|
8
12
|
name: string;
|
9
13
|
version: string;
|
@@ -12,6 +16,9 @@ type ParsedPackageJson = {
|
|
12
16
|
extraPages?: string[];
|
13
17
|
extraThemeProperties?: string[];
|
14
18
|
areAppAndKeycloakServerSharingSameDomain?: boolean;
|
19
|
+
artifactId?: string;
|
20
|
+
groupId?: string;
|
21
|
+
bundler?: Bundler;
|
15
22
|
};
|
16
23
|
};
|
17
24
|
|
@@ -23,7 +30,10 @@ const zParsedPackageJson = z.object({
|
|
23
30
|
.object({
|
24
31
|
"extraPages": z.array(z.string()).optional(),
|
25
32
|
"extraThemeProperties": z.array(z.string()).optional(),
|
26
|
-
"areAppAndKeycloakServerSharingSameDomain": z.boolean().optional()
|
33
|
+
"areAppAndKeycloakServerSharingSameDomain": z.boolean().optional(),
|
34
|
+
"artifactId": z.string().optional(),
|
35
|
+
"groupId": z.string().optional(),
|
36
|
+
"bundler": z.enum(bundlers).optional()
|
27
37
|
})
|
28
38
|
.optional()
|
29
39
|
});
|
@@ -40,8 +50,9 @@ export namespace BuildOptions {
|
|
40
50
|
themeName: string;
|
41
51
|
extraPages?: string[];
|
42
52
|
extraThemeProperties?: string[];
|
43
|
-
//NOTE: Only for the pom.xml file, questionable utility...
|
44
53
|
groupId: string;
|
54
|
+
artifactId: string;
|
55
|
+
bundler: Bundler;
|
45
56
|
};
|
46
57
|
|
47
58
|
export type Standalone = Common & {
|
@@ -108,7 +119,7 @@ export function readBuildOptions(params: {
|
|
108
119
|
const common: BuildOptions.Common = (() => {
|
109
120
|
const { name, keycloakify = {}, version, homepage } = parsedPackageJson;
|
110
121
|
|
111
|
-
const { extraPages, extraThemeProperties } = keycloakify ?? {};
|
122
|
+
const { extraPages, extraThemeProperties, groupId, artifactId, bundler } = keycloakify ?? {};
|
112
123
|
|
113
124
|
const themeName = name
|
114
125
|
.replace(/^@(.*)/, "$1")
|
@@ -117,10 +128,26 @@ export function readBuildOptions(params: {
|
|
117
128
|
|
118
129
|
return {
|
119
130
|
themeName,
|
131
|
+
"bundler": (() => {
|
132
|
+
const { KEYCLOAKIFY_BUNDLER } = process.env;
|
133
|
+
|
134
|
+
assert(
|
135
|
+
typeGuard<Bundler | undefined>(
|
136
|
+
KEYCLOAKIFY_BUNDLER,
|
137
|
+
[undefined, ...id<readonly string[]>(bundlers)].includes(KEYCLOAKIFY_BUNDLER)
|
138
|
+
),
|
139
|
+
`${symToStr({ KEYCLOAKIFY_BUNDLER })} should be one of ${bundlers.join(", ")}`
|
140
|
+
);
|
141
|
+
|
142
|
+
return KEYCLOAKIFY_BUNDLER ?? bundler ?? "mvn";
|
143
|
+
})(),
|
144
|
+
"artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? artifactId ?? `${themeName}-keycloak-theme`,
|
120
145
|
"groupId": (() => {
|
121
146
|
const fallbackGroupId = `${themeName}.keycloak`;
|
122
147
|
|
123
148
|
return (
|
149
|
+
process.env.KEYCLOAKIFY_GROUP_ID ??
|
150
|
+
groupId ??
|
124
151
|
(!homepage
|
125
152
|
? fallbackGroupId
|
126
153
|
: urlParse(homepage)
|
@@ -130,7 +157,7 @@ export function readBuildOptions(params: {
|
|
130
157
|
.join(".") ?? fallbackGroupId) + ".keycloak"
|
131
158
|
);
|
132
159
|
})(),
|
133
|
-
"version": version,
|
160
|
+
"version": process.env.KEYCLOAKIFY_VERSION ?? version,
|
134
161
|
extraPages,
|
135
162
|
extraThemeProperties,
|
136
163
|
isSilent
|
@@ -7,6 +7,8 @@ import type { BuildOptions } from "./BuildOptions";
|
|
7
7
|
export type BuildOptionsLike = {
|
8
8
|
themeName: string;
|
9
9
|
groupId: string;
|
10
|
+
artifactId?: string;
|
11
|
+
version: string;
|
10
12
|
};
|
11
13
|
|
12
14
|
{
|
@@ -16,7 +18,6 @@ export type BuildOptionsLike = {
|
|
16
18
|
}
|
17
19
|
|
18
20
|
export function generateJavaStackFiles(params: {
|
19
|
-
version: string;
|
20
21
|
keycloakThemeBuildingDirPath: string;
|
21
22
|
doBundlesEmailTemplate: boolean;
|
22
23
|
buildOptions: BuildOptionsLike;
|
@@ -24,14 +25,11 @@ export function generateJavaStackFiles(params: {
|
|
24
25
|
jarFilePath: string;
|
25
26
|
} {
|
26
27
|
const {
|
27
|
-
version,
|
28
|
-
buildOptions: { groupId, themeName },
|
28
|
+
buildOptions: { groupId, themeName, version, artifactId },
|
29
29
|
keycloakThemeBuildingDirPath,
|
30
30
|
doBundlesEmailTemplate
|
31
31
|
} = params;
|
32
32
|
|
33
|
-
const artefactId = `${themeName}-keycloak-theme`;
|
34
|
-
|
35
33
|
{
|
36
34
|
const { pomFileCode } = (function generatePomFileCode(): {
|
37
35
|
pomFileCode: string;
|
@@ -43,9 +41,9 @@ export function generateJavaStackFiles(params: {
|
|
43
41
|
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">`,
|
44
42
|
` <modelVersion>4.0.0</modelVersion>`,
|
45
43
|
` <groupId>${groupId}</groupId>`,
|
46
|
-
` <artifactId>${
|
44
|
+
` <artifactId>${artifactId}</artifactId>`,
|
47
45
|
` <version>${version}</version>`,
|
48
|
-
` <name>${
|
46
|
+
` <name>${artifactId}</name>`,
|
49
47
|
` <description />`,
|
50
48
|
`</project>`
|
51
49
|
].join("\n");
|
@@ -84,6 +82,6 @@ export function generateJavaStackFiles(params: {
|
|
84
82
|
}
|
85
83
|
|
86
84
|
return {
|
87
|
-
"jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${
|
85
|
+
"jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${artifactId}-${version}.jar`)
|
88
86
|
};
|
89
87
|
}
|
@@ -7,6 +7,9 @@ import * as fs from "fs";
|
|
7
7
|
import { readBuildOptions } from "./BuildOptions";
|
8
8
|
import { getLogger } from "../tools/logger";
|
9
9
|
import { getCliOptions } from "../tools/cliOptions";
|
10
|
+
import jar from "../tools/jar";
|
11
|
+
import { assert } from "tsafe/assert";
|
12
|
+
import type { Equals } from "tsafe";
|
10
13
|
|
11
14
|
const reactProjectDirPath = process.cwd();
|
12
15
|
|
@@ -45,17 +48,34 @@ export async function main() {
|
|
45
48
|
});
|
46
49
|
|
47
50
|
const { jarFilePath } = generateJavaStackFiles({
|
48
|
-
"version": buildOptions.version,
|
49
51
|
keycloakThemeBuildingDirPath,
|
50
52
|
doBundlesEmailTemplate,
|
51
53
|
buildOptions
|
52
54
|
});
|
53
55
|
|
54
|
-
|
55
|
-
"
|
56
|
-
|
56
|
+
switch (buildOptions.bundler) {
|
57
|
+
case "none":
|
58
|
+
logger.log("😱 Skipping bundling step, there will be no jar");
|
59
|
+
break;
|
60
|
+
case "keycloakify":
|
61
|
+
logger.log("🫶 Let keycloakify do its thang");
|
62
|
+
await jar({
|
63
|
+
"rootPath": keycloakThemeBuildingDirPath,
|
64
|
+
"version": buildOptions.version,
|
65
|
+
"groupId": buildOptions.groupId,
|
66
|
+
"artifactId": buildOptions.artifactId || `${buildOptions.themeName}-keycloak-theme`,
|
67
|
+
"targetPath": jarFilePath
|
68
|
+
});
|
69
|
+
break;
|
70
|
+
case "mvn":
|
71
|
+
logger.log("🫙 Run maven to deliver a jar");
|
72
|
+
child_process.execSync("mvn package", { "cwd": keycloakThemeBuildingDirPath });
|
73
|
+
break;
|
74
|
+
default:
|
75
|
+
assert<Equals<typeof buildOptions.bundler, never>>(false);
|
76
|
+
}
|
57
77
|
|
58
|
-
//We want, however, to test in a container running the latest Keycloak version
|
78
|
+
// We want, however, to test in a container running the latest Keycloak version
|
59
79
|
const containerKeycloakVersion = "20.0.1";
|
60
80
|
|
61
81
|
generateStartKeycloakTestingContainer({
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import { Readable } from "stream";
|
2
|
+
|
3
|
+
const crc32tab = [
|
4
|
+
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419, 0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4, 0xe0d5e91e, 0x97d2d988,
|
5
|
+
0x09b64c2b, 0x7eb17cbd, 0xe7b82d07, 0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de, 0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7,
|
6
|
+
0x136c9856, 0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9, 0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4, 0xa2677172,
|
7
|
+
0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b, 0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3, 0x45df5c75, 0xdcd60dcf, 0xabd13d59,
|
8
|
+
0x26d930ac, 0x51de003a, 0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599, 0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
9
|
+
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190, 0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f, 0x9fbfe4a5, 0xe8b8d433,
|
10
|
+
0x7807c9a2, 0x0f00f934, 0x9609a88e, 0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01, 0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e,
|
11
|
+
0x6c0695ed, 0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950, 0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3, 0xfbd44c65,
|
12
|
+
0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2, 0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a, 0x346ed9fc, 0xad678846, 0xda60b8d0,
|
13
|
+
0x44042d73, 0x33031de5, 0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010, 0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
14
|
+
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17, 0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6, 0x03b6e20c, 0x74b1d29a,
|
15
|
+
0xead54739, 0x9dd277af, 0x04db2615, 0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8, 0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1,
|
16
|
+
0xf00f9344, 0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb, 0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a, 0x67dd4acc,
|
17
|
+
0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5, 0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1, 0xa6bc5767, 0x3fb506dd, 0x48b2364b,
|
18
|
+
0xd80d2bda, 0xaf0a1b4c, 0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef, 0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
19
|
+
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe, 0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31, 0x2cd99e8b, 0x5bdeae1d,
|
20
|
+
0x9b64c2b0, 0xec63f226, 0x756aa39c, 0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713, 0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38,
|
21
|
+
0x92d28e9b, 0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242, 0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1, 0x18b74777,
|
22
|
+
0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c, 0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278, 0xd70dd2ee, 0x4e048354, 0x3903b3c2,
|
23
|
+
0xa7672661, 0xd06016f7, 0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66, 0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
24
|
+
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605, 0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8, 0x5d681b02, 0x2a6f2b94,
|
25
|
+
0xb40bbe37, 0xc30c8ea1, 0x5a05df1b, 0x2d02ef8d
|
26
|
+
];
|
27
|
+
|
28
|
+
/**
|
29
|
+
*
|
30
|
+
* @param input either a byte stream, a string or a buffer, you want to have the checksum for
|
31
|
+
* @returns a promise for a checksum (uint32)
|
32
|
+
*/
|
33
|
+
export function crc32(input: Readable | String | Buffer): Promise<number> {
|
34
|
+
if (typeof input === "string") {
|
35
|
+
let crc = ~0;
|
36
|
+
for (let i = 0; i < input.length; i++) crc = (crc >>> 8) ^ crc32tab[(crc ^ input.charCodeAt(i)) & 0xff];
|
37
|
+
return Promise.resolve((crc ^ -1) >>> 0);
|
38
|
+
} else if (input instanceof Buffer) {
|
39
|
+
let crc = ~0;
|
40
|
+
for (let i = 0; i < input.length; i++) crc = (crc >>> 8) ^ crc32tab[(crc ^ input[i]) & 0xff];
|
41
|
+
return Promise.resolve((crc ^ -1) >>> 0);
|
42
|
+
} else if (input instanceof Readable) {
|
43
|
+
return new Promise<number>((resolve, reject) => {
|
44
|
+
let crc = ~0;
|
45
|
+
input.on("end", () => resolve((crc ^ -1) >>> 0));
|
46
|
+
input.on("error", e => reject(e));
|
47
|
+
input.on("data", (chunk: Buffer) => {
|
48
|
+
for (let i = 0; i < chunk.length; i++) crc = (crc >>> 8) ^ crc32tab[(crc ^ chunk[i]) & 0xff];
|
49
|
+
});
|
50
|
+
});
|
51
|
+
} else {
|
52
|
+
throw new Error("Unsupported input " + typeof input);
|
53
|
+
}
|
54
|
+
}
|
@@ -0,0 +1,61 @@
|
|
1
|
+
import { PassThrough, Readable, TransformCallback, Writable } from "stream";
|
2
|
+
import { pipeline } from "stream/promises";
|
3
|
+
import { deflateRaw as deflateRawCb, createDeflateRaw } from "zlib";
|
4
|
+
import { promisify } from "util";
|
5
|
+
|
6
|
+
import { crc32 } from "./crc32";
|
7
|
+
import tee from "./tee";
|
8
|
+
|
9
|
+
const deflateRaw = promisify(deflateRawCb);
|
10
|
+
|
11
|
+
/**
|
12
|
+
* A stream transformer that records the number of bytes
|
13
|
+
* passed in its `size` property.
|
14
|
+
*/
|
15
|
+
class ByteCounter extends PassThrough {
|
16
|
+
size: number = 0;
|
17
|
+
_transform(chunk: any, encoding: BufferEncoding, callback: TransformCallback) {
|
18
|
+
if ("length" in chunk) this.size += chunk.length;
|
19
|
+
super._transform(chunk, encoding, callback);
|
20
|
+
}
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* @param data buffer containing the data to be compressed
|
25
|
+
* @returns a buffer containing the compressed/deflated data and the crc32 checksum
|
26
|
+
* of the source data
|
27
|
+
*/
|
28
|
+
export async function deflateBuffer(data: Buffer) {
|
29
|
+
const [deflated, checksum] = await Promise.all([deflateRaw(data), crc32(data)]);
|
30
|
+
return { deflated, crc32: checksum };
|
31
|
+
}
|
32
|
+
|
33
|
+
/**
|
34
|
+
* @param input a byte stream, containing data to be compressed
|
35
|
+
* @param sink a method that will accept chunks of compressed data; We don't pass
|
36
|
+
* a writable here, since we don't want the writablestream to be closed after
|
37
|
+
* a single file
|
38
|
+
* @returns a promise, which will resolve with the crc32 checksum and the
|
39
|
+
* compressed size
|
40
|
+
*/
|
41
|
+
export async function deflateStream(input: Readable, sink: (chunk: Buffer) => void) {
|
42
|
+
const deflateWriter = new Writable({
|
43
|
+
write(chunk, _, callback) {
|
44
|
+
sink(chunk);
|
45
|
+
callback();
|
46
|
+
}
|
47
|
+
});
|
48
|
+
|
49
|
+
// tee the input stream, so we can compress and calc crc32 in parallel
|
50
|
+
const [rs1, rs2] = tee(input);
|
51
|
+
const byteCounter = new ByteCounter();
|
52
|
+
const [_, crc] = await Promise.all([
|
53
|
+
// pipe input into zip compressor, count the bytes
|
54
|
+
// returned and pass compressed data to the sink
|
55
|
+
pipeline(rs1, createDeflateRaw(), byteCounter, deflateWriter),
|
56
|
+
// calc checksum
|
57
|
+
crc32(rs2)
|
58
|
+
]);
|
59
|
+
|
60
|
+
return { crc32: crc, compressedSize: byteCounter.size };
|
61
|
+
}
|
@@ -243,7 +243,7 @@ async function* iterateZipArchive(zipFile: string): ZipRecordGenerator {
|
|
243
243
|
const filenameLength = chunk.readUint16LE(i + 28);
|
244
244
|
const extraLength = chunk.readUint16LE(i + 30);
|
245
245
|
const commentLength = chunk.readUint16LE(i + 32);
|
246
|
-
// Start of
|
246
|
+
// Start of the actual content byte stream is after the 'local' record header,
|
247
247
|
// which is 30 bytes long plus filename and extra field
|
248
248
|
const start = chunk.readUint32LE(i + 42) + 30 + filenameLength + extraLength;
|
249
249
|
const end = start + compressedFileSize;
|
@@ -0,0 +1,100 @@
|
|
1
|
+
import { Readable, Transform } from "stream";
|
2
|
+
import { pipeline } from "stream/promises";
|
3
|
+
import { relative, sep } from "path";
|
4
|
+
import { createWriteStream } from "fs";
|
5
|
+
|
6
|
+
import walk from "./walk";
|
7
|
+
import type { ZipSource } from "./zip";
|
8
|
+
import zip from "./zip";
|
9
|
+
|
10
|
+
/** Trim leading whitespace from every line */
|
11
|
+
const trimIndent = (s: string) => s.replace(/(\n)\s+/g, "$1");
|
12
|
+
|
13
|
+
type JarArgs = {
|
14
|
+
rootPath: string;
|
15
|
+
targetPath: string;
|
16
|
+
groupId: string;
|
17
|
+
artifactId: string;
|
18
|
+
version: string;
|
19
|
+
};
|
20
|
+
|
21
|
+
/**
|
22
|
+
* Create a jar archive, using the resources found at `rootPath` (a directory) and write the
|
23
|
+
* archive to `targetPath` (a file). Use `groupId`, `artifactId` and `version` to define
|
24
|
+
* the contents of the pom.properties file which is going to be added to the archive.
|
25
|
+
*/
|
26
|
+
export default async function jar({ groupId, artifactId, version, rootPath, targetPath }: JarArgs) {
|
27
|
+
const manifest: ZipSource = {
|
28
|
+
path: "META-INF/MANIFEST.MF",
|
29
|
+
data: Buffer.from(
|
30
|
+
trimIndent(
|
31
|
+
`Manifest-Version: 1.0
|
32
|
+
Archiver-Version: Plexus Archiver
|
33
|
+
Created-By: Keycloakify
|
34
|
+
Built-By: unknown
|
35
|
+
Build-Jdk: 19.0.0`
|
36
|
+
)
|
37
|
+
)
|
38
|
+
};
|
39
|
+
|
40
|
+
const pomProps: ZipSource = {
|
41
|
+
path: `META-INF/maven/${groupId}/${artifactId}/pom.properties`,
|
42
|
+
data: Buffer.from(
|
43
|
+
trimIndent(
|
44
|
+
`# Generated by keycloakify
|
45
|
+
# ${new Date()}
|
46
|
+
artifactId=${artifactId}
|
47
|
+
groupId=${groupId}
|
48
|
+
version=${version}`
|
49
|
+
)
|
50
|
+
)
|
51
|
+
};
|
52
|
+
|
53
|
+
/**
|
54
|
+
* Convert every path entry to a ZipSource record, and when all records are
|
55
|
+
* processed, append records for MANIFEST.mf and pom.properties
|
56
|
+
*/
|
57
|
+
const pathToRecord = () =>
|
58
|
+
new Transform({
|
59
|
+
objectMode: true,
|
60
|
+
transform: function (path, _, cb) {
|
61
|
+
const filename = relative(rootPath, path).split(sep).join("/");
|
62
|
+
this.push({ filename, path });
|
63
|
+
cb();
|
64
|
+
},
|
65
|
+
final: function () {
|
66
|
+
this.push(manifest);
|
67
|
+
this.push(pomProps);
|
68
|
+
this.push(null);
|
69
|
+
}
|
70
|
+
});
|
71
|
+
|
72
|
+
/**
|
73
|
+
* Create an async pipeline, wait until everything is fully processed
|
74
|
+
*/
|
75
|
+
await pipeline(
|
76
|
+
// walk all files in `rootPath` recursively
|
77
|
+
Readable.from(walk(rootPath)),
|
78
|
+
// transform every path into a ZipSource object
|
79
|
+
pathToRecord(),
|
80
|
+
// let the zip lib convert all ZipSource objects into a byte stream
|
81
|
+
zip(),
|
82
|
+
// write that byte stream to targetPath
|
83
|
+
createWriteStream(targetPath, { encoding: "binary" })
|
84
|
+
);
|
85
|
+
}
|
86
|
+
|
87
|
+
/**
|
88
|
+
* Standalone usage, call e.g. `ts-node jar.ts dirWithSources some-jar.jar`
|
89
|
+
*/
|
90
|
+
if (require.main === module) {
|
91
|
+
const main = () =>
|
92
|
+
jar({
|
93
|
+
rootPath: process.argv[2],
|
94
|
+
targetPath: process.argv[3],
|
95
|
+
artifactId: process.env.ARTIFACT_ID ?? "artifact",
|
96
|
+
groupId: process.env.GROUP_ID ?? "group",
|
97
|
+
version: process.env.VERSION ?? "1.0.0"
|
98
|
+
});
|
99
|
+
main().catch(e => console.error(e));
|
100
|
+
}
|
@@ -0,0 +1,37 @@
|
|
1
|
+
import { PassThrough, Readable } from "stream";
|
2
|
+
|
3
|
+
export default function tee(input: Readable) {
|
4
|
+
const a = new PassThrough();
|
5
|
+
const b = new PassThrough();
|
6
|
+
|
7
|
+
let aFull = false;
|
8
|
+
let bFull = false;
|
9
|
+
|
10
|
+
a.on("drain", () => {
|
11
|
+
aFull = false;
|
12
|
+
if (!aFull && !bFull) input.resume();
|
13
|
+
});
|
14
|
+
b.on("drain", () => {
|
15
|
+
bFull = false;
|
16
|
+
if (!aFull && !bFull) input.resume();
|
17
|
+
});
|
18
|
+
|
19
|
+
input.on("error", e => {
|
20
|
+
a.emit("error", e);
|
21
|
+
b.emit("error", e);
|
22
|
+
});
|
23
|
+
|
24
|
+
input.on("data", chunk => {
|
25
|
+
aFull = !a.write(chunk);
|
26
|
+
bFull = !b.write(chunk);
|
27
|
+
|
28
|
+
if (aFull || bFull) input.pause();
|
29
|
+
});
|
30
|
+
|
31
|
+
input.on("end", () => {
|
32
|
+
a.end();
|
33
|
+
b.end();
|
34
|
+
});
|
35
|
+
|
36
|
+
return [a, b] as const;
|
37
|
+
}
|
@@ -0,0 +1,19 @@
|
|
1
|
+
import { readdir } from "fs/promises";
|
2
|
+
import { resolve } from "path";
|
3
|
+
|
4
|
+
/**
|
5
|
+
* Asynchronously and recursively walk a directory tree, yielding every file and directory
|
6
|
+
* found
|
7
|
+
*
|
8
|
+
* @param root the starting directory
|
9
|
+
* @returns AsyncGenerator
|
10
|
+
*/
|
11
|
+
export default async function* walk(root: string): AsyncGenerator<string, void, void> {
|
12
|
+
for (const entry of await readdir(root, { withFileTypes: true })) {
|
13
|
+
const absolutePath = resolve(root, entry.name);
|
14
|
+
if (entry.isDirectory()) {
|
15
|
+
yield absolutePath;
|
16
|
+
yield* walk(absolutePath);
|
17
|
+
} else yield absolutePath;
|
18
|
+
}
|
19
|
+
}
|