Compare commits

..

470 Commits

Author SHA1 Message Date
59f106bf9e Update changelog v2.5.0 2021-10-12 00:14:04 +00:00
913a6c3ec3 Bump version (changelog ignore) 2021-10-12 02:09:32 +02:00
57932386bf register-user-profile.ftl tested working 2021-10-12 02:09:09 +02:00
e3df4b83eb Better prettier params (changelog ignore) 2021-10-12 00:26:29 +02:00
ef5b01956a Make kcMessage more easily hackable 2021-10-12 00:24:08 +02:00
0e8984e5b1 fix useKcMessage 2021-10-11 23:35:32 +02:00
403aedf1fe Remove script from other project (changelog ignore) 2021-10-11 21:39:16 +02:00
53d3646523 Fix workflow (changelog ignore) 2021-10-11 21:38:15 +02:00
305ce9e44d Remove eslint and run prettier (changelog ignore) 2021-10-11 21:35:40 +02:00
9f8218efb7 setup prettier and eslint (changelog ignore) 2021-10-11 21:09:05 +02:00
c4ba470dc4 Implement and type validators 2021-10-11 20:56:43 +02:00
637bc75fc2 Remove syntax error in ftl and make it more directly debugable 2021-10-11 13:46:47 +02:00
4ad3affadb fmt (changelog ignore) 2021-10-11 04:06:59 +02:00
bd403beb5c Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-10-11 03:41:13 +02:00
20f528a167 Update tsafe (changelog ignore) 2021-10-11 03:41:05 +02:00
4ca2bc59b6 Support register-user-profile.ftl 2021-10-11 03:25:02 +02:00
91c6839447 Update changelog v2.4.0 2021-10-08 00:10:35 +00:00
c388c77f4a Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-10-08 02:04:31 +02:00
19fb365271 Bump version (changelog ingore) 2021-10-08 02:04:25 +02:00
92946ef6bb #38: Implement messagesPerField existsError and get 2021-10-08 02:03:27 +02:00
7fad7a67a0 Update changelog v2.3.0 2021-10-07 23:52:06 +00:00
392377bf2d Bump version (changelog ignore) 2021-10-08 01:46:53 +02:00
95a53d37f6 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-10-08 01:46:02 +02:00
2d03fbce79 #20: Support advancedMsg 2021-10-08 01:45:51 +02:00
37a0692f9f Update changelog v2.2.0 2021-10-07 19:16:45 +00:00
2c82a2332f Fix build (changelog ignore) 2021-10-07 21:12:58 +02:00
2f78f0f5e2 Bump version (changelog ignore) 2021-10-07 21:01:24 +02:00
c52b8cc98e Feat scrip: download-builtin-keycloak-theme for downloading any version of the builtin themes 2021-10-07 21:00:53 +02:00
fb5f358686 Use the latest version of keycloak for testing 2021-10-07 17:32:38 +02:00
60d5a8e881 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-10-07 16:07:20 +02:00
6cbebf3b13 Test locally with 15.0.2 instead of 11.0.3 2021-10-07 15:37:35 +02:00
f1269bbfe1 Update changelog v2.1.0 2021-10-06 15:27:35 +00:00
d1be21ace3 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-10-06 17:23:14 +02:00
cf2317884c Bump version (changelog ignore) 2021-10-06 17:23:08 +02:00
7a37a6127d Support Hungarian and Danish (use Keycloak 15 language resources) 2021-10-06 17:22:52 +02:00
1d4e5792fd Update changelog v2.0.20 2021-10-05 17:56:03 +00:00
1df329448b Bump version (changelog ignore) 2021-10-05 15:35:59 +02:00
d6762547a3 Update powerhooks (changelog igonre) 2021-10-05 15:35:33 +02:00
a31a6d17d4 Update README.md 2021-09-28 01:11:56 +02:00
8069ede90a Update changelog v2.0.19 2021-09-17 21:03:02 +00:00
ce956172df Bump version (changelog ingore) 2021-09-17 22:57:59 +02:00
874cec81e0 Fix kcContext type definitions 2021-09-17 22:57:38 +02:00
7b24b13dca Update changelog v2.0.18 2021-09-14 23:30:24 +00:00
e4dd3ed898 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-09-15 01:26:26 +02:00
e5804ce778 Bump version (changelog ignore) 2021-09-15 01:26:16 +02:00
b6db47c243 Update deps (changelog ignore) 2021-09-15 01:25:56 +02:00
cde1881ab1 Update changelog v2.0.17 2021-09-14 23:16:31 +00:00
23afef7857 Bump version (changelog ignore) 2021-09-15 01:14:36 +02:00
bdf09c7a45 Update tss-react (changelog ignore) 2021-09-15 01:14:12 +02:00
6957a03a87 Update changelog v2.0.16 2021-09-12 02:57:06 +00:00
11e688d638 Bump version (changelog ignore) 2021-09-12 04:54:44 +02:00
eb39de6b34 Update dependencies (changelog ignore) 2021-09-12 04:54:15 +02:00
a0f937615e Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-09-07 23:48:12 +02:00
2ee7d2e698 Add explaination about errors in logs 2021-09-07 23:48:06 +02:00
7dc39a323a Fix typo (changelog ignore) 2021-09-03 01:22:09 +02:00
672717372e Update changelog v2.0.15 2021-08-31 23:33:11 +00:00
2ad2efa15e Bump version (changelog ignore) 2021-09-01 01:30:01 +02:00
f638fcdf51 Update tss-react 2021-09-01 01:29:43 +02:00
a47695d7e0 Update changelog v2.0.14 2021-08-20 15:08:42 +00:00
1bc1fea5cc Bump version (changelog ignore) 2021-08-20 17:04:43 +02:00
97544a952f Update tss-react 2021-08-20 17:03:50 +02:00
1c2a55f2fd Update changelog v2.0.13 2021-08-04 15:11:03 +00:00
8aa0bd189e Bump version (changelog ignore) 2021-08-04 17:07:28 +02:00
d648caa37f Merge pull request #28 from marcmrf/main
fix(mvn): scoped packages compatibility
2021-08-04 17:04:39 +02:00
34263661d7 fix(mvn): scoped packages compatibility 2021-08-04 16:12:54 +02:00
f4a6fd5c5e Update changelog v2.0.12 2021-07-28 09:26:19 +00:00
fc9ddfb8d8 Bump version (changelog ignore) 2021-07-28 11:23:11 +02:00
f8d0aff386 Better requirements (changelog ignore) 2021-07-28 11:22:54 +02:00
ac100d74bf Merge pull request #27 from jchn-codes/patch-1
add maven to requirements
2021-07-28 11:14:41 +02:00
9b1e4e9111 add maven to requirements 2021-07-28 10:26:12 +02:00
3e54b64bc4 Add #bluehats in the keyworks 2021-07-25 19:32:25 +02:00
914d2a787d Update changelog v2.0.11 2021-07-21 20:45:40 +00:00
95add5b1d0 Bump version (changelog ignore) 2021-07-21 22:42:47 +02:00
b1e24212ea Spaces in file path #22 2021-07-21 22:42:00 +02:00
a1bec78ea2 uptdate dependnecies 2021-07-21 22:12:10 +02:00
05c98eb074 Inport specific powerhooks files to reduce bundle size 2021-07-21 22:10:28 +02:00
2688aefdfb Update changelog v2.0.10 2021-07-16 17:10:54 +00:00
eaa6582e67 Bump version (changelog ignore) 2021-07-16 19:06:17 +02:00
a136bc619d Update dependencies 2021-07-16 19:05:56 +02:00
8780c516fa Update changelog v2.0.9 2021-07-14 23:31:29 +00:00
b4bdec7970 Bump version (changelog ignore) 2021-07-15 01:28:54 +02:00
671aeadf29 Fix #21 2021-07-15 01:28:31 +02:00
341d985610 Update changelog v2.0.8 2021-07-12 13:37:01 +00:00
76b9a78182 Bump version (changelog ignore) 2021-07-12 15:34:09 +02:00
021c0a9429 Fix previous release 2021-07-12 15:33:50 +02:00
7b3462d158 Bump version (changelog ignore) 2021-07-12 15:19:47 +02:00
c180dee414 #20: Add def for clientId and name on kcContext.client 2021-07-12 15:19:31 +02:00
769da5c5ca update language statistics config (changelog ignore) 2021-07-09 08:04:48 +00:00
1a4dd79240 update language statistics config (changelog ignore) 2021-07-09 08:01:50 +00:00
5cf290b033 Update changelog v2.0.6 2021-07-08 01:08:57 +00:00
aec3da25b3 Bump version (changelog ignore) 2021-07-08 02:44:54 +02:00
66d7cb563d Merge pull request #18 from asashay/add-custom-props-to-theme-properties
Add possibility to add custom properties to theme.properties file
2021-07-08 02:43:37 +02:00
551e9c041e add possibility to add custom properties to theme.properties file 2021-07-06 15:52:14 +03:00
fffb6d5b5e Update changelog v2.0.5 2021-07-05 01:10:51 +00:00
ac0bfeb360 Bump version (changelog ignore) 2021-07-05 03:06:49 +02:00
7c30059ca3 Fix broken url for big stylesheet #16 2021-07-05 03:06:31 +02:00
fdb9ae6c40 Update changelog v2.0.4 2021-07-03 00:41:40 +00:00
3c82ffc0ab Bump version (changelog ignore) 2021-07-03 02:39:59 +02:00
5dd3103aba Fix: #7 2021-07-03 02:39:39 +02:00
84fc81f531 Update changelog v2.0.3 2021-06-30 20:05:50 +00:00
a20cbc62a5 Bump version (changelog ignore) 2021-06-30 22:01:25 +02:00
e6a93e2838 Escape double quote in ftl to js conversion #15 2021-06-30 22:01:01 +02:00
3cff54561f Update readme 2021-06-29 02:31:41 +02:00
e50a6a7876 Update changelog v2.0.2 2021-06-28 13:30:05 +00:00
b887ec839b Updagte README for implementing non incuded pages 2021-06-28 15:27:08 +02:00
465daa19a0 Update changelog v2.0.1 2021-06-28 05:04:20 +00:00
6c2b761d95 Bump version (changelog ignore) 2021-06-28 06:58:43 +02:00
0e8f95ce19 Update documentation for v2 2021-06-28 06:58:00 +02:00
6a17f343c6 Update changelog v2.0.0 2021-06-28 03:49:38 +00:00
1a45fb0039 Update changelog v2.0.0 2021-06-28 03:33:08 +00:00
75032898d6 Bump to v2 🚀 (changelog ignore) 2021-06-28 05:31:00 +02:00
88a4c97428 Fix last bugs before relasing v2 2021-06-28 05:30:09 +02:00
82e7a7edae Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-06-28 04:04:56 +02:00
eac28f97b8 Implement a mechanism to overload kcContext 2021-06-28 04:04:48 +02:00
e160882db9 fmt (changelog ignore) 2021-06-23 18:55:36 +02:00
2bc07e77fd Give the option in template to pull the default assets or not 2021-06-23 18:27:41 +02:00
c9b2db625c Enable possiblity to support custom pages (without forking keycloakify) 2021-06-23 18:03:49 +02:00
e3b41c9bd1 Implement a getter for kcContext 2021-06-23 08:16:51 +02:00
4aaee35d9c Update README.md 2021-06-23 06:52:59 +02:00
beedbc695a Update changelog v1.2.1 2021-06-22 16:30:35 +00:00
7123edc986 Bump version (changelog ignore) 2021-06-22 18:26:26 +02:00
3008a754ce Remove unessesary log 2021-06-22 18:26:05 +02:00
70e3fb8de6 Update changelog v1.2.0 2021-06-22 10:42:42 +00:00
9cf75da732 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-06-22 12:37:23 +02:00
2cd266caff Generate kcContext automatically 🚀 2021-06-22 12:37:21 +02:00
28036f1da5 Update changelog v1.1.6 2021-06-21 14:04:36 +00:00
0dacf2fe30 Bump version (changelog ignore) 2021-06-21 15:59:44 +02:00
32f5ef5e5c Fix: Alert messages sometimes includes HTML that is not rendered 2021-06-21 15:59:23 +02:00
98d91bbde7 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-06-21 15:58:40 +02:00
7a92a75d83 Update dist 2021-06-21 15:57:22 +02:00
f5556a02fc Update changelog v1.1.5 2021-06-15 15:38:25 +00:00
1050f4d928 Bump version (changelog ignore) 2021-06-15 17:32:26 +02:00
9276b08f4b #11: Provide socials in the register 2021-06-15 17:32:03 +02:00
a501af669c Update changelog v1.1.4 2021-06-15 12:59:00 +00:00
85343fcefe Bump version (changelog ignore) 2021-06-15 14:49:46 +02:00
b11dfde6e6 Merge pull request #12 from InseeFrLab/email-typo
Fix typo on email
2021-06-15 14:38:27 +02:00
38d2108f02 Fix typo on email 2021-06-15 09:47:22 +02:00
2b67544517 Update changelog v1.1.3 2021-06-14 22:36:25 +00:00
0d443ca88e Add missing key in Login for providers 2021-06-15 00:31:20 +02:00
71f7a5819d Update changelog v1.1.2 2021-06-14 21:33:09 +00:00
5f4abee615 Fix previous build (changelog ignore) 2021-06-14 23:27:15 +02:00
f5ee949006 Fix CI (changelog ignore) 2021-06-14 23:15:41 +02:00
7e85085558 Bump version (changelog ingore) 2021-06-14 23:10:53 +02:00
55a0b27f16 Fix previous build (changelog ignore) 2021-06-14 23:10:35 +02:00
eb0e814f94 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-06-14 22:41:29 +02:00
b7fe20c5a5 Update CI (changelog ignore) 2021-06-14 22:41:22 +02:00
2b23d03ca5 Update changelog v1.1.1 2021-06-14 20:37:47 +00:00
7075be20c8 Bump version (changelog ignore) 2021-06-14 22:30:35 +02:00
3ce8b06246 Add missing shebang directive (changelog ignore) 2021-06-14 22:30:16 +02:00
ee5c29f30f Update changelog v1.1.0 2021-06-14 19:33:53 +00:00
242dad3ea0 Bump version (changelog ignore) 2021-06-14 21:28:23 +02:00
d8701925df Refactor dir structure (changelog ignore) 2021-06-14 21:27:18 +02:00
e2d669ce31 Refactor dir structure (changelog ignore) 2021-06-14 21:24:56 +02:00
af93664c71 Refactor dir structure (changelog ignore) 2021-06-14 21:23:14 +02:00
daa3efa534 Refactor dir structure (changelog ignore) 2021-06-14 21:21:36 +02:00
2c7c8397f0 Add login-idp-link-confirm.ftl 2021-06-14 21:19:46 +02:00
821ba2cbe2 Fix login-update-profile.ftl 2021-06-14 19:19:42 +02:00
a17ddb02fa Add Typescript: strict in readme (changelog ignore) 2021-06-14 19:15:58 +02:00
b89557e8d8 Add login-update-profile.ftl page 2021-06-14 19:06:31 +02:00
cad1f8b957 Fix default background bug 2021-06-14 19:05:50 +02:00
f82cc788bf Remove unused 'markdown' dependency 2021-06-14 15:20:03 +02:00
06f9cd3e68 Fix warning related to powerhooks_useGlobalState_kcLanguageTag 2021-06-12 00:11:56 +02:00
5113a838e7 Update README.md 2021-05-29 08:42:49 +02:00
645a84c82a Update changelog v1.0.4 2021-05-28 17:44:37 +00:00
925fc43d0f Bump version (changelog ignore) 2021-05-28 19:41:15 +02:00
8e33d24c63 Instructions for custom themes with custom components 2021-05-28 19:23:38 +02:00
984ef63661 Update changelog v1.0.3 2021-05-23 20:22:50 +00:00
a8daf175ea Instuction about how to integrate with non CRA projects 2021-05-23 22:19:51 +02:00
055263a3da Add mention to awesome list 2021-05-15 22:41:30 +02:00
9990b0ab05 fmt (changelog ignore) 2021-05-01 18:05:40 +02:00
423397ce3e Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-05-01 18:04:07 +02:00
954567712c Give hint about where to find the ftl files (changelog ignore) 2021-05-01 18:03:35 +02:00
9f52eb8123 Update changelog v1.0.2 2021-05-01 14:17:28 +00:00
744b198fb4 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-05-01 16:15:44 +02:00
15eab797c3 Add key for child in a list (changelog ignore) 2021-05-01 16:15:40 +02:00
8ff86b1e29 Update changelog v1.0.1 2021-05-01 14:12:41 +00:00
e1b8760ee3 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-05-01 16:10:57 +02:00
bd0d890b2c Fix: LoginOtp (and not otc) 2021-05-01 16:10:52 +02:00
2a2118d769 Update changelog v1.0.0 2021-05-01 13:52:33 +00:00
9839b64650 Bump version (changelog ignore) 2021-05-01 15:50:45 +02:00
2bf55e12f9 Guide for implementing a missing page (fix, changelog ignore) 2021-05-01 15:50:12 +02:00
2249fa9232 #4: Guide for implementing a missing page 2021-05-01 15:48:49 +02:00
f673a65304 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-05-01 14:56:01 +02:00
0163459ad6 Support OTP #4 2021-05-01 14:55:58 +02:00
b21123cc9d Update changelog v0.4.4 2021-04-29 21:33:33 +00:00
7800d125b2 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-04-29 23:31:12 +02:00
89ea648f18 Fix previous release 2021-04-29 23:31:05 +02:00
ab7ac3c2d0 Update changelog v0.4.3 2021-04-29 17:07:48 +00:00
b16319d962 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-04-29 19:02:16 +02:00
f8012d5dfb Bump version (changelog ignore) 2021-04-29 19:00:56 +02:00
45a2015597 Add infos about the plugin that defines authorizedMailDomains 2021-04-29 19:00:38 +02:00
524ab000be Update changelog v0.4.2 2021-04-29 16:47:44 +00:00
d73cfb8765 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-04-29 18:42:29 +02:00
8164f5373f Client side validation of allowed email domains 2021-04-29 18:42:14 +02:00
824b0c275e fmt (changelog ignore) 2021-04-21 20:45:03 +02:00
f8d83d7a37 Update README (changelog ignore) 2021-04-20 11:44:57 +02:00
b291526b13 Support email whitlisting 2021-04-15 12:20:08 +02:00
e1c310d383 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-04-12 23:46:29 +02:00
242777a8eb Restore kickstart video in the readme 2021-04-12 23:46:24 +02:00
10a6b70fe9 Update README.md 2021-04-12 19:27:54 +02:00
c829f5969c Update README.md 2021-04-12 18:51:16 +02:00
ba6a5047b1 Add note (changelog ignore) 2021-04-12 04:59:39 +02:00
852f48c05f update gif (changelog ignore) 2021-04-12 04:47:53 +02:00
c342f04a92 Update video (changelog ignore) 2021-04-12 04:21:18 +02:00
42eb8147c6 fmt (changelog ignore) 2021-04-12 02:29:24 +02:00
ebcdbd782f fmt (changelog ignore) 2021-04-12 02:28:22 +02:00
d2059e08d1 fmt (changelog ignore) 2021-04-12 02:25:48 +02:00
4f075882d5 Merge branch 'main' of https://github.com/garronej/keycloakify into main 2021-04-12 02:24:17 +02:00
044ec1a2da Important readme update 2021-04-12 02:24:11 +02:00
a49a32703d Update changelog v0.4.1 2021-04-11 16:24:36 +00:00
46ec832767 Quietly re-introduce --external-assets 2021-04-11 18:18:52 +02:00
fc858b3db6 Update screenshot (changelog ignore) 2021-04-11 01:21:01 +02:00
3cd8843157 Update screenshot in readme for therms and conditions (changelog ignore) 2021-04-10 20:20:26 +02:00
c9358ea8dd Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-09 04:02:30 +02:00
354a4db0f6 Give example of customization 2021-04-09 04:02:23 +02:00
90d435d96b Update changelog v0.4.0 2021-04-09 01:31:07 +00:00
2d804f0f0f Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-09 03:26:06 +02:00
1c9acedac0 Bump version (changelog ignore) 2021-04-09 03:26:01 +02:00
6e914e4ea3 Acual support of Therms of services 2021-04-09 03:25:39 +02:00
f0c4786267 Update changelog v0.3.24 2021-04-08 16:14:10 +00:00
0b16159312 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-08 18:00:04 +02:00
ea8a91e069 Add missing dependency: markdown 2021-04-08 17:59:58 +02:00
59db202fe4 Update changelog v0.3.23 2021-04-08 15:43:34 +00:00
09927afd43 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-08 17:40:54 +02:00
13c6122b9b Bump version (changelog ignore) 2021-04-08 17:06:26 +02:00
1bb19f65a2 Allow to lazily load therms 2021-04-08 17:06:09 +02:00
918a80cfb6 Update changelog v0.3.22 2021-04-08 13:48:41 +00:00
ed7d5eabcb Bump version (changelog ignore) 2021-04-08 15:43:12 +02:00
2795109162 update powerhooks 2021-04-08 15:42:41 +02:00
966f277628 Support terms and condition 2021-04-08 15:41:40 +02:00
36d60411f9 Fix info.ftl 2021-04-08 12:54:29 +02:00
60fe33f3fd Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-08 12:20:14 +02:00
1df685df92 For useKcMessage we prefer returning callbacks with a changing references 2021-04-08 12:20:06 +02:00
7894d95ace Update changelog v0.3.21 2021-04-04 19:20:27 +00:00
a8b4493aa1 update yarn.lock (changelog ignore) 2021-04-04 21:17:35 +02:00
715a7399cf Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-04 21:12:39 +02:00
a1e59bae23 Update powerhooks 2021-04-04 21:12:30 +02:00
b0819314a1 Update changelog v0.3.20 2021-04-01 22:37:41 +00:00
0099442543 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-01 23:43:14 +02:00
66a0b07228 Bump version (changelog ignore) 2021-04-01 23:43:08 +02:00
85f9544754 Always catch freemarker errors 2021-04-01 23:42:31 +02:00
2f16a09ab8 Update changelog v0.3.19 2021-04-01 15:21:10 +00:00
183ae98c30 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-01 17:16:01 +02:00
ba15e63879 Bump version (changelog ignore) 2021-04-01 17:15:55 +02:00
654277feda Fix previous release 2021-04-01 17:15:28 +02:00
81279a5cc5 Update changelog v0.3.18 2021-04-01 15:04:08 +00:00
59f0a843b0 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-04-01 16:52:53 +02:00
c094f70171 Bump version (changelog ignore) 2021-04-01 16:51:50 +02:00
0858fe6319 Fix error.ftt, Adopt best effort strategy to convert ftl values into JS 2021-04-01 16:51:28 +02:00
5012ec0ccc Update changelog v0.3.17 2021-03-29 04:33:07 +00:00
990a24fab2 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-29 06:27:19 +02:00
036b6bf82a Use push instead of replace in keycloak-js adapter to enable going back 2021-03-29 06:27:12 +02:00
8272a02b52 Update changelog v0.3.15 2021-03-28 12:18:37 +00:00
e346b1d9d2 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-28 14:14:52 +02:00
2309bd21c6 Remove all reference to --external-assets, broken feature 2021-03-28 14:14:43 +02:00
7d6476c1b5 Update changelog v0.3.14 2021-03-28 11:39:36 +00:00
e892a0e7e6 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-28 13:37:07 +02:00
ca5b41e730 Fix standalone mode: imports from js 2021-03-28 13:37:02 +02:00
9b18234112 Update changelog v0.3.13 2021-03-26 16:54:38 +00:00
5274368f47 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-26 17:49:36 +01:00
1415c24028 Bump version (changelog ignore) 2021-03-26 17:49:31 +01:00
4a084f5859 Fix previous release (changelog ignore) 2021-03-26 17:49:09 +01:00
a30c9eb0cd Update changelog v0.3.12 2021-03-26 16:35:41 +00:00
85d3b40b8e Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-26 17:31:06 +01:00
335afec230 Bump version (changelog ignore) 2021-03-26 17:31:01 +01:00
69fa49848a Fix mocksContext 2021-03-26 17:30:04 +01:00
7a09051127 Update changelog v0.3.11 2021-03-26 14:36:48 +00:00
07ee0ecb8b Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-26 15:29:23 +01:00
6f133428f8 Fix previous build, improve README 2021-03-26 15:29:17 +01:00
4f733736db fmt (changelog ignore) 2021-03-26 14:06:14 +01:00
d96ff13a67 Update changelog v0.3.10 2021-03-26 13:05:25 +00:00
2c1351ce47 fmt (changelog ignore) 2021-03-26 14:04:45 +01:00
96cd56ec77 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-26 14:02:46 +01:00
e5b2096d65 Bump version (changelog ignore) 2021-03-26 14:02:40 +01:00
3aa140335f Handle <style> tag, improve documentation 2021-03-26 14:02:14 +01:00
4cafaa2492 Update changelog v0.3.9 2021-03-25 11:54:13 +00:00
9c633a7521 Update readme 2021-03-25 12:48:40 +01:00
e27845ba91 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-25 12:01:15 +01:00
2a8708a45b Document --external-assets 2021-03-25 12:01:11 +01:00
6874fa4c24 Update README.md 2021-03-24 09:12:50 +01:00
ba531a4927 Update README.md 2021-03-24 09:12:04 +01:00
20175b57cf Update README.md 2021-03-24 09:10:44 +01:00
ad275e4c34 Update changelog v0.3.8 2021-03-22 22:36:17 +00:00
060b9fe0de Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 23:34:13 +01:00
17b24d14ed Make standalone mode the default 2021-03-22 23:34:07 +01:00
2d278b0680 Update changelog v0.3.7 2021-03-22 19:57:34 +00:00
fb5975e4f1 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 20:54:35 +01:00
24fccaf513 (test) external asset mode by default 2021-03-22 20:54:28 +01:00
293953aa1b Update changelog v0.3.6 2021-03-22 19:02:53 +00:00
1049e312f9 Fix previous release 2021-03-22 20:00:58 +01:00
a2db250600 Update changelog v0.3.5 2021-03-22 18:43:18 +00:00
cf7fe8c337 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 19:41:08 +01:00
f5350097bf Bump version (changelog ignore) 2021-03-22 19:40:58 +01:00
1cb5dd461b support homepage with urlPath 2021-03-22 19:40:38 +01:00
845599a5e8 Update changelog v0.3.4 2021-03-22 06:23:40 +00:00
0cc02c292f Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 07:21:36 +01:00
1919702326 Bugfix: Import assets from CSS 2021-03-22 07:21:31 +01:00
0c0052e1cd Update changelog v0.3.3 2021-03-22 04:27:01 +00:00
78622770ec Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-22 05:21:22 +01:00
7b86727394 Fix submit not receving correct text 2021-03-22 05:21:05 +01:00
0965f8648e Update changelog v0.3.2 2021-03-21 21:31:01 +00:00
98974b4367 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-21 22:25:49 +01:00
597bcadd9e Fix broken previous release 2021-03-21 22:25:47 +01:00
4d9aabcb91 Update changelog v0.3.1 2021-03-21 21:13:36 +00:00
1606c2884d kcHeaderClass can be updated after initial mount 2021-03-21 22:10:33 +01:00
12f69b593f Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-20 05:07:26 +01:00
1ca45f90d0 Update readme (changelog ignore) 2021-03-20 05:07:22 +01:00
a91a5616f9 Update changelog v0.3.0 2021-03-20 04:02:59 +00:00
c525e09368 Bump version 2021-03-20 05:00:14 +01:00
f5bba4a6a0 Feat: Cary over states using URL search params 2021-03-20 04:59:18 +01:00
77a37fb573 Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-20 02:54:21 +01:00
6b24c5878c Bugfix: with kcHtmlClass 2021-03-20 02:54:15 +01:00
f4414e1249 Update changelog v0.2.10 2021-03-19 23:19:03 +00:00
b72971f4ce Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-20 00:17:06 +01:00
b9af4e6804 Bump version (changelog ingnore) 2021-03-20 00:16:58 +01:00
2fd1d42d1e Remove dependency to denoify 2021-03-20 00:16:34 +01:00
3cfc7d7fa9 Update changelog v0.2.9 2021-03-19 23:14:27 +00:00
b5d9055fcf Bump version (changelog ignore) 2021-03-20 00:09:37 +01:00
63d644d95f Update deps and CI workflow 2021-03-20 00:09:12 +01:00
e16192b416 Update changelog v0.2.8 2021-03-19 21:42:31 +00:00
505e018448 Bump version (changelog ignore) 2021-03-19 22:39:48 +01:00
5ced0e2809 Bugfix: keycloak_build that grow and grow in size 2021-03-19 22:39:32 +01:00
0e1d919f7e Add disclaimer about maitainment strategy 2021-03-15 16:22:13 +01:00
a009db998e Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-15 16:05:28 +01:00
d6c6bd933b Add a note for tested version support 2021-03-15 16:05:21 +01:00
859cc03f35 Update changelog v0.2.7 2021-03-13 11:56:59 +00:00
1a3b8ae3b8 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-13 12:51:41 +01:00
863a08abf3 Bump version 2021-03-13 12:51:34 +01:00
fd9c6afa5e Update README.md 2021-03-11 00:10:34 +01:00
8f3797407b Update README.md 2021-03-10 23:55:37 +01:00
7eedb23285 Update changelog v0.2.6 2021-03-10 22:04:25 +00:00
e4a2c95dd8 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-10 23:02:30 +01:00
9429228b71 Bump version (changelog ignore) 2021-03-10 23:02:25 +01:00
aafbc60f12 Fix generated gitignore 2021-03-10 23:02:03 +01:00
7170611791 Update changelog v0.2.5 2021-03-10 21:51:07 +00:00
59e57f3dd5 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-10 22:46:06 +01:00
fd0d25b081 Bump version (changelog ignore) 2021-03-10 22:46:01 +01:00
fa529c911a Fix generated .gitignore 2021-03-10 22:45:09 +01:00
dc997b7ef4 Update changelog v0.2.4 2021-03-10 20:33:36 +00:00
0168d32f96 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-10 21:27:48 +01:00
d6fc0c779c Bump version (changelog ignore) 2021-03-10 21:27:43 +01:00
08be36edfa Update deps (changelog ignore) 2021-03-10 21:27:23 +01:00
990d287953 Update README.md 2021-03-09 05:12:45 +01:00
a629d4ab45 Update readme 2021-03-09 04:56:45 +01:00
78b48af886 Update changelog v0.2.3 2021-03-09 02:07:07 +00:00
72d267853c fix gitignore generation 2021-03-09 03:04:59 +01:00
66a218c2ec Update changelog v0.2.2 2021-03-08 02:02:53 +00:00
d4f3ec2245 Add table of content 2021-03-08 02:59:22 +01:00
7bdc19bf4b Update README.md 2021-03-08 02:55:41 +01:00
103ef788fb Update README.md 2021-03-08 02:54:43 +01:00
b6f6d1f3cc Update README.md 2021-03-08 02:52:58 +01:00
ef3d2e4e04 Update publish.yaml 2021-03-08 02:51:50 +01:00
8ec8b91ead Update changelog v0.2.1 2021-03-08 01:49:47 +00:00
819a1d473d Update ci.yaml 2021-03-08 02:48:02 +01:00
c930337255 Update readme 2021-03-08 02:40:25 +01:00
57bb4a9d96 Bump version (changelog ignore) 2021-03-08 02:36:21 +01:00
1776341242 Update readme 2021-03-08 02:35:58 +01:00
983eec6941 Bump version (changelog ignore) 2021-03-08 02:34:31 +01:00
86d390ee1a update deps 2021-03-08 02:33:52 +01:00
91703409d9 Update readme 2021-03-08 02:29:54 +01:00
3322d0e4a5 Add all mocks for testing 2021-03-08 01:02:06 +01:00
d09038fde2 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-08 00:10:57 +01:00
71966deaac Bump version (changelog ignore) 2021-03-08 00:10:51 +01:00
12e83c9468 many small fixes 2021-03-08 00:09:52 +01:00
fe27357dbb Update changelog v0.1.6 2021-03-07 16:09:08 +00:00
b93003e76d Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 17:07:20 +01:00
d6c0e9f783 Bump version (changelog ignore) 2021-03-07 17:05:28 +01:00
18a1baae59 Fix Turkish 2021-03-07 17:05:10 +01:00
2330788995 Update changelog v0.1.5 2021-03-07 15:43:11 +00:00
40c146022a Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 16:41:32 +01:00
9844e7554f Bump version changelog ignore 2021-03-07 16:41:27 +01:00
b064b8cbe6 Fix getKcLanguageLabel 2021-03-07 16:41:07 +01:00
997941fbf7 Update changelog v0.1.4 2021-03-07 14:46:12 +00:00
8d5e080bd6 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 15:44:38 +01:00
1c1ca25287 Bump version (changelog ignore) 2021-03-07 15:44:33 +01:00
5195b3bc1e Update changelog v0.1.3 2021-03-07 14:39:29 +00:00
3ff56cfea7 Bump version (changelog ignore) 2021-03-07 15:37:58 +01:00
adc6d69201 Implement LoginVerifyEmail 2021-03-07 15:37:37 +01:00
438ca4595f Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 14:57:59 +01:00
740d9b7af5 Implement login-reset-password.ftl 2021-03-07 14:57:53 +01:00
919a6947bc Update changelog v0.1.2 2021-03-07 01:17:32 +00:00
85fdaa2f22 Bump version (changelog version) 2021-03-07 02:15:51 +01:00
6ade3d6375 Fix build 2021-03-07 02:15:21 +01:00
73e9ebbe40 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 01:47:10 +01:00
ad78221025 Fix build 2021-03-07 01:47:03 +01:00
714fec0bf3 Update changelog v0.1.1 2021-03-06 22:06:48 +00:00
de312c60b1 Bump version (changelog ignore) 2021-03-06 23:05:15 +01:00
1d07fd7675 Implement Error page 2021-03-06 23:03:03 +01:00
307650aaea rename pageBasename by pageId 2021-03-06 22:41:36 +01:00
6eccd313b6 Implement reactive programing for language switching 2021-03-06 15:16:21 +01:00
b8751d67db Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-06 14:43:03 +01:00
25d9d3dc26 Add Info page, refactor 2021-03-06 14:42:56 +01:00
68e6c9faaf Update changelog v0.1.0 2021-03-05 19:44:56 +00:00
f3fb360ce0 Rename keycloakify 2021-03-05 20:43:22 +01:00
d3631dd10c Update changelog v0.0.33 2021-03-05 18:52:19 +00:00
891c91aa20 bump version (changelog ignore) 2021-03-05 19:50:40 +01:00
880018e926 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-05 19:50:14 +01:00
06ab2ab82e Fix syncronization with non react pages 2021-03-05 19:50:08 +01:00
aafcfb62f2 Update changelog v0.0.32 2021-03-05 14:48:38 +00:00
a69bee8726 bump version 2021-03-05 15:46:55 +01:00
240208793d Add log to tell when we are using react 2021-03-05 15:46:34 +01:00
e7a320f8f8 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-05 15:44:42 +01:00
f76438dd82 Fix missing parentesis 2021-03-05 15:44:35 +01:00
d6dbb42dea Update changelog v0.0.31 2021-03-05 14:34:34 +00:00
2e076aa058 bump version (changelog ignore) 2021-03-05 15:32:50 +01:00
b59447b840 Fix typo 2021-03-05 15:13:50 +01:00
702352bea2 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-05 15:08:56 +01:00
98f647fadf Fix register page 500 2021-03-05 14:50:46 +01:00
85d3a011d8 Update changelog v0.0.30 2021-03-05 00:40:09 +00:00
26b0b55a7d Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-05 01:38:37 +01:00
50d0e74c03 Edit language statistique 2021-03-05 01:38:15 +01:00
5a7fb7c303 Update changelog v0.0.30 2021-03-05 00:38:08 +00:00
e9f5a79d69 bump version (changelog ignore) 2021-03-05 01:36:17 +01:00
1378dbebee avoid escaping urls 2021-03-05 01:35:50 +01:00
7daa818996 Use default value instead of value 2021-03-05 00:44:27 +01:00
c8e219361b Fix double single quote problem in messages 2021-03-05 00:23:50 +01:00
b74323a48e Fix typo 2021-03-05 00:14:06 +01:00
0cfe240590 Fix non editable username 2021-03-05 00:03:21 +01:00
533105d63a Fix some bugs 2021-03-04 23:24:43 +01:00
62e4af2c78 Fix Object.deepAssign 2021-03-04 21:50:18 +01:00
934c07f365 Make the dongle to download smaller 2021-03-04 21:43:36 +01:00
624409434a Split kcContext among pages 2021-03-04 21:14:54 +01:00
546c74aa28 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-04 18:15:59 +01:00
2ee12abc43 Implement register 2021-03-04 18:15:48 +01:00
b9717e649f Update changelog v0.0.29 2021-03-04 13:44:23 +00:00
09c7b6ac03 Fix build 2021-03-04 14:42:38 +01:00
079da86cf1 bump version (changelog ignore) 2021-03-04 14:31:24 +01:00
57502a57af Fix i18n 2021-03-04 14:30:59 +01:00
6738f6f6cf Login appear to be working now 2021-03-04 13:56:51 +01:00
21763db561 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-03 03:18:02 +01:00
24e07c3e00 closer but not there yet 2021-03-03 03:17:56 +01:00
8820262b0f Update changelog v0.0.28 2021-03-03 01:33:07 +00:00
2d69a1f946 bump version (changelog ignore) 2021-03-03 02:31:25 +01:00
2a5a4c16ea fix build 2021-03-03 02:31:02 +01:00
52ba14cd8f Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-03 00:10:48 +01:00
6222850453 There is no reason not to let use translations outside of keycloak 2021-03-03 00:10:41 +01:00
ec2b085855 Update changelog v0.0.27 2021-03-02 23:05:59 +00:00
305d5b7328 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-03 00:04:28 +01:00
deb4ddaed2 Bump version (changelog ignore) 2021-03-03 00:04:26 +01:00
379f052e6e Implement entrypoint 2021-03-03 00:04:06 +01:00
2f592ab38c Update changelog v0.0.26 2021-03-02 22:50:08 +00:00
43c412405a Login page implemented 2021-03-02 23:48:31 +01:00
7f6ab1d391 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-02 22:48:42 +01:00
e5e39d036d Implement login 2021-03-02 22:48:36 +01:00
6a93c6e894 remove unesseary log 2021-03-02 12:19:12 +01:00
069262c83d Update changelog v0.0.25 2021-03-02 11:19:07 +00:00
69f017df36 Fix build and reduce size 2021-03-02 12:17:24 +01:00
63ccb0609e Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-02 01:05:22 +01:00
292881532a Implement the template 2021-03-02 01:05:15 +01:00
d35eac9661 Update changelog v0.0.24 2021-03-01 20:00:22 +00:00
f92488b80d update 2021-03-01 20:58:50 +01:00
46c482d098 update 2021-03-01 20:56:05 +01:00
1bd05d22c3 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-01 20:52:19 +01:00
9e51ef90c0 update 2021-03-01 20:52:14 +01:00
b6fadad6cc Update changelog v0.0.23 2021-03-01 19:52:03 +00:00
193f66c843 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-01 20:50:38 +01:00
c3459106d6 update 2021-03-01 20:50:13 +01:00
931ce161d7 Update changelog v0.0.23 2021-03-01 19:49:56 +00:00
83ebf93f2a Update changelog v0.0.23 2021-03-01 19:49:37 +00:00
84b9909931 update 2021-03-01 20:48:30 +01:00
8d91ea6307 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-01 20:47:59 +01:00
fa99d8dd9a update 2021-03-01 20:47:53 +01:00
67243ecfc6 Update changelog v0.0.23 2021-03-01 19:47:44 +00:00
6cfefd7fbd Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-03-01 20:45:44 +01:00
f599df2bb7 update 2021-03-01 20:45:37 +01:00
57a0d9f10f Bump version (changelog ignore) 2021-03-01 13:56:18 +01:00
34902b54e4 Handle formatting in translation function 2021-03-01 13:55:58 +01:00
5db564f481 Update changelog v0.0.22 2021-02-28 19:23:45 +00:00
0924a42341 Merge branch 'develop' of https://github.com/garronej/keycloak-react-theming into develop 2021-02-28 20:22:24 +01:00
be295ff328 Split page messages 2021-02-28 20:22:18 +01:00
170 changed files with 26268 additions and 29314 deletions

6
.gitattributes vendored
View File

@ -1,3 +1,3 @@
/src/lib/i18n/messages.generated.ts linguist-vendored=false
/src/bin/download-sample-keycloak-themes.ts linguist-vendored=false
/src/bin/build-keycloak-theme/index.ts linguist-vendored=false
src/lib/i18n/generated_kcMessages/* linguist-documentation
src/bin/build-keycloak-theme/index.ts -linguist-detectable
src/bin/install-builtin-keycloak-themes.ts -linguist-detectable

144
.github/workflows/ci.yaml vendored Normal file
View File

@ -0,0 +1,144 @@
name: ci
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
test_formatting:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/setup-node@v2.1.3
- uses: bahmutov/npm-install@v1
- name: If this step fails run 'yarn format' then commit again.
run: |
PACKAGE_MANAGER=npm
if [ -f "./yarn.lock" ]; then
PACKAGE_MANAGER=yarn
fi
$PACKAGE_MANAGER run format:check
test:
runs-on: macos-10.15
needs: test_formatting
strategy:
matrix:
node: [ '15', '14', '13' ]
name: Test with Node v${{ matrix.node }}
steps:
- name: Tell if project is using npm or yarn
id: step1
uses: garronej/github_actions_toolkit@v2.2
with:
action_name: tell_if_project_uses_npm_or_yarn
- uses: actions/checkout@v2.3.4
- uses: actions/setup-node@v2.1.3
with:
node-version: ${{ matrix.node }}
- uses: bahmutov/npm-install@v1
- if: steps.step1.outputs.npm_or_yarn == 'yarn'
run: |
yarn build
yarn test
- if: steps.step1.outputs.npm_or_yarn == 'npm'
run: |
npm run build
npm test
check_if_version_upgraded:
name: Check if version upgrade
if: github.event_name == 'push'
runs-on: ubuntu-latest
needs: test
outputs:
from_version: ${{ steps.step1.outputs.from_version }}
to_version: ${{ steps.step1.outputs.to_version }}
is_upgraded_version: ${{steps.step1.outputs.is_upgraded_version }}
steps:
- uses: garronej/github_actions_toolkit@v2.2
id: step1
with:
action_name: is_package_json_version_upgraded
update_changelog:
runs-on: ubuntu-latest
needs: check_if_version_upgraded
if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true'
steps:
- uses: garronej/github_actions_toolkit@v2.4
with:
action_name: update_changelog
branch: ${{ github.ref }}
create_github_release:
runs-on: ubuntu-latest
needs:
- update_changelog
- check_if_version_upgraded
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.ref }}
- name: Build GitHub release body
id: step1
run: |
if [ "$FROM_VERSION" = "0.0.0" ]; then
echo "::set-output name=body::🚀"
else
echo "::set-output name=body::📋 [CHANGELOG](https://github.com/$GITHUB_REPOSITORY/blob/v$TO_VERSION/CHANGELOG.md)"
fi
env:
FROM_VERSION: ${{ needs.check_if_version_upgraded.outputs.from_version }}
TO_VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
- uses: garronej/action-gh-release@v0.2.0
with:
name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
target_commitish: ${{ github.ref }}
body: ${{ steps.step1.outputs.body }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_on_npm:
runs-on: macos-10.15
needs:
- update_changelog
- check_if_version_upgraded
steps:
- uses: actions/checkout@v2.3.4
with:
ref: ${{ github.ref }}
- uses: actions/setup-node@v2.1.3
with:
node-version: '15'
registry-url: https://registry.npmjs.org/
- uses: bahmutov/npm-install@v1
- run: |
PACKAGE_MANAGER=npm
if [ -f "./yarn.lock" ]; then
PACKAGE_MANAGER=yarn
fi
$PACKAGE_MANAGER run build
- run: npx -y -p denoify@0.6.5 denoify_enable_short_npm_import_path
env:
DRY_RUN: "0"
- name: Publishing on NPM
run: |
if [ "$(npm show . version)" = "$VERSION" ]; then
echo "This version is already published"
exit 0
fi
if [ "$NODE_AUTH_TOKEN" = "" ]; then
echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
false
fi
npm publish
env:
NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}

1
.gitignore vendored
View File

@ -43,3 +43,4 @@ jspm_packages
/dist
/sample_react_project/
/.yarn_home/

6
.prettierignore Normal file
View File

@ -0,0 +1,6 @@
node_modules/
/dist/
/CHANGELOG.md
/.yarn_home/
/src/test/apps/
/src/tools/types/

11
.prettierrc.json Normal file
View File

@ -0,0 +1,11 @@
{
"printWidth": 150,
"tabWidth": 4,
"useTabs": false,
"semi": true,
"singleQuote": false,
"quoteProps": "preserve",
"trailingComma": "all",
"bracketSpacing": true,
"arrowParens": "avoid"
}

View File

@ -1,3 +1,510 @@
## **2.5.0** (2021-10-12)
- register-user-profile.ftl tested working
- Make kcMessage more easily hackable
- fix useKcMessage
- Implement and type validators
- Remove syntax error in ftl and make it more directly debugable
- Support register-user-profile.ftl
## **2.4.0** (2021-10-08)
- #38: Implement messagesPerField existsError and get
## **2.3.0** (2021-10-07)
- #20: Support advancedMsg
## **2.2.0** (2021-10-07)
- Feat scrip: download-builtin-keycloak-theme for downloading any version of the builtin themes
- Use the latest version of keycloak for testing
- Test locally with 15.0.2 instead of 11.0.3
## **2.1.0** (2021-10-06)
- Support Hungarian and Danish (use Keycloak 15 language resources)
### **2.0.20** (2021-10-05)
- Update README.md
### **2.0.19** (2021-09-17)
- Fix kcContext type definitions
### **2.0.18** (2021-09-14)
### **2.0.17** (2021-09-14)
### **2.0.16** (2021-09-12)
- Add explaination about errors in logs
### **2.0.15** (2021-08-31)
- Update tss-react
### **2.0.14** (2021-08-20)
- Update tss-react
### **2.0.13** (2021-08-04)
- Merge pull request #28 from marcmrf/main
fix(mvn): scoped packages compatibility
- fix(mvn): scoped packages compatibility
### **2.0.12** (2021-07-28)
- Merge pull request #27 from jchn-codes/patch-1
add maven to requirements
- add maven to requirements
- Add #bluehats in the keyworks
### **2.0.11** (2021-07-21)
- Spaces in file path #22
- uptdate dependnecies
- Inport specific powerhooks files to reduce bundle size
### **2.0.10** (2021-07-16)
- Update dependencies
### **2.0.9** (2021-07-14)
- Fix #21
### **2.0.8** (2021-07-12)
- Fix previous release
- #20: Add def for clientId and name on kcContext.client
### **2.0.6** (2021-07-08)
- Merge pull request #18 from asashay/add-custom-props-to-theme-properties
Add possibility to add custom properties to theme.properties file
- add possibility to add custom properties to theme.properties file
### **2.0.5** (2021-07-05)
- Fix broken url for big stylesheet #16
### **2.0.4** (2021-07-03)
- Fix: #7
### **2.0.3** (2021-06-30)
- Escape double quote in ftl to js conversion #15
- Update readme
### **2.0.2** (2021-06-28)
- Updagte README for implementing non incuded pages
### **2.0.1** (2021-06-28)
- Update documentation for v2
# **2.0.0** (2021-06-28)
- Fix last bugs before relasing v2
- Implement a mechanism to overload kcContext
- Give the option in template to pull the default assets or not
- Enable possiblity to support custom pages (without forking keycloakify)
- Implement a getter for kcContext
- Update README.md
# **2.0.0** (2021-06-28)
- Fix last bugs before relasing v2
- Implement a mechanism to overload kcContext
- Give the option in template to pull the default assets or not
- Enable possiblity to support custom pages (without forking keycloakify)
- Implement a getter for kcContext
- Update README.md
### **1.2.1** (2021-06-22)
- Remove unessesary log
## **1.2.0** (2021-06-22)
- Generate kcContext automatically :rocket:
### **1.1.6** (2021-06-21)
- Fix: Alert messages sometimes includes HTML that is not rendered
- Update dist
### **1.1.5** (2021-06-15)
- #11: Provide socials in the register
### **1.1.4** (2021-06-15)
- Merge pull request #12 from InseeFrLab/email-typo
Fix typo on email
- Fix typo on email
### **1.1.3** (2021-06-14)
- Add missing key in Login for providers
### **1.1.2** (2021-06-14)
### **1.1.1** (2021-06-14)
## **1.1.0** (2021-06-14)
- Add login-idp-link-confirm.ftl
- Fix login-update-profile.ftl
- Add login-update-profile.ftl page
- Fix default background bug
- Remove unused 'markdown' dependency
- Fix warning related to powerhooks_useGlobalState_kcLanguageTag
- Update README.md
### **1.0.4** (2021-05-28)
- Instructions for custom themes with custom components
### **1.0.3** (2021-05-23)
- Instuction about how to integrate with non CRA projects
- Add mention to awesome list
### **1.0.2** (2021-05-01)
### **1.0.1** (2021-05-01)
- Fix: LoginOtp (and not otc)
# **1.0.0** (2021-05-01)
- #4: Guide for implementing a missing page
- Support OTP #4
### **0.4.4** (2021-04-29)
- Fix previous release
### **0.4.3** (2021-04-29)
- Add infos about the plugin that defines authorizedMailDomains
### **0.4.2** (2021-04-29)
- Client side validation of allowed email domains
- Support email whitlisting
- Restore kickstart video in the readme
- Update README.md
- Update README.md
- Important readme update
### **0.4.1** (2021-04-11)
- Quietly re-introduce --external-assets
- Give example of customization
## **0.4.0** (2021-04-09)
- Acual support of Therms of services
### **0.3.24** (2021-04-08)
- Add missing dependency: markdown
### **0.3.23** (2021-04-08)
- Allow to lazily load therms
### **0.3.22** (2021-04-08)
- update powerhooks
- Support terms and condition
- Fix info.ftl
- For useKcMessage we prefer returning callbacks with a changing references
### **0.3.21** (2021-04-04)
- Update powerhooks
### **0.3.20** (2021-04-01)
- Always catch freemarker errors
### **0.3.19** (2021-04-01)
- Fix previous release
### **0.3.18** (2021-04-01)
- Fix error.ftt, Adopt best effort strategy to convert ftl values into JS
### **0.3.17** (2021-03-29)
- Use push instead of replace in keycloak-js adapter to enable going back
### **0.3.15** (2021-03-28)
- Remove all reference to --external-assets, broken feature
### **0.3.14** (2021-03-28)
- Fix standalone mode: imports from js
### **0.3.13** (2021-03-26)
### **0.3.12** (2021-03-26)
- Fix mocksContext
### **0.3.11** (2021-03-26)
- Fix previous build, improve README
### **0.3.10** (2021-03-26)
- Handle <style> tag, improve documentation
### **0.3.9** (2021-03-25)
- Update readme
- Document --external-assets
- Update README.md
- Update README.md
- Update README.md
### **0.3.8** (2021-03-22)
- Make standalone mode the default
### **0.3.7** (2021-03-22)
- (test) external asset mode by default
### **0.3.6** (2021-03-22)
- Fix previous release
### **0.3.5** (2021-03-22)
- support homepage with urlPath
### **0.3.4** (2021-03-22)
- Bugfix: Import assets from CSS
### **0.3.3** (2021-03-22)
- Fix submit not receving correct text
### **0.3.2** (2021-03-21)
- Fix broken previous release
### **0.3.1** (2021-03-21)
- kcHeaderClass can be updated after initial mount
## **0.3.0** (2021-03-20)
- Bump version
- Feat: Cary over states using URL search params
- Bugfix: with kcHtmlClass
### **0.2.10** (2021-03-19)
- Remove dependency to denoify
### **0.2.9** (2021-03-19)
- Update deps and CI workflow
### **0.2.8** (2021-03-19)
- Bugfix: keycloak_build that grow and grow in size
- Add disclaimer about maitainment strategy
- Add a note for tested version support
### **0.2.7** (2021-03-13)
- Bump version
- Update README.md
- Update README.md
### **0.2.6** (2021-03-10)
- Fix generated gitignore
### **0.2.5** (2021-03-10)
- Fix generated .gitignore
### **0.2.4** (2021-03-10)
- Update README.md
### **0.2.3** (2021-03-09)
- fix gitignore generation
### **0.2.2** (2021-03-08)
- Add table of content
- Update README.md
- Update README.md
## **0.2.1** (2021-03-08)
- Update ci.yaml
- Update readme
- Update readme
- update deps
- Update readme
- Add all mocks for testing
- many small fixes
### **0.1.6** (2021-03-07)
- Fix Turkish
### **0.1.5** (2021-03-07)
- Fix getKcLanguageLabel
### **0.1.4** (2021-03-07)
### **0.1.3** (2021-03-07)
- Implement LoginVerifyEmail
- Implement login-reset-password.ftl
### **0.1.2** (2021-03-07)
- Fix build
- Fix build
### **0.1.1** (2021-03-06)
- Implement Error page
- rename pageBasename by pageId
- Implement reactive programing for language switching
- Add Info page, refactor
## **0.1.0** (2021-03-05)
- Rename keycloakify
### **0.0.33** (2021-03-05)
- Fix syncronization with non react pages
### **0.0.32** (2021-03-05)
- bump version
- Add log to tell when we are using react
- Fix missing parentesis
### **0.0.31** (2021-03-05)
- Fix typo
- Fix register page 500
### **0.0.30** (2021-03-05)
- Edit language statistique
### **0.0.30** (2021-03-05)
- avoid escaping urls
- Use default value instead of value
- Fix double single quote problem in messages
- Fix typo
- Fix non editable username
- Fix some bugs
- Fix Object.deepAssign
- Make the dongle to download smaller
- Split kcContext among pages
- Implement register
### **0.0.29** (2021-03-04)
- Fix build
- Fix i18n
- Login appear to be working now
- closer but not there yet
### **0.0.28** (2021-03-03)
- fix build
- There is no reason not to let use translations outside of keycloak
### **0.0.27** (2021-03-02)
- Implement entrypoint
### **0.0.26** (2021-03-02)
- Login page implemented
- Implement login
- remove unesseary log
### **0.0.25** (2021-03-02)
- Fix build and reduce size
- Implement the template
### **0.0.24** (2021-03-01)
- update
- update
- update
### **0.0.23** (2021-03-01)
- update
### **0.0.23** (2021-03-01)
- update
- update
### **0.0.23** (2021-03-01)
- update
- update
### **0.0.23** (2021-03-01)
- update
- Handle formatting in translation function
### **0.0.22** (2021-02-28)
- Split page messages
### **0.0.21** (2021-02-28)
- Restore yarn file

460
README.md
View File

@ -2,98 +2,430 @@
<img src="https://user-images.githubusercontent.com/6702424/109387840-eba11f80-7903-11eb-9050-db1dad883f78.png">
</p>
<p align="center">
<i>🔏 Keycloak theme generator for Reacts app 🔏</i>
<i>🔏 Create Keycloak themes using React 🔏</i>
<br>
<br>
<img src="https://github.com/garronej/keycloak-react-theming/workflows/ci/badge.svg?branch=develop">
<img src="https://img.shields.io/bundlephobia/minzip/keycloak-react-theming">
<img src="https://img.shields.io/npm/dw/keycloak-react-theming">
<img src="https://img.shields.io/npm/l/keycloak-react-theming">
<img src="https://github.com/garronej/keycloakify/workflows/ci/badge.svg?branch=develop">
<img src="https://img.shields.io/bundlephobia/minzip/keycloakify">
<img src="https://img.shields.io/npm/dw/keycloakify">
<img src="https://img.shields.io/npm/l/keycloakify">
<img src="https://camo.githubusercontent.com/0f9fcc0ac1b8617ad4989364f60f78b2d6b32985ad6a508f215f14d8f897b8d3/68747470733a2f2f62616467656e2e6e65742f62616467652f547970655363726970742f7374726963742532302546302539462539322541412f626c7565">
<a href="https://github.com/thomasdarimont/awesome-keycloak">
<img src="https://awesome.re/mentioned-badge.svg"/>
</a>
</p>
<p align="center">
<i>Ultimately this build tool generates a Keycloak theme</i>
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
</p>
**NEW in v2**
**!!! This module is under active developement. It is not usable yet!!!**
- It's now possible to implement custom `.ftl` pages.
- Support for Keycloak plugins that introduce non standard ftl values.
(Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) that define `authorizedMailDomains` in `register.ftl`).
# Motivations
The problem:
Keycloak provides [theme support](https://www.keycloak.org/docs/latest/server_development/#_themes) for web pages. This allows customizing the look and feel of end-user facing pages so they can be integrated with your applications.
It involves, however, a lot of raw JS/CSS/[FTL]() hacking, and bundling the theme is not exactly straightforward.
![keycloak_before](https://user-images.githubusercontent.com/6702424/108838381-dbbbcf80-75d3-11eb-8ae8-db41563ef9db.gif)
Beyond that, if you use Keycloak for a specific app you want your login page to be tightly integrated with it.
Ideally, you don't want the user to notice when he is being redirected away.
When we redirected to Keycloak the user suffers from a harsh context switch.
On je login/register pages the language is set back to default and the theme is different that the one on the app.
Trying to reproduce the look and feel of a specific app in another stack is not an easy task not to mention
the cheer amount of maintenance that it involves.
Keycloak does offer a way to customize theses pages but it requires a lot of raw HTML/CSS hacking
to reproduce the look and feel of a specific app. Not mentioning the maintenance cost of such an endeavour.
<p align="center">
<i>Without keycloakify, users suffers from a harsh context switch, no fronted form pre-validation</i><br>
<img src="https://user-images.githubusercontent.com/6702424/134997335-a28b4a57-0884-47ec-9341-a0e49f835c4d.gif">
</p>
Wouldn't it be great if we could just design the login and register pages as if they where part of our app while
still letting Keycloak handle the heavy lifting of actually authenticating the users?
Wouldn't it be great if we could just design the login and register pages as if they were part of our app?
Here is `keycloakify` for you 🍸
Here is `yarn add keycloak-react-theming` for you 🍸
<p align="center">
<i> <a href="https://datalab.sspcloud.fr">With keycloakify:</a> </i>
<br>
<img src="https://user-images.githubusercontent.com/6702424/114332075-c5e37900-9b45-11eb-910b-48a05b3d90d9.gif">
</p>
TODO: Insert video after.
**TL;DR**: [Here](https://github.com/garronej/keycloakify-demo-app) is a Hello World React project with Keycloakify set up.
If you already have a Keycloak custom theme, it can be easily ported to Keycloakify.
---
- [Motivations](#motivations)
- [Requirements](#requirements)
- [My framework doesnt seem to be supported, what can I do?](#my-framework-doesnt-seem-to-be-supported-what-can-i-do)
- [How to use](#how-to-use)
- [Setting up the build tool](#setting-up-the-build-tool)
- [Changing just the look of the default Keycloak theme](#changing-just-the-look-of-the-default-keycloak-theme)
- [Advanced pages configuration](#advanced-pages-configuration)
- [Hot reload](#hot-reload)
- [Enable loading in a blink of an eye of login pages ⚡ (--external-assets)](#enable-loading-in-a-blink-of-an-eye-of-login-pages----external-assets)
- [Support for Terms and conditions](#support-for-terms-and-conditions)
- [Some pages still have the default theme. Why?](#some-pages-still-have-the-default-theme-why)
- [GitHub Actions](#github-actions)
- [Limitations](#limitations)
- [`process.env.PUBLIC_URL` not supported.](#processenvpublic_url-not-supported)
- [`@font-face` importing fonts from the `src/` dir](#font-face-importing-fonts-from-thesrc-dir)
- [Example of setup that **won't** work](#example-of-setup-that-wont-work)
- [Possible workarounds](#possible-workarounds)
- [Implement context persistence (optional)](#implement-context-persistence-optional)
- [Kickstart video](#kickstart-video)
- [About the errors related to `objectToJson` in Keycloak logs.](#about-the-errors-related-to-objecttojson-in-keycloak-logs)
- [Email domain whitelist](#email-domain-whitelist)
# Requirements
Tested with the following Keycloak versions:
- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore)
- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore)
- Tests ongoing with [14.0.0](https://hub.docker.com/layers/jboss/keycloak/14.0.0/images/sha256-ca713e87ad163da71ab329010de2464a41ff030a25ae0aef15c1c290252f3d7f?context=explore)
This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get
(before you customize it) will always be the ones of Keycloak v11.
This tool assumes you are bundling your app with Webpack (tested with 4.44.2) .
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
and a `build/static/` directory generated by webpack.
For more information see [this issue](https://github.com/InseeFrLab/keycloakify/issues/5#issuecomment-832296432)
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3)
- `mvn` ([Maven](https://maven.apache.org/)), `rm`, `mkdir`, `wget`, `unzip` are assumed to be available.
- `docker` must be up and running when running `yarn keycloak`.
On Windows you'll have to use [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10).
## My framework doesnt seem to be supported, what can I do?
Currently Keycloakify is only compatible with `create-react-app` apps.
It doesnt mean that you can't use Keycloakify if you are using Next.js, Express or any other
framework that involves SSR but your Keycloak theme will need to be a standalone project.
Find specific instructions about how to get started [**here**](https://github.com/garronej/keycloakify-demo-app#keycloak-theme-only).
To share your styles between your main app and your login pages you will need to externalize your design system by making it a
separate module. Checkout [ts_ci](https://github.com/garronej/ts_ci), it can help with that.
# How to use
## Setting up the build tool
Add `keycloak-react-theming` to the dev dependencies of your project `npm install --save-dev keycloak-react-theming` or `yarn add --dev keycloak-react-theming`
then configure your `package.json` build's script to build the keycloak's theme by adding `&& build-keycloak-theme`.
Typically you will get:
`package.json`:
```json
"devDependencies": {
"keycloak-react-theming": "^0.0.10"
},
"scripts": {
"build": "react-scripts build && build-keycloak-theme"
},
```bash
yarn add keycloakify
```
Then build your app with `yarn run build` or `npm run build`, you will be provided with instructions
about how to load the theme into Keycloak.
[`package.json`](https://github.com/garronej/keycloakify-demo-app/blob/main/package.json)
```json
"scripts": {
"keycloak": "yarn build && build-keycloak-theme",
}
```
```bash
yarn keycloak # generates keycloak-theme.jar
```
On the console will be printed all the instructions about how to load the generated theme in Keycloak
### Changing just the look of the default Keycloak theme
The first approach is to only customize the style of the default Keycloak login by providing
your own class names.
If you have created a new React project specifically to create a Keycloak theme and nothing else then
your index should look something like:
`src/index.tsx`
```tsx
import { App } from "./<wherever>/App";
import {
KcApp,
defaultKcProps,
getKcContext
} from "keycloakify";
import { css } from "tss-react/@emotion/css";
const { kcContext } = getKcContext();
const myClassName = css({ "color": "red" });
reactDom.render(
<KcApp
kcContext={kcContext}
{...{
...defaultKcProps,
"kcHeaderWrapperClass": myClassName
}}
/>
document.getElementById("root")
);
```
If you share a unique project for your app and the Keycloak theme, your index should look
more like this:
`src/index.tsx`
```tsx
import { App } from "./<wherever>/App";
import { KcApp, defaultKcProps, getKcContext } from "keycloakify";
import { css } from "tss-react/@emotion/css";
const { kcContext } = getKcContext();
const myClassName = css({ "color": "red" });
reactDom.render(
// Unless the app is currently being served by Keycloak
// kcContext is undefined.
kcContext !== undefined ? (
<KcApp
kcContext={kcContext}
{...{
...defaultKcProps,
"kcHeaderWrapperClass": myClassName,
}}
/>
) : (
<App />
), // Your actual app
document.getElementById("root"),
);
```
<p align="center">
<i>result:</i></br>
<img src="https://user-images.githubusercontent.com/6702424/114326299-6892fc00-9b34-11eb-8d75-85696e55458f.png">
</p>
Example of a customization using only CSS: [here](https://github.com/InseeFrLab/onyxia-ui/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/components/KcApp.tsx)
(the [index.tsx](https://github.com/InseeFrLab/onyxia-ui/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/index.tsx#L89-L94) )
and the result you can expect:
<p align="center">
<i> <a href="https://datalab.sspcloud.fr">Customization using only CSS:</a> </i>
<br>
<img src="https://github.com/InseeFrLab/keycloakify/releases/download/v0.3.8/keycloakify_after.gif">
</p>
### Advanced pages configuration
If you want to go beyond only customizing the CSS you can re-implement some of the
pages or even add new ones.
If you want to go this way checkout the demo setup provided [here](https://github.com/garronej/keycloakify-demo-app/tree/look_and_feel).
If you prefer a real life example you can checkout [onyxia-web's source](https://github.com/InseeFrLab/onyxia-web/tree/main/src/app/components/KcApp).
The web app is in production [here](https://datalab.sspcloud.fr).
Main takeaways are:
- You must declare your custom pages in the package.json. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/package.json#L17-L22)
- (TS only) You must declare theses page in the type argument of the getter
function for the `kcContext` in order to have the correct typings. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L16-L21)
- (TS only) If you use Keycloak plugins that defines non standard `.ftl` values
(Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting)
that define `authorizedMailDomains` in `register.ftl`) you should
declare theses value to get the type. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L6-L13)
- You should provide sample data for all the non standard value if you want to be able
to debug the page outside of keycloak. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L28-L43)
WARNING: If you chose to go this way use:
```json
"dependencies": {
"keycloakify": "~X.Y.Z"
}
```
in your `package.json` instead of `^X.Y.Z`. A minor update of Keycloakify might break your app.
### Hot reload
Rebuild the theme each time you make a change to see the result is not practical.
If you want to test your login screens outside of Keycloak you can mock a given `kcContext`:
```tsx
import {
KcApp,
defaultKcProps,
getKcContext
} from "keycloakify";
const { kcContext } = getKcContext({
"mockPageId": "login.ftl"
});
reactDom.render(
<KcApp
kcContext={kcContextMocks.kcLoginContext}
{...defaultKcProps}
/>
document.getElementById("root")
);
```
Then `yarn start`, you will see your login page.
Checkout [this concrete example](https://github.com/garronej/keycloakify-demo-app/blob/main/src/index.tsx)
## Enable loading in a blink of an eye of login pages ⚡ (--external-assets)
By default the theme generated is standalone. Meaning that when your users
reach the login pages all scripts, images and stylesheet are downloaded from the Keycloak server.
If you are specifically building a theme to integrate with an app or a website that allows users
to first browse unauthenticated before logging in, you will get a significant
performance boost if you jump through those hoops:
- Provide the url of your app in the `homepage` field of package.json. [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/package.json#L2)
- Build the theme using `npx build-keycloak-theme --external-assets` [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/.github/workflows/ci.yaml#L21)
- Enable [long-term assets caching](https://create-react-app.dev/docs/production-build/#static-file-caching) on the server hosting your app.
- Make sure not to build your app and the keycloak theme separately
and remember to update the Keycloak theme every time you update your app.
- Be mindful that if your app is down your login pages are down as well.
Checkout a complete setup [here](https://github.com/garronej/keycloakify-demo-app#about-keycloakify)
# Support for Terms and conditions
[Many organizations have a requirement that when a new user logs in for the first time, they need to agree to the terms and conditions of the website.](https://www.keycloak.org/docs/4.8/server_admin/#terms-and-conditions).
First you need to enable the required action on the Keycloak server admin console:
![image](https://user-images.githubusercontent.com/6702424/114280501-dad2e600-9a39-11eb-9c39-a225572dd38a.png)
Then to load your own therms of services using [like this](https://github.com/garronej/keycloakify-demo-app/blob/8168c928a66605f2464f9bd28a4dc85fb0a231f9/src/index.tsx#L42-L66).
# Some pages still have the default theme. Why?
This project only support out of the box the most common user facing pages of Keycloak login.
[Here](https://user-images.githubusercontent.com/6702424/116787906-227fe700-aaa7-11eb-92ee-22e7673717c2.png) is the complete list of pages (you get them after running `yarn test`)
and [here](https://github.com/InseeFrLab/keycloakify/tree/main/src/lib/components) are the pages currently implemented by this module.
If you need to customize pages that are not supported yet or if you need to implement some non standard `.ftl` pages please refer to [Advanced pages configuration](#advanced-pages-configuration).
# GitHub Actions
![image](https://user-images.githubusercontent.com/6702424/114286938-47aea600-9a63-11eb-936e-17159e8826e8.png)
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate
the building and publishing of the theme (the .jar file).
# Limitations
## `process.env.PUBLIC_URL` not supported.
You won't be able to [import things from your public directory **in your JavaScript code**](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system).
(This isn't recommended anyway).
## `@font-face` importing fonts from the `src/` dir
If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-)
this limitation doesn't apply, you can import fonts however you see fit.
### Example of setup that **won't** work
- We have a `fonts/` directory in `src/`
- We import the font like this [`src: url("/fonts/my-font.woff2") format("woff2");`](https://github.com/garronej/keycloakify-demo-app/blob/07d54a3012ef354ee12b1374c6f7ad1cb125d56b/src/fonts.scss#L4) in a `.scss` a file.
### Possible workarounds
- Use [`--external-assets`](#enable-loading-in-a-blink-of-a-eye-of-login-pages-).
- If it is possible, use Google Fonts or any other font provider.
- If you want to host your font recommended approach is to move your fonts into the `public`
directory and to place your `@font-face` statements in the `public/index.html`.
Example [here](https://github.com/InseeFrLab/onyxia-ui/blob/0e3a04610cfe872ca71dad59e05ced8f785dee4b/public/index.html#L6-L51).
- You can also [use non relative url](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/src/fonts.scss#L16) but don't forget [`Access-Control-Allow-Origin`](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/nginx.conf#L17-L19).
# Implement context persistence (optional)
If, before logging in, a user has selected a specific language
you don't want it to be reset to default when the user gets redirected to
the login or register pages.
Same goes for the dark mode, you don't want, if the user had it enabled
to show the login page with light themes.
The problem is that you are probably using `localStorage` to persist theses values across
reload but, as the Keycloak pages are not served on the same domain that the rest of your
app you won't be able to carry over states using `localStorage`.
The only reliable solution is to inject parameters into the URL before
redirecting to Keycloak. We integrate with
[`keycloak-js`](https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/javascript-adapter.adoc),
by providing you a way to tell `keycloak-js` that you would like to inject
some search parameters before redirecting.
The method also works with [`@react-keycloak/web`](https://www.npmjs.com/package/@react-keycloak/web) (use the `initOptions`).
You can implement your own mechanism to pass the states in the URL and
restore it on the other side but we recommend using `powerhooks/useGlobalState`
from the library [`powerhooks`](https://www.powerhooks.dev) that provide an elegant
way to handle states such as `isDarkModeEnabled` or `selectedLanguage`.
Let's modify [the example](https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/javascript-adapter.adoc) from the official `keycloak-js` documentation to
enables the states of `useGlobalStates` to be injected in the URL before redirecting.
Note that the states are automatically restored on the other side by `powerhooks`
```typescript
import keycloak_js from "keycloak-js";
import { injectGlobalStatesInSearchParams } from "powerhooks/useGlobalState";
import { createKeycloakAdapter } from "keycloakify";
//...
const keycloakInstance = keycloak_js({
"url": "http://keycloak-server/auth",
"realm": "myrealm",
"clientId": "myapp",
});
keycloakInstance.init({
"onLoad": "check-sso",
"silentCheckSsoRedirectUri": window.location.origin + "/silent-check-sso.html",
"adapter": createKeycloakAdapter({
"transformUrlBeforeRedirect": injectGlobalStatesInSearchParams,
keycloakInstance,
}),
});
//...
```
If you really want to go the extra miles and avoid having the white
flash of the blank html before the js bundle have been evaluated
[here is a snippet](https://github.com/InseeFrLab/onyxia-ui/blob/a77eb502870cfe6878edd0d956c646d28746d053/public/index.html#L5-L54) that you can place in your `public/index.html` if you are using `powerhooks/useGlobalState`.
# Kickstart video
_NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded_
[![kickstart_video](https://user-images.githubusercontent.com/6702424/108877866-f146ee80-75ff-11eb-8120-003b3c5f6dd8.png)](https://youtu.be/xTz0Rj7i2v8)
## Developing your login and register pages in your React app
# About the errors related to `objectToJson` in Keycloak logs.
TODO
The logs of your keycloak server will always show this kind of errors every time a client request a page:
# How to implement context persistance
```log
FTL stack trace ("~" means nesting-related):
- Failed at: #local value = object[key] [in template "login.ftl" in macro "objectToJson" at line 70, column 21]
- Reached through: @compress [in template "login.ftl" in macro "objectToJson" at line 36, column 5]
- Reached through: @objectToJson object=value depth=(dep... [in template "login.ftl" in macro "objectToJson" at line 81, column 27]
- Reached through: @compress [in template "login.ftl" in macro "objectToJson" at line 36, column 5]
- Reached through: @objectToJson object=(.data_model) de... [in template "login.ftl" at line 163, column 43]
```
If you want dark mode preference, language and others users preferences your can do so
very easily by using [`powerhooks/useGlobalState`](https://github.com/garronej/powerhooks)
Theses are expected and can be safely ignored.
WARNING: `powerhooks` is still a work in progress.
To [converts the `.ftl` values into a JavaScript object](https://github.com/InseeFrLab/keycloakify/blob/main/src/bin/build-keycloak-theme/generateFtl/common.ftl)
without making assumptions on the `.data_model` we have to do things that throws.
It's all-right though because every statement that can fail is inside an `<#attempt><#recorver>` block but it results in errors being printed to the logs.
# REQUIREMENTS
# Email domain whitelist
This tools assumes you are bundling your app with Webpack (tested with 4.44.2) .
It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file
and a `static/` directory generated by webpack.
**All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3=)
- For building the theme: `mvn` (Maven) must be installed
- For development, (testing the theme in a local container ): `rm`, `mkdir`, `wget`, `unzip` are assumed to be available
and `docker` up and running.
NOTE: This build tool has only be tested on MacOS.
# API Reference
## The build tool
Part of the lib that runs with node, at build time.
- `npx build-keycloak-theme`: Builds the theme, the CWD is assumed to be the root of your react project.
- `npx download-sample-keycloak-themes`: Downloads the keycloak default themes (for development purposes)
## The fronted lib ( imported into your react app )
Part of the lib that you import in your react project and runs on the browser.
**TODO**
If you want to restrict the emails domain that can register, you can use [this plugin](https://github.com/micedre/keycloak-mail-whitelisting)
and `kcRegisterContext["authorizedMailDomains"]` to validate on.

View File

@ -1,6 +0,0 @@
export declare const containerLaunchScriptBasename = "start_keycloak_testing_container.sh";
/** Files for being able to run a hot reload keycloak container */
export declare function generateDebugFiles(params: {
packageJsonName: string;
keycloakThemeBuildingDirPath: string;
}): void;

View File

@ -1,68 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateDebugFiles = exports.containerLaunchScriptBasename = void 0;
var fs = __importStar(require("fs"));
var path_1 = require("path");
exports.containerLaunchScriptBasename = "start_keycloak_testing_container.sh";
/** Files for being able to run a hot reload keycloak container */
function generateDebugFiles(params) {
var packageJsonName = params.packageJsonName, keycloakThemeBuildingDirPath = params.keycloakThemeBuildingDirPath;
fs.writeFileSync(path_1.join(keycloakThemeBuildingDirPath, "Dockerfile"), Buffer.from([
"FROM jboss/keycloak:11.0.3",
"",
"USER root",
"",
"WORKDIR /",
"",
"ADD configuration /opt/jboss/keycloak/standalone/configuration/",
"",
'ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ]',
].join("\n"), "utf8"));
var dockerImage = packageJsonName + "/keycloak-hot-reload";
var containerName = "keycloak-testing-container";
fs.writeFileSync(path_1.join(keycloakThemeBuildingDirPath, exports.containerLaunchScriptBasename), Buffer.from([
"#!/bin/bash",
"",
"cd " + keycloakThemeBuildingDirPath,
"",
"docker rm " + containerName + " || true",
"",
"docker build . -t " + dockerImage,
"",
"docker run \\",
" -p 8080:8080 \\",
"\t--name " + containerName + " \\",
" -e KEYCLOAK_USER=admin \\",
" -e KEYCLOAK_PASSWORD=admin \\",
"\t-v " + path_1.join(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", packageJsonName) + ":/opt/jboss/keycloak/themes/" + packageJsonName + ":rw \\",
"\t-it " + dockerImage + ":latest",
""
].join("\n"), "utf8"), { "mode": 493 });
var standaloneHaFilePath = path_1.join(keycloakThemeBuildingDirPath, "configuration", "standalone-ha.xml");
try {
fs.mkdirSync(path_1.dirname(standaloneHaFilePath));
}
catch (_a) { }
fs.writeFileSync(standaloneHaFilePath, fs.readFileSync(path_1.join(__dirname, path_1.basename(standaloneHaFilePath))));
}
exports.generateDebugFiles = generateDebugFiles;
//# sourceMappingURL=index.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/bin/build-keycloak-theme/generateDebugFiles/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AACA,qCAAyB;AACzB,6BAA0F;AAE7E,QAAA,6BAA6B,GAAG,qCAAqC,CAAC;AAEnF,kEAAkE;AAClE,SAAgB,kBAAkB,CAC9B,MAGC;IAGO,IAAA,eAAe,GAAmC,MAAM,gBAAzC,EAAE,4BAA4B,GAAK,MAAM,6BAAX,CAAY;IAEjE,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,4BAA4B,EAAE,YAAY,CAAC,EACpD,MAAM,CAAC,IAAI,CACP;QACI,4BAA4B;QAC5B,EAAE;QACF,WAAW;QACX,EAAE;QACF,WAAW;QACX,EAAE;QACF,iEAAiE;QACjE,EAAE;QACF,wDAAwD;KAC3D,CAAC,IAAI,CAAC,IAAI,CAAC,EACZ,MAAM,CACT,CACJ,CAAC;IAEF,IAAM,WAAW,GAAM,eAAe,yBAAsB,CAAC;IAC7D,IAAM,aAAa,GAAG,4BAA4B,CAAC;IAEnD,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,4BAA4B,EAAE,qCAA6B,CAAC,EACrE,MAAM,CAAC,IAAI,CACP;QACI,aAAa;QACb,EAAE;QACF,QAAM,4BAA8B;QACpC,EAAE;QACF,eAAa,aAAa,aAAU;QACpC,EAAE;QACF,uBAAqB,WAAa;QAClC,EAAE;QACF,eAAe;QACf,kBAAkB;QAClB,cAAW,aAAa,QAAK;QAC7B,4BAA4B;QAC5B,gCAAgC;QAChC,UAAO,WAAQ,CAAC,4BAA4B,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,eAAe,CAAC,oCACpE,eAAe,WAAQ;QACtD,WAAQ,WAAW,YAAS;QAC5B,EAAE;KACL,CAAC,IAAI,CAAC,IAAI,CAAC,EACZ,MAAM,CACT,EACD,EAAE,MAAM,EAAE,GAAK,EAAE,CACpB,CAAC;IAEF,IAAM,oBAAoB,GAAG,WAAQ,CAAC,4BAA4B,EAAE,eAAe,EAAE,mBAAmB,CAAC,CAAC;IAE1G,IAAI;QAAE,EAAE,CAAC,SAAS,CAAC,cAAW,CAAC,oBAAoB,CAAC,CAAC,CAAC;KAAE;IAAC,WAAM,GAAG;IAElE,EAAE,CAAC,aAAa,CACZ,oBAAoB,EACpB,EAAE,CAAC,YAAY,CAAC,WAAQ,CAAC,SAAS,EAAE,eAAY,CAAC,oBAAoB,CAAC,CAAC,CAAC,CAC3E,CAAC;AAEN,CAAC;AAlED,gDAkEC"}

View File

@ -1,113 +0,0 @@
<script>const _=
{
"url": {
"loginAction": "${url.loginAction}",
"resourcesPath": "${url.resourcesPath}",
"resourcesCommonPath": "${url.resourcesCommonPath}",
"loginRestartFlowUrl": "${url.loginRestartFlowUrl}"
},
"realm": {
"displayName": "${realm.displayName!''}" || undefined,
"displayNameHtml": "${realm.displayNameHtml!''}" || undefined,
"internationalizationEnabled": ${realm.internationalizationEnabled?c}
},
"locale": (function (){
<#if realm.internationalizationEnabled>
return {
"supported": (function(){
<#if realm.internationalizationEnabled>
var out= [];
<#list locale.supported as lng>
out.push({
"url": "${lng.url}",
"label": "${lng.label}",
"languageTag": "${lng.languageTag}"
});
</#list>
return out;
</#if>
return undefined;
})(),
"current": "${locale.current}"
};
</#if>
return undefined;
})(),
"auth": (function (){
<#if auth?has_content>
var out= {
"showUsername": ${auth.showUsername()?c},
"showResetCredentials": ${auth.showResetCredentials()?c},
"showTryAnotherWayLink": ${auth.showTryAnotherWayLink()?c}
};
<#if auth.showUsername() && !auth.showResetCredentials()>
Object.assign(
out,
{
"attemptedUsername": "${auth.attemptedUsername}"
}
);
</#if>
return out;
</#if>
return undefined;
})(),
"scripts": (function(){
var out = [];
<#if scripts??>
<#list scripts as script>
out.push("${script}");
</#list>
</#if>
return out;
})(),
"message": (function (){
<#if message?has_content>
return { 
"type": "${message.type}",
"summary": "${kcSanitize(message.summary)?no_esc}"
};
</#if>
return undefined;
})(),
"isAppInitiatedAction": (function (){
<#if isAppInitiatedAction??>
return true;
</#if>
return false;
})()
}
</script>

View File

@ -1,11 +0,0 @@
export declare function generateFtlFilesCodeFactory(params: {
ftlValuesGlobalName: string;
cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string;
}): {
generateFtlFilesCode: (params: {
pageBasename: "login.ftl" | "register.ftl";
}) => {
ftlCode: string;
};
};

View File

@ -1,115 +0,0 @@
"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateFtlFilesCodeFactory = void 0;
var cheerio_1 = __importDefault(require("cheerio"));
var replaceImportFromStatic_1 = require("../replaceImportFromStatic");
var fs_1 = __importDefault(require("fs"));
var path_1 = require("path");
var objectKeys_1 = require("evt/tools/typeSafety/objectKeys");
function generateFtlFilesCodeFactory(params) {
var ftlValuesGlobalName = params.ftlValuesGlobalName, cssGlobalsToDefine = params.cssGlobalsToDefine, indexHtmlCode = params.indexHtmlCode;
var $ = cheerio_1.default.load(indexHtmlCode);
$("script:not([src])").each(function () {
var _a = [];
for (var _i = 0; _i < arguments.length; _i++) {
_a[_i] = arguments[_i];
}
var _b = __read(_a, 2), element = _b[1];
var fixedJsCode = replaceImportFromStatic_1.replaceImportFromStaticInJsCode({
ftlValuesGlobalName: ftlValuesGlobalName,
"jsCode": $(element).html()
}).fixedJsCode;
$(element).text(fixedJsCode);
});
[
["link", "href"],
["script", "src"],
].forEach(function (_a) {
var _b = __read(_a, 2), selector = _b[0], attrName = _b[1];
return $(selector).each(function () {
var _a = [];
for (var _i = 0; _i < arguments.length; _i++) {
_a[_i] = arguments[_i];
}
var _b = __read(_a, 2), element = _b[1];
var href = $(element).attr(attrName);
if (!(href === null || href === void 0 ? void 0 : href.startsWith("/"))) {
return;
}
$(element).attr(attrName, "${url.resourcesPath}" + href);
});
});
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
var ftlPlaceholders = {
'{ "x": "xIdLqMeOed9sdLdIdOxdK0d" }': fs_1.default.readFileSync(path_1.join(__dirname, "ftl2js.ftl"))
.toString("utf8")
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)[1],
'<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->': [
'<#if scripts??>',
' <#list scripts as script>',
' <script src="${script}" type="text/javascript"></script>',
' </#list>',
'</#if>',
].join("\n")
};
$("head").prepend(__spreadArray(__spreadArray([], __read((Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
'',
'<style>',
replaceImportFromStatic_1.generateCssCodeToDefineGlobals({ cssGlobalsToDefine: cssGlobalsToDefine }).cssCodeToPrependInHead,
'</style>',
''
]))), [
'<script>',
' Object.assign(',
" window." + ftlValuesGlobalName + ",",
" " + objectKeys_1.objectKeys(ftlPlaceholders)[0],
' );',
'</script>',
'',
objectKeys_1.objectKeys(ftlPlaceholders)[1],
''
]).join("\n"));
var partiallyFixedIndexHtmlCode = $.html();
function generateFtlFilesCode(params) {
var pageBasename = params.pageBasename;
var $ = cheerio_1.default.load(partiallyFixedIndexHtmlCode);
$("head").prepend([
'',
'<script>',
" window." + ftlValuesGlobalName + " = { \"pageBasename\": \"" + pageBasename + "\" };",
'</script>',
''
].join("\n"));
var ftlCode = $.html();
objectKeys_1.objectKeys(ftlPlaceholders)
.forEach(function (id) { return ftlCode = ftlCode.replace(id, ftlPlaceholders[id]); });
return { ftlCode: ftlCode };
}
return { generateFtlFilesCode: generateFtlFilesCode };
}
exports.generateFtlFilesCodeFactory = generateFtlFilesCodeFactory;
//# sourceMappingURL=index.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/bin/build-keycloak-theme/generateFtl/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,oDAA8B;AAC9B,sEAGoC;AACpC,0CAAoB;AACpB,6BAAwC;AACxC,8DAA6D;AAE7D,SAAgB,2BAA2B,CACvC,MAIC;IAGO,IAAA,mBAAmB,GAAwC,MAAM,oBAA9C,EAAE,kBAAkB,GAAoB,MAAM,mBAA1B,EAAE,aAAa,GAAK,MAAM,cAAX,CAAY;IAE1E,IAAM,CAAC,GAAG,iBAAO,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;IAEtC,CAAC,CAAC,mBAAmB,CAAC,CAAC,IAAI,CAAC;QAAC,YAAc;aAAd,UAAc,EAAd,qBAAc,EAAd,IAAc;YAAd,uBAAc;;QAAd,IAAA,KAAA,aAAc,EAAR,OAAO,QAAA,CAAC;QAE/B,IAAA,WAAW,GAAK,yDAA+B,CAAC;YACpD,mBAAmB,qBAAA;YACnB,QAAQ,EAAE,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,EAAG;SAC/B,CAAC,YAHiB,CAGhB;QAEH,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAEjC,CAAC,CAAC,CAAC;IAGF;QACG,CAAC,MAAM,EAAE,MAAM,CAAC;QAChB,CAAC,QAAQ,EAAE,KAAK,CAAC;KACV,CAAC,OAAO,CAAC,UAAC,EAAoB;YAApB,KAAA,aAAoB,EAAnB,QAAQ,QAAA,EAAE,QAAQ,QAAA;QACpC,OAAA,CAAC,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC;YAAC,YAAc;iBAAd,UAAc,EAAd,qBAAc,EAAd,IAAc;gBAAd,uBAAc;;YAAd,IAAA,KAAA,aAAc,EAAR,OAAO,QAAA,CAAC;YAE5B,IAAM,IAAI,GAAG,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;YAEvC,IAAI,CAAC,CAAA,IAAI,aAAJ,IAAI,uBAAJ,IAAI,CAAE,UAAU,CAAC,GAAG,CAAC,CAAA,EAAE;gBACxB,OAAO;aACV;YAED,CAAC,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,QAAQ,EAAE,sBAAsB,GAAG,IAAI,CAAC,CAAC;QAE7D,CAAC,CAAC;IAVF,CAUE,CACL,CAAC;IAEF,6FAA6F;IAC7F,IAAM,eAAe,GAAG;QACpB,oCAAoC,EAChC,YAAE,CAAC,YAAY,CAAC,WAAQ,CAAC,SAAS,EAAE,YAAY,CAAC,CAAC;aAC7C,QAAQ,CAAC,MAAM,CAAC;aAChB,KAAK,CAAC,8CAA8C,CAAE,CAAC,CAAC,CAAC;QAClE,sCAAsC,EAClC;YACI,iBAAiB;YACjB,+BAA+B;YAC/B,kEAAkE;YAClE,cAAc;YACd,QAAQ;SACX,CAAC,IAAI,CAAC,IAAI,CAAC;KACnB,CAAC;IAEF,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CACb,uCACO,CAAC,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,MAAM,KAAK,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC;QACpD,EAAE;QACF,SAAS;QACT,wDAA8B,CAC1B,EAAE,kBAAkB,oBAAA,EAAE,CACzB,CAAC,sBAAsB;QACxB,UAAU;QACV,EAAE;KACL,CAAC;QACF,UAAU;QACV,oBAAoB;QACpB,oBAAkB,mBAAmB,MAAG;QACxC,aAAW,uBAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAG;QAC3C,QAAQ;QACR,WAAW;QACX,EAAE;QACF,uBAAU,CAAC,eAAe,CAAC,CAAC,CAAC,CAAC;QAC9B,EAAE;OACJ,IAAI,CAAC,IAAI,CAAC,CACf,CAAC;IAGF,IAAM,2BAA2B,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAE7C,SAAS,oBAAoB,CACzB,MAEC;QAGO,IAAA,YAAY,GAAK,MAAM,aAAX,CAAY;QAEhC,IAAM,CAAC,GAAG,iBAAO,CAAC,IAAI,CAAC,2BAA2B,CAAC,CAAC;QAEpD,CAAC,CAAC,MAAM,CAAC,CAAC,OAAO,CACb;YACI,EAAE;YACF,UAAU;YACV,eAAa,mBAAmB,iCAAyB,YAAY,UAAM;YAC3E,WAAW;YACX,EAAE;SACL,CAAC,IAAI,CAAC,IAAI,CAAC,CACf,CAAC;QAEF,IAAI,OAAO,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;QAEvB,uBAAU,CAAC,eAAe,CAAC;aACtB,OAAO,CAAC,UAAA,EAAE,IAAI,OAAA,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,EAAE,EAAE,eAAe,CAAC,EAAE,CAAC,CAAC,EAAlD,CAAkD,CAAC,CAAC;QAEvE,OAAO,EAAE,OAAO,SAAA,EAAE,CAAC;IAEvB,CAAC;IAED,OAAO,EAAE,oBAAoB,sBAAA,EAAE,CAAC;AAGpC,CAAC;AAnHD,kEAmHC"}

View File

@ -1,11 +0,0 @@
export declare type ParsedPackageJson = {
name: string;
version: string;
homepage?: string;
};
export declare function generateJavaStackFiles(params: {
parsedPackageJson: ParsedPackageJson;
keycloakThemeBuildingDirPath: string;
}): {
jarFilePath: string;
};

View File

@ -1,73 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateJavaStackFiles = void 0;
var url = __importStar(require("url"));
var fs = __importStar(require("fs"));
var path_1 = require("path");
function generateJavaStackFiles(params) {
var _a = params.parsedPackageJson, name = _a.name, version = _a.version, homepage = _a.homepage, keycloakThemeBuildingDirPath = params.keycloakThemeBuildingDirPath;
{
var pomFileCode = (function generatePomFileCode() {
var groupId = (function () {
var _a, _b;
var fallbackGroupId = "there.was.no.homepage.field.in.the.package.json." + name;
return (!homepage ?
fallbackGroupId :
(_b = (_a = url.parse(homepage).host) === null || _a === void 0 ? void 0 : _a.split(".").reverse().join(".")) !== null && _b !== void 0 ? _b : fallbackGroupId) + ".keycloak";
})();
var artefactId = name + "-keycloak-theme";
var pomFileCode = [
"<?xml version=\"1.0\"?>",
"<project xmlns=\"http://maven.apache.org/POM/4.0.0\"",
"\txmlns:xsi=\"http://www.w3.org/2001/XMLSchema-instance\"",
"\txsi:schemaLocation=\"http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd\">",
"\t<modelVersion>4.0.0</modelVersion>",
"\t<groupId>" + groupId + "</groupId>",
"\t<artifactId>" + artefactId + "</artifactId>",
"\t<version>" + version + "</version>",
"\t<name>" + artefactId + "</name>",
"\t<description />",
"</project>"
].join("\n");
return { pomFileCode: pomFileCode };
})().pomFileCode;
fs.writeFileSync(path_1.join(keycloakThemeBuildingDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
}
{
var themeManifestFilePath = path_1.join(keycloakThemeBuildingDirPath, "src", "main", "resources", "META-INF", "keycloak-themes.json");
try {
fs.mkdirSync(path_1.dirname(themeManifestFilePath));
}
catch (_b) { }
fs.writeFileSync(themeManifestFilePath, Buffer.from(JSON.stringify({
"themes": [
{
"name": name,
"types": ["login"]
}
]
}, null, 2), "utf8"));
}
return { "jarFilePath": path_1.join(keycloakThemeBuildingDirPath, "target", name + "-" + version + ".jar") };
}
exports.generateJavaStackFiles = generateJavaStackFiles;
//# sourceMappingURL=generateJavaStackFiles.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"generateJavaStackFiles.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/generateJavaStackFiles.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AACA,uCAA2B;AAC3B,qCAAyB;AACzB,6BAAgE;AAQhE,SAAgB,sBAAsB,CAClC,MAGC;IAIG,IAAA,KAEA,MAAM,kBAFwC,EAAzB,IAAI,UAAA,EAAE,OAAO,aAAA,EAAE,QAAQ,cAAA,EAC5C,4BAA4B,GAC5B,MAAM,6BADsB,CACrB;IAEX;QAEY,IAAA,WAAW,GAAK,CAAC,SAAS,mBAAmB;YAGjD,IAAM,OAAO,GAAG,CAAC;;gBAEb,IAAM,eAAe,GAAG,qDAAmD,IAAM,CAAC;gBAElF,OAAO,CAAC,CAAC,QAAQ,CAAC,CAAC;oBACf,eAAe,CAAC,CAAC;oBACjB,MAAA,MAAA,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,IAAI,0CAAE,KAAK,CAAC,GAAG,EAAE,OAAO,GAAG,IAAI,CAAC,GAAG,CAAC,mCAAI,eAAe,CAC9E,GAAG,WAAW,CAAC;YAEpB,CAAC,CAAC,EAAE,CAAC;YAEL,IAAM,UAAU,GAAM,IAAI,oBAAiB,CAAC;YAE5C,IAAM,WAAW,GAAG;gBAChB,yBAAuB;gBACvB,sDAAoD;gBACpD,2DAAwD;gBACxD,sGAAmG;gBACnG,sCAAqC;gBACrC,gBAAa,OAAO,eAAY;gBAChC,mBAAgB,UAAU,kBAAe;gBACzC,gBAAa,OAAO,eAAY;gBAChC,aAAU,UAAU,YAAS;gBAC7B,mBAAkB;gBAClB,YAAY;aACf,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;YAEb,OAAO,EAAE,WAAW,aAAA,EAAE,CAAC;QAE3B,CAAC,CAAC,EAAE,YAhCe,CAgCd;QAEL,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,4BAA4B,EAAE,SAAS,CAAC,EACjD,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,CACnC,CAAC;KAEL;IAED;QAEI,IAAM,qBAAqB,GAAG,WAAQ,CAClC,4BAA4B,EAAE,KAAK,EAAE,MAAM,EAC3C,WAAW,EAAE,UAAU,EAAE,sBAAsB,CAClD,CAAC;QAEF,IAAI;YAEA,EAAE,CAAC,SAAS,CAAC,cAAW,CAAC,qBAAqB,CAAC,CAAC,CAAC;SAEpD;QAAC,WAAM,GAAG;QAEX,EAAE,CAAC,aAAa,CACZ,qBAAqB,EACrB,MAAM,CAAC,IAAI,CACP,IAAI,CAAC,SAAS,CAAC;YACX,QAAQ,EAAE;gBACN;oBACI,MAAM,EAAE,IAAI;oBACZ,OAAO,EAAE,CAAC,OAAO,CAAC;iBACrB;aACJ;SACJ,EAAE,IAAI,EAAE,CAAC,CAAC,EACX,MAAM,CACT,CACJ,CAAC;KAEL;IAED,OAAO,EAAE,aAAa,EAAE,WAAQ,CAAC,4BAA4B,EAAE,QAAQ,EAAK,IAAI,SAAI,OAAO,SAAM,CAAC,EAAE,CAAC;AAEzG,CAAC;AAvFD,wDAuFC"}

View File

@ -1,6 +0,0 @@
export declare const ftlValuesGlobalName = "keycloakPagesContext";
export declare function generateKeycloakThemeResources(params: {
themeName: string;
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
}): void;

View File

@ -1,76 +0,0 @@
"use strict";
var __assign = (this && this.__assign) || function () {
__assign = Object.assign || function(t) {
for (var s, i = 1, n = arguments.length; i < n; i++) {
s = arguments[i];
for (var p in s) if (Object.prototype.hasOwnProperty.call(s, p))
t[p] = s[p];
}
return t;
};
return __assign.apply(this, arguments);
};
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateKeycloakThemeResources = exports.ftlValuesGlobalName = void 0;
var transformCodebase_1 = require("../tools/transformCodebase");
var fs = __importStar(require("fs"));
var path_1 = require("path");
var replaceImportFromStatic_1 = require("./replaceImportFromStatic");
var generateFtl_1 = require("./generateFtl");
exports.ftlValuesGlobalName = "keycloakPagesContext";
function generateKeycloakThemeResources(params) {
var themeName = params.themeName, reactAppBuildDirPath = params.reactAppBuildDirPath, keycloakThemeBuildingDirPath = params.keycloakThemeBuildingDirPath;
var themeDirPath = path_1.join(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
var allCssGlobalsToDefine = {};
transformCodebase_1.transformCodebase({
"destDirPath": path_1.join(themeDirPath, "resources"),
"srcDirPath": reactAppBuildDirPath,
"transformSourceCodeString": function (_a) {
var filePath = _a.filePath, sourceCode = _a.sourceCode;
if (/\.css?$/i.test(filePath)) {
var _b = replaceImportFromStatic_1.replaceImportFromStaticInCssCode({ "cssCode": sourceCode.toString("utf8") }), cssGlobalsToDefine = _b.cssGlobalsToDefine, fixedCssCode = _b.fixedCssCode;
allCssGlobalsToDefine = __assign(__assign({}, allCssGlobalsToDefine), cssGlobalsToDefine);
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
}
if (/\.js?$/i.test(filePath)) {
var fixedJsCode = replaceImportFromStatic_1.replaceImportFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName: exports.ftlValuesGlobalName
}).fixedJsCode;
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
}
return { "modifiedSourceCode": sourceCode };
}
});
var generateFtlFilesCode = generateFtl_1.generateFtlFilesCodeFactory({
"cssGlobalsToDefine": allCssGlobalsToDefine,
ftlValuesGlobalName: exports.ftlValuesGlobalName,
"indexHtmlCode": fs.readFileSync(path_1.join(reactAppBuildDirPath, "index.html")).toString("utf8")
}).generateFtlFilesCode;
["login.ftl", "register.ftl"].forEach(function (pageBasename) {
var ftlCode = generateFtlFilesCode({ pageBasename: pageBasename }).ftlCode;
fs.writeFileSync(path_1.join(themeDirPath, pageBasename), Buffer.from(ftlCode, "utf8"));
});
fs.writeFileSync(path_1.join(themeDirPath, "theme.properties"), Buffer.from("parent=base\n", "utf8"));
}
exports.generateKeycloakThemeResources = generateKeycloakThemeResources;
//# sourceMappingURL=generateKeycloakThemeResources.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"generateKeycloakThemeResources.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/generateKeycloakThemeResources.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,gEAA+D;AAC/D,qCAAyB;AACzB,6BAAwC;AACxC,qEAGmC;AACnC,6CAA4D;AAE/C,QAAA,mBAAmB,GAAG,sBAAsB,CAAC;AAE1D,SAAgB,8BAA8B,CAC1C,MAIC;IAGO,IAAA,SAAS,GAAyD,MAAM,UAA/D,EAAE,oBAAoB,GAAmC,MAAM,qBAAzC,EAAE,4BAA4B,GAAK,MAAM,6BAAX,CAAY;IAEjF,IAAM,YAAY,GAAG,WAAQ,CAAC,4BAA4B,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,OAAO,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;IAErH,IAAI,qBAAqB,GAA2B,EAAE,CAAC;IAEvD,qCAAiB,CAAC;QACd,aAAa,EAAE,WAAQ,CAAC,YAAY,EAAE,WAAW,CAAC;QAClD,YAAY,EAAE,oBAAoB;QAClC,2BAA2B,EAAE,UAAC,EAAwB;gBAAtB,QAAQ,cAAA,EAAE,UAAU,gBAAA;YAEhD,IAAI,UAAU,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAErB,IAAA,KAAuC,0DAAgC,CACzE,EAAE,SAAS,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC,EAAE,CAC7C,EAFO,kBAAkB,wBAAA,EAAE,YAAY,kBAEvC,CAAC;gBAEF,qBAAqB,yBACd,qBAAqB,GACrB,kBAAkB,CACxB,CAAC;gBAEF,OAAO,EAAE,oBAAoB,EAAE,MAAM,CAAC,IAAI,CAAC,YAAY,EAAE,MAAM,CAAC,EAAE,CAAC;aAEtE;YAED,IAAI,SAAS,CAAC,IAAI,CAAC,QAAQ,CAAC,EAAE;gBAElB,IAAA,WAAW,GAAK,yDAA+B,CAAC;oBACpD,QAAQ,EAAE,UAAU,CAAC,QAAQ,CAAC,MAAM,CAAC;oBACrC,mBAAmB,6BAAA;iBACtB,CAAC,YAHiB,CAGhB;gBAEH,OAAO,EAAE,oBAAoB,EAAE,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,MAAM,CAAC,EAAE,CAAC;aAErE;YAED,OAAO,EAAE,oBAAoB,EAAE,UAAU,EAAE,CAAC;QAEhD,CAAC;KACJ,CAAC,CAAC;IAEK,IAAA,oBAAoB,GAAK,yCAA2B,CAAC;QACzD,oBAAoB,EAAE,qBAAqB;QAC3C,mBAAmB,6BAAA;QACnB,eAAe,EAAE,EAAE,CAAC,YAAY,CAC5B,WAAQ,CAAC,oBAAoB,EAAE,YAAY,CAAC,CAC/C,CAAC,QAAQ,CAAC,MAAM,CAAC;KACrB,CAAC,qBAN0B,CAMzB;IAEF,CAAC,WAAW,EAAE,cAAc,CAAW,CAAC,OAAO,CAAC,UAAA,YAAY;QAEjD,IAAA,OAAO,GAAK,oBAAoB,CAAC,EAAE,YAAY,cAAA,EAAE,CAAC,QAA3C,CAA4C;QAE3D,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,YAAY,EAAE,YAAY,CAAC,EACpC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,MAAM,CAAC,CAC/B,CAAA;IAEL,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,YAAY,EAAE,kBAAkB,CAAC,EAC1C,MAAM,CAAC,IAAI,CAAC,eAAe,EAAE,MAAM,CAAC,CACvC,CAAC;AAEN,CAAC;AA1ED,wEA0EC"}

View File

@ -1,2 +0,0 @@
#!/usr/bin/env node
export declare const keycloakThemeBuildingDirPath: string;

View File

@ -1,86 +0,0 @@
#!/usr/bin/env node
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.keycloakThemeBuildingDirPath = void 0;
var generateKeycloakThemeResources_1 = require("./generateKeycloakThemeResources");
var generateJavaStackFiles_1 = require("./generateJavaStackFiles");
var path_1 = require("path");
var child_process = __importStar(require("child_process"));
var generateDebugFiles_1 = require("./generateDebugFiles");
var reactProjectDirPath = process.cwd();
var parsedPackageJson = require(path_1.join(reactProjectDirPath, "package.json"));
exports.keycloakThemeBuildingDirPath = path_1.join(reactProjectDirPath, "build_keycloak");
if (require.main === module) {
console.log("🔏 Building the keycloak theme...⌚");
generateKeycloakThemeResources_1.generateKeycloakThemeResources({
keycloakThemeBuildingDirPath: exports.keycloakThemeBuildingDirPath,
"reactAppBuildDirPath": path_1.join(reactProjectDirPath, "build"),
"themeName": parsedPackageJson.name
});
var jarFilePath = generateJavaStackFiles_1.generateJavaStackFiles({
parsedPackageJson: parsedPackageJson,
keycloakThemeBuildingDirPath: exports.keycloakThemeBuildingDirPath
}).jarFilePath;
child_process.execSync("mvn package", { "cwd": exports.keycloakThemeBuildingDirPath });
generateDebugFiles_1.generateDebugFiles({
keycloakThemeBuildingDirPath: exports.keycloakThemeBuildingDirPath,
"packageJsonName": parsedPackageJson.name
});
console.log([
'',
"\u2705 Your keycloak theme has been generated and bundled into ./" + path_1.relative(reactProjectDirPath, jarFilePath) + " \uD83D\uDE80",
"It is to be placed in \"/opt/jboss/keycloak/standalone/deployments\" in the container running a jboss/keycloak Docker image. (Tested with 11.0.3)",
'',
'Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:',
'',
'value.yaml: ',
' extraInitContainers: |',
' - name: realm-ext-provider',
' image: curlimages/curl',
' imagePullPolicy: IfNotPresent',
' command:',
' - sh',
' args:',
' - -c',
" - curl -L -f -S -o /extensions/" + path_1.basename(jarFilePath) + " https://AN.URL.FOR/" + path_1.basename(jarFilePath),
' volumeMounts:',
' - name: extensions',
' mountPath: /extensions',
' ',
' extraVolumeMounts: |',
' - name: extensions',
' mountPath: /opt/jboss/keycloak/standalone/deployments',
'',
'',
'To test your theme locally, with hot reloading, you can spin up a Keycloak container image with the theme loaded by running:',
'',
"\uD83D\uDC49 $ ./" + path_1.relative(reactProjectDirPath, path_1.join(exports.keycloakThemeBuildingDirPath, generateDebugFiles_1.containerLaunchScriptBasename)) + " \uD83D\uDC48",
'',
'To enable the theme within keycloak log into the admin console ( 👉 http://localhost:8080 username: admin, password: admin 👈), create a realm (called "myrealm" for example),',
"go to your realm settings, click on the theme tab then select " + parsedPackageJson.name + ".",
"More details: https://www.keycloak.org/getting-started/getting-started-docker",
'',
'Once your container is up and configured 👉 http://localhost:8080/auth/realms/myrealm/account 👈',
'',
].join("\n"));
}
//# sourceMappingURL=index.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;AAEA,mFAAkF;AAClF,mEAAkE;AAElE,6BAA4F;AAC5F,2DAA+C;AAC/C,2DAAyF;AAGzF,IAAM,mBAAmB,GAAG,OAAO,CAAC,GAAG,EAAE,CAAC;AAE1C,IAAM,iBAAiB,GAAsB,OAAO,CAAC,WAAQ,CAAC,mBAAmB,EAAE,cAAc,CAAC,CAAC,CAAC;AAEvF,QAAA,4BAA4B,GAAG,WAAQ,CAAC,mBAAmB,EAAE,gBAAgB,CAAC,CAAC;AAG5F,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;IAEzB,OAAO,CAAC,GAAG,CAAC,oCAAoC,CAAC,CAAC;IAElD,+DAA8B,CAAC;QAC3B,4BAA4B,sCAAA;QAC5B,sBAAsB,EAAE,WAAQ,CAAC,mBAAmB,EAAE,OAAO,CAAC;QAC9D,WAAW,EAAE,iBAAiB,CAAC,IAAI;KACtC,CAAC,CAAC;IAEK,IAAA,WAAW,GAAK,+CAAsB,CAAC;QAC3C,iBAAiB,mBAAA;QACjB,4BAA4B,sCAAA;KAC/B,CAAC,YAHiB,CAGhB;IAEH,aAAa,CAAC,QAAQ,CAClB,aAAa,EACb,EAAE,KAAK,EAAE,oCAA4B,EAAE,CAC1C,CAAC;IAEF,uCAAkB,CAAC;QACf,4BAA4B,sCAAA;QAC5B,iBAAiB,EAAE,iBAAiB,CAAC,IAAI;KAC5C,CAAC,CAAC;IAEH,OAAO,CAAC,GAAG,CAAC;QACR,EAAE;QACF,sEAA+D,eAAY,CAAC,mBAAmB,EAAE,WAAW,CAAC,kBAAK;QAClH,mJAAiJ;QACjJ,EAAE;QACF,2EAA2E;QAC3E,EAAE;QACF,cAAc;QACd,4BAA4B;QAC5B,oCAAoC;QACpC,kCAAkC;QAClC,yCAAyC;QACzC,oBAAoB;QACpB,kBAAkB;QAClB,iBAAiB;QACjB,kBAAkB;QAClB,gDAA8C,eAAY,CAAC,WAAW,CAAC,4BAAuB,eAAY,CAAC,WAAW,CAAG;QACzH,yBAAyB;QACzB,gCAAgC;QAChC,sCAAsC;QACtC,UAAU;QACV,8BAA8B;QAC9B,gCAAgC;QAChC,qEAAqE;QACrE,EAAE;QACF,EAAE;QACF,8HAA8H;QAC9H,EAAE;QACF,sBAAU,eAAY,CAAC,mBAAmB,EAAE,WAAQ,CAAC,oCAA4B,EAAE,kDAA6B,CAAC,CAAC,kBAAK;QACvH,EAAE;QACF,gLAAgL;QAChL,mEAAiE,iBAAiB,CAAC,IAAI,MAAG;QAC1F,+EAA+E;QAC/E,EAAE;QACF,kGAAkG;QAClG,EAAE;KACL,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,CAAC;CAEjB"}

View File

@ -1,17 +0,0 @@
export declare function replaceImportFromStaticInJsCode(params: {
ftlValuesGlobalName: string;
jsCode: string;
}): {
fixedJsCode: string;
};
export declare function replaceImportFromStaticInCssCode(params: {
cssCode: string;
}): {
fixedCssCode: string;
cssGlobalsToDefine: Record<string, string>;
};
export declare function generateCssCodeToDefineGlobals(params: {
cssGlobalsToDefine: Record<string, string>;
}): {
cssCodeToPrependInHead: string;
};

View File

@ -1,94 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.generateCssCodeToDefineGlobals = exports.replaceImportFromStaticInCssCode = exports.replaceImportFromStaticInJsCode = void 0;
var crypto = __importStar(require("crypto"));
function replaceImportFromStaticInJsCode(params) {
var jsCode = params.jsCode, ftlValuesGlobalName = params.ftlValuesGlobalName;
var fixedJsCode = jsCode.replace(/"static\//g, "window." + ftlValuesGlobalName + ".url.resourcesPath.replace(/^\\//,\"\") + \"/\" + \"static/");
return { fixedJsCode: fixedJsCode };
}
exports.replaceImportFromStaticInJsCode = replaceImportFromStaticInJsCode;
function replaceImportFromStaticInCssCode(params) {
var _a;
var cssCode = params.cssCode;
var cssGlobalsToDefine = {};
new Set((_a = cssCode.match(/(url\(\/[^)]+\))/g)) !== null && _a !== void 0 ? _a : [])
.forEach(function (match) {
return cssGlobalsToDefine["url" + crypto
.createHash("sha256")
.update(match)
.digest("hex")
.substring(0, 15)] = match;
});
var fixedCssCode = cssCode;
Object.keys(cssGlobalsToDefine).forEach(function (cssVariableName) {
//NOTE: split/join pattern ~ replace all
return fixedCssCode =
fixedCssCode.split(cssGlobalsToDefine[cssVariableName])
.join("var(--" + cssVariableName + ")");
});
return { fixedCssCode: fixedCssCode, cssGlobalsToDefine: cssGlobalsToDefine };
}
exports.replaceImportFromStaticInCssCode = replaceImportFromStaticInCssCode;
function generateCssCodeToDefineGlobals(params) {
var cssGlobalsToDefine = params.cssGlobalsToDefine;
return {
"cssCodeToPrependInHead": __spreadArray(__spreadArray([
":root {"
], __read(Object.keys(cssGlobalsToDefine)
.map(function (cssVariableName) { return [
"--" + cssVariableName + ":",
[
"url(",
"${url.resourcesPath}" +
cssGlobalsToDefine[cssVariableName].match(/^url\(([^)]+)\)$/)[1],
")"
].join("")
].join(" "); })
.map(function (line) { return " " + line + ";"; }))), [
"}"
]).join("\n")
};
}
exports.generateCssCodeToDefineGlobals = generateCssCodeToDefineGlobals;
//# sourceMappingURL=replaceImportFromStatic.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"replaceImportFromStatic.js","sourceRoot":"","sources":["../../src/bin/build-keycloak-theme/replaceImportFromStatic.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,6CAAiC;AAEjC,SAAgB,+BAA+B,CAC3C,MAGC;IAGO,IAAA,MAAM,GAA0B,MAAM,OAAhC,EAAE,mBAAmB,GAAK,MAAM,oBAAX,CAAY;IAE/C,IAAM,WAAW,GAAG,MAAO,CAAC,OAAO,CAC/B,YAAY,EACZ,YAAU,mBAAmB,gEAAwD,CACxF,CAAC;IAEF,OAAO,EAAE,WAAW,aAAA,EAAE,CAAC;AAE3B,CAAC;AAhBD,0EAgBC;AAED,SAAgB,gCAAgC,CAC5C,MAEC;;IAMO,IAAA,OAAO,GAAK,MAAM,QAAX,CAAY;IAE3B,IAAM,kBAAkB,GAA2B,EAAE,CAAC;IAEtD,IAAI,GAAG,CAAC,MAAA,OAAO,CAAC,KAAK,CAAC,mBAAmB,CAAC,mCAAI,EAAE,CAAC;SAC5C,OAAO,CAAC,UAAA,KAAK;QACV,OAAA,kBAAkB,CAClB,KAAK,GAAG,MAAM;aACT,UAAU,CAAC,QAAQ,CAAC;aACpB,MAAM,CAAC,KAAK,CAAC;aACb,MAAM,CAAC,KAAK,CAAC;aACb,SAAS,CAAC,CAAC,EAAE,EAAE,CAAC,CACpB,GAAG,KAAK;IANT,CAMS,CACZ,CAAC;IAEN,IAAI,YAAY,GAAG,OAAO,CAAC;IAE3B,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC,CAAC,OAAO,CACnC,UAAA,eAAe;QACX,wCAAwC;QACxC,OAAA,YAAY;YACZ,YAAY,CAAC,KAAK,CAAC,kBAAkB,CAAC,eAAe,CAAC,CAAC;iBAClD,IAAI,CAAC,WAAS,eAAe,MAAG,CAAC;IAFtC,CAEsC,CAC7C,CAAC;IAEF,OAAO,EAAE,YAAY,cAAA,EAAE,kBAAkB,oBAAA,EAAE,CAAC;AAEhD,CAAC;AApCD,4EAoCC;AAED,SAAgB,8BAA8B,CAC1C,MAEC;IAKO,IAAA,kBAAkB,GAAK,MAAM,mBAAX,CAAY;IAEtC,OAAO;QACH,wBAAwB,EAAE;YACtB,SAAS;kBACN,MAAM,CAAC,IAAI,CAAC,kBAAkB,CAAC;aAC7B,GAAG,CAAC,UAAA,eAAe,IAAI,OAAA;YACpB,OAAK,eAAe,MAAG;YACvB;gBACI,MAAM;gBACN,sBAAsB;oBACtB,kBAAkB,CAAC,eAAe,CAAC,CAAC,KAAK,CAAC,kBAAkB,CAAE,CAAC,CAAC,CAAC;gBACjE,GAAG;aACN,CAAC,IAAI,CAAC,EAAE,CAAC;SACb,CAAC,IAAI,CAAC,GAAG,CAAC,EARa,CAQb,CAAC;aACX,GAAG,CAAC,UAAA,IAAI,IAAI,OAAA,SAAO,IAAI,MAAG,EAAd,CAAc,CAAC;YAChC,GAAG;WACL,IAAI,CAAC,IAAI,CAAC;KACf,CAAC;AAEN,CAAC;AA5BD,wEA4BC"}

View File

@ -1,2 +0,0 @@
#!/usr/bin/env node
export declare const keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl = "https://github.com/garronej/keycloak-react-theming/releases/download/v0.0.1/other_keycloak_thems.zip";

View File

@ -1,15 +0,0 @@
#!/usr/bin/env node
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl = void 0;
var build_keycloak_theme_1 = require("./build-keycloak-theme");
var downloadAndUnzip_1 = require("./tools/downloadAndUnzip");
exports.keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl = "https://github.com/garronej/keycloak-react-theming/releases/download/v0.0.1/other_keycloak_thems.zip";
if (require.main === module) {
console.log("execute!");
downloadAndUnzip_1.downloadAndUnzip({
"url": exports.keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl,
"destDirPath": build_keycloak_theme_1.keycloakThemeBuildingDirPath
});
}
//# sourceMappingURL=download-sample-keycloak-themes.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"download-sample-keycloak-themes.js","sourceRoot":"","sources":["../src/bin/download-sample-keycloak-themes.ts"],"names":[],"mappings":";;;;AAEA,+DAAsE;AACtE,6DAA4D;AAE/C,QAAA,kDAAkD,GAC3D,sGAAsG,CAAC;AAE3G,IAAI,OAAO,CAAC,IAAI,KAAK,MAAM,EAAE;IAEzB,OAAO,CAAC,GAAG,CAAC,UAAU,CAAC,CAAC;IAExB,mCAAgB,CAAC;QACb,KAAK,EAAE,0DAAkD;QACzD,aAAa,EAAE,mDAA4B;KAC9C,CAAC,CAAC;CAEN"}

View File

@ -1 +0,0 @@
export {};

View File

@ -1,78 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
Object.defineProperty(exports, "__esModule", { value: true });
var fs = __importStar(require("fs"));
var path_1 = require("path");
var crawl_1 = require("./tools/crawl");
var downloadAndUnzip_1 = require("./tools/downloadAndUnzip");
var download_sample_keycloak_themes_1 = require("./download-sample-keycloak-themes");
var getProjectRoot_1 = require("./tools/getProjectRoot");
var child_process = __importStar(require("child_process"));
//@ts-ignore
var propertiesParser = require("properties-parser");
console.log(propertiesParser);
var tmpDirPath = path_1.join(getProjectRoot_1.getProjectRoot(), "tmp_xImOef9dOd44");
child_process.execSync("rm -rf " + tmpDirPath);
downloadAndUnzip_1.downloadAndUnzip({
"destDirPath": tmpDirPath,
"url": download_sample_keycloak_themes_1.keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl
});
var record = {};
process.chdir(path_1.join(tmpDirPath, "base"));
crawl_1.crawl(".").forEach(function (filePath) {
var _a;
var match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/);
if (match === null) {
return;
}
var _b = __read(match, 3), typeOfPage = _b[1], language = _b[2];
((_a = record[typeOfPage]) !== null && _a !== void 0 ? _a : (record[typeOfPage] = {}))[language] =
propertiesParser.parse(fs.readFileSync(filePath)
.toString("utf8"));
});
child_process.execSync("rm -r " + tmpDirPath);
var targetFilePath = path_1.join("src", "lib", "i18n", "messages.generated.ts");
fs.writeFileSync(path_1.join(getProjectRoot_1.getProjectRoot(), targetFilePath), Buffer.from([
"//This code was automatically generated by running " + path_1.relative(getProjectRoot_1.getProjectRoot(), __filename),
'//PLEASE DO NOT EDIT MANUALLY',
'',
'/* spell-checker: disable */',
"export const messages= " + JSON.stringify(record, null, 2) + " as const;",
'/* spell-checker: enable */'
].join("\n"), "utf8"));
console.log(targetFilePath + " wrote");
//# sourceMappingURL=generate-i18n-messages.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"generate-i18n-messages.js","sourceRoot":"","sources":["../src/bin/generate-i18n-messages.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qCAAyB;AACzB,6BAAkE;AAClE,uCAAsC;AACtC,6DAA4D;AAC5D,qFAAuG;AACvG,yDAAwD;AACxD,2DAA+C;AAE/C,YAAY;AACZ,IAAM,gBAAgB,GAAG,OAAO,CAAC,mBAAmB,CAAC,CAAC;AAEtD,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,CAAC;AAE9B,IAAM,UAAU,GAAG,WAAQ,CAAC,+BAAc,EAAE,EAAE,kBAAkB,CAAC,CAAC;AAElE,aAAa,CAAC,QAAQ,CAAC,YAAU,UAAY,CAAC,CAAC;AAE/C,mCAAgB,CAAC;IACb,aAAa,EAAE,UAAU;IACzB,KAAK,EAAE,oFAAkD;CAC5D,CAAC,CAAC;AAIH,IAAM,MAAM,GAAiE,EAAE,CAAC;AAEhF,OAAO,CAAC,KAAK,CAAC,WAAQ,CAAC,UAAU,EAAE,MAAM,CAAC,CAAC,CAAC;AAE5C,aAAK,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,UAAA,QAAQ;;IAEvB,IAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,mDAAmD,CAAC,CAAC;IAElF,IAAI,KAAK,KAAK,IAAI,EAAE;QAChB,OAAO;KACV;IAEK,IAAA,KAAA,OAA2B,KAAK,IAAA,EAA7B,UAAU,QAAA,EAAE,QAAQ,QAAS,CAAC;IAEvC,OAAC,MAAM,CAAC,UAAU,qCAAjB,MAAM,CAAC,UAAU,IAAM,EAAE,EAAC,CAAC,QAAQ,CAAC;QACjC,gBAAgB,CAAC,KAAK,CAClB,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC;aACpB,QAAQ,CAAC,MAAM,CAAC,CACxB,CAAC;AAEV,CAAC,CAAC,CAAC;AAEH,aAAa,CAAC,QAAQ,CAAC,WAAS,UAAY,CAAC,CAAC;AAE9C,IAAM,cAAc,GAAG,WAAQ,CAAC,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,CAAC,CAAC;AAE/E,EAAE,CAAC,aAAa,CACZ,WAAQ,CAAC,+BAAc,EAAE,EAAE,cAAc,CAAC,EAC1C,MAAM,CAAC,IAAI,CACP;IACI,wDAAsD,eAAY,CAAC,+BAAc,EAAE,EAAE,UAAU,CAAG;IAClG,+BAA+B;IAC/B,EAAE;IACF,8BAA8B;IAC9B,4BAA0B,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,eAAY;IACrE,6BAA6B;CAChC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,MAAM,CAAC,CAC5B,CAAC;AAEF,OAAO,CAAC,GAAG,CAAI,cAAc,WAAQ,CAAC,CAAC"}

View File

@ -1,2 +0,0 @@
/** List all files in a given directory return paths relative to the dir_path */
export declare const crawl: (dir_path: string) => string[];

View File

@ -1,65 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.crawl = void 0;
var fs = __importStar(require("fs"));
var path = __importStar(require("path"));
/** List all files in a given directory return paths relative to the dir_path */
exports.crawl = (function () {
var crawlRec = function (dir_path, paths) {
var e_1, _a;
try {
for (var _b = __values(fs.readdirSync(dir_path)), _c = _b.next(); !_c.done; _c = _b.next()) {
var file_name = _c.value;
var file_path = path.join(dir_path, file_name);
if (fs.lstatSync(file_path).isDirectory()) {
crawlRec(file_path, paths);
continue;
}
paths.push(file_path);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
};
return function crawl(dir_path) {
var paths = [];
crawlRec(dir_path, paths);
return paths.map(function (file_path) { return path.relative(dir_path, file_path); });
};
})();
//# sourceMappingURL=crawl.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"crawl.js","sourceRoot":"","sources":["../../src/bin/tools/crawl.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,qCAAyB;AACzB,yCAA6B;AAE7B,gFAAgF;AACnE,QAAA,KAAK,GAAG,CAAC;IAElB,IAAM,QAAQ,GAAG,UAAC,QAAgB,EAAE,KAAe;;;YAE/C,KAAwB,IAAA,KAAA,SAAA,EAAE,CAAC,WAAW,CAAC,QAAQ,CAAC,CAAA,gBAAA,4BAAE;gBAA7C,IAAM,SAAS,WAAA;gBAEhB,IAAM,SAAS,GAAG,IAAI,CAAC,IAAI,CAAC,QAAQ,EAAE,SAAS,CAAC,CAAC;gBAEjD,IAAI,EAAE,CAAC,SAAS,CAAC,SAAS,CAAC,CAAC,WAAW,EAAE,EAAE;oBAEvC,QAAQ,CAAC,SAAS,EAAE,KAAK,CAAC,CAAC;oBAE3B,SAAS;iBAEZ;gBAED,KAAK,CAAC,IAAI,CAAC,SAAS,CAAC,CAAC;aAEzB;;;;;;;;;IAEL,CAAC,CAAC;IAEF,OAAO,SAAS,KAAK,CAAC,QAAgB;QAElC,IAAM,KAAK,GAAa,EAAE,CAAC;QAE3B,QAAQ,CAAC,QAAQ,EAAE,KAAK,CAAC,CAAC;QAE1B,OAAO,KAAK,CAAC,GAAG,CAAC,UAAA,SAAS,IAAI,OAAA,IAAI,CAAC,QAAQ,CAAC,QAAQ,EAAE,SAAS,CAAC,EAAlC,CAAkC,CAAC,CAAC;IAEtE,CAAC,CAAA;AAEL,CAAC,CAAC,EAAE,CAAC"}

View File

@ -1,4 +0,0 @@
export declare function downloadAndUnzip(params: {
url: string;
destDirPath: string;
}): void;

View File

@ -1,39 +0,0 @@
"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from) {
for (var i = 0, il = from.length, j = to.length; i < il; i++, j++)
to[j] = from[i];
return to;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.downloadAndUnzip = void 0;
var path_1 = require("path");
var child_process_1 = __importDefault(require("child_process"));
var fs_1 = __importDefault(require("fs"));
function downloadAndUnzip(params) {
var url = params.url, destDirPath = params.destDirPath;
fs_1.default.mkdirSync(destDirPath, { "recursive": true });
__spreadArray([
"wget " + url
], __read(["unzip", "rm"].map(function (prg) { return prg + " " + path_1.basename(url); }))).forEach(function (cmd) { return child_process_1.default.execSync(cmd, { "cwd": destDirPath }); });
}
exports.downloadAndUnzip = downloadAndUnzip;
//# sourceMappingURL=downloadAndUnzip.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"downloadAndUnzip.js","sourceRoot":"","sources":["../../src/bin/tools/downloadAndUnzip.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,6BAAgD;AAChD,gEAA0C;AAC1C,0CAAoB;AAEpB,SAAgB,gBAAgB,CAC5B,MAGC;IAGO,IAAA,GAAG,GAAkB,MAAM,IAAxB,EAAE,WAAW,GAAK,MAAM,YAAX,CAAY;IAEpC,YAAE,CAAC,SAAS,CAAC,WAAW,EAAE,EAAE,WAAW,EAAE,IAAI,EAAE,CAAC,CAAC;IAEjD;QACI,UAAQ,GAAK;cACV,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,GAAG,CAAC,UAAA,GAAG,IAAI,OAAG,GAAG,SAAI,eAAY,CAAC,GAAG,CAAG,EAA7B,CAA6B,CAAC,GAC9D,OAAO,CAAC,UAAA,GAAG,IAAI,OAAA,uBAAa,CAAC,QAAQ,CAAC,GAAG,EAAE,EAAE,KAAK,EAAE,WAAW,EAAE,CAAC,EAAnD,CAAmD,CAAC,CAAC;AAE1E,CAAC;AAhBD,4CAgBC"}

View File

@ -1 +0,0 @@
export declare function getProjectRoot(): string;

View File

@ -1,39 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.getProjectRoot = void 0;
var fs = __importStar(require("fs"));
var path = __importStar(require("path"));
function getProjectRootRec(dirPath) {
if (fs.existsSync(path.join(dirPath, "package.json"))) {
return dirPath;
}
return getProjectRootRec(path.join(dirPath, ".."));
}
var result = undefined;
function getProjectRoot() {
if (result !== undefined) {
return result;
}
return (result = getProjectRootRec(__dirname));
}
exports.getProjectRoot = getProjectRoot;
//# sourceMappingURL=getProjectRoot.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"getProjectRoot.js","sourceRoot":"","sources":["../../src/bin/tools/getProjectRoot.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAAA,qCAAyB;AACzB,yCAA6B;AAE7B,SAAS,iBAAiB,CAAC,OAAe;IACtC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,cAAc,CAAC,CAAC,EAAE;QACnD,OAAO,OAAO,CAAC;KAClB;IACD,OAAO,iBAAiB,CAAC,IAAI,CAAC,IAAI,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC,CAAC;AACvD,CAAC;AAED,IAAI,MAAM,GAAuB,SAAS,CAAC;AAE3C,SAAgB,cAAc;IAC1B,IAAI,MAAM,KAAK,SAAS,EAAE;QACtB,OAAO,MAAM,CAAC;KACjB;IAED,OAAO,CAAC,MAAM,GAAG,iBAAiB,CAAC,SAAS,CAAC,CAAC,CAAC;AACnD,CAAC;AAND,wCAMC"}

View File

@ -1 +0,0 @@
export {};

View File

@ -1,30 +0,0 @@
"use strict";
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
var getProjectRoot_1 = require("./getProjectRoot");
var path_1 = require("path");
var child_process_1 = __importDefault(require("child_process"));
Object.entries(require(path_1.join(getProjectRoot_1.getProjectRoot(), "package.json"))["bin"])
.forEach(function (_a) {
var _b = __read(_a, 2), scriptPath = _b[1];
return child_process_1.default.execSync("chmod +x " + scriptPath, { "cwd": getProjectRoot_1.getProjectRoot() });
});
//# sourceMappingURL=grant-exec-perms.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"grant-exec-perms.js","sourceRoot":"","sources":["../../src/bin/tools/grant-exec-perms.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,mDAAkD;AAClD,6BAAwC;AACxC,gEAA0C;AAE1C,MAAM,CAAC,OAAO,CAAS,OAAO,CAAC,WAAQ,CAAC,+BAAc,EAAE,EAAE,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC;KAC7E,OAAO,CAAC,UAAC,EAAc;QAAd,KAAA,aAAc,EAAX,UAAU,QAAA;IAAM,OAAA,uBAAa,CAAC,QAAQ,CAAC,cAAY,UAAY,EAAE,EAAE,KAAK,EAAE,+BAAc,EAAE,EAAE,CAAC;AAA7E,CAA6E,CAAC,CAAC"}

View File

@ -1,13 +0,0 @@
/// <reference types="node" />
/** Apply a transformation function to every file of directory */
export declare function transformCodebase(params: {
srcDirPath: string;
destDirPath: string;
transformSourceCodeString: (params: {
sourceCode: Buffer;
filePath: string;
}) => {
modifiedSourceCode: Buffer;
newFileName?: string;
} | undefined;
}): void;

View File

@ -1,66 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
if (mod && mod.__esModule) return mod;
var result = {};
if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
__setModuleDefault(result, mod);
return result;
};
var __values = (this && this.__values) || function(o) {
var s = typeof Symbol === "function" && Symbol.iterator, m = s && o[s], i = 0;
if (m) return m.call(o);
if (o && typeof o.length === "number") return {
next: function () {
if (o && i >= o.length) o = void 0;
return { value: o && o[i++], done: !o };
}
};
throw new TypeError(s ? "Object is not iterable." : "Symbol.iterator is not defined.");
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.transformCodebase = void 0;
var fs = __importStar(require("fs"));
var path = __importStar(require("path"));
var crawl_1 = require("./crawl");
/** Apply a transformation function to every file of directory */
function transformCodebase(params) {
var e_1, _a;
var srcDirPath = params.srcDirPath, destDirPath = params.destDirPath, transformSourceCodeString = params.transformSourceCodeString;
try {
for (var _b = __values(crawl_1.crawl(srcDirPath)), _c = _b.next(); !_c.done; _c = _b.next()) {
var file_relative_path = _c.value;
var filePath = path.join(srcDirPath, file_relative_path);
var transformSourceCodeStringResult = transformSourceCodeString({
"sourceCode": fs.readFileSync(filePath),
"filePath": path.join(srcDirPath, file_relative_path)
});
if (transformSourceCodeStringResult === undefined) {
continue;
}
fs.mkdirSync(path.dirname(path.join(destDirPath, file_relative_path)), { "recursive": true });
var newFileName = transformSourceCodeStringResult.newFileName, modifiedSourceCode = transformSourceCodeStringResult.modifiedSourceCode;
fs.writeFileSync(path.join(path.dirname(path.join(destDirPath, file_relative_path)), newFileName !== null && newFileName !== void 0 ? newFileName : path.basename(file_relative_path)), modifiedSourceCode);
}
}
catch (e_1_1) { e_1 = { error: e_1_1 }; }
finally {
try {
if (_c && !_c.done && (_a = _b.return)) _a.call(_b);
}
finally { if (e_1) throw e_1.error; }
}
}
exports.transformCodebase = transformCodebase;
//# sourceMappingURL=transformCodebase.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"transformCodebase.js","sourceRoot":"","sources":["../../src/bin/tools/transformCodebase.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAEA,qCAAyB;AACzB,yCAA6B;AAC7B,iCAAgC;AAEhC,iEAAiE;AACjE,SAAgB,iBAAiB,CAC7B,MAUC;;IAGO,IAAA,UAAU,GAA6C,MAAM,WAAnD,EAAE,WAAW,GAAgC,MAAM,YAAtC,EAAE,yBAAyB,GAAK,MAAM,0BAAX,CAAY;;QAEtE,KAAiC,IAAA,KAAA,SAAA,aAAK,CAAC,UAAU,CAAC,CAAA,gBAAA,4BAAE;YAA/C,IAAM,kBAAkB,WAAA;YAEzB,IAAM,QAAQ,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC,CAAC;YAE3D,IAAM,+BAA+B,GAAG,yBAAyB,CAAC;gBAC9D,YAAY,EAAE,EAAE,CAAC,YAAY,CAAC,QAAQ,CAAC;gBACvC,UAAU,EAAE,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,kBAAkB,CAAC;aACxD,CAAC,CAAC;YAEH,IAAI,+BAA+B,KAAK,SAAS,EAAE;gBAC/C,SAAS;aACZ;YAED,EAAE,CAAC,SAAS,CACR,IAAI,CAAC,OAAO,CACR,IAAI,CAAC,IAAI,CACL,WAAW,EACX,kBAAkB,CACrB,CACJ,EACD,EAAE,WAAW,EAAE,IAAI,EAAE,CACxB,CAAC;YAEM,IAAA,WAAW,GAAyB,+BAA+B,YAAxD,EAAE,kBAAkB,GAAK,+BAA+B,mBAApC,CAAqC;YAE5E,EAAE,CAAC,aAAa,CACZ,IAAI,CAAC,IAAI,CACL,IAAI,CAAC,OAAO,CAAC,IAAI,CAAC,IAAI,CAAC,WAAW,EAAE,kBAAkB,CAAC,CAAC,EACxD,WAAW,aAAX,WAAW,cAAX,WAAW,GAAI,IAAI,CAAC,QAAQ,CAAC,kBAAkB,CAAC,CACnD,EACD,kBAAkB,CACrB,CAAC;SAEL;;;;;;;;;AAGL,CAAC;AApDD,8CAoDC"}

View File

@ -1,3 +0,0 @@
import type { AvailableLanguages } from "./useKeycloakLanguage";
export declare function getLanguageLabel(language: AvailableLanguages): LanguageLabel;
export declare type LanguageLabel = "Deutsch" | "Norsk" | "Русский" | "Svenska" | "Português (Brasil)" | "Lietuvių" | "English" | "Italiano" | "Français" | "中文简体" | "Español" | "Čeština" | "日本語" | "Slovenčina" | "Polish" | "Català" | "Nederlands" | "tr";

View File

@ -1,32 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getLanguageLabel = void 0;
function getLanguageLabel(language) {
switch (language) {
/* spell-checker: disable */
case "es": return "Español";
case "it": return "Italiano";
case "fr": return "Français";
case "ca": return "Català";
case "en": return "English";
case "de": return "Deutsch";
case "no": return "Norsk";
case "pt_BR": return "Português (Brasil)";
case "ru": return "Русский";
case "sk":
case "sv": return "Slovenčina";
case "ja": return "日本語";
case "pl": return "Polish";
case "zh_CN": return "中文简体";
case "sv": return "Svenska";
case "lt": return "Lietuvių";
case "cs": return "Čeština";
case "nl": return "Nederlands";
case "tr": return "tr";
/* spell-checker: enable */
}
return language;
}
exports.getLanguageLabel = getLanguageLabel;
/* spell-checker: enable */
//# sourceMappingURL=getLanguageLabel.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"getLanguageLabel.js","sourceRoot":"","sources":["../../src/lib/i18n/getLanguageLabel.ts"],"names":[],"mappings":";;;AAGA,SAAgB,gBAAgB,CAAC,QAA4B;IAEzD,QAAQ,QAAQ,EAAE;QACd,4BAA4B;QAC5B,KAAK,IAAI,CAAC,CAAC,OAAO,SAAS,CAAC;QAC5B,KAAK,IAAI,CAAC,CAAC,OAAO,UAAU,CAAC;QAC7B,KAAK,IAAI,CAAC,CAAC,OAAO,UAAU,CAAC;QAC7B,KAAK,IAAI,CAAC,CAAC,OAAO,QAAQ,CAAC;QAC3B,KAAK,IAAI,CAAC,CAAC,OAAO,SAAS,CAAC;QAC5B,KAAK,IAAI,CAAC,CAAC,OAAO,SAAS,CAAC;QAC5B,KAAK,IAAI,CAAC,CAAC,OAAO,OAAO,CAAC;QAC1B,KAAK,OAAO,CAAC,CAAC,OAAO,oBAAoB,CAAC;QAC1C,KAAK,IAAI,CAAC,CAAC,OAAO,SAAS,CAAC;QAC5B,KAAK,IAAI,CAAC;QACV,KAAK,IAAI,CAAC,CAAC,OAAO,YAAY,CAAC;QAC/B,KAAK,IAAI,CAAC,CAAC,OAAO,KAAK,CAAC;QACxB,KAAK,IAAI,CAAC,CAAC,OAAO,QAAQ,CAAC;QAC3B,KAAK,OAAO,CAAC,CAAC,OAAO,MAAM,CAAA;QAC3B,KAAK,IAAI,CAAC,CAAC,OAAO,SAAS,CAAC;QAC5B,KAAK,IAAI,CAAC,CAAC,OAAO,UAAU,CAAC;QAC7B,KAAK,IAAI,CAAC,CAAC,OAAO,SAAS,CAAC;QAC5B,KAAK,IAAI,CAAC,CAAC,OAAO,YAAY,CAAC;QAC/B,KAAK,IAAI,CAAC,CAAC,OAAO,IAAI,CAAA;QACtB,2BAA2B;KAC9B;IAED,OAAO,QAAQ,CAAC;AAEpB,CAAC;AA5BD,4CA4BC;AAOD,2BAA2B"}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@ -1,11 +0,0 @@
declare const availableLanguages: ("tr" | "no" | "en" | "ca" | "cs" | "de" | "es" | "fr" | "it" | "ja" | "lt" | "nl" | "pl" | "pt_BR" | "ru" | "sk" | "sv" | "zh_CN")[];
export declare type AvailableLanguages = typeof availableLanguages[number];
export declare const useKeycloakLanguage: () => import("powerhooks").UseNamedStateReturnType<"tr" | "no" | "en" | "ca" | "cs" | "de" | "es" | "fr" | "it" | "ja" | "lt" | "nl" | "pl" | "pt_BR" | "ru" | "sk" | "sv" | "zh_CN", "keycloakLanguage">;
/**
* Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
* it corresponds to.
* If there is no reasonable match it's guessed from navigator.language.
* If still no matches en is returned.
*/
export declare function getKeycloakAvailableLanguageBestGuess(languageLike?: string): AvailableLanguages;
export {};

View File

@ -1,29 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.getKeycloakAvailableLanguageBestGuess = exports.useKeycloakLanguage = void 0;
var powerhooks_1 = require("powerhooks");
var messages_generated_1 = require("./messages.generated");
var objectKeys_1 = require("evt/tools/typeSafety/objectKeys");
var getLanguageLabel_1 = require("./getLanguageLabel");
var availableLanguages = objectKeys_1.objectKeys(messages_generated_1.messages["login"]);
exports.useKeycloakLanguage = powerhooks_1.createUseGlobalState("keycloakLanguage", getKeycloakAvailableLanguageBestGuess, { "persistance": "cookies" }).useKeycloakLanguage;
/**
* Pass in "fr-FR" or "français" for example, it will return the AvailableLanguage
* it corresponds to.
* If there is no reasonable match it's guessed from navigator.language.
* If still no matches en is returned.
*/
function getKeycloakAvailableLanguageBestGuess(languageLike) {
if (languageLike === void 0) { languageLike = navigator.language; }
var iso2LanguageLike = languageLike.split("-")[0].toLowerCase();
var language = availableLanguages.find(function (language) {
return language.toLowerCase().includes(iso2LanguageLike) ||
getLanguageLabel_1.getLanguageLabel(language).toLocaleLowerCase() === languageLike.toLocaleLowerCase();
});
if (language === undefined && languageLike !== navigator.language) {
return getKeycloakAvailableLanguageBestGuess(navigator.language);
}
return "en";
}
exports.getKeycloakAvailableLanguageBestGuess = getKeycloakAvailableLanguageBestGuess;
//# sourceMappingURL=useKeycloakLanguage.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"useKeycloakLanguage.js","sourceRoot":"","sources":["../../src/lib/i18n/useKeycloakLanguage.ts"],"names":[],"mappings":";;;AACA,yCAAkD;AAClD,2DAAgD;AAChD,8DAA6D;AAC7D,uDAAsD;AAEtD,IAAM,kBAAkB,GAAG,uBAAU,CAAC,6BAAQ,CAAC,OAAO,CAAC,CAAC,CAAC;AAI1C,QAAA,mBAAmB,GAAK,iCAAoB,CACvD,kBAAkB,EAClB,qCAAqC,EACrC,EAAE,aAAa,EAAE,SAAS,EAAE,CAC/B,qBAAC;AAEF;;;;;EAKE;AACF,SAAgB,qCAAqC,CACjD,YAAyC;IAAzC,6BAAA,EAAA,eAAuB,SAAS,CAAC,QAAQ;IAGzC,IAAM,gBAAgB,GAAG,YAAY,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC;IAElE,IAAM,QAAQ,GAAG,kBAAkB,CAAC,IAAI,CAAC,UAAA,QAAQ;QAC7C,OAAA,QAAQ,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,gBAAgB,CAAC;YACjD,mCAAgB,CAAC,QAAQ,CAAC,CAAC,iBAAiB,EAAE,KAAK,YAAY,CAAC,iBAAiB,EAAE;IADnF,CACmF,CACtF,CAAC;IAEF,IAAI,QAAQ,KAAK,SAAS,IAAI,YAAY,KAAK,SAAS,CAAC,QAAQ,EAAE;QAC/D,OAAO,qCAAqC,CAAC,SAAS,CAAC,QAAQ,CAAC,CAAC;KACpE;IAED,OAAO,IAAI,CAAC;AAChB,CAAC;AAhBD,sFAgBC"}

View File

@ -1,5 +0,0 @@
import { messages } from "./messages.generated";
export declare type MessageKey = keyof typeof messages["login"]["en"];
export declare function useKeycloakThemeTranslation(): {
t: (key: MessageKey) => string;
};

View File

@ -1,19 +0,0 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.useKeycloakThemeTranslation = void 0;
var useKeycloakLanguage_1 = require("./useKeycloakLanguage");
var messages_generated_1 = require("./messages.generated");
var powerhooks_1 = require("powerhooks");
function useKeycloakThemeTranslation() {
var keycloakLanguage = useKeycloakLanguage_1.useKeycloakLanguage().keycloakLanguage;
var t = powerhooks_1.useConstCallback(function (key) {
var out = messages_generated_1.messages["login"][keycloakLanguage][key];
if (out !== undefined) {
return out;
}
return messages_generated_1.messages["login"]["en"][key];
});
return { t: t };
}
exports.useKeycloakThemeTranslation = useKeycloakThemeTranslation;
//# sourceMappingURL=useKeycloakTranslation.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"useKeycloakTranslation.js","sourceRoot":"","sources":["../../src/lib/i18n/useKeycloakTranslation.ts"],"names":[],"mappings":";;;AACA,6DAA4D;AAC5D,2DAAgD;AAChD,yCAA8C;AAI9C,SAAgB,2BAA2B;IAE/B,IAAA,gBAAgB,GAAK,yCAAmB,EAAE,iBAA1B,CAA2B;IAEnD,IAAM,CAAC,GAAG,6BAAgB,CACtB,UAAC,GAAe;QAEZ,IAAM,GAAG,GAAuB,6BAAQ,CAAC,OAAO,CAAC,CAAC,gBAA+B,CAAC,CAAC,GAAG,CAAC,CAAC;QAExF,IAAI,GAAG,KAAK,SAAS,EAAE;YACnB,OAAO,GAAG,CAAC;SACd;QAED,OAAO,6BAAQ,CAAC,OAAO,CAAC,CAAC,IAAI,CAAC,CAAC,GAAG,CAAC,CAAC;IAExC,CAAC,CACJ,CAAC;IAEF,OAAO,EAAE,CAAC,GAAA,EAAE,CAAC;AAEjB,CAAC;AApBD,kEAoBC"}

4
lib/index.d.ts vendored
View File

@ -1,4 +0,0 @@
export * from "./keycloakFtlValues";
export * from "./i18n/useKeycloakLanguage";
export * from "./i18n/useKeycloakTranslation";
export * from "./i18n/getLanguageLabel";

View File

@ -1,17 +0,0 @@
"use strict";
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } });
}) : (function(o, m, k, k2) {
if (k2 === undefined) k2 = k;
o[k2] = m[k];
}));
var __exportStar = (this && this.__exportStar) || function(m, exports) {
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
};
Object.defineProperty(exports, "__esModule", { value: true });
__exportStar(require("./keycloakFtlValues"), exports);
__exportStar(require("./i18n/useKeycloakLanguage"), exports);
__exportStar(require("./i18n/useKeycloakTranslation"), exports);
__exportStar(require("./i18n/getLanguageLabel"), exports);
//# sourceMappingURL=index.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/lib/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AAAA,sDAAoC;AACpC,6DAA2C;AAC3C,gEAA8C;AAC9C,0DAAwC"}

View File

@ -1,28 +0,0 @@
import type { generateFtlFilesCodeFactory } from "../bin/build-keycloak-theme/generateFtl";
export declare type KeycloakFtlValues = {
pageBasename: Parameters<ReturnType<typeof generateFtlFilesCodeFactory>["generateFtlFilesCode"]>[0]["pageBasename"];
url: {
loginAction: string;
resourcesPath: string;
resourcesCommonPath: string;
loginRestartFlowUrl: string;
};
realm: {
displayName?: string;
displayNameHtml?: string;
internationalizationEnabled: boolean;
};
auth?: {
showUsername: boolean;
showResetCredentials: boolean;
showTryAnotherWayLink: boolean;
attemptedUsername?: boolean;
};
scripts: string[];
message?: {
type: "success" | "warning" | "error" | "info";
summary: string;
};
isAppInitiatedAction: boolean;
};
export declare const keycloakPagesContext: KeycloakFtlValues | undefined;

View File

@ -1,9 +0,0 @@
"use strict";
var _a;
Object.defineProperty(exports, "__esModule", { value: true });
exports.keycloakPagesContext = void 0;
var generateKeycloakThemeResources_1 = require("../bin/build-keycloak-theme/generateKeycloakThemeResources");
var id_1 = require("evt/tools/typeSafety/id");
exports.keycloakPagesContext = (_a = {}, _a[generateKeycloakThemeResources_1.ftlValuesGlobalName] = id_1.id(window[generateKeycloakThemeResources_1.ftlValuesGlobalName]), _a).keycloakPagesContext;
;
//# sourceMappingURL=keycloakFtlValues.js.map

View File

@ -1 +0,0 @@
{"version":3,"file":"keycloakFtlValues.js","sourceRoot":"","sources":["../src/lib/keycloakFtlValues.ts"],"names":[],"mappings":";;;;AAGA,6GAAiG;AAEjG,8CAA6C;AA2C9B,QAAA,oBAAoB,aAC7B,GAAC,oDAAmB,IAAG,OAAE,CAAiC,MAAc,CAAC,oDAAmB,CAAC,CAAC,2BAAG;AACvG,CAAC"}

View File

@ -1,102 +1,50 @@
{
"name": "keycloak-react-theming",
"version": "0.0.21",
"name": "keycloakify",
"version": "2.5.0",
"description": "Keycloak theme generator for Reacts app",
"repository": {
"type": "git",
"url": "git://github.com/garronej/keycloak-react-theming.git"
"url": "git://github.com/garronej/keycloakify.git"
},
"main": "dist/lib/index.js",
"types": "dist/lib/index.d.ts",
"scripts": {
"clean": "rimraf dist/",
"build": "yarn clean && tsc && yarn grant-exec-perms && yarn copy-files",
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"test": "node dist/test/bin/main && node dist/test/lib",
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
"generate-messages": "node dist/bin/generate-i18n-messages.js",
"link_in_test_app": "node dist/bin/link_in_test_app.js",
"_format": "prettier '**/*.{ts,tsx,json,md}'",
"format": "yarn _format --write",
"format:check": "yarn _format --list-different"
},
"main": "lib/index.js",
"types": "lib/index.d.ts",
"bin": {
"build-keycloak-theme": "bin/build-keycloak-theme/index.js",
"download-sample-keycloak-themes": "bin/download-sample-keycloak-themes.js"
"build-keycloak-theme": "dist/bin/build-keycloak-theme/index.js",
"download-builtin-keycloak-theme": "dist/bin/download-builtin-keycloak-theme.js"
},
"lint-staged": {
"*.{ts,tsx,json,md}": [
"prettier --write"
]
},
"husky": {
"hooks": {
"pre-commit": "lint-staged -v"
}
},
"author": "u/garronej",
"license": "MIT",
"files": [
"src/bin/build-keycloak-theme/generateDebugFiles/index.ts",
"src/bin/build-keycloak-theme/generateDebugFiles/standalone-ha.xml",
"src/bin/build-keycloak-theme/generateFtl/ftl2js.ftl",
"src/bin/build-keycloak-theme/generateFtl/index.ts",
"src/bin/build-keycloak-theme/generateJavaStackFiles.ts",
"src/bin/build-keycloak-theme/generateKeycloakThemeResources.ts",
"src/bin/build-keycloak-theme/index.ts",
"src/bin/build-keycloak-theme/replaceImportFromStatic.ts",
"src/bin/download-sample-keycloak-themes.ts",
"src/bin/generate-i18n-messages.ts",
"src/bin/tools/crawl.ts",
"src/bin/tools/downloadAndUnzip.ts",
"src/bin/tools/getProjectRoot.ts",
"src/bin/tools/grant-exec-perms.ts",
"src/bin/tools/transformCodebase.ts",
"src/lib/i18n/getLanguageLabel.ts",
"src/lib/i18n/messages.generated.ts",
"src/lib/i18n/useKeycloakLanguage.ts",
"src/lib/i18n/useKeycloakTranslation.ts",
"src/lib/index.ts",
"src/lib/keycloakFtlValues.ts",
"bin/build-keycloak-theme/generateDebugFiles/index.d.ts",
"bin/build-keycloak-theme/generateDebugFiles/index.js",
"bin/build-keycloak-theme/generateDebugFiles/index.js.map",
"bin/build-keycloak-theme/generateDebugFiles/standalone-ha.xml",
"bin/build-keycloak-theme/generateFtl/ftl2js.ftl",
"bin/build-keycloak-theme/generateFtl/index.d.ts",
"bin/build-keycloak-theme/generateFtl/index.js",
"bin/build-keycloak-theme/generateFtl/index.js.map",
"bin/build-keycloak-theme/generateJavaStackFiles.d.ts",
"bin/build-keycloak-theme/generateJavaStackFiles.js",
"bin/build-keycloak-theme/generateJavaStackFiles.js.map",
"bin/build-keycloak-theme/generateKeycloakThemeResources.d.ts",
"bin/build-keycloak-theme/generateKeycloakThemeResources.js",
"bin/build-keycloak-theme/generateKeycloakThemeResources.js.map",
"bin/build-keycloak-theme/index.d.ts",
"bin/build-keycloak-theme/index.js",
"bin/build-keycloak-theme/index.js.map",
"bin/build-keycloak-theme/replaceImportFromStatic.d.ts",
"bin/build-keycloak-theme/replaceImportFromStatic.js",
"bin/build-keycloak-theme/replaceImportFromStatic.js.map",
"bin/download-sample-keycloak-themes.d.ts",
"bin/download-sample-keycloak-themes.js",
"bin/download-sample-keycloak-themes.js.map",
"bin/generate-i18n-messages.d.ts",
"bin/generate-i18n-messages.js",
"bin/generate-i18n-messages.js.map",
"bin/tools/crawl.d.ts",
"bin/tools/crawl.js",
"bin/tools/crawl.js.map",
"bin/tools/downloadAndUnzip.d.ts",
"bin/tools/downloadAndUnzip.js",
"bin/tools/downloadAndUnzip.js.map",
"bin/tools/getProjectRoot.d.ts",
"bin/tools/getProjectRoot.js",
"bin/tools/getProjectRoot.js.map",
"bin/tools/grant-exec-perms.d.ts",
"bin/tools/grant-exec-perms.js",
"bin/tools/grant-exec-perms.js.map",
"bin/tools/transformCodebase.d.ts",
"bin/tools/transformCodebase.js",
"bin/tools/transformCodebase.js.map",
"lib/i18n/getLanguageLabel.d.ts",
"lib/i18n/getLanguageLabel.js",
"lib/i18n/getLanguageLabel.js.map",
"lib/i18n/messages.generated.d.ts",
"lib/i18n/messages.generated.js",
"lib/i18n/messages.generated.js.map",
"lib/i18n/useKeycloakLanguage.d.ts",
"lib/i18n/useKeycloakLanguage.js",
"lib/i18n/useKeycloakLanguage.js.map",
"lib/i18n/useKeycloakTranslation.d.ts",
"lib/i18n/useKeycloakTranslation.js",
"lib/i18n/useKeycloakTranslation.js.map",
"lib/index.d.ts",
"lib/index.js",
"lib/index.js.map",
"lib/keycloakFtlValues.d.ts",
"lib/keycloakFtlValues.js",
"lib/keycloakFtlValues.js.map"
"src/",
"!src/test/",
"dist/",
"!dist/test/",
"!dist/tsconfig.tsbuildinfo"
],
"keywords": [
"bluehats",
"keycloak",
"react",
"theme",
@ -105,21 +53,29 @@
"login",
"register"
],
"homepage": "https://github.com/garronej/keycloak-react-theming",
"homepage": "https://github.com/garronej/keycloakify",
"devDependencies": {
"@types/node": "^10.0.0",
"copyfiles": "^2.4.1",
"denoify": "^0.6.5",
"properties-parser": "^0.3.1",
"rimraf": "^3.0.2",
"scripting-tools": "^0.19.13",
"typescript": "^4.1.5",
"@types/react": "^17.0.0",
"react": "^17.0.1"
"copyfiles": "^2.4.1",
"properties-parser": "^0.3.1",
"react": "^17.0.1",
"rimraf": "^3.0.2",
"typescript": "^4.2.3",
"husky": "^4.3.8",
"lint-staged": "^11.0.0",
"prettier": "^2.3.0"
},
"dependencies": {
"@emotion/react": "^11.4.1",
"cheerio": "^1.0.0-rc.5",
"evt": "^1.9.12",
"powerhooks": "^0.0.14"
"evt": "2.0.0-beta.38",
"minimal-polyfills": "^2.2.1",
"path": "^0.12.7",
"powerhooks": "^0.9.3",
"react-markdown": "^5.0.3",
"scripting-tools": "^0.19.13",
"tsafe": "^0.8.1",
"tss-react": "^1.0.0"
}
}

View File

@ -0,0 +1,3 @@
export const keycloakVersions = ["11.0.3", "15.0.2"] as const;
export type KeycloakVersion = typeof keycloakVersions[number];

View File

@ -0,0 +1,132 @@
import { generateKeycloakThemeResources } from "./generateKeycloakThemeResources";
import { generateJavaStackFiles } from "./generateJavaStackFiles";
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
import * as child_process from "child_process";
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
import { URL } from "url";
type ParsedPackageJson = {
name: string;
version: string;
homepage?: string;
};
const reactProjectDirPath = process.cwd();
const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets";
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
function sanitizeThemeName(name: string) {
return name
.replace(/^@(.*)/, "$1")
.split("/")
.join("-");
}
export function main() {
console.log("🔏 Building the keycloak theme...⌚");
const extraPagesId: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraPages"] ?? [];
const extraThemeProperties: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraThemeProperties"] ?? [];
const themeName = sanitizeThemeName(parsedPackageJson.name);
generateKeycloakThemeResources({
keycloakThemeBuildingDirPath,
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
themeName,
...(() => {
const url = (() => {
const { homepage } = parsedPackageJson;
return homepage === undefined ? undefined : new URL(homepage);
})();
return {
"urlPathname": url === undefined ? "/" : url.pathname.replace(/([^/])$/, "$1/"),
"urlOrigin": !doUseExternalAssets
? undefined
: (() => {
if (url === undefined) {
console.error("ERROR: You must specify 'homepage' in your package.json");
process.exit(-1);
}
return url.origin;
})(),
};
})(),
extraPagesId,
extraThemeProperties,
//We have to leave it at that otherwise we break our default theme.
//Problem is that we can't guarantee that the the old resources common
//will still be available on the newer keycloak version.
"keycloakVersion": "11.0.3",
});
const { jarFilePath } = generateJavaStackFiles({
version: parsedPackageJson.version,
themeName,
homepage: parsedPackageJson.homepage,
keycloakThemeBuildingDirPath,
});
child_process.execSync("mvn package", {
"cwd": keycloakThemeBuildingDirPath,
});
generateDebugFiles({
keycloakThemeBuildingDirPath,
themeName,
"keycloakVersion": "15.0.2",
});
console.log(
[
"",
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`,
`It is to be placed in "/opt/jboss/keycloak/standalone/deployments" in the container running a jboss/keycloak Docker image.`,
"",
"Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:",
"",
"value.yaml: ",
" extraInitContainers: |",
" - name: realm-ext-provider",
" image: curlimages/curl",
" imagePullPolicy: IfNotPresent",
" command:",
" - sh",
" args:",
" - -c",
` - curl -L -f -S -o /extensions/${pathBasename(jarFilePath)} https://AN.URL.FOR/${pathBasename(jarFilePath)}`,
" volumeMounts:",
" - name: extensions",
" mountPath: /extensions",
" ",
" extraVolumeMounts: |",
" - name: extensions",
" mountPath: /opt/jboss/keycloak/standalone/deployments",
" extraEnv: |",
" - name: KEYCLOAK_USER",
" value: admin",
" - name: KEYCLOAK_PASSWORD",
" value: xxxxxxxxx",
" - name: JAVA_OPTS",
" value: -Dkeycloak.profile=preview",
"",
"",
"To test your theme locally, with hot reloading, you can spin up a Keycloak container image with the theme loaded by running:",
"",
`👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename))} 👈`,
"",
'To enable the theme within keycloak log into the admin console ( 👉 http://localhost:8080 username: admin, password: admin 👈), create a realm (called "myrealm" for example),',
`go to your realm settings, click on the theme tab then select ${themeName}.`,
`More details: https://www.keycloak.org/getting-started/getting-started-docker`,
"",
"Once your container is up and configured 👉 http://localhost:8080/auth/realms/myrealm/account 👈",
"",
].join("\n"),
);
}

View File

@ -0,0 +1 @@
export const ftlValuesGlobalName = "kcContext";

View File

@ -0,0 +1,84 @@
import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname } from "path";
export const containerLaunchScriptBasename = "start_keycloak_testing_container.sh";
/** Files for being able to run a hot reload keycloak container */
export function generateDebugFiles(params: { keycloakVersion: "11.0.3" | "15.0.2"; themeName: string; keycloakThemeBuildingDirPath: string }) {
const { themeName, keycloakThemeBuildingDirPath, keycloakVersion } = params;
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, "Dockerfile"),
Buffer.from(
[
`FROM jboss/keycloak:${keycloakVersion}`,
"",
"USER root",
"",
"WORKDIR /",
"",
"ADD configuration /opt/jboss/keycloak/standalone/configuration/",
"",
'ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ]',
].join("\n"),
"utf8",
),
);
const dockerImage = `${themeName}/keycloak-hot-reload`;
const containerName = "keycloak-testing-container";
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename),
Buffer.from(
[
"#!/bin/bash",
"",
`cd ${keycloakThemeBuildingDirPath}`,
"",
`docker rm ${containerName} || true`,
"",
`docker build . -t ${dockerImage}`,
"",
"docker run \\",
" -p 8080:8080 \\",
` --name ${containerName} \\`,
" -e KEYCLOAK_USER=admin \\",
" -e KEYCLOAK_PASSWORD=admin \\",
" -e JAVA_OPTS=-Dkeycloak.profile=preview \\",
` -v ${pathJoin(
keycloakThemeBuildingDirPath,
"src",
"main",
"resources",
"theme",
themeName,
)}:/opt/jboss/keycloak/themes/${themeName}:rw \\`,
` -it ${dockerImage}:latest`,
"",
].join("\n"),
"utf8",
),
{ "mode": 0o755 },
);
const standaloneHaFilePath = pathJoin(keycloakThemeBuildingDirPath, "configuration", `standalone-ha.xml`);
try {
fs.mkdirSync(pathDirname(standaloneHaFilePath));
} catch {}
fs.writeFileSync(
standaloneHaFilePath,
fs
.readFileSync(pathJoin(__dirname, `standalone-ha_${keycloakVersion}.xml`))
.toString("utf8")
.replace(
new RegExp(
["<staticMaxAge>2592000</staticMaxAge>", "<cacheThemes>true</cacheThemes>", "<cacheTemplates>true</cacheTemplates>"].join("\\s*"),
"g",
),
["<staticMaxAge>-1</staticMaxAge>", "<cacheThemes>false</cacheThemes>", "<cacheTemplates>false</cacheTemplates>"].join("\n"),
),
);
}

View File

@ -1,74 +1 @@
import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname, basename as pathBasename } from "path";
export const containerLaunchScriptBasename = "start_keycloak_testing_container.sh";
/** Files for being able to run a hot reload keycloak container */
export function generateDebugFiles(
params: {
packageJsonName: string;
keycloakThemeBuildingDirPath: string;
}
) {
const { packageJsonName, keycloakThemeBuildingDirPath } = params;
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, "Dockerfile"),
Buffer.from(
[
"FROM jboss/keycloak:11.0.3",
"",
"USER root",
"",
"WORKDIR /",
"",
"ADD configuration /opt/jboss/keycloak/standalone/configuration/",
"",
'ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ]',
].join("\n"),
"utf8"
)
);
const dockerImage = `${packageJsonName}/keycloak-hot-reload`;
const containerName = "keycloak-testing-container";
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename),
Buffer.from(
[
"#!/bin/bash",
"",
`cd ${keycloakThemeBuildingDirPath}`,
"",
`docker rm ${containerName} || true`,
"",
`docker build . -t ${dockerImage}`,
"",
"docker run \\",
" -p 8080:8080 \\",
` --name ${containerName} \\`,
" -e KEYCLOAK_USER=admin \\",
" -e KEYCLOAK_PASSWORD=admin \\",
` -v ${pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", packageJsonName)
}:/opt/jboss/keycloak/themes/${packageJsonName}:rw \\`,
` -it ${dockerImage}:latest`,
""
].join("\n"),
"utf8"
),
{ "mode": 0o755 }
);
const standaloneHaFilePath = pathJoin(keycloakThemeBuildingDirPath, "configuration", "standalone-ha.xml");
try { fs.mkdirSync(pathDirname(standaloneHaFilePath)); } catch { }
fs.writeFileSync(
standaloneHaFilePath,
fs.readFileSync(pathJoin(__dirname, pathBasename(standaloneHaFilePath)))
);
}
export * from "./generateDebugFiles";

View File

@ -462,9 +462,9 @@
<master-realm-name>master</master-realm-name>
<scheduled-task-interval>900</scheduled-task-interval>
<theme>
<staticMaxAge>-1</staticMaxAge>
<cacheThemes>false</cacheThemes>
<cacheTemplates>false</cacheTemplates>
<staticMaxAge>2592000</staticMaxAge>
<cacheThemes>true</cacheThemes>
<cacheTemplates>true</cacheTemplates>
<welcomeTheme>${env.KEYCLOAK_WELCOME_THEME:keycloak}</welcomeTheme>
<default>${env.KEYCLOAK_DEFAULT_THEME:keycloak}</default>
<dir>${jboss.home.dir}/themes</dir>

View File

@ -1,6 +1,6 @@
<?xml version='1.0' encoding='UTF-8'?>
<server xmlns="urn:jboss:domain:13.0">
<server xmlns="urn:jboss:domain:16.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>
<extension module="org.jboss.as.clustering.jgroups"/>
@ -23,10 +23,9 @@
<extension module="org.wildfly.extension.bean-validation"/>
<extension module="org.wildfly.extension.core-management"/>
<extension module="org.wildfly.extension.elytron"/>
<extension module="org.wildfly.extension.health"/>
<extension module="org.wildfly.extension.io"/>
<extension module="org.wildfly.extension.microprofile.config-smallrye"/>
<extension module="org.wildfly.extension.microprofile.health-smallrye"/>
<extension module="org.wildfly.extension.microprofile.metrics-smallrye"/>
<extension module="org.wildfly.extension.metrics"/>
<extension module="org.wildfly.extension.request-controller"/>
<extension module="org.wildfly.extension.security.manager"/>
<extension module="org.wildfly.extension.undertow"/>
@ -136,9 +135,6 @@
<datasource jndi-name="java:jboss/datasources/KeycloakDS" pool-name="KeycloakDS" enabled="true" use-java-context="true" statistics-enabled="${wildfly.datasources.statistics-enabled:${wildfly.statistics-enabled:false}}">
<connection-url>jdbc:h2:${jboss.server.data.dir}/keycloak;AUTO_SERVER=TRUE</connection-url>
<driver>h2</driver>
<pool>
<max-pool-size>100</max-pool-size>
</pool>
<security>
<user-name>sa</user-name>
<password>sa</password>
@ -154,7 +150,7 @@
<subsystem xmlns="urn:jboss:domain:deployment-scanner:2.0">
<deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="${jboss.deployment.scanner.rollback.on.failure:false}"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:ee:5.0">
<subsystem xmlns="urn:jboss:domain:ee:6.0">
<spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
<concurrent>
<context-services>
@ -164,15 +160,15 @@
<managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" context-service="default"/>
</managed-thread-factories>
<managed-executor-services>
<managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" context-service="default" hung-task-threshold="60000" keepalive-time="5000"/>
<managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" context-service="default" hung-task-termination-period="0" hung-task-threshold="60000" keepalive-time="5000"/>
</managed-executor-services>
<managed-scheduled-executor-services>
<managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-threshold="60000" keepalive-time="3000"/>
<managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-termination-period="0" hung-task-threshold="60000" keepalive-time="3000"/>
</managed-scheduled-executor-services>
</concurrent>
<default-bindings context-service="java:jboss/ee/concurrency/context/default" datasource="java:jboss/datasources/ExampleDS" managed-executor-service="java:jboss/ee/concurrency/executor/default" managed-scheduled-executor-service="java:jboss/ee/concurrency/scheduler/default" managed-thread-factory="java:jboss/ee/concurrency/factory/default"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:ejb3:7.0">
<subsystem xmlns="urn:jboss:domain:ejb3:9.0">
<session-bean>
<stateless>
<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
@ -199,7 +195,7 @@
<file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
</data-stores>
</timer-service>
<remote connector-ref="http-remoting-connector" thread-pool-name="default">
<remote cluster="ejb" connectors="http-remoting-connector" thread-pool-name="default">
<channel-creation-options>
<option name="MAX_OUTBOUND_MESSAGES" value="1234" type="remoting"/>
</channel-creation-options>
@ -215,7 +211,7 @@
<statistics enabled="${wildfly.ejb3.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
<log-system-exceptions value="true"/>
</subsystem>
<subsystem xmlns="urn:wildfly:elytron:10.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
<subsystem xmlns="urn:wildfly:elytron:13.0" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
<providers>
<aggregate-providers name="combined-providers">
<providers name="elytron"/>
@ -273,6 +269,7 @@
<permission class-name="org.wildfly.extension.batch.jberet.deployment.BatchPermission" module="org.wildfly.extension.batch.jberet" target-name="*"/>
<permission class-name="org.wildfly.transaction.client.RemoteTransactionPermission" module="org.wildfly.transaction.client"/>
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
</permission-set>
</permission-sets>
<http>
@ -314,42 +311,82 @@
</mechanism-provider-filtering-sasl-server-factory>
<provider-sasl-server-factory name="global"/>
</sasl>
<tls>
<key-stores>
<key-store name="applicationKS">
<credential-reference clear-text="password"/>
<implementation type="JKS"/>
<file path="application.keystore" relative-to="jboss.server.config.dir"/>
</key-store>
</key-stores>
<key-managers>
<key-manager name="applicationKM" key-store="applicationKS" generate-self-signed-certificate-host="localhost">
<credential-reference clear-text="password"/>
</key-manager>
</key-managers>
<server-ssl-contexts>
<server-ssl-context name="applicationSSC" key-manager="applicationKM"/>
</server-ssl-contexts>
</tls>
</subsystem>
<subsystem xmlns="urn:jboss:domain:infinispan:10.0">
<cache-container name="keycloak" module="org.keycloak.keycloak-model-infinispan">
<subsystem xmlns="urn:wildfly:health:1.0" security-enabled="false"/>
<subsystem xmlns="urn:jboss:domain:infinispan:12.0">
<cache-container name="ejb" default-cache="dist" aliases="sfsb" modules="org.wildfly.clustering.ejb.infinispan">
<transport lock-timeout="60000"/>
<local-cache name="realms">
<object-memory size="10000"/>
</local-cache>
<local-cache name="users">
<object-memory size="10000"/>
</local-cache>
<local-cache name="authorization">
<object-memory size="10000"/>
</local-cache>
<local-cache name="keys">
<object-memory size="1000"/>
<expiration max-idle="3600000"/>
</local-cache>
<replicated-cache name="work"/>
<distributed-cache name="sessions" owners="1"/>
<distributed-cache name="authenticationSessions" owners="1"/>
<distributed-cache name="offlineSessions" owners="1"/>
<distributed-cache name="clientSessions" owners="1"/>
<distributed-cache name="offlineClientSessions" owners="1"/>
<distributed-cache name="loginFailures" owners="1"/>
<distributed-cache name="actionTokens" owners="2">
<object-memory size="-1"/>
<expiration interval="300000" max-idle="-1"/>
<distributed-cache name="dist">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store/>
</distributed-cache>
</cache-container>
<cache-container name="server" aliases="singleton cluster" default-cache="default" module="org.wildfly.clustering.server">
<cache-container name="keycloak" modules="org.keycloak.keycloak-model-infinispan">
<transport lock-timeout="60000"/>
<local-cache name="realms">
<heap-memory size="10000"/>
</local-cache>
<local-cache name="users">
<heap-memory size="10000"/>
</local-cache>
<local-cache name="authorization">
<heap-memory size="10000"/>
</local-cache>
<local-cache name="keys">
<heap-memory size="1000"/>
<expiration max-idle="3600000"/>
</local-cache>
<replicated-cache name="work">
<expiration lifespan="900000000000000000"/>
</replicated-cache>
<distributed-cache name="sessions" owners="1">
<expiration lifespan="900000000000000000"/>
</distributed-cache>
<distributed-cache name="authenticationSessions" owners="1">
<expiration lifespan="900000000000000000"/>
</distributed-cache>
<distributed-cache name="offlineSessions" owners="1">
<expiration lifespan="900000000000000000"/>
</distributed-cache>
<distributed-cache name="clientSessions" owners="1">
<expiration lifespan="900000000000000000"/>
</distributed-cache>
<distributed-cache name="offlineClientSessions" owners="1">
<expiration lifespan="900000000000000000"/>
</distributed-cache>
<distributed-cache name="loginFailures" owners="1">
<expiration lifespan="900000000000000000"/>
</distributed-cache>
<distributed-cache name="actionTokens" owners="2">
<heap-memory size="-1"/>
<expiration interval="300000" lifespan="900000000000000000" max-idle="-1"/>
</distributed-cache>
</cache-container>
<cache-container name="server" default-cache="default" aliases="singleton cluster" modules="org.wildfly.clustering.server">
<transport lock-timeout="60000"/>
<replicated-cache name="default">
<transaction mode="BATCH"/>
</replicated-cache>
</cache-container>
<cache-container name="web" default-cache="dist" module="org.wildfly.clustering.web.infinispan">
<cache-container name="web" default-cache="dist" modules="org.wildfly.clustering.web.infinispan">
<transport lock-timeout="60000"/>
<replicated-cache name="sso">
<locking isolation="REPEATABLE_READ"/>
@ -362,23 +399,15 @@
</distributed-cache>
<distributed-cache name="routing"/>
</cache-container>
<cache-container name="ejb" aliases="sfsb" default-cache="dist" module="org.wildfly.clustering.ejb.infinispan">
<transport lock-timeout="60000"/>
<distributed-cache name="dist">
<locking isolation="REPEATABLE_READ"/>
<transaction mode="BATCH"/>
<file-store/>
</distributed-cache>
</cache-container>
<cache-container name="hibernate" module="org.infinispan.hibernate-cache">
<cache-container name="hibernate" modules="org.infinispan.hibernate-cache">
<transport lock-timeout="60000"/>
<local-cache name="local-query">
<object-memory size="10000"/>
<heap-memory size="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<invalidation-cache name="entity">
<transaction mode="NON_XA"/>
<object-memory size="10000"/>
<heap-memory size="10000"/>
<expiration max-idle="100000"/>
</invalidation-cache>
<replicated-cache name="timestamps"/>
@ -450,7 +479,7 @@
<remoting-connector/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jpa:1.1">
<jpa default-datasource="" default-extended-persistence-inheritance="DEEP"/>
<jpa default-extended-persistence-inheritance="DEEP"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:keycloak-server:1.1">
<web-context>auth</web-context>
@ -462,9 +491,9 @@
<master-realm-name>master</master-realm-name>
<scheduled-task-interval>900</scheduled-task-interval>
<theme>
<staticMaxAge>-1</staticMaxAge>
<cacheThemes>false</cacheThemes>
<cacheTemplates>false</cacheTemplates>
<staticMaxAge>2592000</staticMaxAge>
<cacheThemes>true</cacheThemes>
<cacheTemplates>true</cacheTemplates>
<welcomeTheme>${env.KEYCLOAK_WELCOME_THEME:keycloak}</welcomeTheme>
<default>${env.KEYCLOAK_DEFAULT_THEME:keycloak}</default>
<dir>${jboss.home.dir}/themes</dir>
@ -547,9 +576,7 @@
<smtp-server outbound-socket-binding-ref="mail-smtp"/>
</mail-session>
</subsystem>
<subsystem xmlns="urn:wildfly:microprofile-config-smallrye:1.0"/>
<subsystem xmlns="urn:wildfly:microprofile-health-smallrye:2.0" security-enabled="false" empty-liveness-checks-status="${env.MP_HEALTH_EMPTY_LIVENESS_CHECKS_STATUS:UP}" empty-readiness-checks-status="${env.MP_HEALTH_EMPTY_READINESS_CHECKS_STATUS:UP}"/>
<subsystem xmlns="urn:wildfly:microprofile-metrics-smallrye:2.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:wildfly}"/>
<subsystem xmlns="urn:wildfly:metrics:1.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:wildfly}"/>
<subsystem xmlns="urn:jboss:domain:modcluster:5.0">
<proxy name="default" advertise-socket="modcluster" listener="ajp">
<dynamic-load-provider>
@ -603,7 +630,7 @@
</maximum-set>
</deployment-permissions>
</subsystem>
<subsystem xmlns="urn:jboss:domain:transactions:5.0">
<subsystem xmlns="urn:jboss:domain:transactions:6.0">
<core-environment node-identifier="${jboss.tx.node.id:1}">
<process-id>
<uuid/>
@ -613,12 +640,12 @@
<coordinator-environment statistics-enabled="${wildfly.transactions.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
<object-store path="tx-object-store" relative-to="jboss.server.data.dir"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:undertow:11.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other" statistics-enabled="${wildfly.undertow.statistics-enabled:${wildfly.statistics-enabled:false}}">
<subsystem xmlns="urn:jboss:domain:undertow:12.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other" statistics-enabled="${wildfly.undertow.statistics-enabled:${wildfly.statistics-enabled:false}}">
<buffer-cache name="default"/>
<server name="default-server">
<ajp-listener name="ajp" socket-binding="ajp"/>
<http-listener name="default" read-timeout="30000" socket-binding="http" redirect-socket="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" enable-http2="true"/>
<https-listener name="https" read-timeout="30000" socket-binding="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" security-realm="ApplicationRealm" enable-http2="true"/>
<http-listener name="default" socket-binding="http" redirect-socket="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" enable-http2="true"/>
<https-listener name="https" socket-binding="https" proxy-address-forwarding="${env.PROXY_ADDRESS_FORWARDING:false}" security-realm="ApplicationRealm" enable-http2="true"/>
<host name="default-host" alias="localhost">
<location name="/" handler="welcome-content"/>
<http-invoker security-realm="ApplicationRealm"/>
@ -660,7 +687,7 @@
<socket-binding name="txn-recovery-environment" port="4712"/>
<socket-binding name="txn-status-manager" port="4713"/>
<outbound-socket-binding name="mail-smtp">
<remote-destination host="localhost" port="25"/>
<remote-destination host="${jboss.mail.server.host:localhost}" port="${jboss.mail.server.port:25}"/>
</outbound-socket-binding>
</socket-binding-group>
</server>

View File

@ -0,0 +1,28 @@
Object.defineProperty(
Object,
"deepAssign",
{
"value": function callee(target, source) {
Object.keys(source).forEach(function (key) {
var value = source[key];
if (target[key] === undefined) {
target[key] = value;
return;
}
if (value instanceof Object) {
if (value instanceof Array) {
value.forEach(function (entry) {
target[key].push(entry);
});
return;
}
callee(target[key], value);
return;
}
target[key] = value;
});
return target;
}
}
);

View File

@ -0,0 +1,208 @@
<script>const _=
<#macro objectToJson object depth>
<@compress>
<#local isHash = false>
<#attempt>
<#local isHash = object?is_hash || object?is_hash_ex>
<#recover>
/* can't evaluate if object is hash */
undefined
<#return>
</#attempt>
<#if isHash>
<#local keys = "">
<#attempt>
<#local keys = object?keys>
<#recover>
/* can't list keys of object */
undefined
<#return>
</#attempt>
{${'\n'}
<#list keys as key>
<#if key == "class">
/* skipping "class" property of object */
<#continue>
</#if>
<#local value = "">
<#attempt>
<#local value = object[key]>
<#recover>
/* couldn't dereference ${key} of object */
<#continue>
</#attempt>
<#if depth gt 4>
/* Avoid calling recustively too many times depth: ${depth}, key: ${key} */
<#continue>
</#if>
"${key}": <@objectToJson object=value depth=depth+1/>,
</#list>
}${'\n'}
<#return>
</#if>
<#local isMethod = "">
<#attempt>
<#local isMethod = object?is_method>
<#recover>
/* can't test if object is a method */
undefined
<#return>
</#attempt>
<#if isMethod>
undefined
<#return>
</#if>
<#local isBoolean = "">
<#attempt>
<#local isBoolean = object?is_boolean>
<#recover>
/* can't test if object is a boolean */
undefined
<#return>
</#attempt>
<#if isBoolean>
${object?c}
<#return>
</#if>
<#local isEnumerable = "">
<#attempt>
<#local isEnumerable = object?is_enumerable>
<#recover>
/* can't test if object is enumerable */
undefined
<#return>
</#attempt>
<#if isEnumerable>
[${'\n'}
<#list object as item>
<@objectToJson object=item depth=depth+1/>,
</#list>
]${'\n'}
<#return>
</#if>
<#attempt>
"${object?replace('"', '\\"')?no_esc}"
<#recover>
/* couldn't convert into string non hash, non method, non boolean, non enumerable object */
undefined;
<#return>
</#attempt>
</@compress>
</#macro>
(()=>{
const nonAutomaticallyConvertible = {
"messagesPerField": {
<#assign fieldNames = ["global", "userLabel", "username", "email", "firstName", "lastName", "password", "password-confirm"]>
<#attempt>
<#list profile.attributes as attribute>
<#assign fieldNames += [attribute.name]>
</#list>
<#recover>
</#attempt>
"printIfExists": function (fieldName, x) {
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
return "${messagesPerField.printIfExists(fieldName,'1')}" ? x : undefined;
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
},
"existsError": function (fieldName) {
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
return <#if messagesPerField.existsError('${fieldName}')>true<#else>false</#if>;
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
},
"get": function (fieldName) {
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
<#if messagesPerField.existsError('${fieldName}')>
return "${messagesPerField.get('${fieldName}')?no_esc}";
</#if>
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
},
"exists": function (fieldName) {
<#list fieldNames as fieldName>
if(fieldName === "${fieldName}" ){
<#attempt>
return <#if messagesPerField.exists('${fieldName}')>true<#else>false</#if>;
<#recover>
</#attempt>
}
</#list>
throw new Error("There is no " + fieldName + " field");
}
},
"msg": function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); },
"advancedMsg": function(){ throw new Error("use import { useKcMessage } from 'keycloakify'"); }
};
const out = {};
Object.deepAssign(
out,
//Removing all the undefined
JSON.parse(JSON.stringify(<@objectToJson object=.data_model depth=0 />))
);
Object.deepAssign(
out,
nonAutomaticallyConvertible
);
return out;
})()
</script>

View File

@ -1,113 +0,0 @@
<script>const _=
{
"url": {
"loginAction": "${url.loginAction}",
"resourcesPath": "${url.resourcesPath}",
"resourcesCommonPath": "${url.resourcesCommonPath}",
"loginRestartFlowUrl": "${url.loginRestartFlowUrl}"
},
"realm": {
"displayName": "${realm.displayName!''}" || undefined,
"displayNameHtml": "${realm.displayNameHtml!''}" || undefined,
"internationalizationEnabled": ${realm.internationalizationEnabled?c}
},
"locale": (function (){
<#if realm.internationalizationEnabled>
return {
"supported": (function(){
<#if realm.internationalizationEnabled>
var out= [];
<#list locale.supported as lng>
out.push({
"url": "${lng.url}",
"label": "${lng.label}",
"languageTag": "${lng.languageTag}"
});
</#list>
return out;
</#if>
return undefined;
})(),
"current": "${locale.current}"
};
</#if>
return undefined;
})(),
"auth": (function (){
<#if auth?has_content>
var out= {
"showUsername": ${auth.showUsername()?c},
"showResetCredentials": ${auth.showResetCredentials()?c},
"showTryAnotherWayLink": ${auth.showTryAnotherWayLink()?c}
};
<#if auth.showUsername() && !auth.showResetCredentials()>
Object.assign(
out,
{
"attemptedUsername": "${auth.attemptedUsername}"
}
);
</#if>
return out;
</#if>
return undefined;
})(),
"scripts": (function(){
var out = [];
<#if scripts??>
<#list scripts as script>
out.push("${script}");
</#list>
</#if>
return out;
})(),
"message": (function (){
<#if message?has_content>
return { 
"type": "${message.type}",
"summary": "${kcSanitize(message.summary)?no_esc}"
};
</#if>
return undefined;
})(),
"isAppInitiatedAction": (function (){
<#if isAppInitiatedAction??>
return true;
</#if>
return false;
})()
}
</script>

View File

@ -0,0 +1,150 @@
import cheerio from "cheerio";
import { replaceImportsFromStaticInJsCode, replaceImportsInInlineCssCode, generateCssCodeToDefineGlobals } from "../replaceImportFromStatic";
import fs from "fs";
import { join as pathJoin } from "path";
import { objectKeys } from "tsafe/objectKeys";
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
export const pageIds = [
"login.ftl",
"register.ftl",
"register-user-profile.ftl",
"info.ftl",
"error.ftl",
"login-reset-password.ftl",
"login-verify-email.ftl",
"terms.ftl",
"login-otp.ftl",
"login-update-profile.ftl",
"login-idp-link-confirm.ftl",
] as const;
export type PageId = typeof pageIds[number];
function loadAdjacentFile(fileBasename: string) {
return fs.readFileSync(pathJoin(__dirname, fileBasename)).toString("utf8");
}
export function generateFtlFilesCodeFactory(params: {
cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string;
urlPathname: string;
urlOrigin: undefined | string;
}) {
const { cssGlobalsToDefine, indexHtmlCode, urlPathname, urlOrigin } = params;
const $ = cheerio.load(indexHtmlCode);
$("script:not([src])").each((...[, element]) => {
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": $(element).html()!,
urlOrigin,
});
$(element).text(fixedJsCode);
});
$("style").each((...[, element]) => {
const { fixedCssCode } = replaceImportsInInlineCssCode({
"cssCode": $(element).html()!,
"urlPathname": params.urlPathname,
urlOrigin,
});
$(element).text(fixedCssCode);
});
(
[
["link", "href"],
["script", "src"],
] as const
).forEach(([selector, attrName]) =>
$(selector).each((...[, element]) => {
const href = $(element).attr(attrName);
if (href === undefined) {
return;
}
$(element).attr(
attrName,
urlOrigin !== undefined
? href.replace(/^\//, `${urlOrigin}/`)
: href.replace(new RegExp(`^${urlPathname.replace(/\//g, "\\/")}`), "${url.resourcesPath}/build/"),
);
}),
);
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
const ftlPlaceholders = {
'{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadAdjacentFile("common.ftl").match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1],
"<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
"<#if scripts??>",
" <#list scripts as script>",
' <script src="${script}" type="text/javascript"></script>',
" </#list>",
"</#if>",
].join("\n"),
};
const pageSpecificCodePlaceholder = "<!-- dIddLqMeOedErIdLsPdNdI9dSl42sw -->";
$("head").prepend(
[
...(Object.keys(cssGlobalsToDefine).length === 0
? []
: [
"",
"<style>",
generateCssCodeToDefineGlobals({
cssGlobalsToDefine,
urlPathname,
}).cssCodeToPrependInHead,
"</style>",
"",
]),
"<script>",
loadAdjacentFile("Object.deepAssign.js"),
"</script>",
"<script>",
` window.${ftlValuesGlobalName}= Object.assign(`,
` {},`,
` ${objectKeys(ftlPlaceholders)[0]}`,
" );",
"</script>",
"",
pageSpecificCodePlaceholder,
"",
objectKeys(ftlPlaceholders)[1],
].join("\n"),
);
const partiallyFixedIndexHtmlCode = $.html();
function generateFtlFilesCode(params: { pageId: string }): {
ftlCode: string;
} {
const { pageId } = params;
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
let ftlCode = $.html().replace(
pageSpecificCodePlaceholder,
[
"<script>",
` Object.deepAssign(`,
` window.${ftlValuesGlobalName},`,
` { "pageId": "${pageId}" }`,
" );",
"</script>",
].join("\n"),
);
objectKeys(ftlPlaceholders).forEach(id => (ftlCode = ftlCode.replace(id, ftlPlaceholders[id])));
return { ftlCode };
}
return { generateFtlFilesCode };
}

View File

@ -1,127 +1 @@
import cheerio from "cheerio";
import {
replaceImportFromStaticInJsCode,
generateCssCodeToDefineGlobals
} from "../replaceImportFromStatic";
import fs from "fs";
import { join as pathJoin } from "path";
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
export function generateFtlFilesCodeFactory(
params: {
ftlValuesGlobalName: string;
cssGlobalsToDefine: Record<string, string>;
indexHtmlCode: string;
}
) {
const { ftlValuesGlobalName, cssGlobalsToDefine, indexHtmlCode } = params;
const $ = cheerio.load(indexHtmlCode);
$("script:not([src])").each((...[, element]) => {
const { fixedJsCode } = replaceImportFromStaticInJsCode({
ftlValuesGlobalName,
"jsCode": $(element).html()!
});
$(element).text(fixedJsCode);
});
([
["link", "href"],
["script", "src"],
] as const).forEach(([selector, attrName]) =>
$(selector).each((...[, element]) => {
const href = $(element).attr(attrName);
if (!href?.startsWith("/")) {
return;
}
$(element).attr(attrName, "${url.resourcesPath}" + href);
})
);
//FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later.
const ftlPlaceholders = {
'{ "x": "xIdLqMeOed9sdLdIdOxdK0d" }':
fs.readFileSync(pathJoin(__dirname, "ftl2js.ftl"))
.toString("utf8")
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1],
'<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->':
[
'<#if scripts??>',
' <#list scripts as script>',
' <script src="${script}" type="text/javascript"></script>',
' </#list>',
'</#if>',
].join("\n")
};
$("head").prepend(
[
...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
'',
'<style>',
generateCssCodeToDefineGlobals(
{ cssGlobalsToDefine }
).cssCodeToPrependInHead,
'</style>',
''
]),
'<script>',
' Object.assign(',
` window.${ftlValuesGlobalName},`,
` ${objectKeys(ftlPlaceholders)[0]}`,
' );',
'</script>',
'',
objectKeys(ftlPlaceholders)[1],
''
].join("\n"),
);
const partiallyFixedIndexHtmlCode = $.html();
function generateFtlFilesCode(
params: {
pageBasename: "login.ftl" | "register.ftl"
}
): { ftlCode: string; } {
const { pageBasename } = params;
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
$("head").prepend(
[
'',
'<script>',
` window.${ftlValuesGlobalName} = { "pageBasename": "${pageBasename}" };`,
'</script>',
''
].join("\n")
);
let ftlCode = $.html();
objectKeys(ftlPlaceholders)
.forEach(id => ftlCode = ftlCode.replace(id, ftlPlaceholders[id]));
return { ftlCode };
}
return { generateFtlFilesCode };
}
export * from "./generateFtl";

View File

@ -1,43 +1,32 @@
import * as url from "url";
import * as fs from "fs";
import { join as pathJoin, dirname as pathDirname } from "path";
export type ParsedPackageJson = {
name: string;
version: string;
homepage?: string;
};
export function generateJavaStackFiles(
params: {
parsedPackageJson: ParsedPackageJson;
keycloakThemeBuildingDirPath: string;
}
): { jarFilePath: string; } {
const {
parsedPackageJson: { name, version, homepage },
keycloakThemeBuildingDirPath
} = params;
export function generateJavaStackFiles(params: { version: string; themeName: string; homepage?: string; keycloakThemeBuildingDirPath: string }): {
jarFilePath: string;
} {
const { themeName, version, homepage, keycloakThemeBuildingDirPath } = params;
{
const { pomFileCode } = (function generatePomFileCode(): { pomFileCode: string; } {
const { pomFileCode } = (function generatePomFileCode(): {
pomFileCode: string;
} {
const groupId = (() => {
const fallbackGroupId = `there.was.no.homepage.field.in.the.package.json.${themeName}`;
const fallbackGroupId = `there.was.no.homepage.field.in.the.package.json.${name}`;
return (!homepage ?
fallbackGroupId :
url.parse(homepage).host?.split(".").reverse().join(".") ?? fallbackGroupId
) + ".keycloak";
return (
(!homepage
? fallbackGroupId
: url
.parse(homepage)
.host?.replace(/:[0-9]+$/, "")
?.split(".")
.reverse()
.join(".") ?? fallbackGroupId) + ".keycloak"
);
})();
const artefactId = `${name}-keycloak-theme`;
const artefactId = `${themeName}-keycloak-theme`;
const pomFileCode = [
`<?xml version="1.0"?>`,
@ -50,51 +39,43 @@ export function generateJavaStackFiles(
` <version>${version}</version>`,
` <name>${artefactId}</name>`,
` <description />`,
`</project>`
`</project>`,
].join("\n");
return { pomFileCode };
})();
fs.writeFileSync(
pathJoin(keycloakThemeBuildingDirPath, "pom.xml"),
Buffer.from(pomFileCode, "utf8")
);
fs.writeFileSync(pathJoin(keycloakThemeBuildingDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
}
{
const themeManifestFilePath = pathJoin(
keycloakThemeBuildingDirPath, "src", "main",
"resources", "META-INF", "keycloak-themes.json"
);
const themeManifestFilePath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "META-INF", "keycloak-themes.json");
try {
fs.mkdirSync(pathDirname(themeManifestFilePath));
} catch { }
} catch {}
fs.writeFileSync(
themeManifestFilePath,
Buffer.from(
JSON.stringify({
"themes": [
{
"name": name,
"types": ["login"]
}
]
}, null, 2),
"utf8"
)
JSON.stringify(
{
"themes": [
{
"name": themeName,
"types": ["login"],
},
],
},
null,
2,
),
"utf8",
),
);
}
return { "jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${name}-${version}.jar`) };
return {
"jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${themeName}-${version}.jar`),
};
}

View File

@ -1,88 +1,140 @@
import { transformCodebase } from "../tools/transformCodebase";
import * as fs from "fs";
import { join as pathJoin } from "path";
import {
replaceImportFromStaticInCssCode,
replaceImportFromStaticInJsCode
} from "./replaceImportFromStatic";
import { generateFtlFilesCodeFactory } from "./generateFtl";
import { replaceImportsInCssCode, replaceImportsFromStaticInJsCode } from "./replaceImportFromStatic";
import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl";
import { downloadBuiltinKeycloakTheme } from "../download-builtin-keycloak-theme";
import * as child_process from "child_process";
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/getKcContext/kcContextMocks/urlResourcesPath";
import { isInside } from "../tools/isInside";
export const ftlValuesGlobalName = "keycloakPagesContext";
export function generateKeycloakThemeResources(
params: {
themeName: string;
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
}
) {
const { themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath } = params;
export function generateKeycloakThemeResources(params: {
themeName: string;
reactAppBuildDirPath: string;
keycloakThemeBuildingDirPath: string;
urlPathname: string;
//If urlOrigin is not undefined then it means --externals-assets
urlOrigin: undefined | string;
extraPagesId: string[];
extraThemeProperties: string[];
keycloakVersion: "11.0.3" | "15.0.2";
}) {
const {
themeName,
reactAppBuildDirPath,
keycloakThemeBuildingDirPath,
urlPathname,
urlOrigin,
extraPagesId,
extraThemeProperties,
keycloakVersion,
} = params;
const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login");
let allCssGlobalsToDefine: Record<string, string> = {};
transformCodebase({
"destDirPath": pathJoin(themeDirPath, "resources"),
"destDirPath": urlOrigin === undefined ? pathJoin(themeDirPath, "resources", "build") : reactAppBuildDirPath,
"srcDirPath": reactAppBuildDirPath,
"transformSourceCodeString": ({ filePath, sourceCode }) => {
"transformSourceCode": ({ filePath, sourceCode }) => {
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
if (
urlOrigin === undefined &&
isInside({
"dirPath": pathJoin(reactAppBuildDirPath, subDirOfPublicDirBasename),
filePath,
})
) {
return undefined;
}
if (/\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode(
{ "cssCode": sourceCode.toString("utf8") }
);
if (urlOrigin === undefined && /\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode({
"cssCode": sourceCode.toString("utf8"),
});
allCssGlobalsToDefine = {
...allCssGlobalsToDefine,
...cssGlobalsToDefine
...cssGlobalsToDefine,
};
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
return {
"modifiedSourceCode": Buffer.from(fixedCssCode, "utf8"),
};
}
if (/\.js?$/i.test(filePath)) {
const { fixedJsCode } = replaceImportFromStaticInJsCode({
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
"jsCode": sourceCode.toString("utf8"),
ftlValuesGlobalName
urlOrigin,
});
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
return {
"modifiedSourceCode": Buffer.from(fixedJsCode, "utf8"),
};
}
return { "modifiedSourceCode": sourceCode };
}
return urlOrigin === undefined ? { "modifiedSourceCode": sourceCode } : undefined;
},
});
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
"cssGlobalsToDefine": allCssGlobalsToDefine,
ftlValuesGlobalName,
"indexHtmlCode": fs.readFileSync(
pathJoin(reactAppBuildDirPath, "index.html")
).toString("utf8")
"indexHtmlCode": fs.readFileSync(pathJoin(reactAppBuildDirPath, "index.html")).toString("utf8"),
urlPathname,
urlOrigin,
});
(["login.ftl", "register.ftl"] as const).forEach(pageBasename => {
[...pageIds, ...extraPagesId].forEach(pageId => {
const { ftlCode } = generateFtlFilesCode({ pageId });
const { ftlCode } = generateFtlFilesCode({ pageBasename });
fs.mkdirSync(themeDirPath, { "recursive": true });
fs.writeFileSync(pathJoin(themeDirPath, pageId), Buffer.from(ftlCode, "utf8"));
});
{
const tmpDirPath = pathJoin(themeDirPath, "..", "tmp_xxKdLpdIdLd");
downloadBuiltinKeycloakTheme({
keycloakVersion,
"destDirPath": tmpDirPath,
});
const themeResourcesDirPath = pathJoin(themeDirPath, "resources");
transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "login", "resources"),
"destDirPath": themeResourcesDirPath,
});
const reactAppPublicDirPath = pathJoin(reactAppBuildDirPath, "..", "public");
transformCodebase({
"srcDirPath": themeResourcesDirPath,
"destDirPath": pathJoin(reactAppPublicDirPath, resourcesPath),
});
transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"),
"destDirPath": pathJoin(reactAppPublicDirPath, resourcesCommonPath),
});
const keycloakResourcesWithinPublicDirPath = pathJoin(reactAppPublicDirPath, subDirOfPublicDirBasename);
fs.writeFileSync(
pathJoin(themeDirPath, pageBasename),
Buffer.from(ftlCode, "utf8")
)
pathJoin(keycloakResourcesWithinPublicDirPath, "README.txt"),
Buffer.from(["This is just a test folder that helps develop", "the login and register page without having to yarn build"].join(" ")),
);
});
fs.writeFileSync(pathJoin(keycloakResourcesWithinPublicDirPath, ".gitignore"), Buffer.from("*", "utf8"));
child_process.execSync(`rm -r ${tmpDirPath}`);
}
fs.writeFileSync(
pathJoin(themeDirPath, "theme.properties"),
Buffer.from("parent=base\n", "utf8")
Buffer.from("parent=keycloak".concat("\n\n", extraThemeProperties.join("\n\n")), "utf8"),
);
}

View File

@ -1,81 +1,8 @@
#!/usr/bin/env node
import { generateKeycloakThemeResources } from "./generateKeycloakThemeResources";
import { generateJavaStackFiles } from "./generateJavaStackFiles";
import type { ParsedPackageJson } from "./generateJavaStackFiles";
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
import * as child_process from "child_process";
import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles";
const reactProjectDirPath = process.cwd();
const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json"));
export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak");
export * from "./build-keycloak-theme";
import { main } from "./build-keycloak-theme";
if (require.main === module) {
console.log("🔏 Building the keycloak theme...⌚");
generateKeycloakThemeResources({
keycloakThemeBuildingDirPath,
"reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"),
"themeName": parsedPackageJson.name
});
const { jarFilePath } = generateJavaStackFiles({
parsedPackageJson,
keycloakThemeBuildingDirPath
});
child_process.execSync(
"mvn package",
{ "cwd": keycloakThemeBuildingDirPath }
);
generateDebugFiles({
keycloakThemeBuildingDirPath,
"packageJsonName": parsedPackageJson.name
});
console.log([
'',
`✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`,
`It is to be placed in "/opt/jboss/keycloak/standalone/deployments" in the container running a jboss/keycloak Docker image. (Tested with 11.0.3)`,
'',
'Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:',
'',
'value.yaml: ',
' extraInitContainers: |',
' - name: realm-ext-provider',
' image: curlimages/curl',
' imagePullPolicy: IfNotPresent',
' command:',
' - sh',
' args:',
' - -c',
` - curl -L -f -S -o /extensions/${pathBasename(jarFilePath)} https://AN.URL.FOR/${pathBasename(jarFilePath)}`,
' volumeMounts:',
' - name: extensions',
' mountPath: /extensions',
' ',
' extraVolumeMounts: |',
' - name: extensions',
' mountPath: /opt/jboss/keycloak/standalone/deployments',
'',
'',
'To test your theme locally, with hot reloading, you can spin up a Keycloak container image with the theme loaded by running:',
'',
`👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename))} 👈`,
'',
'To enable the theme within keycloak log into the admin console ( 👉 http://localhost:8080 username: admin, password: admin 👈), create a realm (called "myrealm" for example),',
`go to your realm settings, click on the theme tab then select ${parsedPackageJson.name}.`,
`More details: https://www.keycloak.org/getting-started/getting-started-docker`,
'',
'Once your container is up and configured 👉 http://localhost:8080/auth/realms/myrealm/account 👈',
'',
].join("\n"));
main();
}

View File

@ -1,91 +1,92 @@
import * as crypto from "crypto";
import { ftlValuesGlobalName } from "./ftlValuesGlobalName";
export function replaceImportFromStaticInJsCode(
params: {
ftlValuesGlobalName: string;
jsCode: string;
}
): { fixedJsCode: string; } {
export function replaceImportsFromStaticInJsCode(params: { jsCode: string; urlOrigin: undefined | string }): { fixedJsCode: string } {
/*
NOTE:
const { jsCode, ftlValuesGlobalName } = params;
When we have urlOrigin defined it means that
we are building with --external-assets
so we have to make sur that the fixed js code will run
inside and outside keycloak.
const fixedJsCode = jsCode!.replace(
/"static\//g,
`window.${ftlValuesGlobalName}.url.resourcesPath.replace(/^\\//,"") + "/" + "static/`
);
When urlOrigin isn't defined we can assume the fixedJsCode
will always run in keycloak context.
*/
const { jsCode, urlOrigin } = params;
const fixedJsCode = jsCode
.replace(/([a-z]+\.[a-z]+)\+"static\//g, (...[, group]) =>
urlOrigin === undefined
? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`
: `("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group} + "static/`,
)
.replace(/".chunk.css",([a-z])+=([a-z]+\.[a-z]+)\+([a-z]+),/, (...[, group1, group2, group3]) =>
urlOrigin === undefined
? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},`
: `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group2} + ${group3},`,
);
return { fixedJsCode };
}
export function replaceImportFromStaticInCssCode(
params: {
cssCode: string;
}
): {
export function replaceImportsInInlineCssCode(params: { cssCode: string; urlPathname: string; urlOrigin: undefined | string }): {
fixedCssCode: string;
} {
const { cssCode, urlPathname, urlOrigin } = params;
const fixedCssCode = cssCode.replace(
urlPathname === "/" ? /url\(\/([^/][^)]+)\)/g : new RegExp(`url\\(${urlPathname}([^)]+)\\)`, "g"),
(...[, group]) => `url(${urlOrigin === undefined ? "${url.resourcesPath}/build/" + group : params.urlOrigin + urlPathname + group})`,
);
return { fixedCssCode };
}
export function replaceImportsInCssCode(params: { cssCode: string }): {
fixedCssCode: string;
cssGlobalsToDefine: Record<string, string>;
} {
const { cssCode } = params;
const cssGlobalsToDefine: Record<string, string> = {};
new Set(cssCode.match(/(url\(\/[^)]+\))/g) ?? [])
.forEach(match =>
cssGlobalsToDefine[
"url" + crypto
.createHash("sha256")
.update(match)
.digest("hex")
.substring(0, 15)
] = match
);
new Set(cssCode.match(/url\(\/[^/][^)]+\)[^;}]*/g) ?? []).forEach(
match => (cssGlobalsToDefine["url" + crypto.createHash("sha256").update(match).digest("hex").substring(0, 15)] = match),
);
let fixedCssCode = cssCode;
Object.keys(cssGlobalsToDefine).forEach(
cssVariableName =>
//NOTE: split/join pattern ~ replace all
fixedCssCode =
fixedCssCode.split(cssGlobalsToDefine[cssVariableName])
.join(`var(--${cssVariableName})`)
(fixedCssCode = fixedCssCode.split(cssGlobalsToDefine[cssVariableName]).join(`var(--${cssVariableName})`)),
);
return { fixedCssCode, cssGlobalsToDefine };
}
export function generateCssCodeToDefineGlobals(
params: {
cssGlobalsToDefine: Record<string, string>;
}
): {
export function generateCssCodeToDefineGlobals(params: { cssGlobalsToDefine: Record<string, string>; urlPathname: string }): {
cssCodeToPrependInHead: string;
} {
const { cssGlobalsToDefine } = params;
const { cssGlobalsToDefine, urlPathname } = params;
return {
"cssCodeToPrependInHead": [
":root {",
...Object.keys(cssGlobalsToDefine)
.map(cssVariableName => [
`--${cssVariableName}:`,
.map(cssVariableName =>
[
"url(",
"${url.resourcesPath}" +
cssGlobalsToDefine[cssVariableName].match(/^url\(([^)]+)\)$/)![1],
")"
].join("")
].join(" "))
`--${cssVariableName}:`,
cssGlobalsToDefine[cssVariableName].replace(
new RegExp(`url\\(${urlPathname.replace(/\//g, "\\/")}`, "g"),
"url(${url.resourcesPath}/build/",
),
].join(" "),
)
.map(line => ` ${line};`),
"}"
].join("\n")
"}",
].join("\n"),
};
}

View File

@ -0,0 +1,39 @@
#!/usr/bin/env node
import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme";
import { join as pathJoin } from "path";
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
import type { KeycloakVersion } from "./KeycloakVersion";
export function downloadBuiltinKeycloakTheme(params: { keycloakVersion: KeycloakVersion; destDirPath: string }) {
const { keycloakVersion, destDirPath } = params;
for (const ext of ["", "-community"]) {
downloadAndUnzip({
"destDirPath": destDirPath,
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
"pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`,
});
}
}
if (require.main === module) {
const keycloakVersion = (() => {
const keycloakVersion = process.argv[2] as KeycloakVersion | undefined;
if (keycloakVersion === undefined) {
return "15.0.2";
}
return keycloakVersion;
})();
const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme");
console.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`);
downloadBuiltinKeycloakTheme({
keycloakVersion,
destDirPath,
});
}

View File

@ -1,19 +0,0 @@
#!/usr/bin/env node
import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme";
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
export const keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl =
"https://github.com/garronej/keycloak-react-theming/releases/download/v0.0.1/other_keycloak_thems.zip";
if (require.main === module) {
console.log("execute!");
downloadAndUnzip({
"url": keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl,
"destDirPath": keycloakThemeBuildingDirPath
});
}

View File

@ -1,64 +1,75 @@
import "minimal-polyfills/Object.fromEntries";
import * as fs from "fs";
import { join as pathJoin, relative as pathRelative } from "path";
import { crawl } from "./tools/crawl";
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
import { keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl } from "./download-sample-keycloak-themes";
import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme";
import { getProjectRoot } from "./tools/getProjectRoot";
import * as child_process from "child_process";
import { rm_rf, rm_r } from "./tools/rm";
import { keycloakVersions } from "./KeycloakVersion";
//@ts-ignore
const propertiesParser = require("properties-parser");
console.log(propertiesParser);
for (const keycloakVersion of keycloakVersions) {
console.log({ keycloakVersion });
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
child_process.execSync(`rm -rf ${tmpDirPath}`);
rm_rf(tmpDirPath);
downloadAndUnzip({
"destDirPath": tmpDirPath,
"url": keycloakBuiltinThemesAndThirdPartyExamplesThemsUrl
});
downloadBuiltinKeycloakTheme({
keycloakVersion,
"destDirPath": tmpDirPath,
});
type Dictionary = { [idiomId: string]: string };
type Dictionary = { [idiomId: string]: string };
const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = {};
const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = {};
process.chdir(pathJoin(tmpDirPath, "base"));
{
const baseThemeDirPath = pathJoin(tmpDirPath, "base");
crawl(".").forEach(filePath => {
crawl(baseThemeDirPath).forEach(filePath => {
const match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/);
const match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/);
if (match === null) {
return;
}
if (match === null) {
return;
const [, typeOfPage, language] = match;
(record[typeOfPage] ??= {})[language.replace(/_/g, "-")] = Object.fromEntries(
Object.entries(propertiesParser.parse(fs.readFileSync(pathJoin(baseThemeDirPath, filePath)).toString("utf8"))).map(
([key, value]: any) => [key, value.replace(/''/g, "'")],
),
);
});
}
const [, typeOfPage, language] = match;
rm_r(tmpDirPath);
(record[typeOfPage] ??= {})[language] =
propertiesParser.parse(
fs.readFileSync(filePath)
.toString("utf8")
const targetDirPath = pathJoin(getProjectRoot(), "src", "lib", "i18n", "generated_kcMessages", keycloakVersion);
fs.mkdirSync(targetDirPath, { "recursive": true });
Object.keys(record).forEach(pageType => {
const filePath = pathJoin(targetDirPath, `${pageType}.ts`);
fs.writeFileSync(
filePath,
Buffer.from(
[
`//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`,
"//PLEASE DO NOT EDIT MANUALLY",
"",
"/* spell-checker: disable */",
`export const kcMessages= ${JSON.stringify(record[pageType], null, 2)};`,
"/* spell-checker: enable */",
].join("\n"),
"utf8",
),
);
});
child_process.execSync(`rm -r ${tmpDirPath}`);
const targetFilePath = pathJoin("src", "lib", "i18n", "messages.generated.ts");
fs.writeFileSync(
pathJoin(getProjectRoot(), targetFilePath),
Buffer.from(
[
`//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`,
'//PLEASE DO NOT EDIT MANUALLY',
'',
'/* spell-checker: disable */',
`export const messages= ${JSON.stringify(record, null, 2)} as const;`,
'/* spell-checker: enable */'
].join("\n"), "utf8")
);
console.log(`${targetFilePath} wrote`);
console.log(`${filePath} wrote`);
});
}

102
src/bin/link_in_test_app.ts Normal file
View File

@ -0,0 +1,102 @@
import { execSync } from "child_process";
import { join as pathJoin, relative as pathRelative } from "path";
import * as fs from "fs";
const keycloakifyDirPath = pathJoin(__dirname, "..", "..");
fs.writeFileSync(
pathJoin(keycloakifyDirPath, "dist", "package.json"),
Buffer.from(
JSON.stringify(
(() => {
const packageJsonParsed = JSON.parse(fs.readFileSync(pathJoin(keycloakifyDirPath, "package.json")).toString("utf8"));
return {
...packageJsonParsed,
"main": packageJsonParsed["main"].replace(/^dist\//, ""),
"types": packageJsonParsed["types"].replace(/^dist\//, ""),
};
})(),
null,
2,
),
"utf8",
),
);
const commonThirdPartyDeps = (() => {
const namespaceModuleNames = ["@emotion"];
const standaloneModuleNames = ["react", "@types/react", "powerhooks", "tss-react", "evt"];
return [
...namespaceModuleNames
.map(namespaceModuleName =>
fs
.readdirSync(pathJoin(keycloakifyDirPath, "node_modules", namespaceModuleName))
.map(submoduleName => `${namespaceModuleName}/${submoduleName}`),
)
.reduce((prev, curr) => [...prev, ...curr], []),
...standaloneModuleNames,
];
})();
const yarnHomeDirPath = pathJoin(keycloakifyDirPath, ".yarn_home");
execSync(["rm -rf", "mkdir"].map(cmd => `${cmd} ${yarnHomeDirPath}`).join(" && "));
const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => {
const { targetModuleName, cwd } = params;
const cmd = ["yarn", "link", ...(targetModuleName !== undefined ? [targetModuleName] : [])].join(" ");
console.log(`$ cd ${pathRelative(keycloakifyDirPath, cwd) || "."} && ${cmd}`);
execSync(cmd, {
cwd,
"env": {
...process.env,
"HOME": yarnHomeDirPath,
},
});
};
const testAppNames = ["keycloakify-demo-app"] as const;
const getTestAppPath = (testAppName: typeof testAppNames[number]) => pathJoin(keycloakifyDirPath, "..", testAppName);
testAppNames.forEach(testAppName => execSync("yarn install", { "cwd": getTestAppPath(testAppName) }));
console.log("=== Linking common dependencies ===");
const total = commonThirdPartyDeps.length;
let current = 0;
commonThirdPartyDeps.forEach(commonThirdPartyDep => {
current++;
console.log(`${current}/${total} ${commonThirdPartyDep}`);
const localInstallPath = pathJoin(
...[keycloakifyDirPath, "node_modules", ...(commonThirdPartyDep.startsWith("@") ? commonThirdPartyDep.split("/") : [commonThirdPartyDep])],
);
execYarnLink({ "cwd": localInstallPath });
testAppNames.forEach(testAppName =>
execYarnLink({
"cwd": getTestAppPath(testAppName),
"targetModuleName": commonThirdPartyDep,
}),
);
});
console.log("=== Linking in house dependencies ===");
execYarnLink({ "cwd": pathJoin(keycloakifyDirPath, "dist") });
testAppNames.forEach(testAppName =>
execYarnLink({
"cwd": getTestAppPath(testAppName),
"targetModuleName": "keycloakify",
}),
);

View File

@ -3,35 +3,25 @@ import * as path from "path";
/** List all files in a given directory return paths relative to the dir_path */
export const crawl = (() => {
const crawlRec = (dir_path: string, paths: string[]) => {
for (const file_name of fs.readdirSync(dir_path)) {
const file_path = path.join(dir_path, file_name);
if (fs.lstatSync(file_path).isDirectory()) {
crawlRec(file_path, paths);
continue;
}
paths.push(file_path);
}
};
return function crawl(dir_path: string): string[] {
const paths: string[] = [];
crawlRec(dir_path, paths);
return paths.map(file_path => path.relative(dir_path, file_path));
}
})();
};
})();

View File

@ -1,22 +1,31 @@
import { basename as pathBasename } from "path";
import child_process from "child_process";
import { basename as pathBasename, join as pathJoin } from "path";
import { execSync } from "child_process";
import fs from "fs";
import { transformCodebase } from "../tools/transformCodebase";
import { rm_rf, rm, rm_r } from "./rm";
export function downloadAndUnzip(
params: {
url: string;
destDirPath: string;
}
) {
/** assert url ends with .zip */
export function downloadAndUnzip(params: { url: string; destDirPath: string; pathOfDirToExtractInArchive?: string }) {
const { url, destDirPath, pathOfDirToExtractInArchive } = params;
const { url, destDirPath } = params;
const tmpDirPath = pathJoin(destDirPath, "..", "tmp_xxKdOxnEdx");
fs.mkdirSync(destDirPath, { "recursive": true });
rm_rf(tmpDirPath);
[
`wget ${url}`,
...["unzip", "rm"].map(prg => `${prg} ${pathBasename(url)}`),
].forEach(cmd => child_process.execSync(cmd, { "cwd": destDirPath }));
fs.mkdirSync(tmpDirPath, { "recursive": true });
}
execSync(`wget ${url}`, { "cwd": tmpDirPath });
execSync(`unzip ${pathBasename(url)}${pathOfDirToExtractInArchive === undefined ? "" : ` "${pathOfDirToExtractInArchive}/*"`}`, {
"cwd": tmpDirPath,
});
rm(pathBasename(url), { "cwd": tmpDirPath });
transformCodebase({
"srcDirPath": pathOfDirToExtractInArchive === undefined ? tmpDirPath : pathJoin(tmpDirPath, pathOfDirToExtractInArchive),
destDirPath,
});
rm_r(tmpDirPath);
}

View File

@ -16,4 +16,4 @@ export function getProjectRoot(): string {
}
return (result = getProjectRootRec(__dirname));
}
}

View File

@ -1,8 +1,9 @@
import { getProjectRoot } from "./getProjectRoot";
import { join as pathJoin } from "path";
import child_process from "child_process";
import { getProjectRoot } from "./getProjectRoot";
import { join as pathJoin } from "path";
import child_process from "child_process";
Object.entries<string>(require(pathJoin(getProjectRoot(), "package.json"))["bin"])
.forEach(([, scriptPath]) => child_process.execSync(`chmod +x ${scriptPath}`, { "cwd": getProjectRoot() }));
Object.entries<string>(require(pathJoin(getProjectRoot(), "package.json"))["bin"]).forEach(([, scriptPath]) =>
child_process.execSync(`chmod +x ${scriptPath}`, {
"cwd": getProjectRoot(),
}),
);

View File

@ -0,0 +1,7 @@
import { relative as pathRelative } from "path";
export function isInside(params: { dirPath: string; filePath: string }) {
const { dirPath, filePath } = params;
return !pathRelative(dirPath, filePath).startsWith("..");
}

31
src/bin/tools/rm.ts Normal file
View File

@ -0,0 +1,31 @@
import { execSync } from "child_process";
function rmInternal(params: { pathToRemove: string; args: string | undefined; cwd: string | undefined }) {
const { pathToRemove, args, cwd } = params;
execSync(`rm ${args ? `-${args} ` : ""}${pathToRemove.replace(/ /g, "\\ ")}`, cwd !== undefined ? { cwd } : undefined);
}
export function rm(pathToRemove: string, options?: { cwd: string }) {
rmInternal({
pathToRemove,
"args": undefined,
"cwd": options?.cwd,
});
}
export function rm_r(pathToRemove: string, options?: { cwd: string }) {
rmInternal({
pathToRemove,
"args": "r",
"cwd": options?.cwd,
});
}
export function rm_rf(pathToRemove: string, options?: { cwd: string }) {
rmInternal({
pathToRemove,
"args": "rf",
"cwd": options?.cwd,
});
}

View File

@ -1,61 +1,46 @@
import * as fs from "fs";
import * as path from "path";
import { crawl } from "./crawl";
import { id } from "tsafe/id";
type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string }) =>
| {
modifiedSourceCode: Buffer;
newFileName?: string;
}
| undefined;
/** Apply a transformation function to every file of directory */
export function transformCodebase(
params: {
srcDirPath: string;
destDirPath: string;
transformSourceCodeString: (params: {
sourceCode: Buffer;
filePath: string;
}) => {
modifiedSourceCode: Buffer;
newFileName?: string;
} | undefined;
}
) {
const { srcDirPath, destDirPath, transformSourceCodeString } = params;
export function transformCodebase(params: { srcDirPath: string; destDirPath: string; transformSourceCode?: TransformSourceCode }) {
const {
srcDirPath,
destDirPath,
transformSourceCode = id<TransformSourceCode>(({ sourceCode }) => ({
"modifiedSourceCode": sourceCode,
})),
} = params;
for (const file_relative_path of crawl(srcDirPath)) {
const filePath = path.join(srcDirPath, file_relative_path);
const transformSourceCodeStringResult = transformSourceCodeString({
const transformSourceCodeResult = transformSourceCode({
"sourceCode": fs.readFileSync(filePath),
"filePath": path.join(srcDirPath, file_relative_path)
"filePath": path.join(srcDirPath, file_relative_path),
});
if (transformSourceCodeStringResult === undefined) {
if (transformSourceCodeResult === undefined) {
continue;
}
fs.mkdirSync(
path.dirname(
path.join(
destDirPath,
file_relative_path
)
),
{ "recursive": true }
);
fs.mkdirSync(path.dirname(path.join(destDirPath, file_relative_path)), {
"recursive": true,
});
const { newFileName, modifiedSourceCode } = transformSourceCodeStringResult;
const { newFileName, modifiedSourceCode } = transformSourceCodeResult;
fs.writeFileSync(
path.join(
path.dirname(path.join(destDirPath, file_relative_path)),
newFileName ?? path.basename(file_relative_path)
),
modifiedSourceCode
path.join(path.dirname(path.join(destDirPath, file_relative_path)), newFileName ?? path.basename(file_relative_path)),
modifiedSourceCode,
);
}
}

View File

@ -0,0 +1,32 @@
import { memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage";
export const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Error } & KcProps) => {
const { msg } = useKcMessage();
const { message, client } = kcContext;
return (
<Template
{...{ kcContext, ...props }}
doFetchDefaultThemeResources={true}
displayMessage={false}
headerNode={msg("errorTitle")}
formNode={
<div id="kc-error-message">
<p className="instruction">{message.summary}</p>
{client !== undefined && client.baseUrl !== undefined && (
<p>
<a id="backToApplication" href={client.baseUrl}>
{msg("backToApplication")}
</a>
</p>
)}
</div>
}
/>
);
});

View File

@ -0,0 +1,49 @@
import { memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import { assert } from "../tools/assert";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage";
export const Info = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Info } & KcProps) => {
const { msg } = useKcMessage();
assert(kcContext.message !== undefined);
const { messageHeader, message, requiredActions, skipLink, pageRedirectUri, actionUri, client } = kcContext;
return (
<Template
{...{ kcContext, ...props }}
doFetchDefaultThemeResources={true}
displayMessage={false}
headerNode={messageHeader !== undefined ? <>{messageHeader}</> : <>{message.summary}</>}
formNode={
<div id="kc-info-message">
<p className="instruction">
{message.summary}
{requiredActions !== undefined && (
<b>{requiredActions.map(requiredAction => msg(`requiredAction.${requiredAction}` as const)).join(",")}</b>
)}
</p>
{!skipLink && pageRedirectUri !== undefined ? (
<p>
<a href={pageRedirectUri}>{msg("backToApplication")}</a>
</p>
) : actionUri !== undefined ? (
<p>
<a href={actionUri}>{msg("proceedWithAction")}</a>
</p>
) : (
client.baseUrl !== undefined && (
<p>
<a href={client.baseUrl}>{msg("backToApplication")}</a>
</p>
)
)}
</div>
}
/>
);
});

View File

@ -0,0 +1,41 @@
import { memo } from "react";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import type { KcProps } from "./KcProps";
import { Login } from "./Login";
import { Register } from "./Register";
import { RegisterUserProfile } from "./RegisterUserProfile";
import { Info } from "./Info";
import { Error } from "./Error";
import { LoginResetPassword } from "./LoginResetPassword";
import { LoginVerifyEmail } from "./LoginVerifyEmail";
import { Terms } from "./Terms";
import { LoginOtp } from "./LoginOtp";
import { LoginUpdateProfile } from "./LoginUpdateProfile";
import { LoginIdpLinkConfirm } from "./LoginIdpLinkConfirm";
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContextBase } & KcProps) => {
switch (kcContext.pageId) {
case "login.ftl":
return <Login {...{ kcContext, ...props }} />;
case "register.ftl":
return <Register {...{ kcContext, ...props }} />;
case "register-user-profile.ftl":
return <RegisterUserProfile {...{ kcContext, ...props }} />;
case "info.ftl":
return <Info {...{ kcContext, ...props }} />;
case "error.ftl":
return <Error {...{ kcContext, ...props }} />;
case "login-reset-password.ftl":
return <LoginResetPassword {...{ kcContext, ...props }} />;
case "login-verify-email.ftl":
return <LoginVerifyEmail {...{ kcContext, ...props }} />;
case "terms.ftl":
return <Terms {...{ kcContext, ...props }} />;
case "login-otp.ftl":
return <LoginOtp {...{ kcContext, ...props }} />;
case "login-update-profile.ftl":
return <LoginUpdateProfile {...{ kcContext, ...props }} />;
case "login-idp-link-confirm.ftl":
return <LoginIdpLinkConfirm {...{ kcContext, ...props }} />;
}
});

View File

@ -0,0 +1,207 @@
import { allPropertiesValuesToUndefined } from "../tools/allPropertiesValuesToUndefined";
import { assert } from "tsafe/assert";
/** Class names can be provided as an array or separated by whitespace */
export type KcPropsGeneric<CssClasses extends string> = {
[key in CssClasses]: readonly string[] | string | undefined;
};
export type KcTemplateClassKey =
| "stylesCommon"
| "styles"
| "scripts"
| "kcHtmlClass"
| "kcLoginClass"
| "kcHeaderClass"
| "kcHeaderWrapperClass"
| "kcFormCardClass"
| "kcFormCardAccountClass"
| "kcFormHeaderClass"
| "kcLocaleWrapperClass"
| "kcContentWrapperClass"
| "kcLabelWrapperClass"
| "kcContentWrapperClass"
| "kcLabelWrapperClass"
| "kcFormGroupClass"
| "kcResetFlowIcon"
| "kcResetFlowIcon"
| "kcFeedbackSuccessIcon"
| "kcFeedbackWarningIcon"
| "kcFeedbackErrorIcon"
| "kcFeedbackInfoIcon"
| "kcContentWrapperClass"
| "kcFormSocialAccountContentClass"
| "kcFormSocialAccountClass"
| "kcSignUpClass"
| "kcInfoAreaWrapperClass";
export type KcTemplateProps = KcPropsGeneric<KcTemplateClassKey>;
export const defaultKcTemplateProps = {
"stylesCommon": [
"node_modules/patternfly/dist/css/patternfly.min.css",
"node_modules/patternfly/dist/css/patternfly-additions.min.css",
"lib/zocial/zocial.css",
],
"styles": ["css/login.css"],
"scripts": [],
"kcHtmlClass": ["login-pf"],
"kcLoginClass": ["login-pf-page"],
"kcContentWrapperClass": ["row"],
"kcHeaderClass": ["login-pf-page-header"],
"kcHeaderWrapperClass": [],
"kcFormCardClass": ["card-pf"],
"kcFormCardAccountClass": ["login-pf-accounts"],
"kcFormSocialAccountClass": ["login-pf-social-section"],
"kcFormSocialAccountContentClass": ["col-xs-12", "col-sm-6"],
"kcFormHeaderClass": ["login-pf-header"],
"kcLocaleWrapperClass": [],
"kcFeedbackErrorIcon": ["pficon", "pficon-error-circle-o"],
"kcFeedbackWarningIcon": ["pficon", "pficon-warning-triangle-o"],
"kcFeedbackSuccessIcon": ["pficon", "pficon-ok"],
"kcFeedbackInfoIcon": ["pficon", "pficon-info"],
"kcResetFlowIcon": ["pficon", "pficon-arrow fa-2x"],
"kcFormGroupClass": ["form-group"],
"kcLabelWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcSignUpClass": ["login-pf-signup"],
"kcInfoAreaWrapperClass": [],
} as const;
assert<typeof defaultKcTemplateProps extends KcTemplateProps ? true : false>();
/** Tu use if you don't want any default */
export const allClearKcTemplateProps = allPropertiesValuesToUndefined(defaultKcTemplateProps);
assert<typeof allClearKcTemplateProps extends KcTemplateProps ? true : false>();
export type KcProps = KcPropsGeneric<
| KcTemplateClassKey
| "kcLogoLink"
| "kcLogoClass"
| "kcContainerClass"
| "kcContentClass"
| "kcFeedbackAreaClass"
| "kcLocaleClass"
| "kcAlertIconClasserror"
| "kcFormAreaClass"
| "kcFormSocialAccountListClass"
| "kcFormSocialAccountDoubleListClass"
| "kcFormSocialAccountListLinkClass"
| "kcWebAuthnKeyIcon"
| "kcFormClass"
| "kcFormGroupErrorClass"
| "kcLabelClass"
| "kcInputClass"
| "kcInputErrorMessageClass"
| "kcInputWrapperClass"
| "kcFormOptionsClass"
| "kcFormButtonsClass"
| "kcFormSettingClass"
| "kcTextareaClass"
| "kcInfoAreaClass"
| "kcFormGroupHeader"
| "kcButtonClass"
| "kcButtonPrimaryClass"
| "kcButtonDefaultClass"
| "kcButtonLargeClass"
| "kcButtonBlockClass"
| "kcInputLargeClass"
| "kcSrOnlyClass"
| "kcSelectAuthListClass"
| "kcSelectAuthListItemClass"
| "kcSelectAuthListItemInfoClass"
| "kcSelectAuthListItemLeftClass"
| "kcSelectAuthListItemBodyClass"
| "kcSelectAuthListItemDescriptionClass"
| "kcSelectAuthListItemHeadingClass"
| "kcSelectAuthListItemHelpTextClass"
| "kcAuthenticatorDefaultClass"
| "kcAuthenticatorPasswordClass"
| "kcAuthenticatorOTPClass"
| "kcAuthenticatorWebAuthnClass"
| "kcAuthenticatorWebAuthnPasswordlessClass"
| "kcSelectOTPListClass"
| "kcSelectOTPListItemClass"
| "kcAuthenticatorOtpCircleClass"
| "kcSelectOTPItemHeadingClass"
| "kcFormOptionsWrapperClass"
>;
export const defaultKcProps = {
...defaultKcTemplateProps,
"kcLogoLink": "http://www.keycloak.org",
"kcLogoClass": "login-pf-brand",
"kcContainerClass": "container-fluid",
"kcContentClass": ["col-sm-8", "col-sm-offset-2", "col-md-6", "col-md-offset-3", "col-lg-6", "col-lg-offset-3"],
"kcFeedbackAreaClass": ["col-md-12"],
"kcLocaleClass": ["col-xs-12", "col-sm-1"],
"kcAlertIconClasserror": ["pficon", "pficon-error-circle-o"],
"kcFormAreaClass": ["col-sm-10", "col-sm-offset-1", "col-md-8", "col-md-offset-2", "col-lg-8", "col-lg-offset-2"],
"kcFormSocialAccountListClass": ["login-pf-social", "list-unstyled", "login-pf-social-all"],
"kcFormSocialAccountDoubleListClass": ["login-pf-social-double-col"],
"kcFormSocialAccountListLinkClass": ["login-pf-social-link"],
"kcWebAuthnKeyIcon": ["pficon", "pficon-key"],
"kcFormClass": ["form-horizontal"],
"kcFormGroupErrorClass": ["has-error"],
"kcLabelClass": ["control-label"],
"kcInputClass": ["form-control"],
"kcInputErrorMessageClass": ["pf-c-form__helper-text", "pf-m-error", "required", "kc-feedback-text"],
"kcInputWrapperClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormOptionsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormButtonsClass": ["col-xs-12", "col-sm-12", "col-md-12", "col-lg-12"],
"kcFormSettingClass": ["login-pf-settings"],
"kcTextareaClass": ["form-control"],
"kcInfoAreaClass": ["col-xs-12", "col-sm-4", "col-md-4", "col-lg-5", "details"],
// user-profile grouping
"kcFormGroupHeader": ["pf-c-form__group"],
// css classes for form buttons main class used for all buttons
"kcButtonClass": ["btn"],
// classes defining priority of the button - primary or default (there is typically only one priority button for the form)
"kcButtonPrimaryClass": ["btn-primary"],
"kcButtonDefaultClass": ["btn-default"],
// classes defining size of the button
"kcButtonLargeClass": ["btn-lg"],
"kcButtonBlockClass": ["btn-block"],
// css classes for input
"kcInputLargeClass": ["input-lg"],
// css classes for form accessability
"kcSrOnlyClass": ["sr-only"],
// css classes for select-authenticator form
"kcSelectAuthListClass": ["list-group", "list-view-pf"],
"kcSelectAuthListItemClass": ["list-group-item", "list-view-pf-stacked"],
"kcSelectAuthListItemInfoClass": ["list-view-pf-main-info"],
"kcSelectAuthListItemLeftClass": ["list-view-pf-left"],
"kcSelectAuthListItemBodyClass": ["list-view-pf-body"],
"kcSelectAuthListItemDescriptionClass": ["list-view-pf-description"],
"kcSelectAuthListItemHeadingClass": ["list-group-item-heading"],
"kcSelectAuthListItemHelpTextClass": ["list-group-item-text"],
// css classes for the authenticators
"kcAuthenticatorDefaultClass": ["fa", "list-view-pf-icon-lg"],
"kcAuthenticatorPasswordClass": ["fa", "fa-unlock list-view-pf-icon-lg"],
"kcAuthenticatorOTPClass": ["fa", "fa-mobile", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
"kcAuthenticatorWebAuthnPasswordlessClass": ["fa", "fa-key", "list-view-pf-icon-lg"],
//css classes for the OTP Login Form
"kcSelectOTPListClass": ["card-pf", "card-pf-view", "card-pf-view-select", "card-pf-view-single-select"],
"kcSelectOTPListItemClass": ["card-pf-body", "card-pf-top-element"],
"kcAuthenticatorOtpCircleClass": ["fa", "fa-mobile", "card-pf-icon-circle"],
"kcSelectOTPItemHeadingClass": ["card-pf-title", "text-center"],
"kcFormOptionsWrapperClass": [],
} as const;
assert<typeof defaultKcProps extends KcProps ? true : false>();
/** Tu use if you don't want any default */
export const allClearKcProps = allPropertiesValuesToUndefined(defaultKcProps);
assert<typeof allClearKcProps extends KcProps ? true : false>();

View File

@ -0,0 +1,167 @@
import { useState, memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage";
import { useCssAndCx } from "tss-react";
import { useConstCallback } from "powerhooks/useConstCallback";
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Login } & KcProps) => {
const { social, realm, url, usernameEditDisabled, login, auth, registrationDisabled } = kcContext;
const { msg, msgStr } = useKcMessage();
const { cx } = useCssAndCx();
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
const onSubmit = useConstCallback(() => (setIsLoginButtonDisabled(true), true));
return (
<Template
{...{ kcContext, ...props }}
doFetchDefaultThemeResources={true}
displayInfo={social.displayInfo}
displayWide={realm.password && social.providers !== undefined}
headerNode={msg("doLogIn")}
formNode={
<div id="kc-form" className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}>
<div
id="kc-form-wrapper"
className={cx(realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}
>
{realm.password && (
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>
{!realm.loginWithEmailAllowed
? msg("username")
: !realm.registrationEmailAsUsername
? msg("usernameOrEmail")
: msg("email")}
</label>
<input
tabIndex={1}
id="username"
className={cx(props.kcInputClass)}
name="username"
defaultValue={login.username ?? ""}
type="text"
{...(usernameEditDisabled
? { "disabled": true }
: {
"autoFocus": true,
"autoComplete": "off",
})}
/>
</div>
<div className={cx(props.kcFormGroupClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}>
{msg("password")}
</label>
<input
tabIndex={2}
id="password"
className={cx(props.kcInputClass)}
name="password"
type="password"
autoComplete="off"
/>
</div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-options">
{realm.rememberMe && !usernameEditDisabled && (
<div className="checkbox">
<label>
<input
tabIndex={3}
id="rememberMe"
name="rememberMe"
type="checkbox"
{...(login.rememberMe
? {
"checked": true,
}
: {})}
/>
{msg("rememberMe")}
</label>
</div>
)}
</div>
<div className={cx(props.kcFormOptionsWrapperClass)}>
{realm.resetPasswordAllowed && (
<span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>
{msg("doForgotPassword")}
</a>
</span>
)}
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
<input
type="hidden"
id="id-hidden-input"
name="credentialId"
{...(auth?.selectedCredential !== undefined
? {
"value": auth.selectedCredential,
}
: {})}
/>
<input
tabIndex={4}
className={cx(
props.kcButtonClass,
props.kcButtonPrimaryClass,
props.kcButtonBlockClass,
props.kcButtonLargeClass,
)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled}
/>
</div>
</form>
)}
</div>
{realm.password && social.providers !== undefined && (
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
<ul
className={cx(
props.kcFormSocialAccountListClass,
social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass,
)}
>
{social.providers.map(p => (
<li key={p.providerId} className={cx(props.kcFormSocialAccountListLinkClass)}>
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
<span>{p.displayName}</span>
</a>
</li>
))}
</ul>
</div>
)}
</div>
}
infoNode={
realm.password &&
realm.registrationAllowed &&
!registrationDisabled && (
<div id="kc-registration">
<span>
{msg("noAccount")}
<a tabIndex={6} href={url.registrationUrl}>
{msg("doRegister")}
</a>
</span>
</div>
)
}
/>
);
});

View File

@ -0,0 +1,46 @@
import { memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage";
import { useCssAndCx } from "tss-react";
export const LoginIdpLinkConfirm = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginIdpLinkConfirm } & KcProps) => {
const { url, idpAlias } = kcContext;
const { msg } = useKcMessage();
const { cx } = useCssAndCx();
return (
<Template
{...{ kcContext, ...props }}
doFetchDefaultThemeResources={true}
headerNode={msg("confirmLinkIdpTitle")}
formNode={
<form id="kc-register-form" action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}>
<button
type="submit"
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
name="submitAction"
id="updateProfile"
value="updateProfile"
>
{msg("confirmLinkIdpReviewProfile")}
</button>
<button
type="submit"
className={cx(props.kcButtonClass, props.kcButtonDefaultClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
name="submitAction"
id="linkAccount"
value="linkAccount"
>
{msg("confirmLinkIdpContinue", idpAlias)}
</button>
</div>
</form>
}
/>
);
});

View File

@ -0,0 +1,111 @@
import { useEffect, memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContextBase } from "../getKcContext/KcContextBase";
import { useKcMessage } from "../i18n/useKcMessage";
import { appendHead } from "../tools/appendHead";
import { join as pathJoin } from "path";
import { useCssAndCx } from "tss-react";
export const LoginOtp = memo(({ kcContext, ...props }: { kcContext: KcContextBase.LoginOtp } & KcProps) => {
const { otpLogin, url } = kcContext;
const { cx } = useCssAndCx();
const { msg, msgStr } = useKcMessage();
useEffect(() => {
let isCleanedUp = false;
appendHead({
"type": "javascript",
"src": pathJoin(kcContext.url.resourcesCommonPath, "node_modules/jquery/dist/jquery.min.js"),
}).then(() => {
if (isCleanedUp) return;
evaluateInlineScript();
});
return () => {
isCleanedUp = true;
};
}, []);
return (
<Template
{...{ kcContext, ...props }}
doFetchDefaultThemeResources={true}
headerNode={msg("doLogIn")}
formNode={
<form id="kc-otp-login-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
{otpLogin.userOtpCredentials.length > 1 && (
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcInputWrapperClass)}>
{otpLogin.userOtpCredentials.map(otpCredential => (
<div key={otpCredential.id} className={cx(props.kcSelectOTPListClass)}>
<input type="hidden" value="${otpCredential.id}" />
<div className={cx(props.kcSelectOTPListItemClass)}>
<span className={cx(props.kcAuthenticatorOtpCircleClass)} />
<h2 className={cx(props.kcSelectOTPItemHeadingClass)}>{otpCredential.userLabel}</h2>
</div>
</div>
))}
</div>
</div>
)}
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="otp" className={cx(props.kcLabelClass)}>
{msg("loginOtpOneTime")}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input id="otp" name="otp" autoComplete="off" type="text" className={cx(props.kcInputClass)} autoFocus />
</div>
</div>
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)} />
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)}
name="login"
id="kc-login"
type="submit"
value={msgStr("doLogIn")}
/>
</div>
</div>
</form>
}
/>
);
});
declare const $: any;
function evaluateInlineScript() {
$(document).ready(function () {
// Card Single Select
$(".card-pf-view-single-select").click(function (this: any) {
if ($(this).hasClass("active")) {
$(this).removeClass("active");
$(this).children().removeAttr("name");
} else {
$(".card-pf-view-single-select").removeClass("active");
$(".card-pf-view-single-select").children().removeAttr("name");
$(this).addClass("active");
$(this).children().attr("name", "selectedCredentialId");
}
});
var defaultCred = $(".card-pf-view-single-select")[0];
if (defaultCred) {
defaultCred.click();
}
});
}

Some files were not shown because too many files have changed in this diff Show More