keycloakify 7.3.2 → 7.4.0

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 (52) hide show
  1. package/bin/download-builtin-keycloak-theme.js +3 -3
  2. package/bin/download-builtin-keycloak-theme.js.map +1 -1
  3. package/bin/eject-keycloak-page.js +2 -2
  4. package/bin/eject-keycloak-page.js.map +1 -1
  5. package/bin/initialize-email-theme.d.ts +1 -3
  6. package/bin/initialize-email-theme.js +4 -9
  7. package/bin/initialize-email-theme.js.map +1 -1
  8. package/bin/keycloakify/BuildOptions.d.ts +4 -4
  9. package/bin/keycloakify/BuildOptions.js +12 -28
  10. package/bin/keycloakify/BuildOptions.js.map +1 -1
  11. package/bin/keycloakify/build-paths.d.ts +12 -0
  12. package/bin/keycloakify/build-paths.js +93 -0
  13. package/bin/keycloakify/build-paths.js.map +1 -0
  14. package/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl +1 -2
  15. package/bin/keycloakify/generateFtl/generateFtl.d.ts +6 -3
  16. package/bin/keycloakify/generateFtl/generateFtl.js +3 -6
  17. package/bin/keycloakify/generateFtl/generateFtl.js.map +1 -1
  18. package/bin/keycloakify/generateKeycloakThemeResources.d.ts +1 -0
  19. package/bin/keycloakify/generateKeycloakThemeResources.js +2 -6
  20. package/bin/keycloakify/generateKeycloakThemeResources.js.map +1 -1
  21. package/bin/keycloakify/index.js +1 -1
  22. package/bin/keycloakify/index.js.map +1 -1
  23. package/bin/keycloakify/keycloakify.d.ts +0 -1
  24. package/bin/keycloakify/keycloakify.js +13 -15
  25. package/bin/keycloakify/keycloakify.js.map +1 -1
  26. package/bin/keycloakify/parsed-package-json.d.ts +21 -0
  27. package/bin/keycloakify/parsed-package-json.js +63 -0
  28. package/bin/keycloakify/parsed-package-json.js.map +1 -0
  29. package/bin/tools/downloadAndUnzip.d.ts +1 -1
  30. package/bin/tools/downloadAndUnzip.js +16 -16
  31. package/bin/tools/downloadAndUnzip.js.map +1 -1
  32. package/bin/tools/unzip.d.ts +1 -30
  33. package/bin/tools/unzip.js +99 -288
  34. package/bin/tools/unzip.js.map +1 -1
  35. package/package.json +13 -6
  36. package/src/bin/download-builtin-keycloak-theme.ts +3 -4
  37. package/src/bin/eject-keycloak-page.ts +1 -1
  38. package/src/bin/initialize-email-theme.ts +2 -10
  39. package/src/bin/keycloakify/BuildOptions.ts +15 -52
  40. package/src/bin/keycloakify/build-paths.ts +72 -0
  41. package/src/bin/keycloakify/generateFtl/ftl_object_to_js_code_declaring_an_object.ftl +1 -2
  42. package/src/bin/keycloakify/generateFtl/generateFtl.ts +22 -17
  43. package/src/bin/keycloakify/generateKeycloakThemeResources.ts +3 -7
  44. package/src/bin/keycloakify/index.ts +1 -1
  45. package/src/bin/keycloakify/keycloakify.ts +12 -16
  46. package/src/bin/keycloakify/parsed-package-json.ts +60 -0
  47. package/src/bin/tools/downloadAndUnzip.ts +5 -11
  48. package/src/bin/tools/unzip.ts +82 -174
  49. package/bin/getThemeSrcDirPath.d.ts +0 -5
  50. package/bin/getThemeSrcDirPath.js +0 -53
  51. package/bin/getThemeSrcDirPath.js.map +0 -1
  52. package/src/bin/getThemeSrcDirPath.ts +0 -33
@@ -1,184 +1,92 @@
1
- import { createReadStream, createWriteStream } from "fs";
2
- import { mkdir, stat, unlink } from "fs/promises";
3
- import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path";
4
- import { type Readable } from "stream";
5
- import { createInflateRaw } from "zlib";
6
- import { partitionPromiseSettledResults } from "./partitionPromiseSettledResults";
7
-
8
- export type MultiError = Error & { cause: Error[] };
9
-
10
- /**
11
- * Extract the archive `zipFile` into the directory `dir`. If `archiveDir` is given,
12
- * only that directory will be extracted, stripping the given path components.
13
- *
14
- * If dir does not exist, it will be created.
15
- *
16
- * If any archive file exists, it will be overwritten.
17
- *
18
- * Will unzip using all available nodejs worker threads.
19
- *
20
- * Will try to clean up extracted files on failure.
21
- *
22
- * If unpacking fails, will either throw an regular error, or
23
- * possibly an `MultiError`, which contains a `cause` field with
24
- * a number of root cause errors.
25
- *
26
- * Warning this method is not optimized for continuous reading of the zip
27
- * archive, but is a trade-off between simplicity and allowing extraction
28
- * of a single directory from the archive.
29
- *
30
- * @param zipFilePath the file to unzip
31
- * @param extractDirPath the target directory
32
- * @param pathOfDirToExtractInArchive if given, unpack only files from this archive directory
33
- * @throws {MultiError} error
34
- * @returns Promise for a list of full file paths pointing to actually extracted files
35
- */
36
- export async function unzip(zipFilePath: string, extractDirPath: string, pathOfDirToExtractInArchive?: string): Promise<string[]> {
37
- const dirsCreated: (string | undefined)[] = [];
38
- dirsCreated.push(await mkdir(extractDirPath, { recursive: true }));
39
- const promises: Promise<string>[] = [];
40
-
41
- // Iterate over all files in the zip, skip files which are not in archiveDir,
42
- // if given.
43
- for await (const record of iterateZipArchive(zipFilePath)) {
44
- const { path: recordPath, createReadStream: createRecordReadStream } = record;
45
- if (pathOfDirToExtractInArchive && !recordPath.startsWith(pathOfDirToExtractInArchive)) {
46
- continue;
1
+ import fsp from "node:fs/promises";
2
+ import fs from "fs";
3
+ import path from "node:path";
4
+ import yauzl from "yauzl";
5
+ import stream from "node:stream";
6
+ import { promisify } from "node:util";
7
+
8
+ const pipeline = promisify(stream.pipeline);
9
+
10
+ async function pathExists(path: string) {
11
+ try {
12
+ await fsp.stat(path);
13
+ return true;
14
+ } catch (error) {
15
+ if ((error as { code: string }).code === "ENOENT") {
16
+ return false;
47
17
  }
48
- const relativePath = pathOfDirToExtractInArchive ? pathRelative(pathOfDirToExtractInArchive, recordPath) : recordPath;
49
- const filePath = pathJoin(extractDirPath, relativePath);
50
- const parent = pathDirname(filePath);
51
- promises.push(
52
- new Promise<string>(async (resolve, reject) => {
53
- if (!dirsCreated.includes(parent)) dirsCreated.push(await mkdir(parent, { recursive: true }));
54
-
55
- // Pull the file out of the archive, write it to the target directory
56
- const output = createWriteStream(filePath);
57
- output.on("error", e => reject(Object.assign(e, { filePath })));
58
- output.on("finish", () => resolve(filePath));
59
- createRecordReadStream().pipe(output);
60
- })
61
- );
18
+ throw error;
62
19
  }
20
+ }
63
21
 
64
- // Wait until _all_ files are either extracted or failed
65
- const [success, failure] = (await Promise.allSettled(promises)).reduce(...partitionPromiseSettledResults<string>());
66
-
67
- // If any extraction failed, try to clean up, then throw a MultiError,
68
- // which has a `cause` field, containing a list of root cause errors.
69
- if (failure.length) {
70
- await Promise.all([
71
- ...success.map(path => unlink(path).catch(_unused => undefined)),
72
- ...failure.map(e => e && e.path && unlink(e.path as string).catch(_unused => undefined))
73
- ]);
74
- await Promise.all(dirsCreated.filter(Boolean).sort(sortByFolderDepth("desc")));
75
- const e = new Error("Failed to extract: " + failure.map(e => e.message).join(";"));
76
- (e as any).cause = failure;
77
- throw e;
22
+ export async function unzip(file: string, targetFolder: string, unzipSubPath?: string) {
23
+ // add trailing slash to unzipSubPath and targetFolder
24
+ if (unzipSubPath && (!unzipSubPath.endsWith("/") || !unzipSubPath.endsWith("\\"))) {
25
+ unzipSubPath += "/";
78
26
  }
79
27
 
80
- return success;
81
- }
28
+ if (!targetFolder.endsWith("/") || !targetFolder.endsWith("\\")) {
29
+ targetFolder += "/";
30
+ }
31
+ if (!fs.existsSync(targetFolder)) {
32
+ fs.mkdirSync(targetFolder, { recursive: true });
33
+ }
82
34
 
83
- function depth(dir: string) {
84
- return dir.match(/\//g)?.length ?? 0;
85
- }
35
+ return new Promise<void>((resolve, reject) => {
36
+ yauzl.open(file, { lazyEntries: true }, async (err, zipfile) => {
37
+ if (err) {
38
+ reject(err);
39
+ return;
40
+ }
86
41
 
87
- function sortByFolderDepth(order: "asc" | "desc") {
88
- const ord = order === "asc" ? 1 : -1;
89
- return (a: string | undefined, b: string | undefined) => ord * depth(a ?? "") + -ord * depth(b ?? "");
90
- }
42
+ zipfile.readEntry();
91
43
 
92
- /**
93
- *
94
- * @param file file to read
95
- * @param start first byte to read
96
- * @param end last byte to read
97
- * @returns Promise of a buffer of read bytes
98
- */
99
- async function readFileChunk(file: string, start: number, end: number): Promise<Buffer> {
100
- const chunks: Buffer[] = [];
101
- return new Promise((resolve, reject) => {
102
- const stream = createReadStream(file, { start, end });
103
- stream.setMaxListeners(Infinity);
104
- stream.on("error", e => reject(e));
105
- stream.on("end", () => resolve(Buffer.concat(chunks)));
106
- stream.on("data", chunk => chunks.push(chunk as Buffer));
107
- });
108
- }
44
+ zipfile.on("entry", async entry => {
45
+ if (unzipSubPath) {
46
+ // Skip files outside of the unzipSubPath
47
+ if (!entry.fileName.startsWith(unzipSubPath)) {
48
+ zipfile.readEntry();
49
+ return;
50
+ }
109
51
 
110
- type ZipRecord = {
111
- path: string;
112
- createReadStream: () => Readable;
113
- compressionMethod: "deflate" | undefined;
114
- };
115
-
116
- type ZipRecordGenerator = AsyncGenerator<ZipRecord, void, unknown>;
117
-
118
- /**
119
- * Iterate over all records of a zipfile, and yield a ZipRecord.
120
- * Use `record.createReadStream()` to actually read the file.
121
- *
122
- * Warning this method will only work with single-disk zip files.
123
- * Warning this method may fail if the zip archive has an crazy amount
124
- * of files and the central directory is not fully contained within the
125
- * last 65k bytes of the zip file.
126
- *
127
- * @param zipFile
128
- * @returns AsyncGenerator which will yield ZipRecords
129
- */
130
- async function* iterateZipArchive(zipFile: string): ZipRecordGenerator {
131
- // Need to know zip file size before we can do anything else
132
- const { size } = await stat(zipFile);
133
- const chunkSize = 65_535 + 22 + 1; // max comment size + end header size + wiggle
134
- // Read last ~65k bytes. Zip files have an comment up to 65_535 bytes at the very end,
135
- // before that comes the zip central directory end header.
136
- let chunk = await readFileChunk(zipFile, size - chunkSize, size);
137
- const unread = size - chunk.length;
138
- let i = chunk.length - 4;
139
- let found = false;
140
- // Find central directory end header, reading backwards from the end
141
- while (!found && i-- > 0) if (chunk[i] === 0x50 && chunk.readUInt32LE(i) === 0x06054b50) found = true;
142
- if (!found) throw new Error("Not a zip file");
143
- // This method will fail on a multi-disk zip, so bail early.
144
- if (chunk.readUInt16LE(i + 4) !== 0) throw new Error("Multi-disk zip not supported");
145
- let nFiles = chunk.readUint16LE(i + 10);
146
- // Get the position of the central directory
147
- const directorySize = chunk.readUint32LE(i + 12);
148
- const directoryOffset = chunk.readUint32LE(i + 16);
149
- if (directoryOffset === 0xffff_ffff) throw new Error("zip64 not supported");
150
- if (directoryOffset > size) throw new Error(`Central directory offset ${directoryOffset} is outside file`);
151
- i = directoryOffset - unread;
152
- // If i < 0, it means that the central directory is not contained within `chunk`
153
- if (i < 0) {
154
- chunk = await readFileChunk(zipFile, directoryOffset, directoryOffset + directorySize);
155
- i = 0;
156
- }
157
- // Now iterate the central directory records, yield an `ZipRecord` for every entry
158
- while (nFiles-- > 0) {
159
- // Check for marker bytes
160
- if (chunk.readUInt32LE(i) !== 0x02014b50) throw new Error("No central directory record at position " + (unread + i));
161
- const compressionMethod = ({ 8: "deflate" } as const)[chunk.readUint16LE(i + 10)];
162
- const compressedFileSize = chunk.readUint32LE(i + 20);
163
- const filenameLength = chunk.readUint16LE(i + 28);
164
- const extraLength = chunk.readUint16LE(i + 30);
165
- const commentLength = chunk.readUint16LE(i + 32);
166
- // Start of the actual content byte stream is after the 'local' record header,
167
- // which is 30 bytes long plus filename and extra field
168
- const start = chunk.readUint32LE(i + 42) + 30 + filenameLength + extraLength;
169
- const end = start + compressedFileSize;
170
- const filename = chunk.slice(i + 46, i + 46 + filenameLength).toString("utf-8");
171
- const createRecordReadStream = () => {
172
- const input = createReadStream(zipFile, { start, end });
173
- if (compressionMethod === "deflate") {
174
- const inflate = createInflateRaw();
175
- input.pipe(inflate);
176
- return inflate;
177
- }
178
- return input;
179
- };
180
- if (end > start) yield { path: filename, createReadStream: createRecordReadStream, compressionMethod };
181
- // advance pointer to next central directory entry
182
- i += 46 + filenameLength + extraLength + commentLength;
183
- }
52
+ // Remove the unzipSubPath from the file name
53
+ entry.fileName = entry.fileName.substring(unzipSubPath.length);
54
+ }
55
+
56
+ const target = path.join(targetFolder, entry.fileName);
57
+
58
+ // Directory file names end with '/'.
59
+ // Note that entries for directories themselves are optional.
60
+ // An entry's fileName implicitly requires its parent directories to exist.
61
+ if (/[\/\\]$/.test(target)) {
62
+ await fsp.mkdir(target, { recursive: true });
63
+
64
+ zipfile.readEntry();
65
+ return;
66
+ }
67
+
68
+ // Skip existing files
69
+ if (await pathExists(target)) {
70
+ zipfile.readEntry();
71
+ return;
72
+ }
73
+
74
+ zipfile.openReadStream(entry, async (err, readStream) => {
75
+ if (err) {
76
+ reject(err);
77
+ return;
78
+ }
79
+
80
+ await pipeline(readStream, fs.createWriteStream(target));
81
+
82
+ zipfile.readEntry();
83
+ });
84
+ });
85
+
86
+ zipfile.once("end", function () {
87
+ zipfile.close();
88
+ resolve();
89
+ });
90
+ });
91
+ });
184
92
  }
@@ -1,5 +0,0 @@
1
- export declare function getThemeSrcDirPath(): {
2
- themeSrcDirPath: string;
3
- } | {
4
- themeSrcDirPath: undefined;
5
- };
@@ -1,53 +0,0 @@
1
- "use strict";
2
- var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
- if (k2 === undefined) k2 = k;
4
- var desc = Object.getOwnPropertyDescriptor(m, k);
5
- if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
- desc = { enumerable: true, get: function() { return m[k]; } };
7
- }
8
- Object.defineProperty(o, k2, desc);
9
- }) : (function(o, m, k, k2) {
10
- if (k2 === undefined) k2 = k;
11
- o[k2] = m[k];
12
- }));
13
- var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
- Object.defineProperty(o, "default", { enumerable: true, value: v });
15
- }) : function(o, v) {
16
- o["default"] = v;
17
- });
18
- var __importStar = (this && this.__importStar) || function (mod) {
19
- if (mod && mod.__esModule) return mod;
20
- var result = {};
21
- if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
22
- __setModuleDefault(result, mod);
23
- return result;
24
- };
25
- Object.defineProperty(exports, "__esModule", { value: true });
26
- exports.getThemeSrcDirPath = void 0;
27
- var path_1 = require("path");
28
- var fs = __importStar(require("fs"));
29
- var crawl_1 = require("./tools/crawl");
30
- var exclude_1 = require("tsafe/exclude");
31
- var reactProjectDirPath = process.cwd();
32
- var themeSrcDirBasename = "keycloak-theme";
33
- function getThemeSrcDirPath() {
34
- var srcDirPath = (0, path_1.join)(reactProjectDirPath, "src");
35
- var themeSrcDirPath = (0, crawl_1.crawl)(srcDirPath)
36
- .map(function (fileRelativePath) {
37
- var split = fileRelativePath.split(themeSrcDirBasename);
38
- if (split.length !== 2) {
39
- return undefined;
40
- }
41
- return (0, path_1.join)(srcDirPath, split[0] + themeSrcDirBasename);
42
- })
43
- .filter((0, exclude_1.exclude)(undefined))[0];
44
- if (themeSrcDirPath === undefined) {
45
- if (fs.existsSync((0, path_1.join)(srcDirPath, "login")) || fs.existsSync((0, path_1.join)(srcDirPath, "account"))) {
46
- return { "themeSrcDirPath": srcDirPath };
47
- }
48
- return { "themeSrcDirPath": undefined };
49
- }
50
- return { themeSrcDirPath: themeSrcDirPath };
51
- }
52
- exports.getThemeSrcDirPath = getThemeSrcDirPath;
53
- //# sourceMappingURL=getThemeSrcDirPath.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"getThemeSrcDirPath.js","sourceRoot":"","sources":["../src/bin/getThemeSrcDirPath.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,6BAAwC;AACxC,qCAAyB;AACzB,uCAAsC;AACtC,yCAAwC;AAExC,IAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;AAE1C,IAAM,mBAAmB,GAAG,gBAAgB,CAAC;AAE7C,SAAgB,kBAAkB;IAC9B,IAAM,UAAU,GAAG,IAAA,WAAQ,EAAC,mBAAmB,EAAE,KAAK,CAAC,CAAC;IAExD,IAAM,eAAe,GAAuB,IAAA,aAAK,EAAC,UAAU,CAAC;SACxD,GAAG,CAAC,UAAA,gBAAgB;QACjB,IAAM,KAAK,GAAG,gBAAgB,CAAC,KAAK,CAAC,mBAAmB,CAAC,CAAC;QAE1D,IAAI,KAAK,CAAC,MAAM,KAAK,CAAC,EAAE;YACpB,OAAO,SAAS,CAAC;SACpB;QAED,OAAO,IAAA,WAAQ,EAAC,UAAU,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,mBAAmB,CAAC,CAAC;IAChE,CAAC,CAAC;SACD,MAAM,CAAC,IAAA,iBAAO,EAAC,SAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;IAEnC,IAAI,eAAe,KAAK,SAAS,EAAE;QAC/B,IAAI,EAAE,CAAC,UAAU,CAAC,IAAA,WAAQ,EAAC,UAAU,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAA,WAAQ,EAAC,UAAU,EAAE,SAAS,CAAC,CAAC,EAAE;YAChG,OAAO,EAAE,iBAAiB,EAAE,UAAU,EAAE,CAAC;SAC5C;QACD,OAAO,EAAE,iBAAiB,EAAE,SAAS,EAAE,CAAC;KAC3C;IAED,OAAO,EAAE,eAAe,iBAAA,EAAE,CAAC;AAC/B,CAAC;AAvBD,gDAuBC"}
@@ -1,33 +0,0 @@
1
- import { join as pathJoin } from "path";
2
- import * as fs from "fs";
3
- import { crawl } from "./tools/crawl";
4
- import { exclude } from "tsafe/exclude";
5
-
6
- const reactProjectDirPath = process.cwd();
7
-
8
- const themeSrcDirBasename = "keycloak-theme";
9
-
10
- export function getThemeSrcDirPath() {
11
- const srcDirPath = pathJoin(reactProjectDirPath, "src");
12
-
13
- const themeSrcDirPath: string | undefined = crawl(srcDirPath)
14
- .map(fileRelativePath => {
15
- const split = fileRelativePath.split(themeSrcDirBasename);
16
-
17
- if (split.length !== 2) {
18
- return undefined;
19
- }
20
-
21
- return pathJoin(srcDirPath, split[0] + themeSrcDirBasename);
22
- })
23
- .filter(exclude(undefined))[0];
24
-
25
- if (themeSrcDirPath === undefined) {
26
- if (fs.existsSync(pathJoin(srcDirPath, "login")) || fs.existsSync(pathJoin(srcDirPath, "account"))) {
27
- return { "themeSrcDirPath": srcDirPath };
28
- }
29
- return { "themeSrcDirPath": undefined };
30
- }
31
-
32
- return { themeSrcDirPath };
33
- }