keycloakify 11.8.46 → 11.8.47-rc.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/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} +101 -147
  6. package/bin/{84.index.js → 502.index.js} +147 -101
  7. package/bin/626.index.js +194 -0
  8. package/bin/{698.index.js → 656.index.js} +101 -147
  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 +537 -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 +19 -30
  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 +2 -37
  35. package/src/bin/shared/customHandler.ts +3 -1
  36. package/src/bin/sync-extensions/extensionModuleMeta.ts +89 -73
  37. package/src/bin/sync-extensions/managedGitignoreFiles.ts +32 -2
  38. package/vite-plugin/index.js +1 -24
  39. package/bin/433.index.js +0 -140
@@ -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",
@@ -2,7 +2,6 @@ import { parse as urlParse } from "url";
2
2
  import {
3
3
  join as pathJoin,
4
4
  sep as pathSep,
5
- relative as pathRelative,
6
5
  resolve as pathResolve,
7
6
  dirname as pathDirname
8
7
  } from "path";
@@ -13,8 +12,7 @@ import { assert, type Equals, is } from "tsafe/assert";
13
12
  import * as child_process from "child_process";
14
13
  import {
15
14
  VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES,
16
- BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME,
17
- THEME_TYPES
15
+ BUILD_FOR_KEYCLOAK_MAJOR_VERSION_ENV_NAME
18
16
  } from "./constants";
19
17
  import type { KeycloakVersionRange } from "./KeycloakVersionRange";
20
18
  import { exclude } from "tsafe";
@@ -167,40 +165,7 @@ export function getBuildContext(params: {
167
165
  return { themeSrcDirPath };
168
166
  }
169
167
 
170
- {
171
- const basenames = fs.readdirSync(srcDirPath);
172
-
173
- for (const basename of basenames) {
174
- const path = pathJoin(srcDirPath, basename);
175
-
176
- if (!fs.statSync(path).isFile()) {
177
- continue;
178
- }
179
-
180
- if (fs.readFileSync(path).toString("utf8").includes("./kc.gen")) {
181
- return { themeSrcDirPath: srcDirPath };
182
- }
183
- }
184
- }
185
-
186
- for (const themeType of [...THEME_TYPES, "email"]) {
187
- if (!fs.existsSync(pathJoin(srcDirPath, themeType))) {
188
- continue;
189
- }
190
- return { themeSrcDirPath: srcDirPath };
191
- }
192
-
193
- console.log(
194
- chalk.red(
195
- [
196
- `Can't locate your Keycloak theme source directory in .${pathSep}${pathRelative(process.cwd(), srcDirPath)}`,
197
- `Make sure to either use the Keycloakify CLI in the root of your Keycloakify project or use the --project CLI option`,
198
- `If you are collocating your Keycloak theme with your app you must have a directory named '${KEYCLOAK_THEME}' or '${KEYCLOAK_THEME.replace(/-/g, "_")}' in your 'src' directory`
199
- ].join("\n")
200
- )
201
- );
202
-
203
- process.exit(1);
168
+ return { themeSrcDirPath: srcDirPath };
204
169
  })();
205
170
 
206
171
  const { resolvedViteConfig } = (() => {
@@ -10,11 +10,13 @@ export type CommandName =
10
10
  | "update-kc-gen"
11
11
  | "eject-page"
12
12
  | "add-story"
13
+ | "initialize-login-theme"
13
14
  | "initialize-account-theme"
14
15
  | "initialize-admin-theme"
15
16
  | "initialize-admin-theme"
16
17
  | "initialize-email-theme"
17
- | "copy-keycloak-resources-to-public";
18
+ | "copy-keycloak-resources-to-public"
19
+ | "init";
18
20
 
19
21
  export type ApiVersion = "v1";
20
22