keycloakify 11.8.46 → 11.8.47-rc.2

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 (43) hide show
  1. package/bin/254.index.js +283 -0
  2. package/bin/30.index.js +25 -0
  3. package/bin/{864.index.js → 309.index.js} +6944 -28439
  4. package/bin/311.index.js +198 -0
  5. package/bin/{313.index.js → 355.index.js} +148 -172
  6. package/bin/{84.index.js → 502.index.js} +151 -103
  7. package/bin/626.index.js +216 -0
  8. package/bin/656.index.js +274 -0
  9. package/bin/675.index.js +177 -0
  10. package/bin/69.index.js +1 -1
  11. package/bin/762.index.js +1475 -0
  12. package/bin/780.index.js +4 -2
  13. package/bin/786.index.js +115 -0
  14. package/bin/895.index.js +21501 -0
  15. package/bin/{618.index.js → 932.index.js} +117 -163
  16. package/bin/949.index.js +1 -1
  17. package/bin/97.index.js +539 -4
  18. package/bin/init/index.d.ts +1 -0
  19. package/bin/init/init.d.ts +3 -0
  20. package/bin/init/setupEslint.d.ts +4 -0
  21. package/bin/init/setupVitePluginIfNeeded.d.ts +4 -0
  22. package/bin/initialize-login-theme.d.ts +4 -0
  23. package/bin/main.js +42 -49
  24. package/bin/shared/customHandler.d.ts +1 -1
  25. package/bin/shared/customHandler.js.map +1 -1
  26. package/package.json +23 -7
  27. package/src/bin/init/index.ts +1 -0
  28. package/src/bin/init/init.ts +354 -0
  29. package/src/bin/init/setupEslint.ts +80 -0
  30. package/src/bin/init/setupVitePluginIfNeeded.ts +143 -0
  31. package/src/bin/initialize-account-theme/initialize-account-theme.ts +4 -0
  32. package/src/bin/initialize-login-theme.ts +323 -0
  33. package/src/bin/main.ts +14 -0
  34. package/src/bin/shared/buildContext.ts +28 -59
  35. package/src/bin/shared/customHandler.ts +3 -1
  36. package/src/bin/shared/initializeSpa.ts +28 -0
  37. package/src/bin/sync-extensions/extensionModuleMeta.ts +89 -73
  38. package/src/bin/sync-extensions/managedGitignoreFiles.ts +32 -2
  39. package/src/bin/tools/fetchProxyOptions.ts +2 -1
  40. package/src/bin/tools/npmInstall.ts +13 -6
  41. package/vite-plugin/index.js +24 -43
  42. package/bin/433.index.js +0 -140
  43. package/bin/698.index.js +0 -298
@@ -0,0 +1,80 @@
1
+ import { join as pathJoin } from "path";
2
+ import { existsAsync } from "../tools/fs.existsAsync";
3
+ import * as fs from "fs/promises";
4
+ import * as recast from "recast";
5
+ import { runPrettier, getIsPrettierAvailable } from "../tools/runPrettier";
6
+ import * as babelParser from "@babel/parser";
7
+ import babelGenerate from "@babel/generator";
8
+ import * as babelTypes from "@babel/types";
9
+
10
+ /** This function will just set reportUnusedDisableDirectives to off so that we don't get warning on generated files */
11
+ export async function setupEslint(params: { projectDirPath: string }) {
12
+ const { projectDirPath } = params;
13
+
14
+ const eslintConfigJsFilePath = pathJoin(projectDirPath, "eslint.config.js");
15
+
16
+ if (!(await existsAsync(eslintConfigJsFilePath))) {
17
+ return;
18
+ }
19
+
20
+ const eslintConfigJsContent = (await fs.readFile(eslintConfigJsFilePath)).toString(
21
+ "utf8"
22
+ );
23
+
24
+ if (eslintConfigJsContent.includes("reportUnusedDisableDirectives")) {
25
+ return;
26
+ }
27
+
28
+ const root = recast.parse(eslintConfigJsContent, {
29
+ parser: {
30
+ parse: (code: string) =>
31
+ babelParser.parse(code, {
32
+ sourceType: "module",
33
+ plugins: ["typescript"]
34
+ })
35
+ }
36
+ });
37
+
38
+ recast.visit(root, {
39
+ visitExportDefaultDeclaration(path) {
40
+ // @ts-expect-error
41
+ const args = path.node.declaration.arguments;
42
+
43
+ if (!Array.isArray(args)) return false;
44
+
45
+ args.push(
46
+ babelTypes.objectExpression([
47
+ babelTypes.objectProperty(
48
+ babelTypes.identifier("linterOptions"),
49
+ babelTypes.objectExpression([
50
+ babelTypes.objectProperty(
51
+ babelTypes.identifier("reportUnusedDisableDirectives"),
52
+ babelTypes.stringLiteral("off")
53
+ )
54
+ ])
55
+ )
56
+ ])
57
+ );
58
+
59
+ return false;
60
+ }
61
+ });
62
+
63
+ let eslintConfigJsContent_modified = babelGenerate(root).code;
64
+
65
+ format: {
66
+ if (!(await getIsPrettierAvailable())) {
67
+ break format;
68
+ }
69
+
70
+ eslintConfigJsContent_modified = await runPrettier({
71
+ sourceCode: eslintConfigJsContent_modified,
72
+ filePath: eslintConfigJsFilePath
73
+ });
74
+ }
75
+
76
+ await fs.writeFile(
77
+ eslintConfigJsFilePath,
78
+ Buffer.from(eslintConfigJsContent_modified, "utf8")
79
+ );
80
+ }
@@ -0,0 +1,143 @@
1
+ import { join as pathJoin } from "path";
2
+ import { existsAsync } from "../tools/fs.existsAsync";
3
+ import * as fs from "fs/promises";
4
+ import * as recast from "recast";
5
+ import { runPrettier, getIsPrettierAvailable } from "../tools/runPrettier";
6
+ import * as babelParser from "@babel/parser";
7
+ import babelGenerate from "@babel/generator";
8
+ import * as babelTypes from "@babel/types";
9
+
10
+ /** Best effort to setup the Keycloakify vite plugin automatically */
11
+ export async function setupVitePluginIfNeeded(params: { projectDirPath: string }) {
12
+ const { projectDirPath } = params;
13
+
14
+ const viteConfigTsFilePath = pathJoin(projectDirPath, "vite.config.ts");
15
+
16
+ if (!(await existsAsync(viteConfigTsFilePath))) {
17
+ return;
18
+ }
19
+
20
+ const viteConfigTsContent = (await fs.readFile(viteConfigTsFilePath)).toString(
21
+ "utf8"
22
+ );
23
+
24
+ if (viteConfigTsContent.includes("keycloakify")) {
25
+ return;
26
+ }
27
+
28
+ const root = recast.parse(viteConfigTsContent, {
29
+ parser: {
30
+ parse: (code: string) =>
31
+ babelParser.parse(code, {
32
+ sourceType: "module",
33
+ plugins: ["typescript"]
34
+ }),
35
+ generator: babelGenerate,
36
+ types: babelTypes
37
+ }
38
+ });
39
+
40
+ /* Before:
41
+ import { defineConfig } from "vite";
42
+ import react from "@vitejs/plugin-react";
43
+
44
+ // https://vitejs.dev/config/
45
+ export default defineConfig({
46
+ plugins: [ react() ]
47
+ });
48
+ */
49
+
50
+ /* After:
51
+ import { defineConfig } from "vite";
52
+ import react from "@vitejs/plugin-react";
53
+ import { keycloakify } from "keycloakify/vite-plugin";
54
+
55
+ // https://vitejs.dev/config/
56
+ export default defineConfig({
57
+ plugins: [
58
+ react(),
59
+ keycloakify({
60
+ accountThemeImplementation: "none"
61
+ })
62
+ ]
63
+ });
64
+ */
65
+
66
+ recast.visit(root, {
67
+ visitProgram(path) {
68
+ const body = path.node.body;
69
+
70
+ // Add import: import { keycloakify } from "keycloakify/vite-plugin";
71
+ const importDeclaration = babelTypes.importDeclaration(
72
+ [
73
+ babelTypes.importSpecifier(
74
+ babelTypes.identifier("keycloakify"),
75
+ babelTypes.identifier("keycloakify")
76
+ )
77
+ ],
78
+ babelTypes.stringLiteral("keycloakify/vite-plugin")
79
+ );
80
+ // @ts-expect-error
81
+ body.unshift(importDeclaration);
82
+
83
+ this.traverse(path);
84
+ },
85
+ visitCallExpression(path) {
86
+ const { node } = path;
87
+
88
+ if (
89
+ // @ts-expect-error
90
+ babelTypes.isIdentifier(node.callee, { name: "defineConfig" }) &&
91
+ node.arguments.length === 1 &&
92
+ // @ts-expect-error
93
+ babelTypes.isObjectExpression(node.arguments[0])
94
+ ) {
95
+ const configObject = node.arguments[0];
96
+ const pluginsProp = configObject.properties.find(
97
+ prop =>
98
+ // @ts-expect-error
99
+ babelTypes.isObjectProperty(prop) &&
100
+ babelTypes.isIdentifier(prop.key, { name: "plugins" }) &&
101
+ babelTypes.isArrayExpression(prop.value)
102
+ ) as babelTypes.ObjectProperty | undefined;
103
+
104
+ if (pluginsProp && babelTypes.isArrayExpression(pluginsProp.value)) {
105
+ // Append keycloakify plugin config
106
+ const keycloakifyCall = babelTypes.callExpression(
107
+ babelTypes.identifier("keycloakify"),
108
+ [
109
+ babelTypes.objectExpression([
110
+ babelTypes.objectProperty(
111
+ babelTypes.identifier("accountThemeImplementation"),
112
+ babelTypes.stringLiteral("none")
113
+ )
114
+ ])
115
+ ]
116
+ );
117
+
118
+ pluginsProp.value.elements.push(keycloakifyCall);
119
+ }
120
+ }
121
+
122
+ this.traverse(path);
123
+ }
124
+ });
125
+
126
+ let viteConfigTsContent_modified = babelGenerate(root).code;
127
+
128
+ format: {
129
+ if (!(await getIsPrettierAvailable())) {
130
+ break format;
131
+ }
132
+
133
+ viteConfigTsContent_modified = await runPrettier({
134
+ sourceCode: viteConfigTsContent_modified,
135
+ filePath: viteConfigTsFilePath
136
+ });
137
+ }
138
+
139
+ await fs.writeFile(
140
+ viteConfigTsFilePath,
141
+ Buffer.from(viteConfigTsContent_modified, "utf8")
142
+ );
143
+ }
@@ -26,12 +26,16 @@ export async function command(params: { buildContext: BuildContext }) {
26
26
  projectDirPath: buildContext.projectDirPath
27
27
  });
28
28
 
29
+ console.log(chalk.cyan("Which account theme type?"));
30
+
29
31
  const { value: accountThemeType } = await cliSelect({
30
32
  values: ["Single-Page" as const, "Multi-Page" as const]
31
33
  }).catch(() => {
32
34
  process.exit(-1);
33
35
  });
34
36
 
37
+ console.log(`${accountThemeType}\n`);
38
+
35
39
  switch (accountThemeType) {
36
40
  case "Multi-Page":
37
41
  {
@@ -0,0 +1,323 @@
1
+ import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
2
+ import { dirname as pathDirname, join as pathJoin } from "path";
3
+ import type { BuildContext } from "./shared/buildContext";
4
+ import * as fs from "fs/promises";
5
+ import { assert, is, type Equals } from "tsafe/assert";
6
+ import { id } from "tsafe/id";
7
+ import { addSyncExtensionsToPostinstallScript } from "./shared/addSyncExtensionsToPostinstallScript";
8
+ import { getIsPrettierAvailable, runPrettier } from "./tools/runPrettier";
9
+ import { npmInstall } from "./tools/npmInstall";
10
+ import * as child_process from "child_process";
11
+ import { z } from "zod";
12
+ import chalk from "chalk";
13
+ import cliSelect from "cli-select";
14
+ import { existsAsync } from "./tools/fs.existsAsync";
15
+
16
+ export async function command(params: { buildContext: BuildContext }) {
17
+ const { buildContext } = params;
18
+
19
+ const { hasBeenHandled } = await maybeDelegateCommandToCustomHandler({
20
+ commandName: "initialize-login-theme",
21
+ buildContext
22
+ });
23
+
24
+ if (hasBeenHandled) {
25
+ return;
26
+ }
27
+
28
+ if (
29
+ buildContext.implementedThemeTypes.login.isImplemented ||
30
+ buildContext.implementedThemeTypes.login.isImplemented_native
31
+ ) {
32
+ console.warn(chalk.red("There is already a login theme in your project"));
33
+
34
+ process.exit(-1);
35
+ }
36
+
37
+ const parsedPackageJson = await (async () => {
38
+ type ParsedPackageJson = {
39
+ scripts?: Record<string, string | undefined>;
40
+ dependencies?: Record<string, string | undefined>;
41
+ devDependencies?: Record<string, string | undefined>;
42
+ };
43
+
44
+ const zParsedPackageJson = (() => {
45
+ type TargetType = ParsedPackageJson;
46
+
47
+ const zTargetType = z.object({
48
+ scripts: z.record(z.union([z.string(), z.undefined()])).optional(),
49
+ dependencies: z.record(z.union([z.string(), z.undefined()])).optional(),
50
+ devDependencies: z.record(z.union([z.string(), z.undefined()])).optional()
51
+ });
52
+
53
+ assert<Equals<z.infer<typeof zTargetType>, TargetType>>;
54
+
55
+ return id<z.ZodType<TargetType>>(zTargetType);
56
+ })();
57
+ const parsedPackageJson = JSON.parse(
58
+ (await fs.readFile(buildContext.packageJsonFilePath)).toString("utf8")
59
+ );
60
+
61
+ zParsedPackageJson.parse(parsedPackageJson);
62
+
63
+ assert(is<ParsedPackageJson>(parsedPackageJson));
64
+
65
+ return parsedPackageJson;
66
+ })();
67
+
68
+ addSyncExtensionsToPostinstallScript({
69
+ parsedPackageJson,
70
+ buildContext
71
+ });
72
+
73
+ const doInstallStories = await (async () => {
74
+ console.log(chalk.cyan(`\nDo you want to install the Stories?`));
75
+
76
+ const YES = "Yes (Recommended)";
77
+ const NO = "No";
78
+
79
+ const { value } = await cliSelect({
80
+ values: [YES, NO]
81
+ }).catch(() => {
82
+ process.exit(-1);
83
+ });
84
+
85
+ console.log(`${value}\n`);
86
+
87
+ return value === YES;
88
+ })();
89
+
90
+ install_storybook: {
91
+ if (!doInstallStories) {
92
+ break install_storybook;
93
+ }
94
+
95
+ if (buildContext.bundler !== "vite") {
96
+ break install_storybook;
97
+ }
98
+
99
+ if (
100
+ Object.keys({
101
+ ...parsedPackageJson.dependencies,
102
+ ...parsedPackageJson.devDependencies
103
+ }).includes("storybook")
104
+ ) {
105
+ break install_storybook;
106
+ }
107
+
108
+ (parsedPackageJson.scripts ??= {})["storybook"] = "storybook dev -p 6006";
109
+ parsedPackageJson.scripts["build-storybook"] = "storybook build";
110
+
111
+ (parsedPackageJson.devDependencies ??= {})["storybook"] = "^9.0.4";
112
+ parsedPackageJson.devDependencies["@storybook/react-vite"] = "^9.0.4";
113
+
114
+ const files: { relativeFilePath: string; fileContent: string }[] = [
115
+ {
116
+ relativeFilePath: "main.ts",
117
+ fileContent: [
118
+ `import type { StorybookConfig } from "@storybook/react-vite";`,
119
+ ``,
120
+ `const config: StorybookConfig = {`,
121
+ ` stories: ["../src/**/*.mdx", "../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],`,
122
+ ` addons: [],`,
123
+ ` framework: {`,
124
+ ` name: "@storybook/react-vite",`,
125
+ ` options: {}`,
126
+ ` },`,
127
+ `};`,
128
+ `export default config;`,
129
+ ``
130
+ ].join("\n")
131
+ },
132
+ {
133
+ relativeFilePath: "preview.ts",
134
+ fileContent: storybookPreviewTsFileContent
135
+ }
136
+ ];
137
+
138
+ for (let { relativeFilePath, fileContent } of files) {
139
+ const filePath = pathJoin(
140
+ buildContext.projectDirPath,
141
+ ".storybook",
142
+ relativeFilePath
143
+ );
144
+
145
+ {
146
+ const dirPath = pathDirname(filePath);
147
+
148
+ if (!(await existsAsync(dirPath))) {
149
+ await fs.mkdir(dirPath, { recursive: true });
150
+ }
151
+ }
152
+
153
+ run_prettier: {
154
+ if (!(await getIsPrettierAvailable())) {
155
+ break run_prettier;
156
+ }
157
+
158
+ fileContent = await runPrettier({
159
+ filePath: filePath,
160
+ sourceCode: fileContent
161
+ });
162
+ }
163
+
164
+ await fs.writeFile(filePath, Buffer.from(fileContent, "utf8"));
165
+ }
166
+ }
167
+
168
+ {
169
+ const moduleName = "@keycloakify/login-ui";
170
+
171
+ const latestVersion = await getModuleLatestVersion({ moduleName });
172
+
173
+ (parsedPackageJson.dependencies ??= {})[moduleName] = `~${latestVersion}`;
174
+
175
+ if (parsedPackageJson.devDependencies !== undefined) {
176
+ delete parsedPackageJson.devDependencies[moduleName];
177
+ }
178
+ }
179
+
180
+ install_stories: {
181
+ if (!doInstallStories) {
182
+ break install_stories;
183
+ }
184
+
185
+ const moduleName = "@keycloakify/login-ui-storybook";
186
+
187
+ const latestVersion = await getModuleLatestVersion({ moduleName });
188
+
189
+ (parsedPackageJson.devDependencies ??= {})[moduleName] = `~${latestVersion}`;
190
+
191
+ delete parsedPackageJson.dependencies[moduleName];
192
+ }
193
+
194
+ {
195
+ let sourceCode = JSON.stringify(parsedPackageJson, null, 2);
196
+
197
+ if (await getIsPrettierAvailable()) {
198
+ sourceCode = await runPrettier({
199
+ sourceCode,
200
+ filePath: buildContext.packageJsonFilePath
201
+ });
202
+ }
203
+
204
+ await fs.writeFile(
205
+ buildContext.packageJsonFilePath,
206
+ Buffer.from(sourceCode, "utf8")
207
+ );
208
+ }
209
+
210
+ await npmInstall({
211
+ packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
212
+ });
213
+ }
214
+
215
+ async function getModuleLatestVersion(params: { moduleName: string }) {
216
+ const { moduleName } = params;
217
+
218
+ const versions = ((): string[] => {
219
+ const cmdOutput = child_process
220
+ .execSync(`npm show ${moduleName} versions --json`)
221
+ .toString("utf8")
222
+ .trim();
223
+
224
+ const versions = JSON.parse(cmdOutput) as string | string[];
225
+
226
+ // NOTE: Bug in some older npm versions
227
+ if (typeof versions === "string") {
228
+ return [versions];
229
+ }
230
+
231
+ return versions;
232
+ })();
233
+
234
+ const version = versions.reverse().filter(version => !version.includes("-"))[0];
235
+
236
+ assert(version !== undefined);
237
+
238
+ return version;
239
+ }
240
+
241
+ const storybookPreviewTsFileContent = [
242
+ `import type { Preview } from "@storybook/react-vite";`,
243
+ ``,
244
+ `const preview: Preview = {`,
245
+ ` parameters: {`,
246
+ ` controls: {`,
247
+ ` matchers: {`,
248
+ ` color: /(background|color)$/i,`,
249
+ ` date: /Date$/i`,
250
+ ` }`,
251
+ ` },`,
252
+ ` options: {`,
253
+ ` storySort: (a, b)=> {`,
254
+ ``,
255
+ ` const orderedPagesPrefix = [`,
256
+ ` "Introduction",`,
257
+ ` "login/login.ftl",`,
258
+ ` "login/register.ftl",`,
259
+ ` "login/terms.ftl",`,
260
+ ` "login/error.ftl",`,
261
+ ` "login/code.ftl",`,
262
+ ` "login/delete-account-confirm.ftl",`,
263
+ ` "login/delete-credential.ftl",`,
264
+ ` "login/frontchannel-logout.ftl",`,
265
+ ` "login/idp-review-user-profile.ftl",`,
266
+ ` "login/info.ftl",`,
267
+ ` "login/login-config-totp.ftl",`,
268
+ ` "login/login-idp-link-confirm.ftl",`,
269
+ ` "login/login-idp-link-email.ftl",`,
270
+ ` "login/login-oauth-grant.ftl",`,
271
+ ` "login/login-otp.ftl",`,
272
+ ` "login/login-page-expired.ftl",`,
273
+ ` "login/login-password.ftl",`,
274
+ ` "login/login-reset-otp.ftl",`,
275
+ ` "login/login-reset-password.ftl",`,
276
+ ` "login/login-update-password.ftl",`,
277
+ ` "login/login-update-profile.ftl",`,
278
+ ` "login/login-username.ftl",`,
279
+ ` "login/login-verify-email.ftl",`,
280
+ ` "login/login-x509-info.ftl",`,
281
+ ` "login/logout-confirm.ftl",`,
282
+ ` "login/saml-post-form.ftl",`,
283
+ ` "login/select-authenticator.ftl",`,
284
+ ` "login/update-email.ftl",`,
285
+ ` "login/webauthn-authenticate.ftl",`,
286
+ ` "login/webauthn-error.ftl",`,
287
+ ` "login/webauthn-register.ftl",`,
288
+ ` "login/login-oauth2-device-verify-user-code.ftl",`,
289
+ ` "login/login-recovery-authn-code-config.ftl",`,
290
+ ` "login/login-recovery-authn-code-input.ftl",`,
291
+ ` "account/account.ftl",`,
292
+ ` "account/password.ftl",`,
293
+ ` "account/federatedIdentity.ftl",`,
294
+ ` "account/log.ftl",`,
295
+ ` "account/sessions.ftl",`,
296
+ ` "account/totp.ftl"`,
297
+ ` ];`,
298
+ ``,
299
+ ` function getHardCodedWeight(title) {`,
300
+ ` for (let i = 0; i < orderedPagesPrefix.length; i++) {`,
301
+ ` if (`,
302
+ ` title`,
303
+ ` .toLowerCase()`,
304
+ ` .startsWith(orderedPagesPrefix[i].toLowerCase())`,
305
+ ` ) {`,
306
+ ` return orderedPagesPrefix.length - i;`,
307
+ ` }`,
308
+ ` }`,
309
+ ``,
310
+ ` return 0;`,
311
+ ` }`,
312
+ ``,
313
+ ` return getHardCodedWeight(b.title) - getHardCodedWeight(a.title);`,
314
+ ``,
315
+ ` }`,
316
+ ``,
317
+ ` }`,
318
+ ` }`,
319
+ `};`,
320
+ ``,
321
+ `export default preview;`,
322
+ ``
323
+ ].join("\n");
package/src/bin/main.ts CHANGED
@@ -146,6 +146,20 @@ program
146
146
  }
147
147
  });
148
148
 
149
+ program
150
+ .command({
151
+ name: "init",
152
+ description: "(BETA) Initialize a new theme type (login/account/admin/email)"
153
+ })
154
+ .task({
155
+ skip,
156
+ handler: async ({ projectDirPath }) => {
157
+ const { command } = await import("./init");
158
+
159
+ await command({ projectDirPath: projectDirPath ?? process.cwd() });
160
+ }
161
+ });
162
+
149
163
  program
150
164
  .command({
151
165
  name: "eject-page",