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.
Files changed (39) hide show
  1. package/README.md +6 -0
  2. package/bin/keycloakify/BuildOptions.d.ts +5 -0
  3. package/bin/keycloakify/BuildOptions.js +47 -8
  4. package/bin/keycloakify/BuildOptions.js.map +1 -1
  5. package/bin/keycloakify/generateJavaStackFiles.d.ts +2 -1
  6. package/bin/keycloakify/generateJavaStackFiles.js +4 -5
  7. package/bin/keycloakify/generateJavaStackFiles.js.map +1 -1
  8. package/bin/keycloakify/keycloakify.js +39 -8
  9. package/bin/keycloakify/keycloakify.js.map +1 -1
  10. package/bin/tools/crc32.d.ts +9 -0
  11. package/bin/tools/crc32.js +63 -0
  12. package/bin/tools/crc32.js.map +1 -0
  13. package/bin/tools/deflate.d.ts +24 -0
  14. package/bin/tools/deflate.js +155 -0
  15. package/bin/tools/deflate.js.map +1 -0
  16. package/bin/tools/jar.d.ts +14 -0
  17. package/bin/tools/jar.js +124 -0
  18. package/bin/tools/jar.js.map +1 -0
  19. package/bin/tools/tee.d.ts +3 -0
  20. package/bin/tools/tee.js +36 -0
  21. package/bin/tools/tee.js.map +1 -0
  22. package/bin/tools/walk.d.ts +8 -0
  23. package/bin/tools/walk.js +125 -0
  24. package/bin/tools/walk.js.map +1 -0
  25. package/bin/tools/zip.d.ts +29 -0
  26. package/bin/tools/zip.js +329 -0
  27. package/bin/tools/zip.js.map +1 -0
  28. package/bin/tsconfig.tsbuildinfo +1 -1
  29. package/package.json +25 -1
  30. package/src/bin/keycloakify/BuildOptions.ts +31 -4
  31. package/src/bin/keycloakify/generateJavaStackFiles.ts +6 -8
  32. package/src/bin/keycloakify/keycloakify.ts +25 -5
  33. package/src/bin/tools/crc32.ts +54 -0
  34. package/src/bin/tools/deflate.ts +61 -0
  35. package/src/bin/tools/downloadAndUnzip.ts +1 -1
  36. package/src/bin/tools/jar.ts +100 -0
  37. package/src/bin/tools/tee.ts +37 -0
  38. package/src/bin/tools/walk.ts +19 -0
  39. 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.10.1",
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>${artefactId}</artifactId>`,
44
+ ` <artifactId>${artifactId}</artifactId>`,
47
45
  ` <version>${version}</version>`,
48
- ` <name>${artefactId}</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", `${artefactId}-${version}.jar`)
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
- child_process.execSync("mvn package", {
55
- "cwd": keycloakThemeBuildingDirPath
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 thea actual content byte stream is after the 'local' record header,
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
+ }