Compare commits
161 Commits
Author | SHA1 | Date | |
---|---|---|---|
6e71da62f0 | |||
5bf33aae75 | |||
06b2dc63ff | |||
1bb0c9dfc2 | |||
a2b167e120 | |||
0909a4b7cc | |||
fd7d2bb9bf | |||
63c40fd816 | |||
0569fa5e58 | |||
ba74952e0b | |||
20c28f785a | |||
e9b249ddc7 | |||
604bb484a3 | |||
010c93793a | |||
dc1d4a66f4 | |||
8ef633d7ef | |||
2176d33da1 | |||
5b794e2d22 | |||
ccd75d56c5 | |||
b700066833 | |||
546ee006d3 | |||
7f333a6a36 | |||
ae757ee371 | |||
69936750d5 | |||
442bfa4ed6 | |||
2a88e6802f | |||
bcc8b12e13 | |||
9b974505eb | |||
29b1c26771 | |||
02db20d98b | |||
757354df7d | |||
563518cf46 | |||
7c42d9082a | |||
040284af71 | |||
34f64184d9 | |||
b9abd74156 | |||
a1c0bfda6c | |||
617dcef09d | |||
d9c406800a | |||
54b869def1 | |||
d80a583979 | |||
99bfd7379b | |||
5f257382fa | |||
e3e6847c82 | |||
4ee0823acb | |||
d466123b1c | |||
21cbc14a48 | |||
b2f2c3e386 | |||
b03340ed10 | |||
5b563d8e9b | |||
2790487fc7 | |||
ad5a368065 | |||
7c0a631a9a | |||
4a8920749a | |||
8ab118dd06 | |||
e6661cb898 | |||
1671850714 | |||
d568bafe04 | |||
43dcce8478 | |||
ad70a4cffd | |||
6d4a948dd8 | |||
839ba6a964 | |||
b5cfdb9d0a | |||
9706338182 | |||
05f52c3d23 | |||
df3acb6932 | |||
b3c242595e | |||
26985f8d81 | |||
05e5e4efec | |||
4d67f16e94 | |||
334ec1870a | |||
ef5e4fccd3 | |||
8535edcfd4 | |||
bda76200d7 | |||
db0dc96cc7 | |||
6d62b5a150 | |||
217439d673 | |||
1f79a8f7dc | |||
7596786b18 | |||
2540b06c94 | |||
43eeaf3002 | |||
037cd150de | |||
ae0b059217 | |||
8255ce1158 | |||
5bf905723c | |||
3e336f4937 | |||
cd1cc37916 | |||
4ad7183d7e | |||
e1b52e7439 | |||
dca8c9f9d7 | |||
7e4eba6376 | |||
f642a56eaa | |||
c091089830 | |||
18900d20e1 | |||
6c622b1580 | |||
4290cd23b2 | |||
5076c1e93f | |||
884b701fc6 | |||
73a8ec0295 | |||
a29b6097a4 | |||
a9231e2ed8 | |||
5f4669a7a6 | |||
75c54df109 | |||
2a07f7151d | |||
b6ecff2dd3 | |||
83df27ec99 | |||
ca255985c0 | |||
82f34c38f6 | |||
694b4c8027 | |||
bd25621b2c | |||
fde34be270 | |||
7c7ce159fe | |||
5a57bb59e5 | |||
cd278f4ab5 | |||
8b24e23721 | |||
22fa1411bf | |||
2799a52d0c | |||
4c2e01a7a8 | |||
7267d2ef38 | |||
1eb6b154f7 | |||
f55d61bf0b | |||
5b350274bd | |||
b6d2f9f691 | |||
81106b5deb | |||
66e595e649 | |||
33b7bb6184 | |||
7d9130b2af | |||
482d71743b | |||
1db37a4727 | |||
194d16ff91 | |||
b1e2284c0e | |||
70d1aa70a3 | |||
3b17d6e0ab | |||
9a5819b93b | |||
a260cd67b0 | |||
64111fb0ec | |||
faf2be23d9 | |||
0eb4a6a315 | |||
85673250ed | |||
09daa741ce | |||
55e2379aab | |||
9937977203 | |||
c897e7491a | |||
0a74a95283 | |||
74ef2c3dff | |||
9976dfacc0 | |||
659f8ddc7a | |||
9e4cc2ae57 | |||
a27d78fcdf | |||
e507435bcb | |||
d5f234909f | |||
c17f721625 | |||
600705130f | |||
5c5dce1422 | |||
53585bf2f0 | |||
116f88a503 | |||
aaba8cd2c7 | |||
b67aeb0d3a | |||
f620562d68 | |||
5231d0eaa1 | |||
cb470e3573 |
@ -204,6 +204,33 @@
|
|||||||
"contributions": [
|
"contributions": [
|
||||||
"code"
|
"code"
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "msiemens",
|
||||||
|
"name": "Markus Siemens",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/1873922?v=4",
|
||||||
|
"profile": "https://m-siemens.de/",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "law108000",
|
||||||
|
"name": "Rlok",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/8112024?v=4",
|
||||||
|
"profile": "https://github.com/law108000",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"login": "Moulyy",
|
||||||
|
"name": "Moulyy",
|
||||||
|
"avatar_url": "https://avatars.githubusercontent.com/u/115405804?v=4",
|
||||||
|
"profile": "https://github.com/Moulyy",
|
||||||
|
"contributions": [
|
||||||
|
"code"
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"contributorsPerLine": 7,
|
"contributorsPerLine": 7,
|
||||||
|
3
.github/workflows/ci.yaml
vendored
3
.github/workflows/ci.yaml
vendored
@ -3,9 +3,6 @@ on:
|
|||||||
push:
|
push:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
- v5
|
|
||||||
- v6
|
|
||||||
- v7
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
- main
|
- main
|
||||||
|
55
README.md
55
README.md
@ -14,9 +14,6 @@
|
|||||||
<a href="https://github.com/garronej/keycloakify/blob/main/LICENSE">
|
<a href="https://github.com/garronej/keycloakify/blob/main/LICENSE">
|
||||||
<img src="https://img.shields.io/npm/l/keycloakify">
|
<img src="https://img.shields.io/npm/l/keycloakify">
|
||||||
</a>
|
</a>
|
||||||
<a href="https://github.com/keycloakify/keycloakify/blob/729503fe31a155a823f46dd66ad4ff34ca274e0a/tsconfig.json#L14">
|
|
||||||
<img src="https://camo.githubusercontent.com/0f9fcc0ac1b8617ad4989364f60f78b2d6b32985ad6a508f215f14d8f897b8d3/68747470733a2f2f62616467656e2e6e65742f62616467652f547970655363726970742f7374726963742532302546302539462539322541412f626c7565">
|
|
||||||
</a>
|
|
||||||
<a href="https://github.com/thomasdarimont/awesome-keycloak">
|
<a href="https://github.com/thomasdarimont/awesome-keycloak">
|
||||||
<img src="https://awesome.re/mentioned-badge.svg"/>
|
<img src="https://awesome.re/mentioned-badge.svg"/>
|
||||||
</a>
|
</a>
|
||||||
@ -43,7 +40,7 @@
|
|||||||
|
|
||||||
Keycloakify is fully compatible with Keycloak 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, [~~22~~](https://github.com/keycloakify/keycloakify/issues/389#issuecomment-1822509763), **23** [and up](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)!
|
Keycloakify is fully compatible with Keycloak 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, [~~22~~](https://github.com/keycloakify/keycloakify/issues/389#issuecomment-1822509763), **23** [and up](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)!
|
||||||
|
|
||||||
## Sponsor 👼
|
## Sponsor
|
||||||
|
|
||||||
We are exclusively sponsored by [Cloud IAM](https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github), a French company offering Keycloak as a service.
|
We are exclusively sponsored by [Cloud IAM](https://cloud-iam.com/?mtm_campaign=keycloakify-deal&mtm_source=keycloakify-github), a French company offering Keycloak as a service.
|
||||||
Their dedicated support helps us continue the development and maintenance of this project.
|
Their dedicated support helps us continue the development and maintenance of this project.
|
||||||
@ -111,6 +108,10 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BlackVoid"><img src="https://avatars.githubusercontent.com/u/673720?v=4?s=100" width="100px;" alt="Felix Gustavsson"/><br /><sub><b>Felix Gustavsson</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=BlackVoid" title="Code">💻</a></td>
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/BlackVoid"><img src="https://avatars.githubusercontent.com/u/673720?v=4?s=100" width="100px;" alt="Felix Gustavsson"/><br /><sub><b>Felix Gustavsson</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=BlackVoid" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://m-siemens.de/"><img src="https://avatars.githubusercontent.com/u/1873922?v=4?s=100" width="100px;" alt="Markus Siemens"/><br /><sub><b>Markus Siemens</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=msiemens" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/law108000"><img src="https://avatars.githubusercontent.com/u/8112024?v=4?s=100" width="100px;" alt="Rlok"/><br /><sub><b>Rlok</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=law108000" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/Moulyy"><img src="https://avatars.githubusercontent.com/u/115405804?v=4?s=100" width="100px;" alt="Moulyy"/><br /><sub><b>Moulyy</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=Moulyy" title="Code">💻</a></td>
|
||||||
|
<td align="center" valign="top" width="14.28%"><a href="https://github.com/giorgoslytos"><img src="https://avatars.githubusercontent.com/u/50946162?v=4?s=100" width="100px;" alt="giorgoslytos"/><br /><sub><b>giorgoslytos</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=giorgoslytos" title="Code">💻</a></td>
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
@ -122,7 +123,21 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
|||||||
|
|
||||||
# Changelog highlights
|
# Changelog highlights
|
||||||
|
|
||||||
## v9.0
|
## 9.5
|
||||||
|
|
||||||
|
- Post build hook: You can now apply custom transformation to your theme files. [Learn more](https://docs.keycloakify.dev/build-options#postbuild-hook).
|
||||||
|
- You can now specify your option in the Keycloakify's Vite plugin instead in the package.json. [See example](https://docs.keycloakify.dev/build-options#themename).
|
||||||
|
|
||||||
|
## 9.4
|
||||||
|
|
||||||
|
**Vite Support! 🎉**
|
||||||
|
|
||||||
|
- [The starter is now a Vite project](https://github.com/keycloakify/keycloakify-starter).
|
||||||
|
The Webpack based starter is accessible [here](https://github.com/keycloakify/keycloakify-starter-cra).
|
||||||
|
- CRA (Webpack) remains supported for the forseable future.
|
||||||
|
- If you have a CRA Keycloakify theme that you wish to migrate to Vite checkout [this migration guide](https://docs.keycloakify.dev/migration-guides/cra-greater-than-vite).
|
||||||
|
|
||||||
|
## 9.0
|
||||||
|
|
||||||
Bring back support for account themes in Keycloak v23 and up! [See issue](https://github.com/keycloakify/keycloakify/issues/389).
|
Bring back support for account themes in Keycloak v23 and up! [See issue](https://github.com/keycloakify/keycloakify/issues/389).
|
||||||
|
|
||||||
@ -255,79 +270,79 @@ Checkout [the migration guide](https://docs.keycloakify.dev/v5-to-v6)
|
|||||||
|
|
||||||
Fix `login-verify-email.ftl` page. [Before](https://user-images.githubusercontent.com/6702424/177436014-0bad22c4-5bfb-45bb-8fc9-dad65143cd0c.png) - [After](https://user-images.githubusercontent.com/6702424/177435797-ec5d7db3-84cf-49cb-8efc-3427a81f744e.png)
|
Fix `login-verify-email.ftl` page. [Before](https://user-images.githubusercontent.com/6702424/177436014-0bad22c4-5bfb-45bb-8fc9-dad65143cd0c.png) - [After](https://user-images.githubusercontent.com/6702424/177435797-ec5d7db3-84cf-49cb-8efc-3427a81f744e.png)
|
||||||
|
|
||||||
## v5.6.0
|
## 5.6.0
|
||||||
|
|
||||||
Add support for `login-config-totp.ftl` page [#127](https://github.com/keycloakify/keycloakify/pull/127).
|
Add support for `login-config-totp.ftl` page [#127](https://github.com/keycloakify/keycloakify/pull/127).
|
||||||
|
|
||||||
## v5.3.0
|
## 5.3.0
|
||||||
|
|
||||||
Rename `keycloak_theme_email` to `keycloak_email`.
|
Rename `keycloak_theme_email` to `keycloak_email`.
|
||||||
If you already had a `keycloak_theme_email` you should rename it `keycloak_email`.
|
If you already had a `keycloak_theme_email` you should rename it `keycloak_email`.
|
||||||
|
|
||||||
## v5.0.0
|
## 5.0.0
|
||||||
|
|
||||||
[Migration guide](https://github.com/garronej/keycloakify-demo-app/blob/a5b6a50f24bc25e082931f5ad9ebf47492acd12a/src/index.tsx#L46-L63)
|
[Migration guide](https://github.com/garronej/keycloakify-demo-app/blob/a5b6a50f24bc25e082931f5ad9ebf47492acd12a/src/index.tsx#L46-L63)
|
||||||
New i18n system.
|
New i18n system.
|
||||||
Import of terms and services have changed. [See example](https://github.com/garronej/keycloakify-demo-app/blob/a5b6a50f24bc25e082931f5ad9ebf47492acd12a/src/index.tsx#L46-L63).
|
Import of terms and services have changed. [See example](https://github.com/garronej/keycloakify-demo-app/blob/a5b6a50f24bc25e082931f5ad9ebf47492acd12a/src/index.tsx#L46-L63).
|
||||||
|
|
||||||
## v4.10.0
|
## 4.10.0
|
||||||
|
|
||||||
Add `login-idp-link-email.ftl` page [See PR](https://github.com/keycloakify/keycloakify/pull/92).
|
Add `login-idp-link-email.ftl` page [See PR](https://github.com/keycloakify/keycloakify/pull/92).
|
||||||
|
|
||||||
## v4.8.0
|
## 4.8.0
|
||||||
|
|
||||||
[Email template customization.](#email-template-customization)
|
[Email template customization.](#email-template-customization)
|
||||||
|
|
||||||
## v4.7.4
|
## 4.7.4
|
||||||
|
|
||||||
**M1 Mac** support (for testing locally with a dockerized Keycloak).
|
**M1 Mac** support (for testing locally with a dockerized Keycloak).
|
||||||
|
|
||||||
## v4.7.2
|
## 4.7.2
|
||||||
|
|
||||||
> WARNING: This is broken.
|
> WARNING: This is broken.
|
||||||
> Testing with local Keycloak container working with M1 Mac. Thanks to [@eduardosanzb](https://github.com/keycloakify/keycloakify/issues/43#issuecomment-975699658).
|
> Testing with local Keycloak container working with M1 Mac. Thanks to [@eduardosanzb](https://github.com/keycloakify/keycloakify/issues/43#issuecomment-975699658).
|
||||||
> Be aware: When running M1s you are testing with Keycloak v15 else the local container spun will be a Keycloak v16.1.0.
|
> Be aware: When running M1s you are testing with Keycloak v15 else the local container spun will be a Keycloak v16.1.0.
|
||||||
|
|
||||||
## v4.7.0
|
## 4.7.0
|
||||||
|
|
||||||
Register with user profile enabled: Out of the box `options` validator support.
|
Register with user profile enabled: Out of the box `options` validator support.
|
||||||
[Example](https://user-images.githubusercontent.com/6702424/158911163-81e6bbe8-feb0-4dc8-abff-de199d7a678e.mov)
|
[Example](https://user-images.githubusercontent.com/6702424/158911163-81e6bbe8-feb0-4dc8-abff-de199d7a678e.mov)
|
||||||
|
|
||||||
## v4.6.0
|
## 4.6.0
|
||||||
|
|
||||||
`tss-react` and `powerhooks` are no longer peer dependencies of `keycloakify`.
|
`tss-react` and `powerhooks` are no longer peer dependencies of `keycloakify`.
|
||||||
After updating Keycloakify you can remove `tss-react` and `powerhooks` from your dependencies if you don't use them explicitly.
|
After updating Keycloakify you can remove `tss-react` and `powerhooks` from your dependencies if you don't use them explicitly.
|
||||||
|
|
||||||
## v4.5.3
|
## 4.5.3
|
||||||
|
|
||||||
There is a new recommended way to setup highly customized theme. See [here](https://github.com/garronej/keycloakify-demo-app/blob/look_and_feel/src/KcApp/KcApp.tsx).
|
There is a new recommended way to setup highly customized theme. See [here](https://github.com/garronej/keycloakify-demo-app/blob/look_and_feel/src/KcApp/KcApp.tsx).
|
||||||
Unlike with [the previous recommended method](https://github.com/garronej/keycloakify-demo-app/blob/a51660578bea15fb3e506b8a2b78e1056c6d68bb/src/KcApp/KcApp.tsx),
|
Unlike with [the previous recommended method](https://github.com/garronej/keycloakify-demo-app/blob/a51660578bea15fb3e506b8a2b78e1056c6d68bb/src/KcApp/KcApp.tsx),
|
||||||
with this new method your theme wont break on minor Keycloakify update.
|
with this new method your theme wont break on minor Keycloakify update.
|
||||||
|
|
||||||
## v4.3.0
|
## 4.3.0
|
||||||
|
|
||||||
Feature [`login-update-password.ftl`](https://user-images.githubusercontent.com/6702424/147517600-6191cf72-93dd-437b-a35c-47180142063e.png).
|
Feature [`login-update-password.ftl`](https://user-images.githubusercontent.com/6702424/147517600-6191cf72-93dd-437b-a35c-47180142063e.png).
|
||||||
Every time a page is added it's a breaking change for non CSS-only theme.
|
Every time a page is added it's a breaking change for non CSS-only theme.
|
||||||
Change [this](https://github.com/garronej/keycloakify-demo-app/blob/df664c13c77ce3c53ac7df0622d94d04e76d3f9f/src/KcApp/KcApp.tsx#L17) and [this](https://github.com/garronej/keycloakify-demo-app/blob/df664c13c77ce3c53ac7df0622d94d04e76d3f9f/src/KcApp/KcApp.tsx#L37) to update.
|
Change [this](https://github.com/garronej/keycloakify-demo-app/blob/df664c13c77ce3c53ac7df0622d94d04e76d3f9f/src/KcApp/KcApp.tsx#L17) and [this](https://github.com/garronej/keycloakify-demo-app/blob/df664c13c77ce3c53ac7df0622d94d04e76d3f9f/src/KcApp/KcApp.tsx#L37) to update.
|
||||||
|
|
||||||
## v4
|
## 4
|
||||||
|
|
||||||
- Out of the box [frontend form validation](#user-profile-and-frontend-form-validation) 🥳
|
- Out of the box [frontend form validation](#user-profile-and-frontend-form-validation) 🥳
|
||||||
- Improvements (and breaking changes in `import { useKcMessage } from "keycloakify"`.
|
- Improvements (and breaking changes in `import { useKcMessage } from "keycloakify"`.
|
||||||
|
|
||||||
## v3
|
## 3
|
||||||
|
|
||||||
No breaking changes except that `@emotion/react`, [`tss-react`](https://www.npmjs.com/package/tss-react) and [`powerhooks`](https://www.npmjs.com/package/powerhooks) are now `peerDependencies` instead of being just dependencies.
|
No breaking changes except that `@emotion/react`, [`tss-react`](https://www.npmjs.com/package/tss-react) and [`powerhooks`](https://www.npmjs.com/package/powerhooks) are now `peerDependencies` instead of being just dependencies.
|
||||||
It's important to avoid problem when using `keycloakify` alongside [`mui`](https://mui.com) and
|
It's important to avoid problem when using `keycloakify` alongside [`mui`](https://mui.com) and
|
||||||
[when passing params from the app to the login page](https://github.com/keycloakify/keycloakify#implement-context-persistence-optional).
|
[when passing params from the app to the login page](https://github.com/keycloakify/keycloakify#implement-context-persistence-optional).
|
||||||
|
|
||||||
## v2.5
|
## 2.5
|
||||||
|
|
||||||
- Feature [Use advanced message](https://github.com/keycloakify/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/i18n/useKcMessage.tsx#L53-L66)
|
- Feature [Use advanced message](https://github.com/keycloakify/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/i18n/useKcMessage.tsx#L53-L66)
|
||||||
and [`messagesPerFields`](https://github.com/keycloakify/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/getKcContext/KcContextBase.ts#L70-L75) (implementation [here](https://github.com/keycloakify/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/generateFtl/common.ftl#L130-L189))
|
and [`messagesPerFields`](https://github.com/keycloakify/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/lib/getKcContext/KcContextBase.ts#L70-L75) (implementation [here](https://github.com/keycloakify/keycloakify/blob/59f106bf9e210b63b190826da2bf5f75fc8b7644/src/bin/build-keycloak-theme/generateFtl/common.ftl#L130-L189))
|
||||||
- Test container now uses Keycloak version `15.0.2`.
|
- Test container now uses Keycloak version `15.0.2`.
|
||||||
|
|
||||||
## v2
|
## 2
|
||||||
|
|
||||||
- It's now possible to implement custom `.ftl` pages.
|
- It's now possible to implement custom `.ftl` pages.
|
||||||
- Support for Keycloak plugins that introduce non standard ftl values.
|
- Support for Keycloak plugins that introduce non standard ftl values.
|
||||||
|
11
package.json
11
package.json
@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "keycloakify",
|
"name": "keycloakify",
|
||||||
"version": "9.1.4",
|
"version": "9.5.8",
|
||||||
"description": "Create Keycloak themes using React",
|
"description": "Create Keycloak themes using React",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"types": "dist/index.d.ts",
|
"types": "dist/index.d.ts",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"prepare": "yarn generate-i18n-messages",
|
"prepare": "yarn generate-i18n-messages",
|
||||||
"build": "rimraf dist/ && tsc -p src/bin && tsc -p src && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/ && cp -r src dist/",
|
"build": "rimraf dist/ && tsc -p src/bin && tsc -p src && tsc -p src/vite-plugin && tsc-alias -p src/tsconfig.json && yarn grant-exec-perms && yarn copy-files dist/ && cp -r src dist/",
|
||||||
"generate:json-schema": "ts-node scripts/generate-json-schema.ts",
|
"generate:json-schema": "ts-node scripts/generate-json-schema.ts",
|
||||||
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
|
||||||
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.java",
|
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.java",
|
||||||
@ -105,7 +105,8 @@
|
|||||||
"tss-react": "^4.8.2",
|
"tss-react": "^4.8.2",
|
||||||
"typescript": "^4.9.1-beta",
|
"typescript": "^4.9.1-beta",
|
||||||
"vitest": "^0.29.8",
|
"vitest": "^0.29.8",
|
||||||
"zod-to-json-schema": "^3.20.4"
|
"zod-to-json-schema": "^3.20.4",
|
||||||
|
"vite": "^5.0.12"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/generator": "^7.22.9",
|
"@babel/generator": "^7.22.9",
|
||||||
@ -118,13 +119,13 @@
|
|||||||
"make-fetch-happen": "^11.0.3",
|
"make-fetch-happen": "^11.0.3",
|
||||||
"minimal-polyfills": "^2.2.2",
|
"minimal-polyfills": "^2.2.2",
|
||||||
"minimist": "^1.2.6",
|
"minimist": "^1.2.6",
|
||||||
"path-browserify": "^1.0.1",
|
|
||||||
"react-markdown": "^5.0.3",
|
"react-markdown": "^5.0.3",
|
||||||
"recast": "^0.23.3",
|
"recast": "^0.23.3",
|
||||||
"rfc4648": "^1.5.2",
|
"rfc4648": "^1.5.2",
|
||||||
"tsafe": "^1.6.0",
|
"tsafe": "^1.6.0",
|
||||||
"yauzl": "^2.10.0",
|
"yauzl": "^2.10.0",
|
||||||
"yazl": "^2.5.1",
|
"yazl": "^2.5.1",
|
||||||
"zod": "^3.17.10"
|
"zod": "^3.17.10",
|
||||||
|
"magic-string": "^0.30.7"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@ import * as fs from "fs";
|
|||||||
import { join as pathJoin, relative as pathRelative, dirname as pathDirname, sep as pathSep } from "path";
|
import { join as pathJoin, relative as pathRelative, dirname as pathDirname, sep as pathSep } from "path";
|
||||||
import { crawl } from "../src/bin/tools/crawl";
|
import { crawl } from "../src/bin/tools/crawl";
|
||||||
import { downloadBuiltinKeycloakTheme } from "../src/bin/download-builtin-keycloak-theme";
|
import { downloadBuiltinKeycloakTheme } from "../src/bin/download-builtin-keycloak-theme";
|
||||||
import { getProjectRoot } from "../src/bin/tools/getProjectRoot";
|
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
|
||||||
import { getLogger } from "../src/bin/tools/logger";
|
import { getLogger } from "../src/bin/tools/logger";
|
||||||
|
|
||||||
// NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
|
// NOTE: To run without argument when we want to generate src/i18n/generated_kcMessages files,
|
||||||
@ -17,17 +17,24 @@ const isSilent = true;
|
|||||||
const logger = getLogger({ isSilent });
|
const logger = getLogger({ isSilent });
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const keycloakVersion = "21.0.1";
|
const keycloakVersion = "23.0.4";
|
||||||
|
|
||||||
const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44");
|
const thisCodebaseRootDirPath = getThisCodebaseRootDirPath();
|
||||||
|
|
||||||
|
const tmpDirPath = pathJoin(thisCodebaseRootDirPath, "tmp_xImOef9dOd44");
|
||||||
|
|
||||||
fs.rmSync(tmpDirPath, { "recursive": true, "force": true });
|
fs.rmSync(tmpDirPath, { "recursive": true, "force": true });
|
||||||
|
|
||||||
|
fs.mkdirSync(tmpDirPath);
|
||||||
|
|
||||||
|
fs.writeFileSync(pathJoin(tmpDirPath, ".gitignore"), Buffer.from("/*\n!.gitignore\n", "utf8"));
|
||||||
|
|
||||||
await downloadBuiltinKeycloakTheme({
|
await downloadBuiltinKeycloakTheme({
|
||||||
keycloakVersion,
|
keycloakVersion,
|
||||||
"destDirPath": tmpDirPath,
|
"destDirPath": tmpDirPath,
|
||||||
"buildOptions": {
|
"buildOptions": {
|
||||||
"cacheDirPath": pathJoin(getProjectRoot(), "node_modules", ".cache", "keycloakify")
|
"cacheDirPath": pathJoin(thisCodebaseRootDirPath, "node_modules", ".cache", "keycloakify"),
|
||||||
|
"npmWorkspaceRootDirPath": thisCodebaseRootDirPath
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -68,14 +75,13 @@ async function main() {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const baseMessagesDirPath = pathJoin(getProjectRoot(), "src", themeType, "i18n", "baseMessages");
|
const baseMessagesDirPath = pathJoin(thisCodebaseRootDirPath, "src", themeType, "i18n", "baseMessages");
|
||||||
|
|
||||||
const languages = Object.keys(recordForPageType);
|
const languages = Object.keys(recordForPageType);
|
||||||
|
|
||||||
const generatedFileHeader = [
|
const generatedFileHeader = [
|
||||||
`//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`,
|
`//This code was automatically generated by running ${pathRelative(thisCodebaseRootDirPath, __filename)}`,
|
||||||
"//PLEASE DO NOT EDIT MANUALLY",
|
"//PLEASE DO NOT EDIT MANUALLY"
|
||||||
""
|
|
||||||
].join("\n");
|
].join("\n");
|
||||||
|
|
||||||
languages.forEach(language => {
|
languages.forEach(language => {
|
||||||
@ -88,6 +94,7 @@ async function main() {
|
|||||||
Buffer.from(
|
Buffer.from(
|
||||||
[
|
[
|
||||||
generatedFileHeader,
|
generatedFileHeader,
|
||||||
|
"",
|
||||||
"/* spell-checker: disable */",
|
"/* spell-checker: disable */",
|
||||||
`const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`,
|
`const messages= ${JSON.stringify(recordForPageType[language], null, 2)};`,
|
||||||
"",
|
"",
|
||||||
@ -106,10 +113,15 @@ async function main() {
|
|||||||
Buffer.from(
|
Buffer.from(
|
||||||
[
|
[
|
||||||
generatedFileHeader,
|
generatedFileHeader,
|
||||||
|
`import * as en from "./en";`,
|
||||||
|
"",
|
||||||
"export async function getMessages(currentLanguageTag: string) {",
|
"export async function getMessages(currentLanguageTag: string) {",
|
||||||
" const { default: messages } = await (() => {",
|
" const { default: messages } = await (() => {",
|
||||||
" switch (currentLanguageTag) {",
|
" switch (currentLanguageTag) {",
|
||||||
...languages.map(language => ` case "${language}": return import("./${language}");`),
|
` case "en": return en;`,
|
||||||
|
...languages
|
||||||
|
.filter(language => language !== "en")
|
||||||
|
.map(language => ` case "${language}": return import("./${language}");`),
|
||||||
' default: return { "default": {} };',
|
' default: return { "default": {} };',
|
||||||
" }",
|
" }",
|
||||||
" })();",
|
" })();",
|
||||||
|
@ -1,14 +0,0 @@
|
|||||||
import fs from "fs";
|
|
||||||
import path from "path";
|
|
||||||
import zodToJsonSchema from "zod-to-json-schema";
|
|
||||||
import { zParsedPackageJson } from "../src/bin/keycloakify/parsedPackageJson";
|
|
||||||
|
|
||||||
const jsonSchemaName = "keycloakifyPackageJsonSchema";
|
|
||||||
const jsonSchema = zodToJsonSchema(zParsedPackageJson, jsonSchemaName);
|
|
||||||
|
|
||||||
const baseProperties = {
|
|
||||||
// merges package.json schema with keycloakify properties
|
|
||||||
"allOf": [{ "$ref": "https://json.schemastore.org/package.json" }, { "$ref": jsonSchemaName }]
|
|
||||||
};
|
|
||||||
|
|
||||||
fs.writeFileSync(path.join(process.cwd(), "keycloakify-json-schema.json"), JSON.stringify({ ...baseProperties, ...jsonSchema }, null, 2));
|
|
@ -1,11 +1,11 @@
|
|||||||
import { execSync } from "child_process";
|
import { execSync } from "child_process";
|
||||||
import { join as pathJoin, relative as pathRelative } from "path";
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
import { getProjectRoot } from "../src/bin/tools/getProjectRoot";
|
import { getThisCodebaseRootDirPath } from "../src/bin/tools/getThisCodebaseRootDirPath";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
|
||||||
const singletonDependencies: string[] = ["react", "@types/react"];
|
const singletonDependencies: string[] = ["react", "@types/react"];
|
||||||
|
|
||||||
const rootDirPath = getProjectRoot();
|
const rootDirPath = getThisCodebaseRootDirPath();
|
||||||
|
|
||||||
//NOTE: This is only required because of: https://github.com/garronej/ts-ci/blob/c0e207b9677523d4ec97fe672ddd72ccbb3c1cc4/README.md?plain=1#L54-L58
|
//NOTE: This is only required because of: https://github.com/garronej/ts-ci/blob/c0e207b9677523d4ec97fe672ddd72ccbb3c1cc4/README.md?plain=1#L54-L58
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
|
21
src/PUBLIC_URL.ts
Normal file
21
src/PUBLIC_URL.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "keycloakify/bin/constants";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is an equivalent of process.env.PUBLIC_URL thay you can use in Webpack projects.
|
||||||
|
* This works both in your main app and in your Keycloak theme.
|
||||||
|
*/
|
||||||
|
export const PUBLIC_URL = (() => {
|
||||||
|
const kcContext = (window as any)[nameOfTheGlobal];
|
||||||
|
|
||||||
|
if (kcContext === undefined || process.env.NODE_ENV === "development") {
|
||||||
|
assert(
|
||||||
|
process.env.PUBLIC_URL !== undefined,
|
||||||
|
`If you use keycloakify/PUBLIC_URL you should be in Webpack and thus process.env.PUBLIC_URL should be defined`
|
||||||
|
);
|
||||||
|
|
||||||
|
return process.env.PUBLIC_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${kcContext.url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}`;
|
||||||
|
})();
|
@ -90,6 +90,7 @@ export declare namespace KcContext {
|
|||||||
lastName?: string;
|
lastName?: string;
|
||||||
username?: string;
|
username?: string;
|
||||||
};
|
};
|
||||||
|
properties: Record<string, string | undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type Password = Common & {
|
export type Password = Common & {
|
||||||
|
@ -1,16 +1,16 @@
|
|||||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
import { deepAssign } from "keycloakify/tools/deepAssign";
|
||||||
|
import { isStorybook } from "keycloakify/lib/isStorybook";
|
||||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||||
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
||||||
import { pathJoin } from "keycloakify/bin/tools/pathJoin";
|
|
||||||
import { symToStr } from "tsafe/symToStr";
|
import { symToStr } from "tsafe/symToStr";
|
||||||
import { resources_common } from "keycloakify/bin/constants";
|
|
||||||
import { kcContextMocks, kcContextCommonMock } from "keycloakify/account/kcContext/kcContextMocks";
|
import { kcContextMocks, kcContextCommonMock } from "keycloakify/account/kcContext/kcContextMocks";
|
||||||
|
|
||||||
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
||||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||||
|
mockProperties?: Record<string, string>;
|
||||||
}) {
|
}) {
|
||||||
const { mockData } = params ?? {};
|
const { mockData, mockProperties } = params ?? {};
|
||||||
|
|
||||||
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: {
|
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: {
|
||||||
mockPageId?: PageId;
|
mockPageId?: PageId;
|
||||||
@ -27,7 +27,13 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
|||||||
if (mockPageId !== undefined && realKcContext === undefined) {
|
if (mockPageId !== undefined && realKcContext === undefined) {
|
||||||
//TODO maybe trow if no mock fo custom page
|
//TODO maybe trow if no mock fo custom page
|
||||||
|
|
||||||
|
warn_that_mock_is_enbaled: {
|
||||||
|
if (isStorybook) {
|
||||||
|
break warn_that_mock_is_enbaled;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`%cKeycloakify: ${symToStr({ mockPageId })} set to ${mockPageId}.`, "background: red; color: yellow; font-size: medium");
|
console.log(`%cKeycloakify: ${symToStr({ mockPageId })} set to ${mockPageId}.`, "background: red; color: yellow; font-size: medium");
|
||||||
|
}
|
||||||
|
|
||||||
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
|
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
|
||||||
|
|
||||||
@ -77,6 +83,13 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mockProperties !== undefined) {
|
||||||
|
deepAssign({
|
||||||
|
"target": kcContext.properties,
|
||||||
|
"source": mockProperties
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return { kcContext };
|
return { kcContext };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,8 +101,6 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
|||||||
return { "kcContext": undefined as any };
|
return { "kcContext": undefined as any };
|
||||||
}
|
}
|
||||||
|
|
||||||
realKcContext.url.resourcesCommonPath = pathJoin(realKcContext.url.resourcesPath, resources_common);
|
|
||||||
|
|
||||||
return { "kcContext": realKcContext as any };
|
return { "kcContext": realKcContext as any };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
||||||
import { ftlValuesGlobalName } from "keycloakify/bin/keycloakify/ftlValuesGlobalName";
|
import { nameOfTheGlobal } from "keycloakify/bin/constants";
|
||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
|
|
||||||
export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [KcContextExtension] extends [never]
|
export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [KcContextExtension] extends [never]
|
||||||
@ -7,5 +7,5 @@ export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [Kc
|
|||||||
: AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>;
|
: AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>;
|
||||||
|
|
||||||
export function getKcContextFromWindow<KcContextExtension extends { pageId: string } = never>(): ExtendKcContext<KcContextExtension> | undefined {
|
export function getKcContextFromWindow<KcContextExtension extends { pageId: string } = never>(): ExtendKcContext<KcContextExtension> | undefined {
|
||||||
return typeof window === "undefined" ? undefined : (window as any)[ftlValuesGlobalName];
|
return typeof window === "undefined" ? undefined : (window as any)[nameOfTheGlobal];
|
||||||
}
|
}
|
||||||
|
@ -1,12 +1,10 @@
|
|||||||
import "minimal-polyfills/Object.fromEntries";
|
import "minimal-polyfills/Object.fromEntries";
|
||||||
import { resources_common, keycloak_resources } from "keycloakify/bin/constants";
|
import { resources_common, keycloak_resources } from "keycloakify/bin/constants";
|
||||||
import { pathJoin } from "keycloakify/bin/tools/pathJoin";
|
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
|
import { BASE_URL } from "keycloakify/lib/BASE_URL";
|
||||||
|
|
||||||
const PUBLIC_URL = (typeof process !== "object" ? undefined : process.env?.["PUBLIC_URL"]) || "/";
|
const resourcesPath = `${BASE_URL}${keycloak_resources}/account/resources`;
|
||||||
|
|
||||||
const resourcesPath = pathJoin(PUBLIC_URL, keycloak_resources, "account", "resources");
|
|
||||||
|
|
||||||
export const kcContextCommonMock: KcContext.Common = {
|
export const kcContextCommonMock: KcContext.Common = {
|
||||||
"themeVersion": "0.0.0",
|
"themeVersion": "0.0.0",
|
||||||
@ -15,7 +13,7 @@ export const kcContextCommonMock: KcContext.Common = {
|
|||||||
"themeName": "my-theme-name",
|
"themeName": "my-theme-name",
|
||||||
"url": {
|
"url": {
|
||||||
resourcesPath,
|
resourcesPath,
|
||||||
"resourcesCommonPath": pathJoin(resourcesPath, resources_common),
|
"resourcesCommonPath": `${resourcesPath}/${resources_common}`,
|
||||||
"resourceUrl": "#",
|
"resourceUrl": "#",
|
||||||
"accountUrl": "#",
|
"accountUrl": "#",
|
||||||
"applicationsUrl": "#",
|
"applicationsUrl": "#",
|
||||||
@ -147,6 +145,17 @@ export const kcContextCommonMock: KcContext.Common = {
|
|||||||
"lastName": "doe",
|
"lastName": "doe",
|
||||||
"email": "john.doe@code.gouv.fr",
|
"email": "john.doe@code.gouv.fr",
|
||||||
"username": "doe_j"
|
"username": "doe_j"
|
||||||
|
},
|
||||||
|
"properties": {
|
||||||
|
"parent": "account-v1",
|
||||||
|
"kcButtonLargeClass": "btn-lg",
|
||||||
|
"locales": "ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
|
||||||
|
"kcButtonPrimaryClass": "btn-primary",
|
||||||
|
"accountResourceProvider": "account-v1",
|
||||||
|
"styles":
|
||||||
|
"css/account.css img/icon-sidebar-active.png img/logo.png resources-common/node_modules/patternfly/dist/css/patternfly.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css resources-common/node_modules/patternfly/dist/css/patternfly-additions.min.css",
|
||||||
|
"kcButtonClass": "btn",
|
||||||
|
"kcButtonDefaultClass": "btn-default"
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import type { LazyExoticComponent } from "react";
|
|
||||||
import type { I18n } from "keycloakify/account/i18n";
|
import type { I18n } from "keycloakify/account/i18n";
|
||||||
import type { TemplateProps, ClassKey } from "keycloakify/account/TemplateProps";
|
import type { TemplateProps, ClassKey } from "keycloakify/account/TemplateProps";
|
||||||
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
|
import type { KcContext } from "keycloakify/account/kcContext";
|
||||||
|
|
||||||
export type PageProps<KcContext, I18nExtended extends I18n> = {
|
export type PageProps<NarowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
|
||||||
Template: LazyExoticComponent<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
||||||
kcContext: KcContext;
|
kcContext: NarowedKcContext;
|
||||||
i18n: I18nExtended;
|
i18n: I18nExtended;
|
||||||
doUseDefaultCss: boolean;
|
doUseDefaultCss: boolean;
|
||||||
classes?: Partial<Record<ClassKey, string>>;
|
classes?: Partial<Record<ClassKey, string>>;
|
||||||
|
@ -1,9 +1,14 @@
|
|||||||
|
export const nameOfTheGlobal = "kcContext";
|
||||||
export const keycloak_resources = "keycloak-resources";
|
export const keycloak_resources = "keycloak-resources";
|
||||||
export const resources_common = "resources-common";
|
export const resources_common = "resources-common";
|
||||||
export const lastKeycloakVersionWithAccountV1 = "21.1.2";
|
export const lastKeycloakVersionWithAccountV1 = "21.1.2";
|
||||||
|
export const resolvedViteConfigJsonBasename = "vite.json";
|
||||||
|
export const basenameOfTheKeycloakifyResourcesDir = "build";
|
||||||
|
|
||||||
export const themeTypes = ["login", "account"] as const;
|
export const themeTypes = ["login", "account"] as const;
|
||||||
export const retrocompatPostfix = "_retrocompat";
|
export const retrocompatPostfix = "_retrocompat";
|
||||||
export const accountV1 = "account-v1";
|
export const accountV1ThemeName = "account-v1";
|
||||||
|
|
||||||
export type ThemeType = (typeof themeTypes)[number];
|
export type ThemeType = (typeof themeTypes)[number];
|
||||||
|
|
||||||
|
export const keycloakifyBuildOptionsForPostPostBuildScriptEnvName = "KEYCLOAKIFY_BUILD_OPTIONS_POST_POST_BUILD_SCRIPT";
|
||||||
|
@ -1,20 +1,44 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { downloadKeycloakStaticResources } from "./keycloakify/generateTheme/downloadKeycloakStaticResources";
|
import { downloadKeycloakStaticResources, type BuildOptionsLike } from "./keycloakify/generateTheme/downloadKeycloakStaticResources";
|
||||||
import { join as pathJoin, relative as pathRelative } from "path";
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
import { readBuildOptions } from "./keycloakify/BuildOptions";
|
import { readBuildOptions } from "./keycloakify/buildOptions";
|
||||||
import { themeTypes, keycloak_resources, lastKeycloakVersionWithAccountV1 } from "./constants";
|
import { themeTypes, keycloak_resources, lastKeycloakVersionWithAccountV1 } from "./constants";
|
||||||
|
import { readThisNpmProjectVersion } from "./tools/readThisNpmProjectVersion";
|
||||||
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { rmSync } from "./tools/fs.rmSync";
|
||||||
|
|
||||||
(async () => {
|
export async function copyKeycloakResourcesToPublic(params: { processArgv: string[] }) {
|
||||||
const reactAppRootDirPath = process.cwd();
|
const { processArgv } = params;
|
||||||
|
|
||||||
const buildOptions = readBuildOptions({
|
const buildOptions = readBuildOptions({ processArgv });
|
||||||
reactAppRootDirPath,
|
|
||||||
"processArgv": process.argv.slice(2)
|
const destDirPath = pathJoin(buildOptions.publicDirPath, keycloak_resources);
|
||||||
|
|
||||||
|
const keycloakifyBuildinfoFilePath = pathJoin(destDirPath, "keycloakify.buildinfo");
|
||||||
|
|
||||||
|
const { keycloakifyBuildinfoRaw } = generateKeycloakifyBuildinfoRaw({
|
||||||
|
destDirPath,
|
||||||
|
"keycloakifyVersion": readThisNpmProjectVersion(),
|
||||||
|
buildOptions
|
||||||
});
|
});
|
||||||
|
|
||||||
const reservedDirPath = pathJoin(buildOptions.publicDirPath, keycloak_resources);
|
skip_if_already_done: {
|
||||||
|
if (!fs.existsSync(keycloakifyBuildinfoFilePath)) {
|
||||||
|
break skip_if_already_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
const keycloakifyBuildinfoRaw_previousRun = fs.readFileSync(keycloakifyBuildinfoFilePath).toString("utf8");
|
||||||
|
|
||||||
|
if (keycloakifyBuildinfoRaw_previousRun !== keycloakifyBuildinfoRaw) {
|
||||||
|
break skip_if_already_done;
|
||||||
|
}
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
rmSync(destDirPath, { "force": true, "recursive": true });
|
||||||
|
|
||||||
for (const themeType of themeTypes) {
|
for (const themeType of themeTypes) {
|
||||||
await downloadKeycloakStaticResources({
|
await downloadKeycloakStaticResources({
|
||||||
@ -27,14 +51,13 @@ import * as fs from "fs";
|
|||||||
}
|
}
|
||||||
})(),
|
})(),
|
||||||
themeType,
|
themeType,
|
||||||
"themeDirPath": reservedDirPath,
|
"themeDirPath": destDirPath,
|
||||||
"usedResources": undefined,
|
|
||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
pathJoin(reservedDirPath, "README.txt"),
|
pathJoin(destDirPath, "README.txt"),
|
||||||
Buffer.from(
|
Buffer.from(
|
||||||
// prettier-ignore
|
// prettier-ignore
|
||||||
[
|
[
|
||||||
@ -44,7 +67,46 @@ import * as fs from "fs";
|
|||||||
)
|
)
|
||||||
);
|
);
|
||||||
|
|
||||||
fs.writeFileSync(pathJoin(buildOptions.publicDirPath, "keycloak-resources", ".gitignore"), Buffer.from("*", "utf8"));
|
fs.writeFileSync(pathJoin(buildOptions.publicDirPath, keycloak_resources, ".gitignore"), Buffer.from("*", "utf8"));
|
||||||
|
|
||||||
console.log(`${pathRelative(reactAppRootDirPath, reservedDirPath)} directory created.`);
|
fs.writeFileSync(keycloakifyBuildinfoFilePath, Buffer.from(keycloakifyBuildinfoRaw, "utf8"));
|
||||||
})();
|
}
|
||||||
|
|
||||||
|
export function generateKeycloakifyBuildinfoRaw(params: {
|
||||||
|
destDirPath: string;
|
||||||
|
keycloakifyVersion: string;
|
||||||
|
buildOptions: BuildOptionsLike & {
|
||||||
|
loginThemeResourcesFromKeycloakVersion: string;
|
||||||
|
};
|
||||||
|
}) {
|
||||||
|
const { destDirPath, keycloakifyVersion, buildOptions } = params;
|
||||||
|
|
||||||
|
const { cacheDirPath, npmWorkspaceRootDirPath, loginThemeResourcesFromKeycloakVersion, ...rest } = buildOptions;
|
||||||
|
|
||||||
|
assert<Equals<typeof rest, {}>>(true);
|
||||||
|
|
||||||
|
const keycloakifyBuildinfoRaw = JSON.stringify(
|
||||||
|
{
|
||||||
|
keycloakifyVersion,
|
||||||
|
"buildOptions": {
|
||||||
|
loginThemeResourcesFromKeycloakVersion,
|
||||||
|
"cacheDirPath": pathRelative(destDirPath, cacheDirPath),
|
||||||
|
"npmWorkspaceRootDirPath": pathRelative(destDirPath, npmWorkspaceRootDirPath)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
);
|
||||||
|
|
||||||
|
return { keycloakifyBuildinfoRaw };
|
||||||
|
}
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
await copyKeycloakResourcesToPublic({
|
||||||
|
"processArgv": process.argv.slice(2)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (require.main === module) {
|
||||||
|
main();
|
||||||
|
}
|
||||||
|
@ -1,16 +1,19 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { downloadAndUnzip } from "./tools/downloadAndUnzip";
|
import { downloadAndUnzip } from "./downloadAndUnzip";
|
||||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||||
import { getLogger } from "./tools/logger";
|
import { getLogger } from "./tools/logger";
|
||||||
import { readBuildOptions } from "./keycloakify/BuildOptions";
|
import { readBuildOptions, type BuildOptions } from "./keycloakify/buildOptions";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { BuildOptions } from "./keycloakify/BuildOptions";
|
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
|
import { rmSync } from "./tools/fs.rmSync";
|
||||||
|
import { lastKeycloakVersionWithAccountV1 } from "./constants";
|
||||||
|
import { transformCodebase } from "./tools/transformCodebase";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
|
npmWorkspaceRootDirPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
@ -19,11 +22,10 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st
|
|||||||
const { keycloakVersion, destDirPath, buildOptions } = params;
|
const { keycloakVersion, destDirPath, buildOptions } = params;
|
||||||
|
|
||||||
await downloadAndUnzip({
|
await downloadAndUnzip({
|
||||||
"doUseCache": true,
|
|
||||||
"cacheDirPath": buildOptions.cacheDirPath,
|
|
||||||
destDirPath,
|
destDirPath,
|
||||||
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
|
"url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`,
|
||||||
"specificDirsToExtract": ["", "-community"].map(ext => `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`),
|
"specificDirsToExtract": ["", "-community"].map(ext => `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`),
|
||||||
|
buildOptions,
|
||||||
"preCacheTransform": {
|
"preCacheTransform": {
|
||||||
"actionCacheId": "npm install and build",
|
"actionCacheId": "npm install and build",
|
||||||
"action": async ({ destDirPath }) => {
|
"action": async ({ destDirPath }) => {
|
||||||
@ -48,32 +50,160 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
install_and_move_to_common_resources_generated_in_keycloak_v2: {
|
remove_keycloak_v2: {
|
||||||
const accountV2DirSrcDirPath = pathJoin(destDirPath, "keycloak.v2", "account", "src");
|
const keycloakV2DirPath = pathJoin(destDirPath, "keycloak.v2");
|
||||||
|
|
||||||
if (!fs.existsSync(accountV2DirSrcDirPath)) {
|
if (!fs.existsSync(keycloakV2DirPath)) {
|
||||||
break install_and_move_to_common_resources_generated_in_keycloak_v2;
|
break remove_keycloak_v2;
|
||||||
}
|
}
|
||||||
|
|
||||||
child_process.execSync("npm install", { "cwd": accountV2DirSrcDirPath, "stdio": "ignore" });
|
rmSync(keycloakV2DirPath, { "recursive": true });
|
||||||
|
}
|
||||||
|
|
||||||
const packageJsonFilePath = pathJoin(accountV2DirSrcDirPath, "package.json");
|
// Note, this is an optimization for reducing the size of the jar
|
||||||
|
remove_unused_node_modules: {
|
||||||
|
const nodeModuleDirPath = pathJoin(destDirPath, "keycloak", "common", "resources", "node_modules");
|
||||||
|
|
||||||
const packageJsonRaw = fs.readFileSync(packageJsonFilePath);
|
if (!fs.existsSync(nodeModuleDirPath)) {
|
||||||
|
break remove_unused_node_modules;
|
||||||
|
}
|
||||||
|
|
||||||
const parsedPackageJson = JSON.parse(packageJsonRaw.toString("utf8"));
|
const toDeletePerfixes = [
|
||||||
|
"angular",
|
||||||
|
"bootstrap",
|
||||||
|
"rcue",
|
||||||
|
"font-awesome",
|
||||||
|
"ng-file-upload",
|
||||||
|
pathJoin("patternfly", "dist", "sass"),
|
||||||
|
pathJoin("patternfly", "dist", "less"),
|
||||||
|
pathJoin("patternfly", "dist", "js"),
|
||||||
|
"d3",
|
||||||
|
pathJoin("jquery", "src"),
|
||||||
|
"c3",
|
||||||
|
"core-js",
|
||||||
|
"eonasdan-bootstrap-datetimepicker",
|
||||||
|
"moment",
|
||||||
|
"react",
|
||||||
|
"patternfly-bootstrap-treeview",
|
||||||
|
"popper.js",
|
||||||
|
"tippy.js",
|
||||||
|
"jquery-match-height",
|
||||||
|
"google-code-prettify",
|
||||||
|
"patternfly-bootstrap-combobox",
|
||||||
|
"focus-trap",
|
||||||
|
"tabbable",
|
||||||
|
"scheduler",
|
||||||
|
"@types",
|
||||||
|
"datatables.net",
|
||||||
|
"datatables.net-colreorder",
|
||||||
|
"tslib",
|
||||||
|
"prop-types",
|
||||||
|
"file-selector",
|
||||||
|
"datatables.net-colreorder-bs",
|
||||||
|
"object-assign",
|
||||||
|
"warning",
|
||||||
|
"js-tokens",
|
||||||
|
"loose-envify",
|
||||||
|
"prop-types-extra",
|
||||||
|
"attr-accept",
|
||||||
|
"datatables.net-select",
|
||||||
|
"drmonty-datatables-colvis",
|
||||||
|
"datatables.net-bs",
|
||||||
|
pathJoin("@patternfly", "react"),
|
||||||
|
pathJoin("@patternfly", "patternfly", "docs")
|
||||||
|
];
|
||||||
|
|
||||||
parsedPackageJson.scripts.build = parsedPackageJson.scripts.build
|
transformCodebase({
|
||||||
.replace("npm run check-types", "true")
|
"srcDirPath": nodeModuleDirPath,
|
||||||
.replace("npm run babel", "true");
|
"destDirPath": nodeModuleDirPath,
|
||||||
|
"transformSourceCode": ({ sourceCode, fileRelativePath }) => {
|
||||||
|
if (fileRelativePath.endsWith(".map")) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
fs.writeFileSync(packageJsonFilePath, Buffer.from(JSON.stringify(parsedPackageJson, null, 2), "utf8"));
|
if (toDeletePerfixes.find(prefix => fileRelativePath.startsWith(prefix)) !== undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
child_process.execSync("npm run build", { "cwd": accountV2DirSrcDirPath, "stdio": "ignore" });
|
if (fileRelativePath.startsWith(pathJoin("patternfly", "dist", "fonts"))) {
|
||||||
|
if (
|
||||||
|
!fileRelativePath.endsWith(".woff2") &&
|
||||||
|
!fileRelativePath.endsWith(".woff") &&
|
||||||
|
!fileRelativePath.endsWith(".ttf")
|
||||||
|
) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fs.writeFileSync(packageJsonFilePath, packageJsonRaw);
|
return { "modifiedSourceCode": sourceCode };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
fs.rmSync(pathJoin(accountV2DirSrcDirPath, "node_modules"), { "recursive": true });
|
// Just like node_modules
|
||||||
|
remove_unused_lib: {
|
||||||
|
const libDirPath = pathJoin(destDirPath, "keycloak", "common", "resources", "lib");
|
||||||
|
|
||||||
|
if (!fs.existsSync(libDirPath)) {
|
||||||
|
break remove_unused_lib;
|
||||||
|
}
|
||||||
|
|
||||||
|
const toDeletePerfixes = ["ui-ace", "filesaver", "fileupload", "angular", "ui-ace", "pficon"];
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
"srcDirPath": libDirPath,
|
||||||
|
"destDirPath": libDirPath,
|
||||||
|
"transformSourceCode": ({ sourceCode, fileRelativePath }) => {
|
||||||
|
if (fileRelativePath.endsWith(".map")) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (toDeletePerfixes.find(prefix => fileRelativePath.startsWith(prefix)) !== undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { "modifiedSourceCode": sourceCode };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
last_account_v1_transformations: {
|
||||||
|
if (lastKeycloakVersionWithAccountV1 !== keycloakVersion) {
|
||||||
|
break last_account_v1_transformations;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const accountCssFilePath = pathJoin(destDirPath, "keycloak", "account", "resources", "css", "account.css");
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
accountCssFilePath,
|
||||||
|
Buffer.from(fs.readFileSync(accountCssFilePath).toString("utf8").replace("top: -34px;", "top: -34px !important;"), "utf8")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note, this is an optimization for reducing the size of the jar,
|
||||||
|
// For this version we know exactly which resources are used.
|
||||||
|
{
|
||||||
|
const nodeModulesDirPath = pathJoin(destDirPath, "keycloak", "common", "resources", "node_modules");
|
||||||
|
|
||||||
|
const toKeepPrefixes = [
|
||||||
|
...["patternfly.min.css", "patternfly-additions.min.css", "patternfly-additions.min.css"].map(fileBasename =>
|
||||||
|
pathJoin("patternfly", "dist", "css", fileBasename)
|
||||||
|
),
|
||||||
|
pathJoin("patternfly", "dist", "fonts")
|
||||||
|
];
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
"srcDirPath": nodeModulesDirPath,
|
||||||
|
"destDirPath": nodeModulesDirPath,
|
||||||
|
"transformSourceCode": ({ sourceCode, fileRelativePath }) => {
|
||||||
|
if (toKeepPrefixes.find(prefix => fileRelativePath.startsWith(prefix)) === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return { "modifiedSourceCode": sourceCode };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -82,7 +212,6 @@ export async function downloadBuiltinKeycloakTheme(params: { keycloakVersion: st
|
|||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const buildOptions = readBuildOptions({
|
const buildOptions = readBuildOptions({
|
||||||
"reactAppRootDirPath": process.cwd(),
|
|
||||||
"processArgv": process.argv.slice(2)
|
"processArgv": process.argv.slice(2)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
203
src/bin/downloadAndUnzip.ts
Normal file
203
src/bin/downloadAndUnzip.ts
Normal file
@ -0,0 +1,203 @@
|
|||||||
|
import { createHash } from "crypto";
|
||||||
|
import { mkdir, writeFile, unlink } from "fs/promises";
|
||||||
|
import fetch from "make-fetch-happen";
|
||||||
|
import { dirname as pathDirname, join as pathJoin, basename as pathBasename } from "path";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import { transformCodebase } from "./tools/transformCodebase";
|
||||||
|
import { unzip, zip } from "./tools/unzip";
|
||||||
|
import { rm } from "./tools/fs.rm";
|
||||||
|
import * as child_process from "child_process";
|
||||||
|
import { existsAsync } from "./tools/fs.existsAsync";
|
||||||
|
import type { BuildOptions } from "./keycloakify/buildOptions";
|
||||||
|
import { getProxyFetchOptions } from "./tools/fetchProxyOptions";
|
||||||
|
|
||||||
|
export type BuildOptionsLike = {
|
||||||
|
cacheDirPath: string;
|
||||||
|
npmWorkspaceRootDirPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
|
|
||||||
|
export async function downloadAndUnzip(params: {
|
||||||
|
url: string;
|
||||||
|
destDirPath: string;
|
||||||
|
specificDirsToExtract?: string[];
|
||||||
|
preCacheTransform?: {
|
||||||
|
actionCacheId: string;
|
||||||
|
action: (params: { destDirPath: string }) => Promise<void>;
|
||||||
|
};
|
||||||
|
buildOptions: BuildOptionsLike;
|
||||||
|
}) {
|
||||||
|
const { url, destDirPath, specificDirsToExtract, preCacheTransform, buildOptions } = params;
|
||||||
|
|
||||||
|
const { extractDirPath, zipFilePath } = (() => {
|
||||||
|
const zipFileBasenameWithoutExt = generateFileNameFromURL({
|
||||||
|
url,
|
||||||
|
"preCacheTransform":
|
||||||
|
preCacheTransform === undefined
|
||||||
|
? undefined
|
||||||
|
: {
|
||||||
|
"actionCacheId": preCacheTransform.actionCacheId,
|
||||||
|
"actionFootprint": preCacheTransform.action.toString()
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const zipFilePath = pathJoin(buildOptions.cacheDirPath, `${zipFileBasenameWithoutExt}.zip`);
|
||||||
|
const extractDirPath = pathJoin(buildOptions.cacheDirPath, `tmp_unzip_${zipFileBasenameWithoutExt}`);
|
||||||
|
|
||||||
|
return { zipFilePath, extractDirPath };
|
||||||
|
})();
|
||||||
|
|
||||||
|
download_zip_and_transform: {
|
||||||
|
if (await existsAsync(zipFilePath)) {
|
||||||
|
break download_zip_and_transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { response, isFromRemoteCache } = await (async () => {
|
||||||
|
const proxyFetchOptions = await getProxyFetchOptions({
|
||||||
|
"npmWorkspaceRootDirPath": buildOptions.npmWorkspaceRootDirPath
|
||||||
|
});
|
||||||
|
|
||||||
|
const response = await fetch(
|
||||||
|
`https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/${pathBasename(zipFilePath)}`,
|
||||||
|
proxyFetchOptions
|
||||||
|
);
|
||||||
|
|
||||||
|
if (response.status === 200) {
|
||||||
|
return {
|
||||||
|
response,
|
||||||
|
"isFromRemoteCache": true
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"response": await fetch(url, proxyFetchOptions),
|
||||||
|
"isFromRemoteCache": false
|
||||||
|
};
|
||||||
|
})();
|
||||||
|
|
||||||
|
await mkdir(pathDirname(zipFilePath), { "recursive": true });
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The correct way to fix this is to upgrade node-fetch beyond 3.2.5
|
||||||
|
* (see https://github.com/node-fetch/node-fetch/issues/1295#issuecomment-1144061991.)
|
||||||
|
* Unfortunately, octokit (a dependency of keycloakify) also uses node-fetch, and
|
||||||
|
* does not support node-fetch 3.x. So we stick around with this band-aid until
|
||||||
|
* octokit upgrades.
|
||||||
|
*/
|
||||||
|
response.body?.setMaxListeners(Number.MAX_VALUE);
|
||||||
|
assert(typeof response.body !== "undefined" && response.body != null);
|
||||||
|
|
||||||
|
await writeFile(zipFilePath, response.body);
|
||||||
|
|
||||||
|
if (isFromRemoteCache) {
|
||||||
|
break download_zip_and_transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (specificDirsToExtract === undefined && preCacheTransform === undefined) {
|
||||||
|
break download_zip_and_transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
await unzip(zipFilePath, extractDirPath, specificDirsToExtract);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await preCacheTransform?.action({
|
||||||
|
"destDirPath": extractDirPath
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await Promise.all([rm(extractDirPath, { "recursive": true }), unlink(zipFilePath)]);
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
await unlink(zipFilePath);
|
||||||
|
|
||||||
|
await zip(extractDirPath, zipFilePath);
|
||||||
|
|
||||||
|
await rm(extractDirPath, { "recursive": true });
|
||||||
|
|
||||||
|
upload_to_remot_cache_if_admin: {
|
||||||
|
const githubToken = process.env["KEYCLOAKIFY_ADMIN_GITHUB_PERSONAL_ACCESS_TOKEN"];
|
||||||
|
|
||||||
|
if (githubToken === undefined) {
|
||||||
|
break upload_to_remot_cache_if_admin;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log("uploading to remote cache");
|
||||||
|
|
||||||
|
try {
|
||||||
|
child_process.execSync(`which putasset`);
|
||||||
|
} catch {
|
||||||
|
child_process.execSync(`npm install -g putasset`);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
child_process.execFileSync("putasset", [
|
||||||
|
"--owner",
|
||||||
|
"keycloakify",
|
||||||
|
"--repo",
|
||||||
|
"keycloakify",
|
||||||
|
"--tag",
|
||||||
|
"v0.0.1",
|
||||||
|
"--filename",
|
||||||
|
zipFilePath,
|
||||||
|
"--token",
|
||||||
|
githubToken
|
||||||
|
]);
|
||||||
|
} catch {
|
||||||
|
console.log("upload failed, asset probably already exists in remote cache");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
await unzip(zipFilePath, extractDirPath);
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
"srcDirPath": extractDirPath,
|
||||||
|
"destDirPath": destDirPath
|
||||||
|
});
|
||||||
|
|
||||||
|
await rm(extractDirPath, { "recursive": true });
|
||||||
|
}
|
||||||
|
|
||||||
|
function generateFileNameFromURL(params: {
|
||||||
|
url: string;
|
||||||
|
preCacheTransform:
|
||||||
|
| {
|
||||||
|
actionCacheId: string;
|
||||||
|
actionFootprint: string;
|
||||||
|
}
|
||||||
|
| undefined;
|
||||||
|
}): string {
|
||||||
|
const { preCacheTransform } = params;
|
||||||
|
|
||||||
|
// Parse the URL
|
||||||
|
const url = new URL(params.url);
|
||||||
|
|
||||||
|
// Extract pathname and remove leading slashes
|
||||||
|
let fileName = url.pathname.replace(/^\//, "").replace(/\//g, "_");
|
||||||
|
|
||||||
|
// Optionally, add query parameters replacing special characters
|
||||||
|
if (url.search) {
|
||||||
|
fileName += url.search.replace(/[&=?]/g, "-");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace any characters that are not valid in filenames
|
||||||
|
fileName = fileName.replace(/[^a-zA-Z0-9-_]/g, "");
|
||||||
|
|
||||||
|
// Trim or pad the fileName to a specific length
|
||||||
|
fileName = fileName.substring(0, 50);
|
||||||
|
|
||||||
|
add_pre_cache_transform: {
|
||||||
|
if (preCacheTransform === undefined) {
|
||||||
|
break add_pre_cache_transform;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sanitize actionCacheId the same way as other components
|
||||||
|
const sanitizedActionCacheId = preCacheTransform.actionCacheId.replace(/[^a-zA-Z0-9-_]/g, "_");
|
||||||
|
|
||||||
|
fileName += `_${sanitizedActionCacheId}_${createHash("sha256").update(preCacheTransform.actionFootprint).digest("hex").substring(0, 5)}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return fileName;
|
||||||
|
}
|
@ -1,6 +1,6 @@
|
|||||||
#!/usr/bin/env node
|
#!/usr/bin/env node
|
||||||
|
|
||||||
import { getProjectRoot } from "./tools/getProjectRoot";
|
import { getThisCodebaseRootDirPath } from "./tools/getThisCodebaseRootDirPath";
|
||||||
import cliSelect from "cli-select";
|
import cliSelect from "cli-select";
|
||||||
import { loginThemePageIds, accountThemePageIds, type LoginThemePageId, type AccountThemePageId } from "./keycloakify/generateFtl";
|
import { loginThemePageIds, accountThemePageIds, type LoginThemePageId, type AccountThemePageId } from "./keycloakify/generateFtl";
|
||||||
import { capitalize } from "tsafe/capitalize";
|
import { capitalize } from "tsafe/capitalize";
|
||||||
@ -9,13 +9,16 @@ import { existsSync } from "fs";
|
|||||||
import { join as pathJoin, relative as pathRelative } from "path";
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
||||||
import { assert, Equals } from "tsafe/assert";
|
import { assert, Equals } from "tsafe/assert";
|
||||||
import { getThemeSrcDirPath } from "./getSrcDirPath";
|
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
|
||||||
import { themeTypes, type ThemeType } from "./constants";
|
import { themeTypes, type ThemeType } from "./constants";
|
||||||
|
import { getReactAppRootDirPath } from "./keycloakify/buildOptions/getReactAppRootDirPath";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
console.log("Select a theme type");
|
console.log("Select a theme type");
|
||||||
|
|
||||||
const reactAppRootDirPath = process.cwd();
|
const { reactAppRootDirPath } = getReactAppRootDirPath({
|
||||||
|
"processArgv": process.argv.slice(2)
|
||||||
|
});
|
||||||
|
|
||||||
const { value: themeType } = await cliSelect<ThemeType>({
|
const { value: themeType } = await cliSelect<ThemeType>({
|
||||||
"values": [...themeTypes]
|
"values": [...themeTypes]
|
||||||
@ -55,7 +58,7 @@ import { themeTypes, type ThemeType } from "./constants";
|
|||||||
process.exit(-1);
|
process.exit(-1);
|
||||||
}
|
}
|
||||||
|
|
||||||
await writeFile(targetFilePath, await readFile(pathJoin(getProjectRoot(), "src", themeType, "pages", pageBasename)));
|
await writeFile(targetFilePath, await readFile(pathJoin(getThisCodebaseRootDirPath(), "src", themeType, "pages", pageBasename)));
|
||||||
|
|
||||||
console.log(`${pathRelative(process.cwd(), targetFilePath)} created`);
|
console.log(`${pathRelative(process.cwd(), targetFilePath)} created`);
|
||||||
})();
|
})();
|
||||||
|
@ -4,23 +4,21 @@ import { downloadBuiltinKeycloakTheme } from "./download-builtin-keycloak-theme"
|
|||||||
import { join as pathJoin, relative as pathRelative } from "path";
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
import { transformCodebase } from "./tools/transformCodebase";
|
import { transformCodebase } from "./tools/transformCodebase";
|
||||||
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
import { promptKeycloakVersion } from "./promptKeycloakVersion";
|
||||||
import { readBuildOptions } from "./keycloakify/BuildOptions";
|
import { readBuildOptions } from "./keycloakify/buildOptions";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { getLogger } from "./tools/logger";
|
import { getLogger } from "./tools/logger";
|
||||||
import { getThemeSrcDirPath } from "./getSrcDirPath";
|
import { getThemeSrcDirPath } from "./getThemeSrcDirPath";
|
||||||
|
import { rmSync } from "./tools/fs.rmSync";
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
const reactAppRootDirPath = process.cwd();
|
|
||||||
|
|
||||||
const buildOptions = readBuildOptions({
|
const buildOptions = readBuildOptions({
|
||||||
reactAppRootDirPath,
|
|
||||||
"processArgv": process.argv.slice(2)
|
"processArgv": process.argv.slice(2)
|
||||||
});
|
});
|
||||||
|
|
||||||
const logger = getLogger({ "isSilent": buildOptions.isSilent });
|
const logger = getLogger({ "isSilent": buildOptions.isSilent });
|
||||||
|
|
||||||
const { themeSrcDirPath } = getThemeSrcDirPath({
|
const { themeSrcDirPath } = getThemeSrcDirPath({
|
||||||
reactAppRootDirPath
|
"reactAppRootDirPath": buildOptions.reactAppRootDirPath
|
||||||
});
|
});
|
||||||
|
|
||||||
const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email");
|
const emailThemeSrcDirPath = pathJoin(themeSrcDirPath, "email");
|
||||||
@ -54,7 +52,7 @@ export async function main() {
|
|||||||
|
|
||||||
logger.log(`${pathRelative(process.cwd(), emailThemeSrcDirPath)} ready to be customized, feel free to remove every file you do not customize`);
|
logger.log(`${pathRelative(process.cwd(), emailThemeSrcDirPath)} ready to be customized, feel free to remove every file you do not customize`);
|
||||||
|
|
||||||
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
|
rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true, "force": true });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
|
@ -1,157 +0,0 @@
|
|||||||
import { parse as urlParse } from "url";
|
|
||||||
import { getParsedPackageJson } from "./parsedPackageJson";
|
|
||||||
import { join as pathJoin } from "path";
|
|
||||||
import parseArgv from "minimist";
|
|
||||||
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
|
|
||||||
|
|
||||||
/** Consolidated build option gathered form CLI arguments and config in package.json */
|
|
||||||
export type BuildOptions = {
|
|
||||||
isSilent: boolean;
|
|
||||||
themeVersion: string;
|
|
||||||
themeNames: string[];
|
|
||||||
extraThemeProperties: string[] | undefined;
|
|
||||||
groupId: string;
|
|
||||||
artifactId: string;
|
|
||||||
doCreateJar: boolean;
|
|
||||||
loginThemeResourcesFromKeycloakVersion: string;
|
|
||||||
reactAppRootDirPath: string;
|
|
||||||
/** Directory of your built react project. Defaults to {cwd}/build */
|
|
||||||
reactAppBuildDirPath: string;
|
|
||||||
/** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */
|
|
||||||
keycloakifyBuildDirPath: string;
|
|
||||||
publicDirPath: string;
|
|
||||||
cacheDirPath: string;
|
|
||||||
/** If your app is hosted under a subpath, it's the case in CRA if you have "homepage": "https://example.com/my-app" in your package.json
|
|
||||||
* In this case the urlPathname will be "/my-app/" */
|
|
||||||
urlPathname: string | undefined;
|
|
||||||
doBuildRetrocompatAccountTheme: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
export function readBuildOptions(params: { reactAppRootDirPath: string; processArgv: string[] }): BuildOptions {
|
|
||||||
const { reactAppRootDirPath, processArgv } = params;
|
|
||||||
|
|
||||||
const { isSilentCliParamProvided } = (() => {
|
|
||||||
const argv = parseArgv(processArgv);
|
|
||||||
|
|
||||||
return {
|
|
||||||
"isSilentCliParamProvided": typeof argv["silent"] === "boolean" ? argv["silent"] : false
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
const parsedPackageJson = getParsedPackageJson({ reactAppRootDirPath });
|
|
||||||
|
|
||||||
const { name, keycloakify = {}, version, homepage } = parsedPackageJson;
|
|
||||||
|
|
||||||
const { extraThemeProperties, groupId, artifactId, doCreateJar, loginThemeResourcesFromKeycloakVersion } = keycloakify ?? {};
|
|
||||||
|
|
||||||
const themeNames = (() => {
|
|
||||||
if (keycloakify.themeName === undefined) {
|
|
||||||
return [
|
|
||||||
name
|
|
||||||
.replace(/^@(.*)/, "$1")
|
|
||||||
.split("/")
|
|
||||||
.join("-")
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof keycloakify.themeName === "string") {
|
|
||||||
return [keycloakify.themeName];
|
|
||||||
}
|
|
||||||
|
|
||||||
return keycloakify.themeName;
|
|
||||||
})();
|
|
||||||
|
|
||||||
return {
|
|
||||||
reactAppRootDirPath,
|
|
||||||
themeNames,
|
|
||||||
"doCreateJar": doCreateJar ?? true,
|
|
||||||
"artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? artifactId ?? `${themeNames[0]}-keycloak-theme`,
|
|
||||||
"groupId": (() => {
|
|
||||||
const fallbackGroupId = `${themeNames[0]}.keycloak`;
|
|
||||||
|
|
||||||
return (
|
|
||||||
process.env.KEYCLOAKIFY_GROUP_ID ??
|
|
||||||
groupId ??
|
|
||||||
(!homepage
|
|
||||||
? fallbackGroupId
|
|
||||||
: urlParse(homepage)
|
|
||||||
.host?.replace(/:[0-9]+$/, "")
|
|
||||||
?.split(".")
|
|
||||||
.reverse()
|
|
||||||
.join(".") ?? fallbackGroupId) + ".keycloak"
|
|
||||||
);
|
|
||||||
})(),
|
|
||||||
"themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? process.env.KEYCLOAKIFY_VERSION ?? version ?? "0.0.0",
|
|
||||||
extraThemeProperties,
|
|
||||||
"isSilent": isSilentCliParamProvided,
|
|
||||||
"loginThemeResourcesFromKeycloakVersion": loginThemeResourcesFromKeycloakVersion ?? "11.0.3",
|
|
||||||
"publicDirPath": (() => {
|
|
||||||
let { PUBLIC_DIR_PATH } = process.env;
|
|
||||||
|
|
||||||
if (PUBLIC_DIR_PATH !== undefined) {
|
|
||||||
return getAbsoluteAndInOsFormatPath({
|
|
||||||
"pathIsh": PUBLIC_DIR_PATH,
|
|
||||||
"cwd": reactAppRootDirPath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathJoin(reactAppRootDirPath, "public");
|
|
||||||
})(),
|
|
||||||
"reactAppBuildDirPath": (() => {
|
|
||||||
const { reactAppBuildDirPath } = parsedPackageJson.keycloakify ?? {};
|
|
||||||
|
|
||||||
if (reactAppBuildDirPath !== undefined) {
|
|
||||||
return getAbsoluteAndInOsFormatPath({
|
|
||||||
"pathIsh": reactAppBuildDirPath,
|
|
||||||
"cwd": reactAppRootDirPath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathJoin(reactAppRootDirPath, "build");
|
|
||||||
})(),
|
|
||||||
"keycloakifyBuildDirPath": (() => {
|
|
||||||
const { keycloakifyBuildDirPath } = parsedPackageJson.keycloakify ?? {};
|
|
||||||
|
|
||||||
if (keycloakifyBuildDirPath !== undefined) {
|
|
||||||
return getAbsoluteAndInOsFormatPath({
|
|
||||||
"pathIsh": keycloakifyBuildDirPath,
|
|
||||||
"cwd": reactAppRootDirPath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathJoin(reactAppRootDirPath, "build_keycloak");
|
|
||||||
})(),
|
|
||||||
"cacheDirPath": pathJoin(
|
|
||||||
(() => {
|
|
||||||
let { XDG_CACHE_HOME } = process.env;
|
|
||||||
|
|
||||||
if (XDG_CACHE_HOME !== undefined) {
|
|
||||||
return getAbsoluteAndInOsFormatPath({
|
|
||||||
"pathIsh": XDG_CACHE_HOME,
|
|
||||||
"cwd": reactAppRootDirPath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathJoin(reactAppRootDirPath, "node_modules", ".cache");
|
|
||||||
})(),
|
|
||||||
"keycloakify"
|
|
||||||
),
|
|
||||||
"urlPathname": (() => {
|
|
||||||
const { homepage } = parsedPackageJson;
|
|
||||||
|
|
||||||
let url: URL | undefined = undefined;
|
|
||||||
|
|
||||||
if (homepage !== undefined) {
|
|
||||||
url = new URL(homepage);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (url === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
const out = url.pathname.replace(/([^/])$/, "$1/");
|
|
||||||
return out === "/" ? undefined : out;
|
|
||||||
})(),
|
|
||||||
"doBuildRetrocompatAccountTheme": parsedPackageJson.keycloakify?.doBuildRetrocompatAccountTheme ?? true
|
|
||||||
};
|
|
||||||
}
|
|
25
src/bin/keycloakify/buildOptions/UserProvidedBuildOptions.ts
Normal file
25
src/bin/keycloakify/buildOptions/UserProvidedBuildOptions.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { z } from "zod";
|
||||||
|
|
||||||
|
export type UserProvidedBuildOptions = {
|
||||||
|
extraThemeProperties?: string[];
|
||||||
|
artifactId?: string;
|
||||||
|
groupId?: string;
|
||||||
|
doCreateJar?: boolean;
|
||||||
|
loginThemeResourcesFromKeycloakVersion?: string;
|
||||||
|
reactAppBuildDirPath?: string;
|
||||||
|
keycloakifyBuildDirPath?: string;
|
||||||
|
themeName?: string | string[];
|
||||||
|
doBuildRetrocompatAccountTheme?: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const zUserProvidedBuildOptions = z.object({
|
||||||
|
"extraThemeProperties": z.array(z.string()).optional(),
|
||||||
|
"artifactId": z.string().optional(),
|
||||||
|
"groupId": z.string().optional(),
|
||||||
|
"doCreateJar": z.boolean().optional(),
|
||||||
|
"loginThemeResourcesFromKeycloakVersion": z.string().optional(),
|
||||||
|
"reactAppBuildDirPath": z.string().optional(),
|
||||||
|
"keycloakifyBuildDirPath": z.string().optional(),
|
||||||
|
"themeName": z.union([z.string(), z.array(z.string())]).optional(),
|
||||||
|
"doBuildRetrocompatAccountTheme": z.boolean().optional()
|
||||||
|
});
|
193
src/bin/keycloakify/buildOptions/buildOptions.ts
Normal file
193
src/bin/keycloakify/buildOptions/buildOptions.ts
Normal file
@ -0,0 +1,193 @@
|
|||||||
|
import { parse as urlParse } from "url";
|
||||||
|
import { readParsedPackageJson } from "./parsedPackageJson";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import parseArgv from "minimist";
|
||||||
|
import { getAbsoluteAndInOsFormatPath } from "../../tools/getAbsoluteAndInOsFormatPath";
|
||||||
|
import { readResolvedViteConfig } from "./resolvedViteConfig";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { getCacheDirPath } from "./getCacheDirPath";
|
||||||
|
import { getReactAppRootDirPath } from "./getReactAppRootDirPath";
|
||||||
|
import { getNpmWorkspaceRootDirPath } from "./getNpmWorkspaceRootDirPath";
|
||||||
|
|
||||||
|
/** Consolidated build option gathered form CLI arguments and config in package.json */
|
||||||
|
export type BuildOptions = {
|
||||||
|
bundler: "vite" | "webpack";
|
||||||
|
isSilent: boolean;
|
||||||
|
themeVersion: string;
|
||||||
|
themeNames: string[];
|
||||||
|
extraThemeProperties: string[] | undefined;
|
||||||
|
groupId: string;
|
||||||
|
artifactId: string;
|
||||||
|
doCreateJar: boolean;
|
||||||
|
loginThemeResourcesFromKeycloakVersion: string;
|
||||||
|
reactAppRootDirPath: string;
|
||||||
|
reactAppBuildDirPath: string;
|
||||||
|
/** Directory that keycloakify outputs to. Defaults to {cwd}/build_keycloak */
|
||||||
|
keycloakifyBuildDirPath: string;
|
||||||
|
publicDirPath: string;
|
||||||
|
cacheDirPath: string;
|
||||||
|
/** If your app is hosted under a subpath, it's the case in CRA if you have "homepage": "https://example.com/my-app" in your package.json
|
||||||
|
* In this case the urlPathname will be "/my-app/" */
|
||||||
|
urlPathname: string | undefined;
|
||||||
|
assetsDirPath: string;
|
||||||
|
doBuildRetrocompatAccountTheme: boolean;
|
||||||
|
npmWorkspaceRootDirPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function readBuildOptions(params: { processArgv: string[] }): BuildOptions {
|
||||||
|
const { processArgv } = params;
|
||||||
|
|
||||||
|
const { reactAppRootDirPath } = getReactAppRootDirPath({ processArgv });
|
||||||
|
|
||||||
|
const { cacheDirPath } = getCacheDirPath({ reactAppRootDirPath });
|
||||||
|
|
||||||
|
const { resolvedViteConfig } = readResolvedViteConfig({ cacheDirPath });
|
||||||
|
|
||||||
|
if (resolvedViteConfig === undefined && fs.existsSync(pathJoin(reactAppRootDirPath, "vite.config.ts"))) {
|
||||||
|
throw new Error("Keycloakify's Vite plugin output not found");
|
||||||
|
}
|
||||||
|
|
||||||
|
const { keycloakify: userProvidedBuildOptionsFromPackageJson, ...parsedPackageJson } = readParsedPackageJson({ reactAppRootDirPath });
|
||||||
|
|
||||||
|
const userProvidedBuildOptions = {
|
||||||
|
...userProvidedBuildOptionsFromPackageJson,
|
||||||
|
...resolvedViteConfig?.userProvidedBuildOptions
|
||||||
|
};
|
||||||
|
|
||||||
|
const themeNames = (() => {
|
||||||
|
if (userProvidedBuildOptions.themeName === undefined) {
|
||||||
|
return [
|
||||||
|
parsedPackageJson.name
|
||||||
|
.replace(/^@(.*)/, "$1")
|
||||||
|
.split("/")
|
||||||
|
.join("-")
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof userProvidedBuildOptions.themeName === "string") {
|
||||||
|
return [userProvidedBuildOptions.themeName];
|
||||||
|
}
|
||||||
|
|
||||||
|
return userProvidedBuildOptions.themeName;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const reactAppBuildDirPath = (() => {
|
||||||
|
webpack: {
|
||||||
|
if (resolvedViteConfig !== undefined) {
|
||||||
|
break webpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (userProvidedBuildOptions.reactAppBuildDirPath !== undefined) {
|
||||||
|
return getAbsoluteAndInOsFormatPath({
|
||||||
|
"pathIsh": userProvidedBuildOptions.reactAppBuildDirPath,
|
||||||
|
"cwd": reactAppRootDirPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathJoin(reactAppRootDirPath, "build");
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathJoin(reactAppRootDirPath, resolvedViteConfig.buildDir);
|
||||||
|
})();
|
||||||
|
|
||||||
|
const argv = parseArgv(processArgv);
|
||||||
|
|
||||||
|
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({ reactAppRootDirPath });
|
||||||
|
|
||||||
|
return {
|
||||||
|
"bundler": resolvedViteConfig !== undefined ? "vite" : "webpack",
|
||||||
|
"isSilent": typeof argv["silent"] === "boolean" ? argv["silent"] : false,
|
||||||
|
"themeVersion": process.env.KEYCLOAKIFY_THEME_VERSION ?? parsedPackageJson.version ?? "0.0.0",
|
||||||
|
themeNames,
|
||||||
|
"extraThemeProperties": userProvidedBuildOptions.extraThemeProperties,
|
||||||
|
"groupId": (() => {
|
||||||
|
const fallbackGroupId = `${themeNames[0]}.keycloak`;
|
||||||
|
|
||||||
|
return (
|
||||||
|
process.env.KEYCLOAKIFY_GROUP_ID ??
|
||||||
|
userProvidedBuildOptions.groupId ??
|
||||||
|
(parsedPackageJson.homepage === undefined
|
||||||
|
? fallbackGroupId
|
||||||
|
: urlParse(parsedPackageJson.homepage)
|
||||||
|
.host?.replace(/:[0-9]+$/, "")
|
||||||
|
?.split(".")
|
||||||
|
.reverse()
|
||||||
|
.join(".") ?? fallbackGroupId) + ".keycloak"
|
||||||
|
);
|
||||||
|
})(),
|
||||||
|
"artifactId": process.env.KEYCLOAKIFY_ARTIFACT_ID ?? userProvidedBuildOptions.artifactId ?? `${themeNames[0]}-keycloak-theme`,
|
||||||
|
"doCreateJar": userProvidedBuildOptions.doCreateJar ?? true,
|
||||||
|
"loginThemeResourcesFromKeycloakVersion": userProvidedBuildOptions.loginThemeResourcesFromKeycloakVersion ?? "11.0.3",
|
||||||
|
reactAppRootDirPath,
|
||||||
|
reactAppBuildDirPath,
|
||||||
|
"keycloakifyBuildDirPath": (() => {
|
||||||
|
if (userProvidedBuildOptions.keycloakifyBuildDirPath !== undefined) {
|
||||||
|
return getAbsoluteAndInOsFormatPath({
|
||||||
|
"pathIsh": userProvidedBuildOptions.keycloakifyBuildDirPath,
|
||||||
|
"cwd": reactAppRootDirPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathJoin(
|
||||||
|
reactAppRootDirPath,
|
||||||
|
resolvedViteConfig?.buildDir === undefined ? "build_keycloak" : `${resolvedViteConfig.buildDir}_keycloak`
|
||||||
|
);
|
||||||
|
})(),
|
||||||
|
"publicDirPath": (() => {
|
||||||
|
webpack: {
|
||||||
|
if (resolvedViteConfig !== undefined) {
|
||||||
|
break webpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (process.env.PUBLIC_DIR_PATH !== undefined) {
|
||||||
|
return getAbsoluteAndInOsFormatPath({
|
||||||
|
"pathIsh": process.env.PUBLIC_DIR_PATH,
|
||||||
|
"cwd": reactAppRootDirPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathJoin(reactAppRootDirPath, "public");
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathJoin(reactAppRootDirPath, resolvedViteConfig.publicDir);
|
||||||
|
})(),
|
||||||
|
cacheDirPath,
|
||||||
|
"urlPathname": (() => {
|
||||||
|
webpack: {
|
||||||
|
if (resolvedViteConfig !== undefined) {
|
||||||
|
break webpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { homepage } = parsedPackageJson;
|
||||||
|
|
||||||
|
let url: URL | undefined = undefined;
|
||||||
|
|
||||||
|
if (homepage !== undefined) {
|
||||||
|
url = new URL(homepage);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (url === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const out = url.pathname.replace(/([^/])$/, "$1/");
|
||||||
|
return out === "/" ? undefined : out;
|
||||||
|
}
|
||||||
|
|
||||||
|
return resolvedViteConfig.urlPathname;
|
||||||
|
})(),
|
||||||
|
"assetsDirPath": (() => {
|
||||||
|
webpack: {
|
||||||
|
if (resolvedViteConfig !== undefined) {
|
||||||
|
break webpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathJoin(reactAppBuildDirPath, "static");
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathJoin(reactAppBuildDirPath, resolvedViteConfig.assetsDir);
|
||||||
|
})(),
|
||||||
|
"doBuildRetrocompatAccountTheme": userProvidedBuildOptions.doBuildRetrocompatAccountTheme ?? true,
|
||||||
|
npmWorkspaceRootDirPath
|
||||||
|
};
|
||||||
|
}
|
25
src/bin/keycloakify/buildOptions/getCacheDirPath.ts
Normal file
25
src/bin/keycloakify/buildOptions/getCacheDirPath.ts
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { getAbsoluteAndInOsFormatPath } from "../../tools/getAbsoluteAndInOsFormatPath";
|
||||||
|
import { getNpmWorkspaceRootDirPath } from "./getNpmWorkspaceRootDirPath";
|
||||||
|
|
||||||
|
export function getCacheDirPath(params: { reactAppRootDirPath: string }) {
|
||||||
|
const { reactAppRootDirPath } = params;
|
||||||
|
|
||||||
|
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({ reactAppRootDirPath });
|
||||||
|
|
||||||
|
const cacheDirPath = pathJoin(
|
||||||
|
(() => {
|
||||||
|
if (process.env.XDG_CACHE_HOME !== undefined) {
|
||||||
|
return getAbsoluteAndInOsFormatPath({
|
||||||
|
"pathIsh": process.env.XDG_CACHE_HOME,
|
||||||
|
"cwd": reactAppRootDirPath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return pathJoin(npmWorkspaceRootDirPath, "node_modules", ".cache");
|
||||||
|
})(),
|
||||||
|
"keycloakify"
|
||||||
|
);
|
||||||
|
|
||||||
|
return { cacheDirPath };
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
import * as child_process from "child_process";
|
||||||
|
import { join as pathJoin, resolve as pathResolve, sep as pathSep } from "path";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
|
||||||
|
let cache:
|
||||||
|
| {
|
||||||
|
reactAppRootDirPath: string;
|
||||||
|
npmWorkspaceRootDirPath: string;
|
||||||
|
}
|
||||||
|
| undefined = undefined;
|
||||||
|
|
||||||
|
export function getNpmWorkspaceRootDirPath(params: { reactAppRootDirPath: string }) {
|
||||||
|
const { reactAppRootDirPath } = params;
|
||||||
|
|
||||||
|
use_cache: {
|
||||||
|
if (cache === undefined || cache.reactAppRootDirPath !== reactAppRootDirPath) {
|
||||||
|
break use_cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { npmWorkspaceRootDirPath } = cache;
|
||||||
|
|
||||||
|
return { npmWorkspaceRootDirPath };
|
||||||
|
}
|
||||||
|
|
||||||
|
const npmWorkspaceRootDirPath = (function callee(depth: number): string {
|
||||||
|
const cwd = pathResolve(pathJoin(...[reactAppRootDirPath, ...Array(depth).fill("..")]));
|
||||||
|
|
||||||
|
try {
|
||||||
|
child_process.execSync("npm config get", { cwd: cwd });
|
||||||
|
} catch (error) {
|
||||||
|
if (String(error).includes("ENOWORKSPACES")) {
|
||||||
|
assert(cwd !== pathSep, "NPM workspace not found");
|
||||||
|
|
||||||
|
return callee(depth + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cwd;
|
||||||
|
})(0);
|
||||||
|
|
||||||
|
cache = {
|
||||||
|
reactAppRootDirPath,
|
||||||
|
npmWorkspaceRootDirPath
|
||||||
|
};
|
||||||
|
|
||||||
|
return { npmWorkspaceRootDirPath };
|
||||||
|
}
|
23
src/bin/keycloakify/buildOptions/getReactAppRootDirPath.ts
Normal file
23
src/bin/keycloakify/buildOptions/getReactAppRootDirPath.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import parseArgv from "minimist";
|
||||||
|
import { getAbsoluteAndInOsFormatPath } from "../../tools/getAbsoluteAndInOsFormatPath";
|
||||||
|
|
||||||
|
export function getReactAppRootDirPath(params: { processArgv: string[] }) {
|
||||||
|
const { processArgv } = params;
|
||||||
|
|
||||||
|
const argv = parseArgv(processArgv);
|
||||||
|
|
||||||
|
const reactAppRootDirPath = (() => {
|
||||||
|
const arg = argv["project"] ?? argv["p"];
|
||||||
|
|
||||||
|
if (typeof arg !== "string") {
|
||||||
|
return process.cwd();
|
||||||
|
}
|
||||||
|
|
||||||
|
return getAbsoluteAndInOsFormatPath({
|
||||||
|
"pathIsh": arg,
|
||||||
|
"cwd": process.cwd()
|
||||||
|
});
|
||||||
|
})();
|
||||||
|
|
||||||
|
return { reactAppRootDirPath };
|
||||||
|
}
|
1
src/bin/keycloakify/buildOptions/index.ts
Normal file
1
src/bin/keycloakify/buildOptions/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./buildOptions";
|
32
src/bin/keycloakify/buildOptions/parsedPackageJson.ts
Normal file
32
src/bin/keycloakify/buildOptions/parsedPackageJson.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { assert } from "tsafe";
|
||||||
|
import type { Equals } from "tsafe";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { type UserProvidedBuildOptions, zUserProvidedBuildOptions } from "./UserProvidedBuildOptions";
|
||||||
|
|
||||||
|
export type ParsedPackageJson = {
|
||||||
|
name: string;
|
||||||
|
version?: string;
|
||||||
|
homepage?: string;
|
||||||
|
keycloakify?: UserProvidedBuildOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zParsedPackageJson = z.object({
|
||||||
|
"name": z.string(),
|
||||||
|
"version": z.string().optional(),
|
||||||
|
"homepage": z.string().optional(),
|
||||||
|
"keycloakify": zUserProvidedBuildOptions.optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, ParsedPackageJson>>();
|
||||||
|
|
||||||
|
let parsedPackageJson: undefined | ParsedPackageJson;
|
||||||
|
export function readParsedPackageJson(params: { reactAppRootDirPath: string }) {
|
||||||
|
const { reactAppRootDirPath } = params;
|
||||||
|
if (parsedPackageJson) {
|
||||||
|
return parsedPackageJson;
|
||||||
|
}
|
||||||
|
parsedPackageJson = zParsedPackageJson.parse(JSON.parse(fs.readFileSync(pathJoin(reactAppRootDirPath, "package.json")).toString("utf8")));
|
||||||
|
return parsedPackageJson;
|
||||||
|
}
|
74
src/bin/keycloakify/buildOptions/resolvedViteConfig.ts
Normal file
74
src/bin/keycloakify/buildOptions/resolvedViteConfig.ts
Normal file
@ -0,0 +1,74 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { assert } from "tsafe";
|
||||||
|
import type { Equals } from "tsafe";
|
||||||
|
import { z } from "zod";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { resolvedViteConfigJsonBasename } from "../../constants";
|
||||||
|
import type { OptionalIfCanBeUndefined } from "../../tools/OptionalIfCanBeUndefined";
|
||||||
|
import { UserProvidedBuildOptions, zUserProvidedBuildOptions } from "./UserProvidedBuildOptions";
|
||||||
|
|
||||||
|
export type ResolvedViteConfig = {
|
||||||
|
buildDir: string;
|
||||||
|
publicDir: string;
|
||||||
|
assetsDir: string;
|
||||||
|
urlPathname: string | undefined;
|
||||||
|
userProvidedBuildOptions: UserProvidedBuildOptions;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zResolvedViteConfig = z.object({
|
||||||
|
"buildDir": z.string(),
|
||||||
|
"publicDir": z.string(),
|
||||||
|
"assetsDir": z.string(),
|
||||||
|
"urlPathname": z.string().optional(),
|
||||||
|
"userProvidedBuildOptions": zUserProvidedBuildOptions
|
||||||
|
});
|
||||||
|
|
||||||
|
{
|
||||||
|
type Got = ReturnType<(typeof zResolvedViteConfig)["parse"]>;
|
||||||
|
type Expected = OptionalIfCanBeUndefined<ResolvedViteConfig>;
|
||||||
|
|
||||||
|
assert<Equals<Got, Expected>>();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function readResolvedViteConfig(params: { cacheDirPath: string }): {
|
||||||
|
resolvedViteConfig: ResolvedViteConfig | undefined;
|
||||||
|
} {
|
||||||
|
const { cacheDirPath } = params;
|
||||||
|
|
||||||
|
const resolvedViteConfigJsonFilePath = pathJoin(cacheDirPath, resolvedViteConfigJsonBasename);
|
||||||
|
|
||||||
|
if (!fs.existsSync(resolvedViteConfigJsonFilePath)) {
|
||||||
|
return { "resolvedViteConfig": undefined };
|
||||||
|
}
|
||||||
|
|
||||||
|
const resolvedViteConfig = (() => {
|
||||||
|
if (!fs.existsSync(resolvedViteConfigJsonFilePath)) {
|
||||||
|
throw new Error("Missing Keycloakify Vite plugin output.");
|
||||||
|
}
|
||||||
|
|
||||||
|
let out: ResolvedViteConfig;
|
||||||
|
|
||||||
|
try {
|
||||||
|
out = JSON.parse(fs.readFileSync(resolvedViteConfigJsonFilePath).toString("utf8"));
|
||||||
|
} catch {
|
||||||
|
throw new Error("The output of the Keycloakify Vite plugin is not a valid JSON.");
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const zodParseReturn = zResolvedViteConfig.parse(out);
|
||||||
|
|
||||||
|
// So that objectKeys from tsafe return the expected result no matter what.
|
||||||
|
Object.keys(zodParseReturn)
|
||||||
|
.filter(key => !(key in out))
|
||||||
|
.forEach(key => {
|
||||||
|
delete (out as any)[key];
|
||||||
|
});
|
||||||
|
} catch {
|
||||||
|
throw new Error("The output of the Keycloakify Vite plugin do not match the expected schema.");
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
})();
|
||||||
|
|
||||||
|
return { resolvedViteConfig };
|
||||||
|
}
|
@ -1 +0,0 @@
|
|||||||
export const ftlValuesGlobalName = "kcContext";
|
|
@ -408,6 +408,14 @@
|
|||||||
out["themeName"] = "KEYCLOAKIFY_THEME_NAME_cXxKd3xEer";
|
out["themeName"] = "KEYCLOAKIFY_THEME_NAME_cXxKd3xEer";
|
||||||
out["pageId"] = "${pageId}";
|
out["pageId"] = "${pageId}";
|
||||||
|
|
||||||
|
try {
|
||||||
|
|
||||||
|
out["url"]["resourcesCommonPath"] = out["url"]["resourcesPath"] + "/" + "RESOURCES_COMMON_cLsLsMrtDkpVv";
|
||||||
|
|
||||||
|
} catch(error) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
return out;
|
return out;
|
||||||
|
|
||||||
})()
|
})()
|
||||||
@ -423,7 +431,7 @@
|
|||||||
<#if isHash>
|
<#if isHash>
|
||||||
|
|
||||||
<#if path?size gt 10>
|
<#if path?size gt 10>
|
||||||
<#return "ABORT: Too many recursive calls">
|
<#return "ABORT: Too many recursive calls, path: " + path?join(".")>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#local keys = "">
|
<#local keys = "">
|
||||||
@ -455,9 +463,10 @@
|
|||||||
<#-- https://github.com/keycloakify/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) -->
|
<#-- https://github.com/keycloakify/keycloakify/issues/91#issue-1212319466 (reports with error.ftl and Kc18) -->
|
||||||
<#-- https://github.com/keycloakify/keycloakify/issues/109#issuecomment-1134610163 -->
|
<#-- https://github.com/keycloakify/keycloakify/issues/109#issuecomment-1134610163 -->
|
||||||
<#-- https://github.com/keycloakify/keycloakify/issues/357 -->
|
<#-- https://github.com/keycloakify/keycloakify/issues/357 -->
|
||||||
|
<#-- https://github.com/keycloakify/keycloakify/discussions/406#discussioncomment-7514787 -->
|
||||||
key == "loginAction" &&
|
key == "loginAction" &&
|
||||||
are_same_path(path, ["url"]) &&
|
are_same_path(path, ["url"]) &&
|
||||||
["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl", "logout-confirm.ftl"]?seq_contains(pageId) &&
|
["saml-post-form.ftl", "error.ftl", "info.ftl", "login-oauth-grant.ftl", "logout-confirm.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
||||||
!(auth?has_content && auth.showTryAnotherWayLink())
|
!(auth?has_content && auth.showTryAnotherWayLink())
|
||||||
) || (
|
) || (
|
||||||
<#-- https://github.com/keycloakify/keycloakify/issues/362 -->
|
<#-- https://github.com/keycloakify/keycloakify/issues/362 -->
|
||||||
@ -480,24 +489,33 @@
|
|||||||
!["name", "displayName", "displayNameHtml", "internationalizationEnabled", "registrationEmailAsUsername" ]?seq_contains(key)
|
!["name", "displayName", "displayNameHtml", "internationalizationEnabled", "registrationEmailAsUsername" ]?seq_contains(key)
|
||||||
) || (
|
) || (
|
||||||
"applications.ftl" == pageId &&
|
"applications.ftl" == pageId &&
|
||||||
are_same_path(path, ["applications", "applications", "*", "client", "realm"])
|
is_subpath(path, ["applications", "applications"]) &&
|
||||||
|
(
|
||||||
|
key == "realm" ||
|
||||||
|
key == "container"
|
||||||
|
)
|
||||||
) || (
|
) || (
|
||||||
"applications.ftl" == pageId &&
|
are_same_path(path, ["user"]) &&
|
||||||
"masterAdminClient" == key
|
key == "delegateForUpdate"
|
||||||
)
|
)
|
||||||
>
|
>
|
||||||
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
<#local out_seq += ["/*If you need '" + path?join(".") + "." + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||||
<#continue>
|
<#continue>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#if pageId == "register.ftl" && key == "attemptedUsername" && are_same_path(path, ["auth"])>
|
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
|
||||||
|
<#if (
|
||||||
|
["register.ftl", "info.ftl", "login.ftl", "login-update-password.ftl", "login-oauth2-device-verify-user-code.ftl"]?seq_contains(pageId) &&
|
||||||
|
key == "attemptedUsername" && are_same_path(path, ["auth"])
|
||||||
|
)>
|
||||||
<#attempt>
|
<#attempt>
|
||||||
<#-- https://github.com/keycloak/keycloak/blob/3a2bf0c04bcde185e497aaa32d0bb7ab7520cf4a/themes/src/main/resources/theme/base/login/template.ftl#L63 -->
|
<#-- https://github.com/keycloak/keycloak/blob/3a2bf0c04bcde185e497aaa32d0bb7ab7520cf4a/themes/src/main/resources/theme/base/login/template.ftl#L63 -->
|
||||||
<#-- https://github.com/keycloakify/keycloakify/discussions/406 -->
|
|
||||||
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
<#if !(auth?has_content && auth.showUsername() && !auth.showResetCredentials())>
|
||||||
|
<#local out_seq += ["/*If you need '" + key + "' on " + pageId + ", please submit an issue to the Keycloakify repo*/"]>
|
||||||
<#continue>
|
<#continue>
|
||||||
</#if>
|
</#if>
|
||||||
<#recover>
|
<#recover>
|
||||||
|
<#local out_seq += ["/*Testing if attemptedUsername should be skipped throwed an exception */"]>
|
||||||
</#attempt>
|
</#attempt>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
@ -650,9 +668,9 @@
|
|||||||
<#return "ABORT: Couldn't convert into string non hash, non method, non boolean, non enumerable object">
|
<#return "ABORT: Couldn't convert into string non hash, non method, non boolean, non enumerable object">
|
||||||
|
|
||||||
</#function>
|
</#function>
|
||||||
<#function are_same_path path searchedPath>
|
<#function is_subpath path searchedPath>
|
||||||
|
|
||||||
<#if path?size != searchedPath?size>
|
<#if path?size < searchedPath?size>
|
||||||
<#return false>
|
<#return false>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
@ -660,8 +678,14 @@
|
|||||||
|
|
||||||
<#list path as property>
|
<#list path as property>
|
||||||
|
|
||||||
|
<#if i == searchedPath?size >
|
||||||
|
<#continue>
|
||||||
|
</#if>
|
||||||
|
|
||||||
<#local searchedProperty=searchedPath[i]>
|
<#local searchedProperty=searchedPath[i]>
|
||||||
|
|
||||||
|
<#local i+= 1>
|
||||||
|
|
||||||
<#if searchedProperty?is_string && searchedProperty == "*">
|
<#if searchedProperty?is_string && searchedProperty == "*">
|
||||||
<#continue>
|
<#continue>
|
||||||
</#if>
|
</#if>
|
||||||
@ -678,11 +702,13 @@
|
|||||||
<#return false>
|
<#return false>
|
||||||
</#if>
|
</#if>
|
||||||
|
|
||||||
<#local i+= 1>
|
|
||||||
|
|
||||||
</#list>
|
</#list>
|
||||||
|
|
||||||
<#return true>
|
<#return true>
|
||||||
|
|
||||||
</#function>
|
</#function>
|
||||||
|
|
||||||
|
<#function are_same_path path searchedPath>
|
||||||
|
<#return path?size == searchedPath?size && is_subpath(path, searchedPath)>
|
||||||
|
</#function>
|
||||||
</script>
|
</script>
|
@ -1,18 +1,20 @@
|
|||||||
import cheerio from "cheerio";
|
import cheerio from "cheerio";
|
||||||
import { replaceImportsFromStaticInJsCode } from "../replacers/replaceImportsFromStaticInJsCode";
|
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
||||||
import { generateCssCodeToDefineGlobals } from "../replacers/replaceImportsInCssCode";
|
import { generateCssCodeToDefineGlobals } from "../replacers/replaceImportsInCssCode";
|
||||||
import { replaceImportsInInlineCssCode } from "../replacers/replaceImportsInInlineCssCode";
|
import { replaceImportsInInlineCssCode } from "../replacers/replaceImportsInInlineCssCode";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { objectKeys } from "tsafe/objectKeys";
|
import { objectKeys } from "tsafe/objectKeys";
|
||||||
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
|
import type { BuildOptions } from "../buildOptions";
|
||||||
import type { BuildOptions } from "../BuildOptions";
|
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { ThemeType } from "../../constants";
|
import { type ThemeType, nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir, resources_common } from "../../constants";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
|
bundler: "vite" | "webpack";
|
||||||
themeVersion: string;
|
themeVersion: string;
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
|
reactAppBuildDirPath: string;
|
||||||
|
assetsDirPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
@ -20,7 +22,6 @@ assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
|||||||
export function generateFtlFilesCodeFactory(params: {
|
export function generateFtlFilesCodeFactory(params: {
|
||||||
themeName: string;
|
themeName: string;
|
||||||
indexHtmlCode: string;
|
indexHtmlCode: string;
|
||||||
//NOTE: Expected to be an empty object if external assets mode is enabled.
|
|
||||||
cssGlobalsToDefine: Record<string, string>;
|
cssGlobalsToDefine: Record<string, string>;
|
||||||
buildOptions: BuildOptionsLike;
|
buildOptions: BuildOptionsLike;
|
||||||
keycloakifyVersion: string;
|
keycloakifyVersion: string;
|
||||||
@ -37,7 +38,7 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
|
|
||||||
assert(jsCode !== null);
|
assert(jsCode !== null);
|
||||||
|
|
||||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({ jsCode });
|
const { fixedJsCode } = replaceImportsInJsCode({ jsCode, buildOptions });
|
||||||
|
|
||||||
$(element).text(fixedJsCode);
|
$(element).text(fixedJsCode);
|
||||||
});
|
});
|
||||||
@ -70,7 +71,10 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
|
|
||||||
$(element).attr(
|
$(element).attr(
|
||||||
attrName,
|
attrName,
|
||||||
href.replace(new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`), "${url.resourcesPath}/build/")
|
href.replace(
|
||||||
|
new RegExp(`^${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`),
|
||||||
|
`\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/`
|
||||||
|
)
|
||||||
);
|
);
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -101,7 +105,8 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
.replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion)
|
.replace("KEYCLOAKIFY_VERSION_xEdKd3xEdr", keycloakifyVersion)
|
||||||
.replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion)
|
.replace("KEYCLOAKIFY_THEME_VERSION_sIgKd3xEdr3dx", buildOptions.themeVersion)
|
||||||
.replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType)
|
.replace("KEYCLOAKIFY_THEME_TYPE_dExKd3xEdr", themeType)
|
||||||
.replace("KEYCLOAKIFY_THEME_NAME_cXxKd3xEer", themeName),
|
.replace("KEYCLOAKIFY_THEME_NAME_cXxKd3xEer", themeName)
|
||||||
|
.replace("RESOURCES_COMMON_cLsLsMrtDkpVv", resources_common),
|
||||||
"<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
|
"<!-- xIdLqMeOedErIdLsPdNdI9dSlxI -->": [
|
||||||
"<#if scripts??>",
|
"<#if scripts??>",
|
||||||
" <#list scripts as script>",
|
" <#list scripts as script>",
|
||||||
@ -114,7 +119,7 @@ export function generateFtlFilesCodeFactory(params: {
|
|||||||
$("head").prepend(
|
$("head").prepend(
|
||||||
[
|
[
|
||||||
"<script>",
|
"<script>",
|
||||||
` window.${ftlValuesGlobalName}= ${objectKeys(replaceValueBySearchValue)[0]};`,
|
` window.${nameOfTheGlobal}= ${objectKeys(replaceValueBySearchValue)[0]};`,
|
||||||
"</script>",
|
"</script>",
|
||||||
"",
|
"",
|
||||||
objectKeys(replaceValueBySearchValue)[1]
|
objectKeys(replaceValueBySearchValue)[1]
|
||||||
|
@ -1,140 +0,0 @@
|
|||||||
import * as fs from "fs";
|
|
||||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import { Reflect } from "tsafe/Reflect";
|
|
||||||
import type { BuildOptions } from "../BuildOptions";
|
|
||||||
import { type ThemeType, retrocompatPostfix, accountV1 } from "../../constants";
|
|
||||||
import { bringInAccountV1 } from "./bringInAccountV1";
|
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
|
||||||
groupId: string;
|
|
||||||
artifactId: string;
|
|
||||||
themeVersion: string;
|
|
||||||
cacheDirPath: string;
|
|
||||||
keycloakifyBuildDirPath: string;
|
|
||||||
themeNames: string[];
|
|
||||||
};
|
|
||||||
|
|
||||||
{
|
|
||||||
const buildOptions = Reflect<BuildOptions>();
|
|
||||||
|
|
||||||
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function generateJavaStackFiles(params: {
|
|
||||||
implementedThemeTypes: Record<ThemeType | "email", boolean>;
|
|
||||||
buildOptions: BuildOptionsLike;
|
|
||||||
}): Promise<{
|
|
||||||
jarFilePath: string;
|
|
||||||
}> {
|
|
||||||
const { implementedThemeTypes, buildOptions } = params;
|
|
||||||
|
|
||||||
{
|
|
||||||
const { pomFileCode } = (function generatePomFileCode(): {
|
|
||||||
pomFileCode: string;
|
|
||||||
} {
|
|
||||||
const pomFileCode = [
|
|
||||||
`<?xml version="1.0"?>`,
|
|
||||||
`<project xmlns="http://maven.apache.org/POM/4.0.0"`,
|
|
||||||
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
|
|
||||||
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">`,
|
|
||||||
` <modelVersion>4.0.0</modelVersion>`,
|
|
||||||
` <groupId>${buildOptions.groupId}</groupId>`,
|
|
||||||
` <artifactId>${buildOptions.artifactId}</artifactId>`,
|
|
||||||
` <version>${buildOptions.themeVersion}</version>`,
|
|
||||||
` <name>${buildOptions.artifactId}</name>`,
|
|
||||||
` <description />`,
|
|
||||||
` <packaging>jar</packaging>`,
|
|
||||||
` <properties>`,
|
|
||||||
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
|
|
||||||
` </properties>`,
|
|
||||||
` <build>`,
|
|
||||||
` <plugins>`,
|
|
||||||
` <plugin>`,
|
|
||||||
` <groupId>org.apache.maven.plugins</groupId>`,
|
|
||||||
` <artifactId>maven-shade-plugin</artifactId>`,
|
|
||||||
` <version>3.5.1</version>`,
|
|
||||||
` <executions>`,
|
|
||||||
` <execution>`,
|
|
||||||
` <phase>package</phase>`,
|
|
||||||
` <goals>`,
|
|
||||||
` <goal>shade</goal>`,
|
|
||||||
` </goals>`,
|
|
||||||
` </execution>`,
|
|
||||||
` </executions>`,
|
|
||||||
` </plugin>`,
|
|
||||||
` </plugins>`,
|
|
||||||
` </build>`,
|
|
||||||
` <dependencies>`,
|
|
||||||
` <dependency>`,
|
|
||||||
` <groupId>io.phasetwo.keycloak</groupId>`,
|
|
||||||
` <artifactId>keycloak-account-v1</artifactId>`,
|
|
||||||
` <version>0.1</version>`,
|
|
||||||
` </dependency>`,
|
|
||||||
` </dependencies>`,
|
|
||||||
`</project>`
|
|
||||||
].join("\n");
|
|
||||||
|
|
||||||
return { pomFileCode };
|
|
||||||
})();
|
|
||||||
|
|
||||||
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (implementedThemeTypes.account) {
|
|
||||||
await bringInAccountV1({ buildOptions });
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const themeManifestFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "META-INF", "keycloak-themes.json");
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.mkdirSync(pathDirname(themeManifestFilePath));
|
|
||||||
} catch {}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
themeManifestFilePath,
|
|
||||||
Buffer.from(
|
|
||||||
JSON.stringify(
|
|
||||||
{
|
|
||||||
"themes": [
|
|
||||||
...(!implementedThemeTypes.account
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
"name": accountV1,
|
|
||||||
"types": ["account"]
|
|
||||||
}
|
|
||||||
]),
|
|
||||||
...buildOptions.themeNames
|
|
||||||
.map(themeName => [
|
|
||||||
{
|
|
||||||
"name": themeName,
|
|
||||||
"types": Object.entries(implementedThemeTypes)
|
|
||||||
.filter(([, isImplemented]) => isImplemented)
|
|
||||||
.map(([themeType]) => themeType)
|
|
||||||
},
|
|
||||||
...(!implementedThemeTypes.account
|
|
||||||
? []
|
|
||||||
: [
|
|
||||||
{
|
|
||||||
"name": `${themeName}${retrocompatPostfix}`,
|
|
||||||
"types": ["account"]
|
|
||||||
}
|
|
||||||
])
|
|
||||||
])
|
|
||||||
.flat()
|
|
||||||
]
|
|
||||||
},
|
|
||||||
null,
|
|
||||||
2
|
|
||||||
),
|
|
||||||
"utf8"
|
|
||||||
)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"jarFilePath": pathJoin(buildOptions.keycloakifyBuildDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
export * from "./generateJavaStackFiles";
|
|
70
src/bin/keycloakify/generatePom.ts
Normal file
70
src/bin/keycloakify/generatePom.ts
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import { Reflect } from "tsafe/Reflect";
|
||||||
|
import type { BuildOptions } from "./buildOptions";
|
||||||
|
|
||||||
|
type BuildOptionsLike = {
|
||||||
|
groupId: string;
|
||||||
|
artifactId: string;
|
||||||
|
themeVersion: string;
|
||||||
|
keycloakifyBuildDirPath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
{
|
||||||
|
const buildOptions = Reflect<BuildOptions>();
|
||||||
|
|
||||||
|
assert<typeof buildOptions extends BuildOptionsLike ? true : false>();
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generatePom(params: { buildOptions: BuildOptionsLike }) {
|
||||||
|
const { buildOptions } = params;
|
||||||
|
|
||||||
|
const { pomFileCode } = (function generatePomFileCode(): {
|
||||||
|
pomFileCode: string;
|
||||||
|
} {
|
||||||
|
const pomFileCode = [
|
||||||
|
`<?xml version="1.0"?>`,
|
||||||
|
`<project xmlns="http://maven.apache.org/POM/4.0.0"`,
|
||||||
|
` xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"`,
|
||||||
|
` xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">`,
|
||||||
|
` <modelVersion>4.0.0</modelVersion>`,
|
||||||
|
` <groupId>${buildOptions.groupId}</groupId>`,
|
||||||
|
` <artifactId>${buildOptions.artifactId}</artifactId>`,
|
||||||
|
` <version>${buildOptions.themeVersion}</version>`,
|
||||||
|
` <name>${buildOptions.artifactId}</name>`,
|
||||||
|
` <description />`,
|
||||||
|
` <packaging>jar</packaging>`,
|
||||||
|
` <properties>`,
|
||||||
|
` <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>`,
|
||||||
|
` </properties>`,
|
||||||
|
` <build>`,
|
||||||
|
` <plugins>`,
|
||||||
|
` <plugin>`,
|
||||||
|
` <groupId>org.apache.maven.plugins</groupId>`,
|
||||||
|
` <artifactId>maven-shade-plugin</artifactId>`,
|
||||||
|
` <version>3.5.1</version>`,
|
||||||
|
` <executions>`,
|
||||||
|
` <execution>`,
|
||||||
|
` <phase>package</phase>`,
|
||||||
|
` <goals>`,
|
||||||
|
` <goal>shade</goal>`,
|
||||||
|
` </goals>`,
|
||||||
|
` </execution>`,
|
||||||
|
` </executions>`,
|
||||||
|
` </plugin>`,
|
||||||
|
` </plugins>`,
|
||||||
|
` </build>`,
|
||||||
|
` <dependencies>`,
|
||||||
|
` <dependency>`,
|
||||||
|
` <groupId>io.phasetwo.keycloak</groupId>`,
|
||||||
|
` <artifactId>keycloak-account-v1</artifactId>`,
|
||||||
|
` <version>0.1</version>`,
|
||||||
|
` </dependency>`,
|
||||||
|
` </dependencies>`,
|
||||||
|
`</project>`
|
||||||
|
].join("\n");
|
||||||
|
|
||||||
|
return { pomFileCode };
|
||||||
|
})();
|
||||||
|
|
||||||
|
return { pomFileCode };
|
||||||
|
}
|
@ -2,7 +2,7 @@ import * as fs from "fs";
|
|||||||
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
|
import { join as pathJoin, relative as pathRelative, basename as pathBasename } from "path";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { Reflect } from "tsafe/Reflect";
|
import { Reflect } from "tsafe/Reflect";
|
||||||
import type { BuildOptions } from "./BuildOptions";
|
import type { BuildOptions } from "./buildOptions";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
keycloakifyBuildDirPath: string;
|
keycloakifyBuildDirPath: string;
|
||||||
@ -30,7 +30,6 @@ export function generateStartKeycloakTestingContainer(params: { jarFilePath: str
|
|||||||
Buffer.from(
|
Buffer.from(
|
||||||
[
|
[
|
||||||
"#!/usr/bin/env bash",
|
"#!/usr/bin/env bash",
|
||||||
`# If you want to test with Keycloak version prior to 23 use the retrocompat-${pathBasename(jarFilePath)}`,
|
|
||||||
"",
|
"",
|
||||||
`docker rm ${containerName} || true`,
|
`docker rm ${containerName} || true`,
|
||||||
"",
|
"",
|
||||||
|
@ -1,15 +1,17 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { Reflect } from "tsafe/Reflect";
|
import { Reflect } from "tsafe/Reflect";
|
||||||
import type { BuildOptions } from "../BuildOptions";
|
import type { BuildOptions } from "../buildOptions";
|
||||||
import { resources_common, lastKeycloakVersionWithAccountV1, accountV1 } from "../../constants";
|
import { resources_common, lastKeycloakVersionWithAccountV1, accountV1ThemeName } from "../../constants";
|
||||||
import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme";
|
import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme";
|
||||||
import { transformCodebase } from "../../tools/transformCodebase";
|
import { transformCodebase } from "../../tools/transformCodebase";
|
||||||
|
import { rmSync } from "../../tools/fs.rmSync";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
type BuildOptionsLike = {
|
||||||
keycloakifyBuildDirPath: string;
|
keycloakifyBuildDirPath: string;
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
|
npmWorkspaceRootDirPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
@ -29,37 +31,24 @@ export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike
|
|||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
|
|
||||||
const accountV1DirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", accountV1, "account");
|
const accountV1DirPath = pathJoin(buildOptions.keycloakifyBuildDirPath, "src", "main", "resources", "theme", accountV1ThemeName, "account");
|
||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "account"),
|
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "base", "account"),
|
||||||
"destDirPath": accountV1DirPath
|
"destDirPath": accountV1DirPath
|
||||||
});
|
});
|
||||||
|
|
||||||
const commonResourceFilePaths = [
|
transformCodebase({
|
||||||
"node_modules/patternfly/dist/css/patternfly.min.css",
|
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "account", "resources"),
|
||||||
"node_modules/patternfly/dist/css/patternfly-additions.min.css"
|
"destDirPath": pathJoin(accountV1DirPath, "resources")
|
||||||
];
|
});
|
||||||
|
|
||||||
for (const relativeFilePath of commonResourceFilePaths.map(path => pathJoin(...path.split("/")))) {
|
transformCodebase({
|
||||||
const destFilePath = pathJoin(accountV1DirPath, "resources", resources_common, relativeFilePath);
|
"srcDirPath": pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "common", "resources"),
|
||||||
|
"destDirPath": pathJoin(accountV1DirPath, "resources", resources_common)
|
||||||
|
});
|
||||||
|
|
||||||
fs.mkdirSync(pathDirname(destFilePath), { "recursive": true });
|
rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true });
|
||||||
|
|
||||||
fs.cpSync(pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "common", "resources", relativeFilePath), destFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resourceFilePaths = ["css/account.css"];
|
|
||||||
|
|
||||||
for (const relativeFilePath of resourceFilePaths.map(path => pathJoin(...path.split("/")))) {
|
|
||||||
const destFilePath = pathJoin(accountV1DirPath, "resources", relativeFilePath);
|
|
||||||
|
|
||||||
fs.mkdirSync(pathDirname(destFilePath), { "recursive": true });
|
|
||||||
|
|
||||||
fs.cpSync(pathJoin(builtinKeycloakThemeTmpDirPath, "keycloak", "account", "resources", relativeFilePath), destFilePath);
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.rmSync(builtinKeycloakThemeTmpDirPath, { "recursive": true });
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(
|
||||||
pathJoin(accountV1DirPath, "theme.properties"),
|
pathJoin(accountV1DirPath, "theme.properties"),
|
||||||
@ -69,7 +58,15 @@ export async function bringInAccountV1(params: { buildOptions: BuildOptionsLike
|
|||||||
"",
|
"",
|
||||||
"locales=ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
|
"locales=ar,ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
|
||||||
"",
|
"",
|
||||||
"styles=" + [...resourceFilePaths, ...commonResourceFilePaths.map(path => `resources_common/${path}`)].join(" "),
|
"styles=" +
|
||||||
|
[
|
||||||
|
"css/account.css",
|
||||||
|
"img/icon-sidebar-active.png",
|
||||||
|
"img/logo.png",
|
||||||
|
...["patternfly.min.css", "patternfly-additions.min.css", "patternfly-additions.min.css"].map(
|
||||||
|
fileBasename => `${resources_common}/node_modules/patternfly/dist/css/${fileBasename}`
|
||||||
|
)
|
||||||
|
].join(" "),
|
||||||
"",
|
"",
|
||||||
"##### css classes for form buttons",
|
"##### css classes for form buttons",
|
||||||
"# main class used for all buttons",
|
"# main class used for all buttons",
|
@ -1,57 +1,27 @@
|
|||||||
import { transformCodebase } from "../../tools/transformCodebase";
|
import { transformCodebase } from "../../tools/transformCodebase";
|
||||||
import * as fs from "fs";
|
import { join as pathJoin } from "path";
|
||||||
import { join as pathJoin, dirname as pathDirname } from "path";
|
|
||||||
import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme";
|
import { downloadBuiltinKeycloakTheme } from "../../download-builtin-keycloak-theme";
|
||||||
import { resources_common, type ThemeType } from "../../constants";
|
import { resources_common, type ThemeType } from "../../constants";
|
||||||
import { BuildOptions } from "../BuildOptions";
|
import { BuildOptions } from "../buildOptions";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
|
import { rmSync } from "../../tools/fs.rmSync";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
|
npmWorkspaceRootDirPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
|
|
||||||
export async function downloadKeycloakStaticResources(
|
export async function downloadKeycloakStaticResources(params: {
|
||||||
// prettier-ignore
|
|
||||||
params: {
|
|
||||||
themeType: ThemeType;
|
themeType: ThemeType;
|
||||||
themeDirPath: string;
|
themeDirPath: string;
|
||||||
keycloakVersion: string;
|
keycloakVersion: string;
|
||||||
usedResources: {
|
|
||||||
resourcesCommonFilePaths: string[];
|
|
||||||
} | undefined;
|
|
||||||
buildOptions: BuildOptionsLike;
|
buildOptions: BuildOptionsLike;
|
||||||
}
|
}) {
|
||||||
) {
|
|
||||||
const { themeType, themeDirPath, keycloakVersion, buildOptions } = params;
|
const { themeType, themeDirPath, keycloakVersion, buildOptions } = params;
|
||||||
|
|
||||||
// NOTE: Hack for 427
|
|
||||||
const usedResources = (() => {
|
|
||||||
const { usedResources } = params;
|
|
||||||
|
|
||||||
if (usedResources === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(usedResources !== undefined);
|
|
||||||
|
|
||||||
return {
|
|
||||||
"resourcesCommonDirPaths": usedResources.resourcesCommonFilePaths.map(filePath => {
|
|
||||||
{
|
|
||||||
const splitArg = "/dist/";
|
|
||||||
|
|
||||||
if (filePath.includes(splitArg)) {
|
|
||||||
return filePath.split(splitArg)[0] + splitArg;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return pathDirname(filePath);
|
|
||||||
})
|
|
||||||
};
|
|
||||||
})();
|
|
||||||
|
|
||||||
const tmpDirPath = pathJoin(
|
const tmpDirPath = pathJoin(
|
||||||
themeDirPath,
|
themeDirPath,
|
||||||
`tmp_suLeKsxId_${crypto.createHash("sha256").update(`${themeType}-${keycloakVersion}`).digest("hex").slice(0, 8)}`
|
`tmp_suLeKsxId_${crypto.createHash("sha256").update(`${themeType}-${keycloakVersion}`).digest("hex").slice(0, 8)}`
|
||||||
@ -72,18 +42,8 @@ export async function downloadKeycloakStaticResources(
|
|||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"),
|
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"),
|
||||||
"destDirPath": pathJoin(resourcesPath, resources_common),
|
"destDirPath": pathJoin(resourcesPath, resources_common)
|
||||||
"transformSourceCode":
|
|
||||||
usedResources === undefined
|
|
||||||
? undefined
|
|
||||||
: ({ fileRelativePath, sourceCode }) => {
|
|
||||||
if (usedResources.resourcesCommonDirPaths.find(dirPath => fileRelativePath.startsWith(dirPath)) === undefined) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return { "modifiedSourceCode": sourceCode };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
fs.rmSync(tmpDirPath, { "recursive": true, "force": true });
|
rmSync(tmpDirPath, { "recursive": true, "force": true });
|
||||||
}
|
}
|
||||||
|
@ -1,28 +1,40 @@
|
|||||||
import { transformCodebase } from "../../tools/transformCodebase";
|
import { transformCodebase } from "../../tools/transformCodebase";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { join as pathJoin, basename as pathBasename, resolve as pathResolve } from "path";
|
import { join as pathJoin, basename as pathBasename, resolve as pathResolve, dirname as pathDirname } from "path";
|
||||||
import { replaceImportsFromStaticInJsCode } from "../replacers/replaceImportsFromStaticInJsCode";
|
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
||||||
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
||||||
import { generateFtlFilesCodeFactory, loginThemePageIds, accountThemePageIds } from "../generateFtl";
|
import { generateFtlFilesCodeFactory, loginThemePageIds, accountThemePageIds } from "../generateFtl";
|
||||||
import { themeTypes, type ThemeType, lastKeycloakVersionWithAccountV1, keycloak_resources, retrocompatPostfix, accountV1 } from "../../constants";
|
import {
|
||||||
|
type ThemeType,
|
||||||
|
lastKeycloakVersionWithAccountV1,
|
||||||
|
keycloak_resources,
|
||||||
|
retrocompatPostfix,
|
||||||
|
accountV1ThemeName,
|
||||||
|
basenameOfTheKeycloakifyResourcesDir
|
||||||
|
} from "../../constants";
|
||||||
import { isInside } from "../../tools/isInside";
|
import { isInside } from "../../tools/isInside";
|
||||||
import type { BuildOptions } from "../BuildOptions";
|
import type { BuildOptions } from "../buildOptions";
|
||||||
import { assert, type Equals } from "tsafe/assert";
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
import { downloadKeycloakStaticResources } from "./downloadKeycloakStaticResources";
|
import { downloadKeycloakStaticResources } from "./downloadKeycloakStaticResources";
|
||||||
import { readFieldNameUsage } from "./readFieldNameUsage";
|
import { readFieldNameUsage } from "./readFieldNameUsage";
|
||||||
import { readExtraPagesNames } from "./readExtraPageNames";
|
import { readExtraPagesNames } from "./readExtraPageNames";
|
||||||
import { generateMessageProperties } from "./generateMessageProperties";
|
import { generateMessageProperties } from "./generateMessageProperties";
|
||||||
import { readStaticResourcesUsage } from "./readStaticResourcesUsage";
|
import { bringInAccountV1 } from "./bringInAccountV1";
|
||||||
|
import { rmSync } from "../../tools/fs.rmSync";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
|
bundler: "vite" | "webpack";
|
||||||
extraThemeProperties: string[] | undefined;
|
extraThemeProperties: string[] | undefined;
|
||||||
themeVersion: string;
|
themeVersion: string;
|
||||||
loginThemeResourcesFromKeycloakVersion: string;
|
loginThemeResourcesFromKeycloakVersion: string;
|
||||||
urlPathname: string | undefined;
|
|
||||||
keycloakifyBuildDirPath: string;
|
keycloakifyBuildDirPath: string;
|
||||||
reactAppBuildDirPath: string;
|
reactAppBuildDirPath: string;
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
|
assetsDirPath: string;
|
||||||
|
urlPathname: string | undefined;
|
||||||
doBuildRetrocompatAccountTheme: boolean;
|
doBuildRetrocompatAccountTheme: boolean;
|
||||||
|
themeNames: string[];
|
||||||
|
npmWorkspaceRootDirPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
@ -49,29 +61,52 @@ export async function generateTheme(params: {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
let allCssGlobalsToDefine: Record<string, string> = {};
|
const cssGlobalsToDefine: Record<string, string> = {};
|
||||||
|
|
||||||
let generateFtlFilesCode_glob: ReturnType<typeof generateFtlFilesCodeFactory>["generateFtlFilesCode"] | undefined = undefined;
|
const implementedThemeTypes: Record<ThemeType | "email", boolean> = {
|
||||||
|
"login": false,
|
||||||
|
"account": false,
|
||||||
|
"email": false
|
||||||
|
};
|
||||||
|
|
||||||
for (const themeType of themeTypes) {
|
for (const themeType of ["login", "account"] as const) {
|
||||||
if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
|
if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementedThemeTypes[themeType] = true;
|
||||||
|
|
||||||
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
const themeTypeDirPath = getThemeTypeDirPath({ themeType });
|
||||||
|
|
||||||
copy_app_resources_to_theme_path: {
|
apply_replacers_and_move_to_theme_resources: {
|
||||||
const isFirstPass = themeType.indexOf(themeType) === 0;
|
const destDirPath = pathJoin(themeTypeDirPath, "resources", basenameOfTheKeycloakifyResourcesDir);
|
||||||
|
|
||||||
if (!isFirstPass) {
|
// NOTE: Prevent accumulation of files in the assets dir, as names are hashed they pile up.
|
||||||
break copy_app_resources_to_theme_path;
|
rmSync(destDirPath, { "recursive": true, "force": true });
|
||||||
|
|
||||||
|
if (themeType === "account" && implementedThemeTypes.login) {
|
||||||
|
// NOTE: We prevend doing it twice, it has been done for the login theme.
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
"srcDirPath": pathJoin(
|
||||||
|
getThemeTypeDirPath({
|
||||||
|
"themeType": "login"
|
||||||
|
}),
|
||||||
|
"resources",
|
||||||
|
basenameOfTheKeycloakifyResourcesDir
|
||||||
|
),
|
||||||
|
destDirPath
|
||||||
|
});
|
||||||
|
|
||||||
|
break apply_replacers_and_move_to_theme_resources;
|
||||||
}
|
}
|
||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
"destDirPath": pathJoin(themeTypeDirPath, "resources", "build"),
|
|
||||||
"srcDirPath": buildOptions.reactAppBuildDirPath,
|
"srcDirPath": buildOptions.reactAppBuildDirPath,
|
||||||
|
destDirPath,
|
||||||
"transformSourceCode": ({ filePath, sourceCode }) => {
|
"transformSourceCode": ({ filePath, sourceCode }) => {
|
||||||
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
|
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
|
||||||
|
// This should not happen if users follow the new instruction setup but we keep it for retrocompatibility.
|
||||||
if (
|
if (
|
||||||
isInside({
|
isInside({
|
||||||
"dirPath": pathJoin(buildOptions.reactAppBuildDirPath, keycloak_resources),
|
"dirPath": pathJoin(buildOptions.reactAppBuildDirPath, keycloak_resources),
|
||||||
@ -82,27 +117,21 @@ export async function generateTheme(params: {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (/\.css?$/i.test(filePath)) {
|
if (/\.css?$/i.test(filePath)) {
|
||||||
const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode({
|
const { cssGlobalsToDefine: cssGlobalsToDefineForThisFile, fixedCssCode } = replaceImportsInCssCode({
|
||||||
"cssCode": sourceCode.toString("utf8")
|
"cssCode": sourceCode.toString("utf8")
|
||||||
});
|
});
|
||||||
|
|
||||||
register_css_variables: {
|
Object.entries(cssGlobalsToDefineForThisFile).forEach(([key, value]) => {
|
||||||
if (!isFirstPass) {
|
cssGlobalsToDefine[key] = value;
|
||||||
break register_css_variables;
|
});
|
||||||
}
|
|
||||||
|
|
||||||
allCssGlobalsToDefine = {
|
|
||||||
...allCssGlobalsToDefine,
|
|
||||||
...cssGlobalsToDefine
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
|
return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") };
|
||||||
}
|
}
|
||||||
|
|
||||||
if (/\.js?$/i.test(filePath)) {
|
if (/\.js?$/i.test(filePath)) {
|
||||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
const { fixedJsCode } = replaceImportsInJsCode({
|
||||||
"jsCode": sourceCode.toString("utf8")
|
"jsCode": sourceCode.toString("utf8"),
|
||||||
|
buildOptions
|
||||||
});
|
});
|
||||||
|
|
||||||
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
|
return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") };
|
||||||
@ -113,13 +142,10 @@ export async function generateTheme(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const generateFtlFilesCode =
|
const { generateFtlFilesCode } = generateFtlFilesCodeFactory({
|
||||||
generateFtlFilesCode_glob !== undefined
|
|
||||||
? generateFtlFilesCode_glob
|
|
||||||
: generateFtlFilesCodeFactory({
|
|
||||||
themeName,
|
themeName,
|
||||||
"indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"),
|
"indexHtmlCode": fs.readFileSync(pathJoin(buildOptions.reactAppBuildDirPath, "index.html")).toString("utf8"),
|
||||||
"cssGlobalsToDefine": allCssGlobalsToDefine,
|
cssGlobalsToDefine,
|
||||||
buildOptions,
|
buildOptions,
|
||||||
keycloakifyVersion,
|
keycloakifyVersion,
|
||||||
themeType,
|
themeType,
|
||||||
@ -128,7 +154,7 @@ export async function generateTheme(params: {
|
|||||||
themeSrcDirPath,
|
themeSrcDirPath,
|
||||||
themeType
|
themeType
|
||||||
})
|
})
|
||||||
}).generateFtlFilesCode;
|
});
|
||||||
|
|
||||||
[
|
[
|
||||||
...(() => {
|
...(() => {
|
||||||
@ -175,11 +201,6 @@ export async function generateTheme(params: {
|
|||||||
})(),
|
})(),
|
||||||
"themeDirPath": pathResolve(pathJoin(themeTypeDirPath, "..")),
|
"themeDirPath": pathResolve(pathJoin(themeTypeDirPath, "..")),
|
||||||
themeType,
|
themeType,
|
||||||
"usedResources": readStaticResourcesUsage({
|
|
||||||
keycloakifySrcDirPath,
|
|
||||||
themeSrcDirPath,
|
|
||||||
themeType
|
|
||||||
}),
|
|
||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -190,7 +211,7 @@ export async function generateTheme(params: {
|
|||||||
`parent=${(() => {
|
`parent=${(() => {
|
||||||
switch (themeType) {
|
switch (themeType) {
|
||||||
case "account":
|
case "account":
|
||||||
return accountV1;
|
return accountV1ThemeName;
|
||||||
case "login":
|
case "login":
|
||||||
return "keycloak";
|
return "keycloak";
|
||||||
}
|
}
|
||||||
@ -209,7 +230,10 @@ export async function generateTheme(params: {
|
|||||||
"transformSourceCode": ({ filePath, sourceCode }) => {
|
"transformSourceCode": ({ filePath, sourceCode }) => {
|
||||||
if (pathBasename(filePath) === "theme.properties") {
|
if (pathBasename(filePath) === "theme.properties") {
|
||||||
return {
|
return {
|
||||||
"modifiedSourceCode": Buffer.from(sourceCode.toString("utf8").replace(`parent=${accountV1}`, "parent=keycloak"), "utf8")
|
"modifiedSourceCode": Buffer.from(
|
||||||
|
sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -226,9 +250,82 @@ export async function generateTheme(params: {
|
|||||||
break email;
|
break email;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
implementedThemeTypes.email = true;
|
||||||
|
|
||||||
transformCodebase({
|
transformCodebase({
|
||||||
"srcDirPath": emailThemeSrcDirPath,
|
"srcDirPath": emailThemeSrcDirPath,
|
||||||
"destDirPath": getThemeTypeDirPath({ "themeType": "email" })
|
"destDirPath": getThemeTypeDirPath({ "themeType": "email" })
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const parsedKeycloakThemeJson: { themes: { name: string; types: string[] }[] } = { "themes": [] };
|
||||||
|
|
||||||
|
buildOptions.themeNames.forEach(themeName =>
|
||||||
|
parsedKeycloakThemeJson.themes.push({
|
||||||
|
"name": themeName,
|
||||||
|
"types": Object.entries(implementedThemeTypes)
|
||||||
|
.filter(([, isImplemented]) => isImplemented)
|
||||||
|
.map(([themeType]) => themeType)
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
account_specific_extra_work: {
|
||||||
|
if (!implementedThemeTypes.account) {
|
||||||
|
break account_specific_extra_work;
|
||||||
|
}
|
||||||
|
|
||||||
|
await bringInAccountV1({ buildOptions });
|
||||||
|
|
||||||
|
parsedKeycloakThemeJson.themes.push({
|
||||||
|
"name": accountV1ThemeName,
|
||||||
|
"types": ["account"]
|
||||||
|
});
|
||||||
|
|
||||||
|
add_retrocompat_account_theme: {
|
||||||
|
if (!buildOptions.doBuildRetrocompatAccountTheme) {
|
||||||
|
break add_retrocompat_account_theme;
|
||||||
|
}
|
||||||
|
|
||||||
|
transformCodebase({
|
||||||
|
"srcDirPath": getThemeTypeDirPath({ "themeType": "account" }),
|
||||||
|
"destDirPath": getThemeTypeDirPath({ "themeType": "account", "isRetrocompat": true }),
|
||||||
|
"transformSourceCode": ({ filePath, sourceCode }) => {
|
||||||
|
if (pathBasename(filePath) === "theme.properties") {
|
||||||
|
return {
|
||||||
|
"modifiedSourceCode": Buffer.from(
|
||||||
|
sourceCode.toString("utf8").replace(`parent=${accountV1ThemeName}`, "parent=keycloak"),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return { "modifiedSourceCode": sourceCode };
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
buildOptions.themeNames.forEach(themeName =>
|
||||||
|
parsedKeycloakThemeJson.themes.push({
|
||||||
|
"name": `${themeName}${retrocompatPostfix}`,
|
||||||
|
"types": ["account"]
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const keycloakThemeJsonFilePath = pathJoin(
|
||||||
|
buildOptions.keycloakifyBuildDirPath,
|
||||||
|
"src",
|
||||||
|
"main",
|
||||||
|
"resources",
|
||||||
|
"META-INF",
|
||||||
|
"keycloak-themes.json"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.mkdirSync(pathDirname(keycloakThemeJsonFilePath));
|
||||||
|
} catch {}
|
||||||
|
|
||||||
|
fs.writeFileSync(keycloakThemeJsonFilePath, Buffer.from(JSON.stringify(parsedKeycloakThemeJson, null, 2), "utf8"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,76 +0,0 @@
|
|||||||
import { crawl } from "../../tools/crawl";
|
|
||||||
import { join as pathJoin, sep as pathSep } from "path";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import type { ThemeType } from "../../constants";
|
|
||||||
|
|
||||||
/** Assumes the theme type exists */
|
|
||||||
export function readStaticResourcesUsage(params: { keycloakifySrcDirPath: string; themeSrcDirPath: string; themeType: ThemeType }): {
|
|
||||||
resourcesCommonFilePaths: string[];
|
|
||||||
} {
|
|
||||||
const { keycloakifySrcDirPath, themeSrcDirPath, themeType } = params;
|
|
||||||
|
|
||||||
const resourcesCommonFilePaths = new Set<string>();
|
|
||||||
|
|
||||||
for (const srcDirPath of [pathJoin(keycloakifySrcDirPath, themeType), pathJoin(themeSrcDirPath, themeType)]) {
|
|
||||||
const filePaths = crawl({ "dirPath": srcDirPath, "returnedPathsType": "absolute" }).filter(filePath => /\.(ts|tsx|js|jsx)$/.test(filePath));
|
|
||||||
|
|
||||||
for (const filePath of filePaths) {
|
|
||||||
const rawSourceFile = fs.readFileSync(filePath).toString("utf8");
|
|
||||||
|
|
||||||
if (!rawSourceFile.includes("resourcesCommonPath") && !rawSourceFile.includes("resourcesPath")) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const wrap = readPaths({ rawSourceFile });
|
|
||||||
|
|
||||||
wrap.resourcesCommonFilePaths.forEach(filePath => resourcesCommonFilePaths.add(filePath));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths)
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Exported for testing purpose */
|
|
||||||
export function readPaths(params: { rawSourceFile: string }): {
|
|
||||||
resourcesCommonFilePaths: string[];
|
|
||||||
} {
|
|
||||||
const { rawSourceFile } = params;
|
|
||||||
|
|
||||||
const resourcesCommonFilePaths = new Set<string>();
|
|
||||||
|
|
||||||
{
|
|
||||||
const regexp = new RegExp(`resourcesCommonPath\\s*}([^\`]+)\``, "g");
|
|
||||||
|
|
||||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
|
||||||
|
|
||||||
for (const match of matches) {
|
|
||||||
const filePath = match[1];
|
|
||||||
|
|
||||||
resourcesCommonFilePaths.add(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
const regexp = new RegExp(`resourcesCommonPath\\s*[+,]\\s*["']([^"'\`]+)["'\`]`, "g");
|
|
||||||
|
|
||||||
const matches = [...rawSourceFile.matchAll(regexp)];
|
|
||||||
|
|
||||||
for (const match of matches) {
|
|
||||||
const filePath = match[1];
|
|
||||||
|
|
||||||
resourcesCommonFilePaths.add(filePath);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const normalizePath = (filePath: string) => {
|
|
||||||
filePath = filePath.startsWith("/") ? filePath.slice(1) : filePath;
|
|
||||||
filePath = filePath.replace(/\//g, pathSep);
|
|
||||||
return filePath;
|
|
||||||
};
|
|
||||||
|
|
||||||
return {
|
|
||||||
"resourcesCommonFilePaths": Array.from(resourcesCommonFilePaths).map(normalizePath)
|
|
||||||
};
|
|
||||||
}
|
|
@ -1,68 +1,67 @@
|
|||||||
import { generateTheme } from "./generateTheme";
|
import { generateTheme } from "./generateTheme";
|
||||||
import { generateJavaStackFiles } from "./generateJavaStackFiles";
|
import { generatePom } from "./generatePom";
|
||||||
import { join as pathJoin, relative as pathRelative, basename as pathBasename, dirname as pathDirname, sep as pathSep } from "path";
|
import { join as pathJoin, relative as pathRelative, basename as pathBasename, dirname as pathDirname, sep as pathSep } from "path";
|
||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
|
import { generateStartKeycloakTestingContainer } from "./generateStartKeycloakTestingContainer";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { readBuildOptions } from "./BuildOptions";
|
import { readBuildOptions } from "./buildOptions";
|
||||||
import { getLogger } from "../tools/logger";
|
import { getLogger } from "../tools/logger";
|
||||||
import { assert } from "tsafe/assert";
|
import { getThemeSrcDirPath } from "../getThemeSrcDirPath";
|
||||||
import { getThemeSrcDirPath } from "../getSrcDirPath";
|
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
|
||||||
import { getProjectRoot } from "../tools/getProjectRoot";
|
import { readThisNpmProjectVersion } from "../tools/readThisNpmProjectVersion";
|
||||||
import { objectKeys } from "tsafe/objectKeys";
|
import { keycloakifyBuildOptionsForPostPostBuildScriptEnvName } from "../constants";
|
||||||
|
|
||||||
export async function main() {
|
export async function main() {
|
||||||
const reactAppRootDirPath = process.cwd();
|
|
||||||
|
|
||||||
const buildOptions = readBuildOptions({
|
const buildOptions = readBuildOptions({
|
||||||
reactAppRootDirPath,
|
|
||||||
"processArgv": process.argv.slice(2)
|
"processArgv": process.argv.slice(2)
|
||||||
});
|
});
|
||||||
|
|
||||||
const logger = getLogger({ "isSilent": buildOptions.isSilent });
|
const logger = getLogger({ "isSilent": buildOptions.isSilent });
|
||||||
logger.log("🔏 Building the keycloak theme...⌚");
|
logger.log("🔏 Building the keycloak theme...⌚");
|
||||||
|
|
||||||
const keycloakifyDirPath = getProjectRoot();
|
const { themeSrcDirPath } = getThemeSrcDirPath({ "reactAppRootDirPath": buildOptions.reactAppRootDirPath });
|
||||||
|
|
||||||
const { themeSrcDirPath } = getThemeSrcDirPath({ reactAppRootDirPath });
|
|
||||||
|
|
||||||
for (const themeName of buildOptions.themeNames) {
|
for (const themeName of buildOptions.themeNames) {
|
||||||
await generateTheme({
|
await generateTheme({
|
||||||
themeName,
|
themeName,
|
||||||
themeSrcDirPath,
|
themeSrcDirPath,
|
||||||
"keycloakifySrcDirPath": pathJoin(keycloakifyDirPath, "src"),
|
"keycloakifySrcDirPath": pathJoin(getThisCodebaseRootDirPath(), "src"),
|
||||||
buildOptions,
|
"keycloakifyVersion": readThisNpmProjectVersion(),
|
||||||
"keycloakifyVersion": (() => {
|
buildOptions
|
||||||
const version = JSON.parse(fs.readFileSync(pathJoin(keycloakifyDirPath, "package.json")).toString("utf8"))["version"];
|
|
||||||
|
|
||||||
assert(typeof version === "string");
|
|
||||||
|
|
||||||
return version;
|
|
||||||
})()
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { jarFilePath } = await generateJavaStackFiles({
|
{
|
||||||
"implementedThemeTypes": (() => {
|
const { pomFileCode } = generatePom({ buildOptions });
|
||||||
const implementedThemeTypes = {
|
|
||||||
"login": false,
|
|
||||||
"account": false,
|
|
||||||
"email": false
|
|
||||||
};
|
|
||||||
|
|
||||||
for (const themeType of objectKeys(implementedThemeTypes)) {
|
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, "pom.xml"), Buffer.from(pomFileCode, "utf8"));
|
||||||
if (!fs.existsSync(pathJoin(themeSrcDirPath, themeType))) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
implementedThemeTypes[themeType] = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return implementedThemeTypes;
|
const containerKeycloakVersion = "23.0.6";
|
||||||
})(),
|
|
||||||
|
const jarFilePath = pathJoin(buildOptions.keycloakifyBuildDirPath, "target", `${buildOptions.artifactId}-${buildOptions.themeVersion}.jar`);
|
||||||
|
|
||||||
|
generateStartKeycloakTestingContainer({
|
||||||
|
"keycloakVersion": containerKeycloakVersion,
|
||||||
|
jarFilePath,
|
||||||
buildOptions
|
buildOptions
|
||||||
});
|
});
|
||||||
|
|
||||||
if (buildOptions.doCreateJar) {
|
fs.writeFileSync(pathJoin(buildOptions.keycloakifyBuildDirPath, ".gitignore"), Buffer.from("*", "utf8"));
|
||||||
|
|
||||||
|
child_process.execSync("npx vite", {
|
||||||
|
"cwd": buildOptions.reactAppRootDirPath,
|
||||||
|
"env": {
|
||||||
|
...process.env,
|
||||||
|
[keycloakifyBuildOptionsForPostPostBuildScriptEnvName]: JSON.stringify(buildOptions)
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
create_jar: {
|
||||||
|
if (!buildOptions.doCreateJar) {
|
||||||
|
break create_jar;
|
||||||
|
}
|
||||||
|
|
||||||
child_process.execSync("mvn clean install", { "cwd": buildOptions.keycloakifyBuildDirPath });
|
child_process.execSync("mvn clean install", { "cwd": buildOptions.keycloakifyBuildDirPath });
|
||||||
|
|
||||||
const jarDirPath = pathDirname(jarFilePath);
|
const jarDirPath = pathDirname(jarFilePath);
|
||||||
@ -75,7 +74,7 @@ export async function main() {
|
|||||||
Buffer.from(
|
Buffer.from(
|
||||||
[
|
[
|
||||||
`- The ${jarFilePath} is to be used in Keycloak 23 and up. `,
|
`- The ${jarFilePath} is to be used in Keycloak 23 and up. `,
|
||||||
`- The ${retrocompatJarFilePath} is to be used in Keycloak 21 and below.`,
|
`- The ${retrocompatJarFilePath} is to be used in Keycloak 22 and below.`,
|
||||||
` Note that Keycloak 22 is only supported for login and email theme but not for account themes. `
|
` Note that Keycloak 22 is only supported for login and email theme but not for account themes. `
|
||||||
].join("\n"),
|
].join("\n"),
|
||||||
"utf8"
|
"utf8"
|
||||||
@ -83,61 +82,24 @@ export async function main() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
const containerKeycloakVersion = "23.0.0";
|
|
||||||
|
|
||||||
generateStartKeycloakTestingContainer({
|
|
||||||
"keycloakVersion": containerKeycloakVersion,
|
|
||||||
jarFilePath,
|
|
||||||
buildOptions
|
|
||||||
});
|
|
||||||
|
|
||||||
logger.log(
|
logger.log(
|
||||||
[
|
[
|
||||||
"",
|
"",
|
||||||
...(!buildOptions.doCreateJar
|
...(!buildOptions.doCreateJar
|
||||||
? []
|
? []
|
||||||
: [
|
: [
|
||||||
`✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative(reactAppRootDirPath, jarFilePath)} 🚀`,
|
`✅ Your keycloak theme has been generated and bundled into .${pathSep}${pathRelative(
|
||||||
`It is to be placed in "/opt/keycloak/providers" in the container running a quay.io/keycloak/keycloak Docker image.`,
|
buildOptions.reactAppRootDirPath,
|
||||||
""
|
jarFilePath
|
||||||
|
)} 🚀`
|
||||||
]),
|
]),
|
||||||
//TODO: Restore when we find a good Helm chart for Keycloak.
|
|
||||||
//"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/keycloak/providers",
|
|
||||||
" extraEnv: |",
|
|
||||||
" - name: KEYCLOAK_USER",
|
|
||||||
" value: admin",
|
|
||||||
" - name: KEYCLOAK_PASSWORD",
|
|
||||||
" value: xxxxxxxxx",
|
|
||||||
" - name: JAVA_OPTS",
|
|
||||||
" value: -Dkeycloak.profile=preview",
|
|
||||||
"",
|
|
||||||
"",
|
"",
|
||||||
`To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`,
|
`To test your theme locally you can spin up a Keycloak ${containerKeycloakVersion} container image with the theme pre loaded by running:`,
|
||||||
"",
|
"",
|
||||||
`👉 $ .${pathSep}${pathRelative(
|
`👉 $ .${pathSep}${pathRelative(
|
||||||
reactAppRootDirPath,
|
buildOptions.reactAppRootDirPath,
|
||||||
pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename)
|
pathJoin(buildOptions.keycloakifyBuildDirPath, generateStartKeycloakTestingContainer.basename)
|
||||||
)} 👈`,
|
)} 👈`,
|
||||||
"",
|
|
||||||
`Test with different Keycloak versions by editing the .sh file. see available versions here: https://quay.io/repository/keycloak/keycloak?tab=tags`,
|
|
||||||
``,
|
``,
|
||||||
`Once your container is up and running: `,
|
`Once your container is up and running: `,
|
||||||
"- Log into the admin console 👉 http://localhost:8080/admin username: admin, password: admin 👈",
|
"- Log into the admin console 👉 http://localhost:8080/admin username: admin, password: admin 👈",
|
||||||
|
@ -1,55 +0,0 @@
|
|||||||
import * as fs from "fs";
|
|
||||||
import { assert } from "tsafe";
|
|
||||||
import type { Equals } from "tsafe";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { pathJoin } from "../tools/pathJoin";
|
|
||||||
|
|
||||||
export type ParsedPackageJson = {
|
|
||||||
name: string;
|
|
||||||
version?: string;
|
|
||||||
homepage?: string;
|
|
||||||
keycloakify?: {
|
|
||||||
extraThemeProperties?: string[];
|
|
||||||
areAppAndKeycloakServerSharingSameDomain?: boolean;
|
|
||||||
artifactId?: string;
|
|
||||||
groupId?: string;
|
|
||||||
doCreateJar?: boolean;
|
|
||||||
loginThemeResourcesFromKeycloakVersion?: string;
|
|
||||||
reactAppBuildDirPath?: string;
|
|
||||||
keycloakifyBuildDirPath?: string;
|
|
||||||
themeName?: string | string[];
|
|
||||||
doBuildRetrocompatAccountTheme?: boolean;
|
|
||||||
};
|
|
||||||
};
|
|
||||||
|
|
||||||
export const zParsedPackageJson = z.object({
|
|
||||||
"name": z.string(),
|
|
||||||
"version": z.string().optional(),
|
|
||||||
"homepage": z.string().optional(),
|
|
||||||
"keycloakify": z
|
|
||||||
.object({
|
|
||||||
"extraThemeProperties": z.array(z.string()).optional(),
|
|
||||||
"areAppAndKeycloakServerSharingSameDomain": z.boolean().optional(),
|
|
||||||
"artifactId": z.string().optional(),
|
|
||||||
"groupId": z.string().optional(),
|
|
||||||
"doCreateJar": z.boolean().optional(),
|
|
||||||
"loginThemeResourcesFromKeycloakVersion": z.string().optional(),
|
|
||||||
"reactAppBuildDirPath": z.string().optional(),
|
|
||||||
"keycloakifyBuildDirPath": z.string().optional(),
|
|
||||||
"themeName": z.union([z.string(), z.array(z.string())]).optional(),
|
|
||||||
"doBuildRetrocompatAccountTheme": z.boolean().optional()
|
|
||||||
})
|
|
||||||
.optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<ReturnType<(typeof zParsedPackageJson)["parse"]>, ParsedPackageJson>>();
|
|
||||||
|
|
||||||
let parsedPackageJson: undefined | ReturnType<(typeof zParsedPackageJson)["parse"]>;
|
|
||||||
export function getParsedPackageJson(params: { reactAppRootDirPath: string }) {
|
|
||||||
const { reactAppRootDirPath } = params;
|
|
||||||
if (parsedPackageJson) {
|
|
||||||
return parsedPackageJson;
|
|
||||||
}
|
|
||||||
parsedPackageJson = zParsedPackageJson.parse(JSON.parse(fs.readFileSync(pathJoin(reactAppRootDirPath, "package.json")).toString("utf8")));
|
|
||||||
return parsedPackageJson;
|
|
||||||
}
|
|
@ -1,51 +0,0 @@
|
|||||||
import { ftlValuesGlobalName } from "../ftlValuesGlobalName";
|
|
||||||
|
|
||||||
export function replaceImportsFromStaticInJsCode(params: { jsCode: string }): { fixedJsCode: string } {
|
|
||||||
/*
|
|
||||||
NOTE:
|
|
||||||
|
|
||||||
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.
|
|
||||||
|
|
||||||
When urlOrigin isn't defined we can assume the fixedJsCode
|
|
||||||
will always run in keycloak context.
|
|
||||||
*/
|
|
||||||
|
|
||||||
const { jsCode } = params;
|
|
||||||
|
|
||||||
const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [
|
|
||||||
new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=(function\\(([a-z]+)\\){return|([a-z]+)=>)"static\\/${language}\\/"`, "g"),
|
|
||||||
(...[, n, u, matchedFunction, eForFunction]) => {
|
|
||||||
const isArrowFunction = matchedFunction.includes("=>");
|
|
||||||
const e = isArrowFunction ? matchedFunction.replace("=>", "").trim() : eForFunction;
|
|
||||||
|
|
||||||
return `
|
|
||||||
${n}[(function(){
|
|
||||||
var pd = Object.getOwnPropertyDescriptor(${n}, "p");
|
|
||||||
if( pd === undefined || pd.configurable ){
|
|
||||||
Object.defineProperty(${n}, "p", {
|
|
||||||
get: function() { return window.${ftlValuesGlobalName}.url.resourcesPath; },
|
|
||||||
set: function() {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return "${u}";
|
|
||||||
})()] = ${isArrowFunction ? `${e} =>` : `function(${e}) { return `} "/build/static/${language}/"`
|
|
||||||
.replace(/\s+/g, " ")
|
|
||||||
.trim();
|
|
||||||
}
|
|
||||||
];
|
|
||||||
|
|
||||||
const fixedJsCode = jsCode
|
|
||||||
.replace(...getReplaceArgs("js"))
|
|
||||||
.replace(...getReplaceArgs("css"))
|
|
||||||
.replace(/[a-zA-Z]+\.[a-zA-Z]+\+"static\//g, `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/`)
|
|
||||||
//TODO: Write a test case for this
|
|
||||||
.replace(
|
|
||||||
/".chunk.css",([a-zA-Z])+=[a-zA-Z]+\.[a-zA-Z]+\+([a-zA-Z]+),/,
|
|
||||||
(...[, group1, group2]) => `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group2},`
|
|
||||||
);
|
|
||||||
|
|
||||||
return { fixedJsCode };
|
|
||||||
}
|
|
@ -1,6 +1,7 @@
|
|||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import type { BuildOptions } from "../BuildOptions";
|
import type { BuildOptions } from "../buildOptions";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
|
import { basenameOfTheKeycloakifyResourcesDir } from "../../constants";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
@ -16,7 +17,7 @@ export function replaceImportsInCssCode(params: { cssCode: string }): {
|
|||||||
|
|
||||||
const cssGlobalsToDefine: Record<string, string> = {};
|
const cssGlobalsToDefine: Record<string, string> = {};
|
||||||
|
|
||||||
new Set(cssCode.match(/url\(["']?\/[^/][^)"']+["']?\)[^;}]*/g) ?? []).forEach(
|
new Set(cssCode.match(/url\(["']?\/[^/][^)"']+["']?\)[^;}]*?/g) ?? []).forEach(
|
||||||
match => (cssGlobalsToDefine["url" + crypto.createHash("sha256").update(match).digest("hex").substring(0, 15)] = match)
|
match => (cssGlobalsToDefine["url" + crypto.createHash("sha256").update(match).digest("hex").substring(0, 15)] = match)
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -45,7 +46,7 @@ export function generateCssCodeToDefineGlobals(params: { cssGlobalsToDefine: Rec
|
|||||||
`--${cssVariableName}:`,
|
`--${cssVariableName}:`,
|
||||||
cssGlobalsToDefine[cssVariableName].replace(
|
cssGlobalsToDefine[cssVariableName].replace(
|
||||||
new RegExp(`url\\(${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`, "g"),
|
new RegExp(`url\\(${(buildOptions.urlPathname ?? "/").replace(/\//g, "\\/")}`, "g"),
|
||||||
"url(${url.resourcesPath}/build/"
|
`url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/`
|
||||||
)
|
)
|
||||||
].join(" ")
|
].join(" ")
|
||||||
)
|
)
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import type { BuildOptions } from "../BuildOptions";
|
import type { BuildOptions } from "../buildOptions";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
|
import { basenameOfTheKeycloakifyResourcesDir } from "../../constants";
|
||||||
|
|
||||||
export type BuildOptionsLike = {
|
export type BuildOptionsLike = {
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
@ -16,7 +17,7 @@ export function replaceImportsInInlineCssCode(params: { cssCode: string; buildOp
|
|||||||
buildOptions.urlPathname === undefined
|
buildOptions.urlPathname === undefined
|
||||||
? /url\(["']?\/([^/][^)"']+)["']?\)/g
|
? /url\(["']?\/([^/][^)"']+)["']?\)/g
|
||||||
: new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"),
|
: new RegExp(`url\\(["']?${buildOptions.urlPathname}([^)"']+)["']?\\)`, "g"),
|
||||||
(...[, group]) => `url(\${url.resourcesPath}/build/${group})`
|
(...[, group]) => `url(\${url.resourcesPath}/${basenameOfTheKeycloakifyResourcesDir}/${group})`
|
||||||
);
|
);
|
||||||
|
|
||||||
return { fixedCssCode };
|
return { fixedCssCode };
|
||||||
|
@ -0,0 +1 @@
|
|||||||
|
export * from "./replaceImportsInJsCode";
|
@ -0,0 +1,66 @@
|
|||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import type { BuildOptions } from "../../buildOptions";
|
||||||
|
import { replaceImportsInJsCode_vite } from "./vite";
|
||||||
|
import { replaceImportsInJsCode_webpack } from "./webpack";
|
||||||
|
import * as fs from "fs";
|
||||||
|
|
||||||
|
export type BuildOptionsLike = {
|
||||||
|
reactAppBuildDirPath: string;
|
||||||
|
assetsDirPath: string;
|
||||||
|
urlPathname: string | undefined;
|
||||||
|
bundler: "vite" | "webpack";
|
||||||
|
};
|
||||||
|
|
||||||
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
|
|
||||||
|
export function replaceImportsInJsCode(params: { jsCode: string; buildOptions: BuildOptionsLike }) {
|
||||||
|
const { jsCode, buildOptions } = params;
|
||||||
|
|
||||||
|
const { fixedJsCode } = (() => {
|
||||||
|
switch (buildOptions.bundler) {
|
||||||
|
case "vite":
|
||||||
|
return replaceImportsInJsCode_vite({
|
||||||
|
jsCode,
|
||||||
|
buildOptions,
|
||||||
|
"basenameOfAssetsFiles": readAssetsDirSync({
|
||||||
|
"assetsDirPath": params.buildOptions.assetsDirPath
|
||||||
|
})
|
||||||
|
});
|
||||||
|
case "webpack":
|
||||||
|
return replaceImportsInJsCode_webpack({
|
||||||
|
jsCode,
|
||||||
|
buildOptions
|
||||||
|
});
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
return { fixedJsCode };
|
||||||
|
}
|
||||||
|
|
||||||
|
const { readAssetsDirSync } = (() => {
|
||||||
|
let cache:
|
||||||
|
| {
|
||||||
|
assetsDirPath: string;
|
||||||
|
basenameOfAssetsFiles: string[];
|
||||||
|
}
|
||||||
|
| undefined = undefined;
|
||||||
|
|
||||||
|
function readAssetsDirSync(params: { assetsDirPath: string }): string[] {
|
||||||
|
const { assetsDirPath } = params;
|
||||||
|
|
||||||
|
if (cache !== undefined && cache.assetsDirPath === assetsDirPath) {
|
||||||
|
return cache.basenameOfAssetsFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
const basenameOfAssetsFiles = fs.readdirSync(assetsDirPath);
|
||||||
|
|
||||||
|
cache = {
|
||||||
|
assetsDirPath,
|
||||||
|
basenameOfAssetsFiles
|
||||||
|
};
|
||||||
|
|
||||||
|
return basenameOfAssetsFiles;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { readAssetsDirSync };
|
||||||
|
})();
|
85
src/bin/keycloakify/replacers/replaceImportsInJsCode/vite.ts
Normal file
85
src/bin/keycloakify/replacers/replaceImportsInJsCode/vite.ts
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "../../../constants";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import type { BuildOptions } from "../../buildOptions";
|
||||||
|
import * as nodePath from "path";
|
||||||
|
import { replaceAll } from "../../../tools/String.prototype.replaceAll";
|
||||||
|
|
||||||
|
export type BuildOptionsLike = {
|
||||||
|
reactAppBuildDirPath: string;
|
||||||
|
assetsDirPath: string;
|
||||||
|
urlPathname: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
|
|
||||||
|
export function replaceImportsInJsCode_vite(params: {
|
||||||
|
jsCode: string;
|
||||||
|
buildOptions: BuildOptionsLike;
|
||||||
|
basenameOfAssetsFiles: string[];
|
||||||
|
systemType?: "posix" | "win32";
|
||||||
|
}): {
|
||||||
|
fixedJsCode: string;
|
||||||
|
} {
|
||||||
|
const { jsCode, buildOptions, basenameOfAssetsFiles, systemType = nodePath.sep === "/" ? "posix" : "win32" } = params;
|
||||||
|
|
||||||
|
const { relative: pathRelative, sep: pathSep } = nodePath[systemType];
|
||||||
|
|
||||||
|
let fixedJsCode = jsCode;
|
||||||
|
|
||||||
|
replace_base_javacript_import: {
|
||||||
|
if (buildOptions.urlPathname === undefined) {
|
||||||
|
break replace_base_javacript_import;
|
||||||
|
}
|
||||||
|
// Optimization
|
||||||
|
if (!jsCode.includes(buildOptions.urlPathname)) {
|
||||||
|
break replace_base_javacript_import;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace `Hv=function(e){return"/abcde12345/"+e}` by `Hv=function(e){return"/"+e}`
|
||||||
|
fixedJsCode = fixedJsCode.replace(
|
||||||
|
new RegExp(
|
||||||
|
`([\\w\\$][\\w\\d\\$]*)=function\\(([\\w\\$][\\w\\d\\$]*)\\)\\{return"${replaceAll(buildOptions.urlPathname, "/", "\\/")}"\\+\\2\\}`,
|
||||||
|
"g"
|
||||||
|
),
|
||||||
|
(...[, funcName, paramName]) => `${funcName}=function(${paramName}){return"/"+${paramName}}`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
replace_javascript_relatives_import_paths: {
|
||||||
|
// Example: "assets/ or "foo/bar/"
|
||||||
|
const staticDir = (() => {
|
||||||
|
let out = pathRelative(buildOptions.reactAppBuildDirPath, buildOptions.assetsDirPath);
|
||||||
|
|
||||||
|
out = replaceAll(out, pathSep, "/") + "/";
|
||||||
|
|
||||||
|
if (out === "/") {
|
||||||
|
throw new Error(`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
})();
|
||||||
|
|
||||||
|
// Optimization
|
||||||
|
if (!jsCode.includes(staticDir)) {
|
||||||
|
break replace_javascript_relatives_import_paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
basenameOfAssetsFiles
|
||||||
|
.map(basenameOfAssetsFile => `${staticDir}${basenameOfAssetsFile}`)
|
||||||
|
.forEach(relativePathOfAssetFile => {
|
||||||
|
fixedJsCode = replaceAll(
|
||||||
|
fixedJsCode,
|
||||||
|
`"${relativePathOfAssetFile}"`,
|
||||||
|
`(window.${nameOfTheGlobal}.url.resourcesPath.substring(1) + "/${basenameOfTheKeycloakifyResourcesDir}/${relativePathOfAssetFile}")`
|
||||||
|
);
|
||||||
|
|
||||||
|
fixedJsCode = replaceAll(
|
||||||
|
fixedJsCode,
|
||||||
|
`"${buildOptions.urlPathname ?? "/"}${relativePathOfAssetFile}"`,
|
||||||
|
`(window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/${relativePathOfAssetFile}")`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return { fixedJsCode };
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
import { nameOfTheGlobal, basenameOfTheKeycloakifyResourcesDir } from "../../../constants";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import type { BuildOptions } from "../../buildOptions";
|
||||||
|
import * as nodePath from "path";
|
||||||
|
import { replaceAll } from "../../../tools/String.prototype.replaceAll";
|
||||||
|
|
||||||
|
export type BuildOptionsLike = {
|
||||||
|
reactAppBuildDirPath: string;
|
||||||
|
assetsDirPath: string;
|
||||||
|
urlPathname: string | undefined;
|
||||||
|
};
|
||||||
|
|
||||||
|
assert<BuildOptions extends BuildOptionsLike ? true : false>();
|
||||||
|
|
||||||
|
export function replaceImportsInJsCode_webpack(params: { jsCode: string; buildOptions: BuildOptionsLike; systemType?: "posix" | "win32" }): {
|
||||||
|
fixedJsCode: string;
|
||||||
|
} {
|
||||||
|
const { jsCode, buildOptions, systemType = nodePath.sep === "/" ? "posix" : "win32" } = params;
|
||||||
|
|
||||||
|
const { relative: pathRelative, sep: pathSep } = nodePath[systemType];
|
||||||
|
|
||||||
|
let fixedJsCode = jsCode;
|
||||||
|
|
||||||
|
if (buildOptions.urlPathname !== undefined) {
|
||||||
|
// "__esModule",{value:!0})},n.p="/foo-bar/",function(){if("undefined" -> ... n.p="/" ...
|
||||||
|
fixedJsCode = fixedJsCode.replace(
|
||||||
|
new RegExp(`,([a-zA-Z]\\.[a-zA-Z])="${replaceAll(buildOptions.urlPathname, "/", "\\/")}",`, "g"),
|
||||||
|
(...[, assignTo]) => `,${assignTo}="/",`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Example: "static/ or "foo/bar/"
|
||||||
|
const staticDir = (() => {
|
||||||
|
let out = pathRelative(buildOptions.reactAppBuildDirPath, buildOptions.assetsDirPath);
|
||||||
|
|
||||||
|
out = replaceAll(out, pathSep, "/") + "/";
|
||||||
|
|
||||||
|
if (out === "/") {
|
||||||
|
throw new Error(`The assetsDirPath must be a subdirectory of reactAppBuildDirPath`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const getReplaceArgs = (language: "js" | "css"): Parameters<typeof String.prototype.replace> => [
|
||||||
|
new RegExp(`([a-zA-Z_]+)\\.([a-zA-Z]+)=(function\\(([a-z]+)\\){return|([a-z]+)=>)"${staticDir.replace(/\//g, "\\/")}${language}\\/"`, "g"),
|
||||||
|
(...[, n, u, matchedFunction, eForFunction]) => {
|
||||||
|
const isArrowFunction = matchedFunction.includes("=>");
|
||||||
|
const e = isArrowFunction ? matchedFunction.replace("=>", "").trim() : eForFunction;
|
||||||
|
|
||||||
|
return `
|
||||||
|
${n}[(function(){
|
||||||
|
var pd = Object.getOwnPropertyDescriptor(${n}, "p");
|
||||||
|
if( pd === undefined || pd.configurable ){
|
||||||
|
Object.defineProperty(${n}, "p", {
|
||||||
|
get: function() { return window.${nameOfTheGlobal}.url.resourcesPath; },
|
||||||
|
set: function() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "${u}";
|
||||||
|
})()] = ${isArrowFunction ? `${e} =>` : `function(${e}) { return `} "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}${language}/"`
|
||||||
|
.replace(/\s+/g, " ")
|
||||||
|
.trim();
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
fixedJsCode = fixedJsCode
|
||||||
|
.replace(...getReplaceArgs("js"))
|
||||||
|
.replace(...getReplaceArgs("css"))
|
||||||
|
.replace(
|
||||||
|
new RegExp(`[a-zA-Z]+\\.[a-zA-Z]+\\+"${staticDir.replace(/\//g, "\\/")}`, "g"),
|
||||||
|
`window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/${staticDir}`
|
||||||
|
);
|
||||||
|
|
||||||
|
return { fixedJsCode };
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import { getLatestsSemVersionedTagFactory } from "./tools/octokit-addons/getLatestsSemVersionedTag";
|
import { getLatestsSemVersionedTagFactory } from "./tools/octokit-addons/getLatestsSemVersionedTag";
|
||||||
import { Octokit } from "@octokit/rest";
|
import { Octokit } from "@octokit/rest";
|
||||||
import cliSelect from "cli-select";
|
import cliSelect from "cli-select";
|
||||||
|
import { lastKeycloakVersionWithAccountV1 } from "./constants";
|
||||||
|
|
||||||
export async function promptKeycloakVersion() {
|
export async function promptKeycloakVersion() {
|
||||||
const { getLatestsSemVersionedTag } = (() => {
|
const { getLatestsSemVersionedTag } = (() => {
|
||||||
@ -22,10 +23,10 @@ export async function promptKeycloakVersion() {
|
|||||||
const tags = [
|
const tags = [
|
||||||
...(await getLatestsSemVersionedTag({
|
...(await getLatestsSemVersionedTag({
|
||||||
"count": 10,
|
"count": 10,
|
||||||
"doIgnoreBeta": true,
|
|
||||||
"owner": "keycloak",
|
"owner": "keycloak",
|
||||||
"repo": "keycloak"
|
"repo": "keycloak"
|
||||||
}).then(arr => arr.map(({ tag }) => tag))),
|
}).then(arr => arr.map(({ tag }) => tag))),
|
||||||
|
lastKeycloakVersionWithAccountV1,
|
||||||
"11.0.3"
|
"11.0.3"
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
export type NpmModuleVersion = {
|
|
||||||
major: number;
|
|
||||||
minor: number;
|
|
||||||
patch: number;
|
|
||||||
betaPreRelease?: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
export namespace NpmModuleVersion {
|
|
||||||
export function parse(versionStr: string): NpmModuleVersion {
|
|
||||||
const match = versionStr.match(/^([0-9]+)\.([0-9]+)\.([0-9]+)(?:-beta.([0-9]+))?/);
|
|
||||||
|
|
||||||
if (!match) {
|
|
||||||
throw new Error(`${versionStr} is not a valid NPM version`);
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
"major": parseInt(match[1]),
|
|
||||||
"minor": parseInt(match[2]),
|
|
||||||
"patch": parseInt(match[3]),
|
|
||||||
...(() => {
|
|
||||||
const str = match[4];
|
|
||||||
return str === undefined ? {} : { "betaPreRelease": parseInt(str) };
|
|
||||||
})()
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
export function stringify(v: NpmModuleVersion) {
|
|
||||||
return `${v.major}.${v.minor}.${v.patch}${v.betaPreRelease === undefined ? "" : `-beta.${v.betaPreRelease}`}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
*
|
|
||||||
* v1 < v2 => -1
|
|
||||||
* v1 === v2 => 0
|
|
||||||
* v1 > v2 => 1
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
export function compare(v1: NpmModuleVersion, v2: NpmModuleVersion): -1 | 0 | 1 {
|
|
||||||
const sign = (diff: number): -1 | 0 | 1 => (diff === 0 ? 0 : diff < 0 ? -1 : 1);
|
|
||||||
const noUndefined = (n: number | undefined) => n ?? Infinity;
|
|
||||||
|
|
||||||
for (const level of ["major", "minor", "patch", "betaPreRelease"] as const) {
|
|
||||||
if (noUndefined(v1[level]) !== noUndefined(v2[level])) {
|
|
||||||
return sign(noUndefined(v1[level]) - noUndefined(v2[level]));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
console.log(compare(parse("3.0.0-beta.3"), parse("3.0.0")) === -1 )
|
|
||||||
console.log(compare(parse("3.0.0-beta.3"), parse("3.0.0-beta.4")) === -1 )
|
|
||||||
console.log(compare(parse("3.0.0-beta.3"), parse("4.0.0")) === -1 )
|
|
||||||
*/
|
|
||||||
|
|
||||||
export function bumpType(params: { versionBehindStr: string; versionAheadStr: string }): "major" | "minor" | "patch" | "betaPreRelease" | "same" {
|
|
||||||
const versionAhead = parse(params.versionAheadStr);
|
|
||||||
const versionBehind = parse(params.versionBehindStr);
|
|
||||||
|
|
||||||
if (compare(versionBehind, versionAhead) === 1) {
|
|
||||||
throw new Error(`Version regression ${versionBehind} -> ${versionAhead}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
for (const level of ["major", "minor", "patch", "betaPreRelease"] as const) {
|
|
||||||
if (versionBehind[level] !== versionAhead[level]) {
|
|
||||||
return level;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return "same";
|
|
||||||
}
|
|
||||||
}
|
|
12
src/bin/tools/OptionalIfCanBeUndefined.ts
Normal file
12
src/bin/tools/OptionalIfCanBeUndefined.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
type PropertiesThatCanBeUndefined<T extends Record<string, unknown>> = {
|
||||||
|
[Key in keyof T]: undefined extends T[Key] ? Key : never;
|
||||||
|
}[keyof T];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* OptionalIfCanBeUndefined<{ p1: string | undefined; p2: string; }>
|
||||||
|
* is
|
||||||
|
* { p1?: string | undefined; p2: string }
|
||||||
|
*/
|
||||||
|
export type OptionalIfCanBeUndefined<T extends Record<string, unknown>> = {
|
||||||
|
[K in PropertiesThatCanBeUndefined<T>]?: T[K];
|
||||||
|
} & { [K in Exclude<keyof T, PropertiesThatCanBeUndefined<T>>]: T[K] };
|
99
src/bin/tools/SemVer.ts
Normal file
99
src/bin/tools/SemVer.ts
Normal file
@ -0,0 +1,99 @@
|
|||||||
|
export type SemVer = {
|
||||||
|
major: number;
|
||||||
|
minor: number;
|
||||||
|
patch: number;
|
||||||
|
rc?: number;
|
||||||
|
parsedFrom: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export namespace SemVer {
|
||||||
|
const bumpTypes = ["major", "minor", "patch", "rc", "no bump"] as const;
|
||||||
|
|
||||||
|
export type BumpType = (typeof bumpTypes)[number];
|
||||||
|
|
||||||
|
export function parse(versionStr: string): SemVer {
|
||||||
|
const match = versionStr.match(/^v?([0-9]+)\.([0-9]+)(?:\.([0-9]+))?(?:-rc.([0-9]+))?$/);
|
||||||
|
|
||||||
|
if (!match) {
|
||||||
|
throw new Error(`${versionStr} is not a valid semantic version`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const semVer: Omit<SemVer, "parsedFrom"> = {
|
||||||
|
"major": parseInt(match[1]),
|
||||||
|
"minor": parseInt(match[2]),
|
||||||
|
"patch": (() => {
|
||||||
|
const str = match[3];
|
||||||
|
|
||||||
|
return str === undefined ? 0 : parseInt(str);
|
||||||
|
})(),
|
||||||
|
...(() => {
|
||||||
|
const str = match[4];
|
||||||
|
return str === undefined ? {} : { "rc": parseInt(str) };
|
||||||
|
})()
|
||||||
|
};
|
||||||
|
|
||||||
|
const initialStr = stringify(semVer);
|
||||||
|
|
||||||
|
Object.defineProperty(semVer, "parsedFrom", {
|
||||||
|
"enumerable": true,
|
||||||
|
"get": function () {
|
||||||
|
const currentStr = stringify(this);
|
||||||
|
|
||||||
|
if (currentStr !== initialStr) {
|
||||||
|
throw new Error(`SemVer.parsedFrom can't be read anymore, the version have been modified from ${initialStr} to ${currentStr}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
return versionStr;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return semVer as any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function stringify(v: Omit<SemVer, "parsedFrom">): string {
|
||||||
|
return `${v.major}.${v.minor}.${v.patch}${v.rc === undefined ? "" : `-rc.${v.rc}`}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* v1 < v2 => -1
|
||||||
|
* v1 === v2 => 0
|
||||||
|
* v1 > v2 => 1
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
export function compare(v1: SemVer, v2: SemVer): -1 | 0 | 1 {
|
||||||
|
const sign = (diff: number): -1 | 0 | 1 => (diff === 0 ? 0 : diff < 0 ? -1 : 1);
|
||||||
|
const noUndefined = (n: number | undefined) => n ?? Infinity;
|
||||||
|
|
||||||
|
for (const level of ["major", "minor", "patch", "rc"] as const) {
|
||||||
|
if (noUndefined(v1[level]) !== noUndefined(v2[level])) {
|
||||||
|
return sign(noUndefined(v1[level]) - noUndefined(v2[level]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
console.log(compare(parse("3.0.0-rc.3"), parse("3.0.0")) === -1 )
|
||||||
|
console.log(compare(parse("3.0.0-rc.3"), parse("3.0.0-rc.4")) === -1 )
|
||||||
|
console.log(compare(parse("3.0.0-rc.3"), parse("4.0.0")) === -1 )
|
||||||
|
*/
|
||||||
|
|
||||||
|
export function bumpType(params: { versionBehind: string | SemVer; versionAhead: string | SemVer }): BumpType | "no bump" {
|
||||||
|
const versionAhead = typeof params.versionAhead === "string" ? parse(params.versionAhead) : params.versionAhead;
|
||||||
|
const versionBehind = typeof params.versionBehind === "string" ? parse(params.versionBehind) : params.versionBehind;
|
||||||
|
|
||||||
|
if (compare(versionBehind, versionAhead) === 1) {
|
||||||
|
throw new Error(`Version regression ${stringify(versionBehind)} -> ${stringify(versionAhead)}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const level of ["major", "minor", "patch", "rc"] as const) {
|
||||||
|
if (versionBehind[level] !== versionAhead[level]) {
|
||||||
|
return level;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return "no bump";
|
||||||
|
}
|
||||||
|
}
|
30
src/bin/tools/String.prototype.replaceAll.ts
Normal file
30
src/bin/tools/String.prototype.replaceAll.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
export function replaceAll(string: string, searchValue: string | RegExp, replaceValue: string): string {
|
||||||
|
if ((string as any).replaceAll !== undefined) {
|
||||||
|
return (string as any).replaceAll(searchValue, replaceValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the searchValue is a string
|
||||||
|
if (typeof searchValue === "string") {
|
||||||
|
// Escape special characters in the string to be used in a regex
|
||||||
|
var escapedSearchValue = searchValue.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
||||||
|
var regex = new RegExp(escapedSearchValue, "g");
|
||||||
|
|
||||||
|
return string.replace(regex, replaceValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the searchValue is a global RegExp, use it directly
|
||||||
|
if (searchValue instanceof RegExp && searchValue.global) {
|
||||||
|
return string.replace(searchValue, replaceValue);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the searchValue is a non-global RegExp, throw an error
|
||||||
|
if (searchValue instanceof RegExp) {
|
||||||
|
throw new TypeError("replaceAll must be called with a global RegExp");
|
||||||
|
}
|
||||||
|
|
||||||
|
// Convert searchValue to string if it's not a string or RegExp
|
||||||
|
var searchString = String(searchValue);
|
||||||
|
var regexFromString = new RegExp(searchString.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"), "g");
|
||||||
|
|
||||||
|
return string.replace(regexFromString, replaceValue);
|
||||||
|
}
|
@ -1,17 +1,17 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import { join as pathJoin, relative as pathRelative } from "path";
|
||||||
|
|
||||||
const crawlRec = (dir_path: string, paths: string[]) => {
|
const crawlRec = (dirPath: string, filePaths: string[]) => {
|
||||||
for (const file_name of fs.readdirSync(dir_path)) {
|
for (const basename of fs.readdirSync(dirPath)) {
|
||||||
const file_path = path.join(dir_path, file_name);
|
const fileOrDirPath = pathJoin(dirPath, basename);
|
||||||
|
|
||||||
if (fs.lstatSync(file_path).isDirectory()) {
|
if (fs.lstatSync(fileOrDirPath).isDirectory()) {
|
||||||
crawlRec(file_path, paths);
|
crawlRec(fileOrDirPath, filePaths);
|
||||||
|
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
paths.push(file_path);
|
filePaths.push(fileOrDirPath);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -27,6 +27,6 @@ export function crawl(params: { dirPath: string; returnedPathsType: "absolute" |
|
|||||||
case "absolute":
|
case "absolute":
|
||||||
return filePaths;
|
return filePaths;
|
||||||
case "relative to dirPath":
|
case "relative to dirPath":
|
||||||
return filePaths.map(filePath => path.relative(dirPath, filePath));
|
return filePaths.map(filePath => pathRelative(dirPath, filePath));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,232 +0,0 @@
|
|||||||
import { exec as execCallback } from "child_process";
|
|
||||||
import { createHash } from "crypto";
|
|
||||||
import { mkdir, readFile, stat, writeFile, unlink, rm } from "fs/promises";
|
|
||||||
import fetch, { type FetchOptions } from "make-fetch-happen";
|
|
||||||
import { dirname as pathDirname, join as pathJoin, resolve as pathResolve, sep as pathSep } from "path";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import { promisify } from "util";
|
|
||||||
import { transformCodebase } from "./transformCodebase";
|
|
||||||
import { unzip, zip } from "./unzip";
|
|
||||||
|
|
||||||
const exec = promisify(execCallback);
|
|
||||||
|
|
||||||
function generateFileNameFromURL(params: {
|
|
||||||
url: string;
|
|
||||||
preCacheTransform:
|
|
||||||
| {
|
|
||||||
actionCacheId: string;
|
|
||||||
actionFootprint: string;
|
|
||||||
}
|
|
||||||
| undefined;
|
|
||||||
}): string {
|
|
||||||
const { preCacheTransform } = params;
|
|
||||||
|
|
||||||
// Parse the URL
|
|
||||||
const url = new URL(params.url);
|
|
||||||
|
|
||||||
// Extract pathname and remove leading slashes
|
|
||||||
let fileName = url.pathname.replace(/^\//, "").replace(/\//g, "_");
|
|
||||||
|
|
||||||
// Optionally, add query parameters replacing special characters
|
|
||||||
if (url.search) {
|
|
||||||
fileName += url.search.replace(/[&=?]/g, "-");
|
|
||||||
}
|
|
||||||
|
|
||||||
// Replace any characters that are not valid in filenames
|
|
||||||
fileName = fileName.replace(/[^a-zA-Z0-9-_]/g, "");
|
|
||||||
|
|
||||||
// Trim or pad the fileName to a specific length
|
|
||||||
fileName = fileName.substring(0, 50);
|
|
||||||
|
|
||||||
add_pre_cache_transform: {
|
|
||||||
if (preCacheTransform === undefined) {
|
|
||||||
break add_pre_cache_transform;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sanitize actionCacheId the same way as other components
|
|
||||||
const sanitizedActionCacheId = preCacheTransform.actionCacheId.replace(/[^a-zA-Z0-9-_]/g, "_");
|
|
||||||
|
|
||||||
fileName += `_${sanitizedActionCacheId}_${createHash("sha256").update(preCacheTransform.actionFootprint).digest("hex").substring(0, 5)}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
return fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function exists(path: string) {
|
|
||||||
try {
|
|
||||||
await stat(path);
|
|
||||||
return true;
|
|
||||||
} catch (error) {
|
|
||||||
if ((error as Error & { code: string }).code === "ENOENT") return false;
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureArray<T>(arg0: T | T[]) {
|
|
||||||
return Array.isArray(arg0) ? arg0 : typeof arg0 === "undefined" ? [] : [arg0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureSingleOrNone<T>(arg0: T | T[]) {
|
|
||||||
if (!Array.isArray(arg0)) return arg0;
|
|
||||||
if (arg0.length === 0) return undefined;
|
|
||||||
if (arg0.length === 1) return arg0[0];
|
|
||||||
throw new Error("Illegal configuration, expected a single value but found multiple: " + arg0.map(String).join(", "));
|
|
||||||
}
|
|
||||||
|
|
||||||
type NPMConfig = Record<string, string | string[]>;
|
|
||||||
|
|
||||||
const npmConfigReducer = (cfg: NPMConfig, [key, value]: [string, string]) =>
|
|
||||||
key in cfg ? { ...cfg, [key]: [...ensureArray(cfg[key]), value] } : { ...cfg, [key]: value };
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get npm configuration as map
|
|
||||||
*/
|
|
||||||
async function getNmpConfig() {
|
|
||||||
return readNpmConfig().then(parseNpmConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
function readNpmConfig(): Promise<string> {
|
|
||||||
return (async function callee(depth: number): Promise<string> {
|
|
||||||
const cwd = pathResolve(pathJoin(...[process.cwd(), ...Array(depth).fill("..")]));
|
|
||||||
|
|
||||||
let stdout: string;
|
|
||||||
|
|
||||||
try {
|
|
||||||
stdout = await exec("npm config get", { "encoding": "utf8", cwd }).then(({ stdout }) => stdout);
|
|
||||||
} catch (error) {
|
|
||||||
if (String(error).includes("ENOWORKSPACES")) {
|
|
||||||
assert(cwd !== pathSep);
|
|
||||||
|
|
||||||
return callee(depth + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
return stdout;
|
|
||||||
})(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function parseNpmConfig(stdout: string) {
|
|
||||||
return stdout
|
|
||||||
.split("\n")
|
|
||||||
.filter(line => !line.startsWith(";"))
|
|
||||||
.map(line => line.trim())
|
|
||||||
.map(line => line.split("=", 2) as [string, string])
|
|
||||||
.reduce(npmConfigReducer, {} as NPMConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
function maybeBoolean(arg0: string | undefined) {
|
|
||||||
return typeof arg0 === "undefined" ? undefined : Boolean(arg0);
|
|
||||||
}
|
|
||||||
|
|
||||||
function chunks<T>(arr: T[], size: number = 2) {
|
|
||||||
return arr.map((_, i) => i % size == 0 && arr.slice(i, i + size)).filter(Boolean) as T[][];
|
|
||||||
}
|
|
||||||
|
|
||||||
async function readCafile(cafile: string) {
|
|
||||||
const cafileContent = await readFile(cafile, "utf-8");
|
|
||||||
return chunks(cafileContent.split(/(-----END CERTIFICATE-----)/), 2).map(ca => ca.join("").replace(/^\n/, "").replace(/\n/g, "\\n"));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get proxy and ssl configuration from npm config files. Note that we don't care about
|
|
||||||
* proxy config in env vars, because make-fetch-happen will do that for us.
|
|
||||||
*
|
|
||||||
* @returns proxy configuration
|
|
||||||
*/
|
|
||||||
async function getFetchOptions(): Promise<Pick<FetchOptions, "proxy" | "noProxy" | "strictSSL" | "ca" | "cert">> {
|
|
||||||
const cfg = await getNmpConfig();
|
|
||||||
|
|
||||||
const proxy = ensureSingleOrNone(cfg["https-proxy"] ?? cfg["proxy"]);
|
|
||||||
const noProxy = cfg["noproxy"] ?? cfg["no-proxy"];
|
|
||||||
const strictSSL = maybeBoolean(ensureSingleOrNone(cfg["strict-ssl"]));
|
|
||||||
const cert = cfg["cert"];
|
|
||||||
const ca = ensureArray(cfg["ca"] ?? cfg["ca[]"]);
|
|
||||||
const cafile = ensureSingleOrNone(cfg["cafile"]);
|
|
||||||
|
|
||||||
if (typeof cafile !== "undefined" && cafile !== "null") ca.push(...(await readCafile(cafile)));
|
|
||||||
|
|
||||||
return { proxy, noProxy, strictSSL, cert, ca: ca.length === 0 ? undefined : ca };
|
|
||||||
}
|
|
||||||
|
|
||||||
export async function downloadAndUnzip(
|
|
||||||
params: {
|
|
||||||
url: string;
|
|
||||||
destDirPath: string;
|
|
||||||
specificDirsToExtract?: string[];
|
|
||||||
preCacheTransform?: {
|
|
||||||
actionCacheId: string;
|
|
||||||
action: (params: { destDirPath: string }) => Promise<void>;
|
|
||||||
};
|
|
||||||
} & (
|
|
||||||
| {
|
|
||||||
doUseCache: true;
|
|
||||||
cacheDirPath: string;
|
|
||||||
}
|
|
||||||
| {
|
|
||||||
doUseCache: false;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
const { url, destDirPath, specificDirsToExtract, preCacheTransform, ...rest } = params;
|
|
||||||
|
|
||||||
const zipFileBasename = generateFileNameFromURL({
|
|
||||||
url,
|
|
||||||
"preCacheTransform":
|
|
||||||
preCacheTransform === undefined
|
|
||||||
? undefined
|
|
||||||
: {
|
|
||||||
"actionCacheId": preCacheTransform.actionCacheId,
|
|
||||||
"actionFootprint": preCacheTransform.action.toString()
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const cacheDirPath = !rest.doUseCache ? `tmp_${Math.random().toString().slice(2, 12)}` : rest.cacheDirPath;
|
|
||||||
|
|
||||||
const zipFilePath = pathJoin(cacheDirPath, `${zipFileBasename}.zip`);
|
|
||||||
const extractDirPath = pathJoin(cacheDirPath, `tmp_unzip_${zipFileBasename}`);
|
|
||||||
|
|
||||||
if (!(await exists(zipFilePath))) {
|
|
||||||
const opts = await getFetchOptions();
|
|
||||||
const response = await fetch(url, opts);
|
|
||||||
await mkdir(pathDirname(zipFilePath), { "recursive": true });
|
|
||||||
/**
|
|
||||||
* The correct way to fix this is to upgrade node-fetch beyond 3.2.5
|
|
||||||
* (see https://github.com/node-fetch/node-fetch/issues/1295#issuecomment-1144061991.)
|
|
||||||
* Unfortunately, octokit (a dependency of keycloakify) also uses node-fetch, and
|
|
||||||
* does not support node-fetch 3.x. So we stick around with this band-aid until
|
|
||||||
* octokit upgrades.
|
|
||||||
*/
|
|
||||||
response.body?.setMaxListeners(Number.MAX_VALUE);
|
|
||||||
assert(typeof response.body !== "undefined" && response.body != null);
|
|
||||||
await writeFile(zipFilePath, response.body);
|
|
||||||
|
|
||||||
if (specificDirsToExtract !== undefined || preCacheTransform !== undefined) {
|
|
||||||
await unzip(zipFilePath, extractDirPath, specificDirsToExtract);
|
|
||||||
|
|
||||||
await preCacheTransform?.action({
|
|
||||||
"destDirPath": extractDirPath
|
|
||||||
});
|
|
||||||
|
|
||||||
await unlink(zipFilePath);
|
|
||||||
|
|
||||||
await zip(extractDirPath, zipFilePath);
|
|
||||||
|
|
||||||
await rm(extractDirPath, { "recursive": true });
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
await unzip(zipFilePath, extractDirPath);
|
|
||||||
|
|
||||||
transformCodebase({
|
|
||||||
"srcDirPath": extractDirPath,
|
|
||||||
"destDirPath": destDirPath
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!rest.doUseCache) {
|
|
||||||
await rm(cacheDirPath, { "recursive": true });
|
|
||||||
} else {
|
|
||||||
await rm(extractDirPath, { "recursive": true });
|
|
||||||
}
|
|
||||||
}
|
|
73
src/bin/tools/fetchProxyOptions.ts
Normal file
73
src/bin/tools/fetchProxyOptions.ts
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { exec as execCallback } from "child_process";
|
||||||
|
import { readFile } from "fs/promises";
|
||||||
|
import { type FetchOptions } from "make-fetch-happen";
|
||||||
|
import { promisify } from "util";
|
||||||
|
|
||||||
|
function ensureArray<T>(arg0: T | T[]) {
|
||||||
|
return Array.isArray(arg0) ? arg0 : typeof arg0 === "undefined" ? [] : [arg0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureSingleOrNone<T>(arg0: T | T[]) {
|
||||||
|
if (!Array.isArray(arg0)) return arg0;
|
||||||
|
if (arg0.length === 0) return undefined;
|
||||||
|
if (arg0.length === 1) return arg0[0];
|
||||||
|
throw new Error("Illegal configuration, expected a single value but found multiple: " + arg0.map(String).join(", "));
|
||||||
|
}
|
||||||
|
|
||||||
|
type NPMConfig = Record<string, string | string[]>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get npm configuration as map
|
||||||
|
*/
|
||||||
|
async function getNmpConfig(params: { npmWorkspaceRootDirPath: string }) {
|
||||||
|
const { npmWorkspaceRootDirPath } = params;
|
||||||
|
|
||||||
|
const exec = promisify(execCallback);
|
||||||
|
|
||||||
|
const stdout = await exec("npm config get", { "encoding": "utf8", "cwd": npmWorkspaceRootDirPath }).then(({ stdout }) => stdout);
|
||||||
|
|
||||||
|
const npmConfigReducer = (cfg: NPMConfig, [key, value]: [string, string]) =>
|
||||||
|
key in cfg ? { ...cfg, [key]: [...ensureArray(cfg[key]), value] } : { ...cfg, [key]: value };
|
||||||
|
|
||||||
|
return stdout
|
||||||
|
.split("\n")
|
||||||
|
.filter(line => !line.startsWith(";"))
|
||||||
|
.map(line => line.trim())
|
||||||
|
.map(line => line.split("=", 2) as [string, string])
|
||||||
|
.reduce(npmConfigReducer, {} as NPMConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ProxyFetchOptions = Pick<FetchOptions, "proxy" | "noProxy" | "strictSSL" | "cert" | "ca">;
|
||||||
|
|
||||||
|
export async function getProxyFetchOptions(params: { npmWorkspaceRootDirPath: string }): Promise<ProxyFetchOptions> {
|
||||||
|
const { npmWorkspaceRootDirPath } = params;
|
||||||
|
|
||||||
|
const cfg = await getNmpConfig({ npmWorkspaceRootDirPath });
|
||||||
|
|
||||||
|
const proxy = ensureSingleOrNone(cfg["https-proxy"] ?? cfg["proxy"]);
|
||||||
|
const noProxy = cfg["noproxy"] ?? cfg["no-proxy"];
|
||||||
|
|
||||||
|
function maybeBoolean(arg0: string | undefined) {
|
||||||
|
return typeof arg0 === "undefined" ? undefined : Boolean(arg0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const strictSSL = maybeBoolean(ensureSingleOrNone(cfg["strict-ssl"]));
|
||||||
|
const cert = cfg["cert"];
|
||||||
|
const ca = ensureArray(cfg["ca"] ?? cfg["ca[]"]);
|
||||||
|
const cafile = ensureSingleOrNone(cfg["cafile"]);
|
||||||
|
|
||||||
|
if (typeof cafile !== "undefined" && cafile !== "null") {
|
||||||
|
ca.push(
|
||||||
|
...(await (async () => {
|
||||||
|
function chunks<T>(arr: T[], size: number = 2) {
|
||||||
|
return arr.map((_, i) => i % size == 0 && arr.slice(i, i + size)).filter(Boolean) as T[][];
|
||||||
|
}
|
||||||
|
|
||||||
|
const cafileContent = await readFile(cafile, "utf-8");
|
||||||
|
return chunks(cafileContent.split(/(-----END CERTIFICATE-----)/), 2).map(ca => ca.join("").replace(/^\n/, "").replace(/\n/g, "\\n"));
|
||||||
|
})())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { proxy, noProxy, strictSSL, cert, "ca": ca.length === 0 ? undefined : ca };
|
||||||
|
}
|
11
src/bin/tools/fs.existsAsync.ts
Normal file
11
src/bin/tools/fs.existsAsync.ts
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
import * as fs from "fs/promises";
|
||||||
|
|
||||||
|
export async function existsAsync(path: string) {
|
||||||
|
try {
|
||||||
|
await fs.stat(path);
|
||||||
|
return true;
|
||||||
|
} catch (error) {
|
||||||
|
if ((error as Error & { code: string }).code === "ENOENT") return false;
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
43
src/bin/tools/fs.rm.ts
Normal file
43
src/bin/tools/fs.rm.ts
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
import * as fs from "fs/promises";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { SemVer } from "./SemVer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polyfill of fs.rm(dirPath, { "recursive": true })
|
||||||
|
* For older version of Node
|
||||||
|
*/
|
||||||
|
export async function rm(dirPath: string, options: { recursive: true; force?: true }) {
|
||||||
|
if (SemVer.compare(SemVer.parse(process.version), SemVer.parse("14.14.0")) > 0) {
|
||||||
|
return fs.rm(dirPath, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
const { force = true } = options;
|
||||||
|
|
||||||
|
if (force && !(await checkDirExists(dirPath))) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeDir_rec = async (dirPath: string) =>
|
||||||
|
Promise.all(
|
||||||
|
(await fs.readdir(dirPath)).map(async basename => {
|
||||||
|
const fileOrDirpath = pathJoin(dirPath, basename);
|
||||||
|
|
||||||
|
if ((await fs.lstat(fileOrDirpath)).isDirectory()) {
|
||||||
|
await removeDir_rec(fileOrDirpath);
|
||||||
|
} else {
|
||||||
|
await fs.unlink(fileOrDirpath);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
await removeDir_rec(dirPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkDirExists(dirPath: string) {
|
||||||
|
try {
|
||||||
|
await fs.access(dirPath, fs.constants.F_OK);
|
||||||
|
return true;
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
34
src/bin/tools/fs.rmSync.ts
Normal file
34
src/bin/tools/fs.rmSync.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
import * as fs from "fs";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { SemVer } from "./SemVer";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Polyfill of fs.rmSync(dirPath, { "recursive": true })
|
||||||
|
* For older version of Node
|
||||||
|
*/
|
||||||
|
export function rmSync(dirPath: string, options: { recursive: true; force?: true }) {
|
||||||
|
if (SemVer.compare(SemVer.parse(process.version), SemVer.parse("14.14.0")) > 0) {
|
||||||
|
fs.rmSync(dirPath, options);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { force = true } = options;
|
||||||
|
|
||||||
|
if (force && !fs.existsSync(dirPath)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const removeDir_rec = (dirPath: string) =>
|
||||||
|
fs.readdirSync(dirPath).forEach(basename => {
|
||||||
|
const fileOrDirpath = pathJoin(dirPath, basename);
|
||||||
|
|
||||||
|
if (fs.lstatSync(fileOrDirpath).isDirectory()) {
|
||||||
|
removeDir_rec(fileOrDirpath);
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
fs.unlinkSync(fileOrDirpath);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
removeDir_rec(dirPath);
|
||||||
|
}
|
@ -7,6 +7,8 @@ export function getAbsoluteAndInOsFormatPath(params: { pathIsh: string; cwd: str
|
|||||||
|
|
||||||
pathOut = pathOut.replace(/\//g, pathSep);
|
pathOut = pathOut.replace(/\//g, pathSep);
|
||||||
|
|
||||||
|
pathOut = pathOut.endsWith(pathSep) ? pathOut.slice(0, -1) : pathOut;
|
||||||
|
|
||||||
if (!pathIsAbsolute(pathOut)) {
|
if (!pathIsAbsolute(pathOut)) {
|
||||||
pathOut = pathJoin(cwd, pathOut);
|
pathOut = pathJoin(cwd, pathOut);
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,19 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
|
|
||||||
function getProjectRootRec(dirPath: string): string {
|
function getThisCodebaseRootDirPath_rec(dirPath: string): string {
|
||||||
if (fs.existsSync(path.join(dirPath, "package.json"))) {
|
if (fs.existsSync(path.join(dirPath, "package.json"))) {
|
||||||
return dirPath;
|
return dirPath;
|
||||||
}
|
}
|
||||||
return getProjectRootRec(path.join(dirPath, ".."));
|
return getThisCodebaseRootDirPath_rec(path.join(dirPath, ".."));
|
||||||
}
|
}
|
||||||
|
|
||||||
let result: string | undefined = undefined;
|
let result: string | undefined = undefined;
|
||||||
|
|
||||||
export function getProjectRoot(): string {
|
export function getThisCodebaseRootDirPath(): string {
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
return (result = getProjectRootRec(__dirname));
|
return (result = getThisCodebaseRootDirPath_rec(__dirname));
|
||||||
}
|
}
|
@ -1,13 +1,15 @@
|
|||||||
import { getProjectRoot } from "./getProjectRoot";
|
import { getThisCodebaseRootDirPath } from "./getThisCodebaseRootDirPath";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import { constants } from "fs";
|
import { constants } from "fs";
|
||||||
import { chmod, stat } from "fs/promises";
|
import { chmod, stat } from "fs/promises";
|
||||||
|
|
||||||
(async () => {
|
(async () => {
|
||||||
const { bin } = await import(pathJoin(getProjectRoot(), "package.json"));
|
const thisCodebaseRootDirPath = getThisCodebaseRootDirPath();
|
||||||
|
|
||||||
|
const { bin } = await import(pathJoin(thisCodebaseRootDirPath, "package.json"));
|
||||||
|
|
||||||
const promises = Object.values<string>(bin).map(async scriptPath => {
|
const promises = Object.values<string>(bin).map(async scriptPath => {
|
||||||
const fullPath = pathJoin(getProjectRoot(), scriptPath);
|
const fullPath = pathJoin(thisCodebaseRootDirPath, scriptPath);
|
||||||
const oldMode = (await stat(fullPath)).mode;
|
const oldMode = (await stat(fullPath)).mode;
|
||||||
const newMode = oldMode | constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH;
|
const newMode = oldMode | constants.S_IXUSR | constants.S_IXGRP | constants.S_IXOTH;
|
||||||
await chmod(fullPath, newMode);
|
await chmod(fullPath, newMode);
|
||||||
|
@ -1,39 +1,39 @@
|
|||||||
import { listTagsFactory } from "./listTags";
|
import { listTagsFactory } from "./listTags";
|
||||||
import type { Octokit } from "@octokit/rest";
|
import type { Octokit } from "@octokit/rest";
|
||||||
import { NpmModuleVersion } from "../NpmModuleVersion";
|
import { SemVer } from "../SemVer";
|
||||||
|
|
||||||
export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
|
export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
|
||||||
const { octokit } = params;
|
const { octokit } = params;
|
||||||
|
|
||||||
async function getLatestsSemVersionedTag(params: { owner: string; repo: string; doIgnoreBeta: boolean; count: number }): Promise<
|
async function getLatestsSemVersionedTag(params: { owner: string; repo: string; count: number }): Promise<
|
||||||
{
|
{
|
||||||
tag: string;
|
tag: string;
|
||||||
version: NpmModuleVersion;
|
version: SemVer;
|
||||||
}[]
|
}[]
|
||||||
> {
|
> {
|
||||||
const { owner, repo, doIgnoreBeta, count } = params;
|
const { owner, repo, count } = params;
|
||||||
|
|
||||||
const semVersionedTags: { tag: string; version: NpmModuleVersion }[] = [];
|
const semVersionedTags: { tag: string; version: SemVer }[] = [];
|
||||||
|
|
||||||
const { listTags } = listTagsFactory({ octokit });
|
const { listTags } = listTagsFactory({ octokit });
|
||||||
|
|
||||||
for await (const tag of listTags({ owner, repo })) {
|
for await (const tag of listTags({ owner, repo })) {
|
||||||
let version: NpmModuleVersion;
|
let version: SemVer;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
version = NpmModuleVersion.parse(tag.replace(/^[vV]?/, ""));
|
version = SemVer.parse(tag.replace(/^[vV]?/, ""));
|
||||||
} catch {
|
} catch {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (doIgnoreBeta && version.betaPreRelease !== undefined) {
|
if (version.rc !== undefined) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
semVersionedTags.push({ tag, version });
|
semVersionedTags.push({ tag, version });
|
||||||
}
|
}
|
||||||
|
|
||||||
return semVersionedTags.sort(({ version: vX }, { version: vY }) => NpmModuleVersion.compare(vY, vX)).slice(0, count);
|
return semVersionedTags.sort(({ version: vX }, { version: vY }) => SemVer.compare(vY, vX)).slice(0, count);
|
||||||
}
|
}
|
||||||
|
|
||||||
return { getLatestsSemVersionedTag };
|
return { getLatestsSemVersionedTag };
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
export function pathJoin(...path: string[]): string {
|
|
||||||
return path
|
|
||||||
.map((part, i) => (i === 0 ? part : part.replace(/^\/+/, "")))
|
|
||||||
.map((part, i) => (i === path.length - 1 ? part : part.replace(/\/+$/, "")))
|
|
||||||
.join(typeof process !== "undefined" && process.platform === "win32" ? "\\" : "/");
|
|
||||||
}
|
|
12
src/bin/tools/readThisNpmProjectVersion.ts
Normal file
12
src/bin/tools/readThisNpmProjectVersion.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { getThisCodebaseRootDirPath } from "./getThisCodebaseRootDirPath";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
|
||||||
|
export function readThisNpmProjectVersion(): string {
|
||||||
|
const version = JSON.parse(fs.readFileSync(pathJoin(getThisCodebaseRootDirPath(), "package.json")).toString("utf8"))["version"];
|
||||||
|
|
||||||
|
assert(typeof version === "string");
|
||||||
|
|
||||||
|
return version;
|
||||||
|
}
|
@ -1,7 +1,7 @@
|
|||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import * as path from "path";
|
import * as path from "path";
|
||||||
import { crawl } from "./crawl";
|
import { crawl } from "./crawl";
|
||||||
import { id } from "tsafe/id";
|
import { rmSync } from "../tools/fs.rmSync";
|
||||||
|
|
||||||
type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; fileRelativePath: string }) =>
|
type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; fileRelativePath: string }) =>
|
||||||
| {
|
| {
|
||||||
@ -10,18 +10,37 @@ type TransformSourceCode = (params: { sourceCode: Buffer; filePath: string; file
|
|||||||
}
|
}
|
||||||
| undefined;
|
| undefined;
|
||||||
|
|
||||||
/** Apply a transformation function to every file of directory */
|
/**
|
||||||
|
* Apply a transformation function to every file of directory
|
||||||
|
* If source and destination are the same this function can be used to apply the transformation in place
|
||||||
|
* like filtering out some files or modifying them.
|
||||||
|
* */
|
||||||
export function transformCodebase(params: { srcDirPath: string; destDirPath: string; transformSourceCode?: TransformSourceCode }) {
|
export function transformCodebase(params: { srcDirPath: string; destDirPath: string; transformSourceCode?: TransformSourceCode }) {
|
||||||
const {
|
const { srcDirPath, transformSourceCode } = params;
|
||||||
srcDirPath,
|
|
||||||
destDirPath,
|
const isTargetSameAsSource = path.relative(srcDirPath, params.destDirPath) === "";
|
||||||
transformSourceCode = id<TransformSourceCode>(({ sourceCode }) => ({
|
|
||||||
"modifiedSourceCode": sourceCode
|
const destDirPath = isTargetSameAsSource ? path.join(srcDirPath, "..", "tmp_xOsPdkPsTdzPs34sOkHs") : params.destDirPath;
|
||||||
}))
|
|
||||||
} = params;
|
fs.mkdirSync(destDirPath, {
|
||||||
|
"recursive": true
|
||||||
|
});
|
||||||
|
|
||||||
for (const fileRelativePath of crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })) {
|
for (const fileRelativePath of crawl({ "dirPath": srcDirPath, "returnedPathsType": "relative to dirPath" })) {
|
||||||
const filePath = path.join(srcDirPath, fileRelativePath);
|
const filePath = path.join(srcDirPath, fileRelativePath);
|
||||||
|
const destFilePath = path.join(destDirPath, fileRelativePath);
|
||||||
|
|
||||||
|
// NOTE: Optimization, if we don't need to transform the file, just copy
|
||||||
|
// it using the lower level implementation.
|
||||||
|
if (transformSourceCode === undefined) {
|
||||||
|
fs.mkdirSync(path.dirname(destFilePath), {
|
||||||
|
"recursive": true
|
||||||
|
});
|
||||||
|
|
||||||
|
fs.copyFileSync(filePath, destFilePath);
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
const transformSourceCodeResult = transformSourceCode({
|
const transformSourceCodeResult = transformSourceCode({
|
||||||
"sourceCode": fs.readFileSync(filePath),
|
"sourceCode": fs.readFileSync(filePath),
|
||||||
@ -33,15 +52,18 @@ export function transformCodebase(params: { srcDirPath: string; destDirPath: str
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
fs.mkdirSync(path.dirname(path.join(destDirPath, fileRelativePath)), {
|
fs.mkdirSync(path.dirname(destFilePath), {
|
||||||
"recursive": true
|
"recursive": true
|
||||||
});
|
});
|
||||||
|
|
||||||
const { newFileName, modifiedSourceCode } = transformSourceCodeResult;
|
const { newFileName, modifiedSourceCode } = transformSourceCodeResult;
|
||||||
|
|
||||||
fs.writeFileSync(
|
fs.writeFileSync(path.join(path.dirname(destFilePath), newFileName ?? path.basename(destFilePath)), modifiedSourceCode);
|
||||||
path.join(path.dirname(path.join(destDirPath, fileRelativePath)), newFileName ?? path.basename(fileRelativePath)),
|
}
|
||||||
modifiedSourceCode
|
|
||||||
);
|
if (isTargetSameAsSource) {
|
||||||
|
rmSync(srcDirPath, { "recursive": true });
|
||||||
|
|
||||||
|
fs.renameSync(destDirPath, srcDirPath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
44
src/lib/BASE_URL.ts
Normal file
44
src/lib/BASE_URL.ts
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* WARNING: Internal use only!!
|
||||||
|
* THIS DOES NOT WORK IN KEYCLOAK! It's only for resolving mock assets.
|
||||||
|
* This is just a way to know what's the base url that works
|
||||||
|
* both in webpack and vite.
|
||||||
|
* You can see this as a polyfill that return `import.meta.env.BASE_URL` when in Vite
|
||||||
|
* and when in Webpack returns the base url in the same format as vite does meaning
|
||||||
|
* "/" if hosted at root or "/foo/" when hosted under a subpath (always start and ends with a "/").
|
||||||
|
*/
|
||||||
|
export const BASE_URL = (() => {
|
||||||
|
vite: {
|
||||||
|
let BASE_URL: string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-expect-error
|
||||||
|
BASE_URL = import.meta.env.BASE_URL;
|
||||||
|
|
||||||
|
assert(typeof BASE_URL === "string");
|
||||||
|
} catch {
|
||||||
|
break vite;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BASE_URL;
|
||||||
|
}
|
||||||
|
|
||||||
|
webpack: {
|
||||||
|
let BASE_URL: string;
|
||||||
|
|
||||||
|
try {
|
||||||
|
// @ts-expect-error
|
||||||
|
BASE_URL = process.env.PUBLIC_URL;
|
||||||
|
|
||||||
|
assert(typeof BASE_URL === "string");
|
||||||
|
} catch {
|
||||||
|
break webpack;
|
||||||
|
}
|
||||||
|
|
||||||
|
return BASE_URL === "" ? "/" : `${BASE_URL}/`;
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error("Bundler not supported");
|
||||||
|
})();
|
1
src/lib/isStorybook.ts
Normal file
1
src/lib/isStorybook.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export const isStorybook = typeof window === "object" && Object.keys(window).find(key => key.startsWith("__STORYBOOK")) !== undefined;
|
@ -36,6 +36,10 @@ export declare namespace keycloak_js {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* @deprecated: This will be removed in the next major version.
|
||||||
|
* If you use this, please copy paste the code into your project.
|
||||||
|
* Better yet migrate away from keycloak-js and use https://docs.oidc-spa.dev instead.
|
||||||
|
*
|
||||||
* NOTE: This is just a slightly modified version of the default adapter in keycloak-js
|
* NOTE: This is just a slightly modified version of the default adapter in keycloak-js
|
||||||
* The goal here is just to be able to inject search param in url before keycloak redirect.
|
* The goal here is just to be able to inject search param in url before keycloak redirect.
|
||||||
* Our use case for it is to pass over the login screen the states of useGlobalState
|
* Our use case for it is to pass over the login screen the states of useGlobalState
|
||||||
|
@ -45,6 +45,8 @@ export default function Template(props: TemplateProps<KcContext, I18n>) {
|
|||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
document.title = i18n.msgStr("loginTitle", kcContext.realm.displayName);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className={getClassName("kcLoginClass")}>
|
<div className={getClassName("kcLoginClass")}>
|
||||||
<div id="kc-header" className={getClassName("kcHeaderClass")}>
|
<div id="kc-header" className={getClassName("kcHeaderClass")}>
|
||||||
|
@ -95,4 +95,5 @@ export type ClassKey =
|
|||||||
| "kcAuthenticatorOtpCircleClass"
|
| "kcAuthenticatorOtpCircleClass"
|
||||||
| "kcSelectOTPItemHeadingClass"
|
| "kcSelectOTPItemHeadingClass"
|
||||||
| "kcFormOptionsWrapperClass"
|
| "kcFormOptionsWrapperClass"
|
||||||
| "kcFormButtonsWrapperClass";
|
| "kcFormButtonsWrapperClass"
|
||||||
|
| "kcInputGroup";
|
||||||
|
@ -84,7 +84,7 @@ export declare namespace KcContext {
|
|||||||
description?: string;
|
description?: string;
|
||||||
attributes: Record<string, string>;
|
attributes: Record<string, string>;
|
||||||
};
|
};
|
||||||
isAppInitiatedAction: boolean;
|
isAppInitiatedAction?: boolean;
|
||||||
messagesPerField: {
|
messagesPerField: {
|
||||||
/**
|
/**
|
||||||
* Return text if message for given field exists. Useful eg. to add css styles for fields with message.
|
* Return text if message for given field exists. Useful eg. to add css styles for fields with message.
|
||||||
@ -116,6 +116,7 @@ export declare namespace KcContext {
|
|||||||
*/
|
*/
|
||||||
exists: (fieldName: string) => boolean;
|
exists: (fieldName: string) => boolean;
|
||||||
};
|
};
|
||||||
|
properties: Record<string, string | undefined>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export type SamlPostForm = Common & {
|
export type SamlPostForm = Common & {
|
||||||
@ -244,6 +245,17 @@ export declare namespace KcContext {
|
|||||||
|
|
||||||
export type Terms = Common & {
|
export type Terms = Common & {
|
||||||
pageId: "terms.ftl";
|
pageId: "terms.ftl";
|
||||||
|
//NOTE: Optional because maybe it wasn't defined in older keycloak versions.
|
||||||
|
user?: {
|
||||||
|
id: string;
|
||||||
|
username: string;
|
||||||
|
attributes: Record<string, string[]>;
|
||||||
|
email: string;
|
||||||
|
emailVerified: boolean;
|
||||||
|
firstName?: string;
|
||||||
|
lastName?: string;
|
||||||
|
markedForEviction?: boolean;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type LoginDeviceVerifyUserCode = Common & {
|
export type LoginDeviceVerifyUserCode = Common & {
|
||||||
|
@ -2,19 +2,19 @@ import type { KcContext, Attribute } from "./KcContext";
|
|||||||
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
import { kcContextMocks, kcContextCommonMock } from "./kcContextMocks";
|
||||||
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
|
||||||
import { deepAssign } from "keycloakify/tools/deepAssign";
|
import { deepAssign } from "keycloakify/tools/deepAssign";
|
||||||
|
import { isStorybook } from "keycloakify/lib/isStorybook";
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import { exclude } from "tsafe/exclude";
|
import { exclude } from "tsafe/exclude";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
import type { ExtendKcContext } from "./getKcContextFromWindow";
|
||||||
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
import { getKcContextFromWindow } from "./getKcContextFromWindow";
|
||||||
import { pathJoin } from "keycloakify/bin/tools/pathJoin";
|
|
||||||
import { symToStr } from "tsafe/symToStr";
|
import { symToStr } from "tsafe/symToStr";
|
||||||
import { resources_common } from "keycloakify/bin/constants";
|
|
||||||
|
|
||||||
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
export function createGetKcContext<KcContextExtension extends { pageId: string } = never>(params?: {
|
||||||
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
mockData?: readonly DeepPartial<ExtendKcContext<KcContextExtension>>[];
|
||||||
|
mockProperties?: Record<string, string>;
|
||||||
}) {
|
}) {
|
||||||
const { mockData } = params ?? {};
|
const { mockData, mockProperties } = params ?? {};
|
||||||
|
|
||||||
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: {
|
function getKcContext<PageId extends ExtendKcContext<KcContextExtension>["pageId"] | undefined = undefined>(params?: {
|
||||||
mockPageId?: PageId;
|
mockPageId?: PageId;
|
||||||
@ -31,7 +31,13 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
|||||||
if (mockPageId !== undefined && realKcContext === undefined) {
|
if (mockPageId !== undefined && realKcContext === undefined) {
|
||||||
//TODO maybe trow if no mock fo custom page
|
//TODO maybe trow if no mock fo custom page
|
||||||
|
|
||||||
|
warn_that_mock_is_enbaled: {
|
||||||
|
if (isStorybook) {
|
||||||
|
break warn_that_mock_is_enbaled;
|
||||||
|
}
|
||||||
|
|
||||||
console.log(`%cKeycloakify: ${symToStr({ mockPageId })} set to ${mockPageId}.`, "background: red; color: yellow; font-size: medium");
|
console.log(`%cKeycloakify: ${symToStr({ mockPageId })} set to ${mockPageId}.`, "background: red; color: yellow; font-size: medium");
|
||||||
|
}
|
||||||
|
|
||||||
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
|
const kcContextDefaultMock = kcContextMocks.find(({ pageId }) => pageId === mockPageId);
|
||||||
|
|
||||||
@ -136,6 +142,13 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (mockProperties !== undefined) {
|
||||||
|
deepAssign({
|
||||||
|
"target": kcContext.properties,
|
||||||
|
"source": mockProperties
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
return { kcContext };
|
return { kcContext };
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -147,8 +160,6 @@ export function createGetKcContext<KcContextExtension extends { pageId: string }
|
|||||||
return { "kcContext": undefined as any };
|
return { "kcContext": undefined as any };
|
||||||
}
|
}
|
||||||
|
|
||||||
realKcContext.url.resourcesCommonPath = pathJoin(realKcContext.url.resourcesPath, resources_common);
|
|
||||||
|
|
||||||
return { "kcContext": realKcContext as any };
|
return { "kcContext": realKcContext as any };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,11 +1,11 @@
|
|||||||
import type { KcContext } from "./KcContext";
|
import type { KcContext } from "./KcContext";
|
||||||
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
import type { AndByDiscriminatingKey } from "keycloakify/tools/AndByDiscriminatingKey";
|
||||||
import { ftlValuesGlobalName } from "keycloakify/bin/keycloakify/ftlValuesGlobalName";
|
import { nameOfTheGlobal } from "keycloakify/bin/constants";
|
||||||
|
|
||||||
export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [KcContextExtension] extends [never]
|
export type ExtendKcContext<KcContextExtension extends { pageId: string }> = [KcContextExtension] extends [never]
|
||||||
? KcContext
|
? KcContext
|
||||||
: AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>;
|
: AndByDiscriminatingKey<"pageId", KcContextExtension & KcContext.Common, KcContext>;
|
||||||
|
|
||||||
export function getKcContextFromWindow<KcContextExtension extends { pageId: string } = never>(): ExtendKcContext<KcContextExtension> | undefined {
|
export function getKcContextFromWindow<KcContextExtension extends { pageId: string } = never>(): ExtendKcContext<KcContextExtension> | undefined {
|
||||||
return typeof window === "undefined" ? undefined : (window as any)[ftlValuesGlobalName];
|
return typeof window === "undefined" ? undefined : (window as any)[nameOfTheGlobal];
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
import "minimal-polyfills/Object.fromEntries";
|
import "minimal-polyfills/Object.fromEntries";
|
||||||
import type { KcContext, Attribute } from "./KcContext";
|
import type { KcContext, Attribute } from "./KcContext";
|
||||||
import { resources_common, keycloak_resources } from "keycloakify/bin/constants";
|
import { resources_common, keycloak_resources } from "keycloakify/bin/constants";
|
||||||
import { pathJoin } from "keycloakify/bin/tools/pathJoin";
|
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import { assert, type Equals } from "tsafe/assert";
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
import type { LoginThemePageId } from "keycloakify/bin/keycloakify/generateFtl";
|
import type { LoginThemePageId } from "keycloakify/bin/keycloakify/generateFtl";
|
||||||
|
import { BASE_URL } from "keycloakify/lib/BASE_URL";
|
||||||
|
|
||||||
const attributes: Attribute[] = [
|
const attributes: Attribute[] = [
|
||||||
{
|
{
|
||||||
@ -100,9 +100,7 @@ const attributes: Attribute[] = [
|
|||||||
|
|
||||||
const attributesByName = Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any;
|
const attributesByName = Object.fromEntries(attributes.map(attribute => [attribute.name, attribute])) as any;
|
||||||
|
|
||||||
const PUBLIC_URL = (typeof process !== "object" ? undefined : process.env?.["PUBLIC_URL"]) || "/";
|
const resourcesPath = `${BASE_URL}${keycloak_resources}/login/resources`;
|
||||||
|
|
||||||
const resourcesPath = pathJoin(PUBLIC_URL, keycloak_resources, "login", "resources");
|
|
||||||
|
|
||||||
export const kcContextCommonMock: KcContext.Common = {
|
export const kcContextCommonMock: KcContext.Common = {
|
||||||
"themeVersion": "0.0.0",
|
"themeVersion": "0.0.0",
|
||||||
@ -112,7 +110,7 @@ export const kcContextCommonMock: KcContext.Common = {
|
|||||||
"url": {
|
"url": {
|
||||||
"loginAction": "#",
|
"loginAction": "#",
|
||||||
resourcesPath,
|
resourcesPath,
|
||||||
"resourcesCommonPath": pathJoin(resourcesPath, resources_common),
|
"resourcesCommonPath": `${resourcesPath}/${resources_common}`,
|
||||||
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
|
"loginRestartFlowUrl": "/auth/realms/myrealm/login-actions/restart?client_id=account&tab_id=HoAx28ja4xg",
|
||||||
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg"
|
"loginUrl": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg"
|
||||||
},
|
},
|
||||||
@ -238,7 +236,127 @@ export const kcContextCommonMock: KcContext.Common = {
|
|||||||
"attributes": {}
|
"attributes": {}
|
||||||
},
|
},
|
||||||
"scripts": [],
|
"scripts": [],
|
||||||
"isAppInitiatedAction": false
|
"isAppInitiatedAction": false,
|
||||||
|
"properties": {
|
||||||
|
"kcLogoIdP-facebook": "fa fa-facebook",
|
||||||
|
"parent": "keycloak",
|
||||||
|
"kcAuthenticatorOTPClass": "fa fa-mobile list-view-pf-icon-lg",
|
||||||
|
"kcLogoIdP-bitbucket": "fa fa-bitbucket",
|
||||||
|
"kcAuthenticatorWebAuthnClass": "fa fa-key list-view-pf-icon-lg",
|
||||||
|
"kcWebAuthnDefaultIcon": "pficon pficon-key",
|
||||||
|
"kcLogoIdP-stackoverflow": "fa fa-stack-overflow",
|
||||||
|
"kcSelectAuthListItemClass": "pf-l-stack__item select-auth-box-parent pf-l-split",
|
||||||
|
"kcLogoIdP-microsoft": "fa fa-windows",
|
||||||
|
"kcLocaleItemClass": "pf-c-dropdown__menu-item",
|
||||||
|
"kcLoginOTPListItemHeaderClass": "pf-c-tile__header",
|
||||||
|
"kcLoginOTPListItemIconBodyClass": "pf-c-tile__icon",
|
||||||
|
"kcInputHelperTextAfterClass": "pf-c-form__helper-text pf-c-form__helper-text-after",
|
||||||
|
"kcFormClass": "form-horizontal",
|
||||||
|
"kcSelectAuthListClass": "pf-l-stack select-auth-container",
|
||||||
|
"kcInputClassRadioCheckboxLabelDisabled": "pf-m-disabled",
|
||||||
|
"kcSelectAuthListItemIconClass": "pf-l-split__item select-auth-box-icon",
|
||||||
|
"kcRecoveryCodesWarning": "kc-recovery-codes-warning",
|
||||||
|
"kcFormSettingClass": "login-pf-settings",
|
||||||
|
"kcWebAuthnBLE": "fa fa-bluetooth-b",
|
||||||
|
"kcInputWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||||
|
"kcSelectAuthListItemArrowIconClass": "fa fa-angle-right fa-lg",
|
||||||
|
"meta": "viewport==width=device-width,initial-scale=1",
|
||||||
|
"styles": "css/login.css css/tile.css",
|
||||||
|
"kcFeedbackAreaClass": "col-md-12",
|
||||||
|
"kcLogoIdP-google": "fa fa-google",
|
||||||
|
"kcCheckLabelClass": "pf-c-check__label",
|
||||||
|
"kcSelectAuthListItemFillClass": "pf-l-split__item pf-m-fill",
|
||||||
|
"kcAuthenticatorDefaultClass": "fa fa-list list-view-pf-icon-lg",
|
||||||
|
"kcLogoIdP-gitlab": "fa fa-gitlab",
|
||||||
|
"kcFormAreaClass": "col-sm-10 col-sm-offset-1 col-md-8 col-md-offset-2 col-lg-8 col-lg-offset-2",
|
||||||
|
"kcFormButtonsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||||
|
"kcInputClassRadioLabel": "pf-c-radio__label",
|
||||||
|
"kcAuthenticatorWebAuthnPasswordlessClass": "fa fa-key list-view-pf-icon-lg",
|
||||||
|
"kcSelectAuthListItemHeadingClass": "pf-l-stack__item select-auth-box-headline pf-c-title",
|
||||||
|
"kcInfoAreaClass": "col-xs-12 col-sm-4 col-md-4 col-lg-5 details",
|
||||||
|
"kcLogoLink": "http://www.keycloak.org",
|
||||||
|
"kcContainerClass": "container-fluid",
|
||||||
|
"kcSelectAuthListItemTitle": "select-auth-box-paragraph",
|
||||||
|
"kcHtmlClass": "login-pf",
|
||||||
|
"kcLoginOTPListItemTitleClass": "pf-c-tile__title",
|
||||||
|
"locales": "ca,cs,da,de,en,es,fr,fi,hu,it,ja,lt,nl,no,pl,pt-BR,ru,sk,sv,tr,zh-CN",
|
||||||
|
"serviceTitle": "CodeGouv",
|
||||||
|
"kcLogoIdP-openshift-v4": "pf-icon pf-icon-openshift",
|
||||||
|
"kcWebAuthnUnknownIcon": "pficon pficon-key unknown-transport-class",
|
||||||
|
"kcFormSocialAccountNameClass": "kc-social-provider-name",
|
||||||
|
"kcLogoIdP-openshift-v3": "pf-icon pf-icon-openshift",
|
||||||
|
"kcLoginOTPListInputClass": "pf-c-tile__input",
|
||||||
|
"kcWebAuthnUSB": "fa fa-usb",
|
||||||
|
"kcInputClassRadio": "pf-c-radio",
|
||||||
|
"kcWebAuthnKeyIcon": "pficon pficon-key",
|
||||||
|
"kcFeedbackInfoIcon": "fa fa-fw fa-info-circle",
|
||||||
|
"kcCommonLogoIdP": "kc-social-provider-logo kc-social-gray",
|
||||||
|
"stylesCommon":
|
||||||
|
"web_modules/@patternfly/react-core/dist/styles/base.css web_modules/@patternfly/react-core/dist/styles/app.css node_modules/patternfly/dist/css/patternfly.min.css node_modules/patternfly/dist/css/patternfly-additions.min.css lib/pficon/pficon.css",
|
||||||
|
"kcRecoveryCodesActions": "kc-recovery-codes-actions",
|
||||||
|
"kcFormGroupHeader": "pf-c-form__group",
|
||||||
|
"kcFormSocialAccountSectionClass": "kc-social-section kc-social-gray",
|
||||||
|
"kcLogoIdP-instagram": "fa fa-instagram",
|
||||||
|
"kcAlertClass": "pf-c-alert pf-m-inline",
|
||||||
|
"kcHeaderClass": "login-pf-page-header",
|
||||||
|
"kcLabelWrapperClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||||
|
"kcFormSocialAccountLinkClass": "pf-c-login__main-footer-links-item-link",
|
||||||
|
"kcLocaleMainClass": "pf-c-dropdown",
|
||||||
|
"kcTextareaClass": "form-control",
|
||||||
|
"kcButtonBlockClass": "pf-m-block",
|
||||||
|
"kcButtonClass": "pf-c-button",
|
||||||
|
"kcWebAuthnNFC": "fa fa-wifi",
|
||||||
|
"kcLocaleClass": "col-xs-12 col-sm-1",
|
||||||
|
"kcInputClassCheckboxInput": "pf-c-check__input",
|
||||||
|
"kcFeedbackErrorIcon": "fa fa-fw fa-exclamation-circle",
|
||||||
|
"kcInputLargeClass": "input-lg",
|
||||||
|
"kcInputErrorMessageClass": "pf-c-form__helper-text pf-m-error required kc-feedback-text",
|
||||||
|
"kcRecoveryCodesList": "kc-recovery-codes-list",
|
||||||
|
"kcFormSocialAccountListClass": "pf-c-login__main-footer-links kc-social-links",
|
||||||
|
"kcAlertTitleClass": "pf-c-alert__title kc-feedback-text",
|
||||||
|
"kcAuthenticatorPasswordClass": "fa fa-unlock list-view-pf-icon-lg",
|
||||||
|
"kcCheckInputClass": "pf-c-check__input",
|
||||||
|
"kcLogoIdP-linkedin": "fa fa-linkedin",
|
||||||
|
"kcLogoIdP-twitter": "fa fa-twitter",
|
||||||
|
"kcFeedbackWarningIcon": "fa fa-fw fa-exclamation-triangle",
|
||||||
|
"kcResetFlowIcon": "pficon pficon-arrow fa",
|
||||||
|
"kcSelectAuthListItemIconPropertyClass": "fa-2x select-auth-box-icon-properties",
|
||||||
|
"kcFeedbackSuccessIcon": "fa fa-fw fa-check-circle",
|
||||||
|
"kcLoginOTPListClass": "pf-c-tile",
|
||||||
|
"kcSrOnlyClass": "sr-only",
|
||||||
|
"kcFormSocialAccountListGridClass": "pf-l-grid kc-social-grid",
|
||||||
|
"kcButtonDefaultClass": "btn-default",
|
||||||
|
"kcFormGroupErrorClass": "has-error",
|
||||||
|
"kcSelectAuthListItemDescriptionClass": "pf-l-stack__item select-auth-box-desc",
|
||||||
|
"kcSelectAuthListItemBodyClass": "pf-l-split__item pf-l-stack",
|
||||||
|
"import": "common/keycloak",
|
||||||
|
"kcWebAuthnInternal": "pficon pficon-key",
|
||||||
|
"kcSelectAuthListItemArrowClass": "pf-l-split__item select-auth-box-arrow",
|
||||||
|
"kcCheckClass": "pf-c-check",
|
||||||
|
"kcContentClass": "col-sm-8 col-sm-offset-2 col-md-6 col-md-offset-3 col-lg-6 col-lg-offset-3",
|
||||||
|
"kcLogoClass": "login-pf-brand",
|
||||||
|
"kcLoginOTPListItemIconClass": "fa fa-mobile",
|
||||||
|
"kcLoginClass": "login-pf-page",
|
||||||
|
"kcSignUpClass": "login-pf-signup",
|
||||||
|
"kcButtonLargeClass": "btn-lg",
|
||||||
|
"kcFormCardClass": "card-pf",
|
||||||
|
"kcLocaleListClass": "pf-c-dropdown__menu pf-m-align-right",
|
||||||
|
"kcInputClass": "pf-c-form-control",
|
||||||
|
"kcFormGroupClass": "form-group",
|
||||||
|
"kcLogoIdP-paypal": "fa fa-paypal",
|
||||||
|
"kcInputClassCheckbox": "pf-c-check",
|
||||||
|
"kcRecoveryCodesConfirmation": "kc-recovery-codes-confirmation",
|
||||||
|
"kcInputClassRadioInput": "pf-c-radio__input",
|
||||||
|
"kcFormSocialAccountListButtonClass": "pf-c-button pf-m-control pf-m-block kc-social-item kc-social-gray",
|
||||||
|
"kcInputClassCheckboxLabel": "pf-c-check__label",
|
||||||
|
"kcFormOptionsClass": "col-xs-12 col-sm-12 col-md-12 col-lg-12",
|
||||||
|
"kcFormHeaderClass": "login-pf-header",
|
||||||
|
"kcFormSocialAccountGridItem": "pf-l-grid__item",
|
||||||
|
"kcButtonPrimaryClass": "pf-m-primary",
|
||||||
|
"kcInputHelperTextBeforeClass": "pf-c-form__helper-text pf-c-form__helper-text-before",
|
||||||
|
"kcLogoIdP-github": "fa fa-github",
|
||||||
|
"kcLabelClass": "pf-c-form__label pf-c-form__label-text"
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const loginUrl = {
|
const loginUrl = {
|
||||||
|
@ -67,6 +67,7 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
|
|||||||
|
|
||||||
// css classes for input
|
// css classes for input
|
||||||
"kcInputLargeClass": "input-lg",
|
"kcInputLargeClass": "input-lg",
|
||||||
|
"kcInputGroup": "pf-c-input-group",
|
||||||
|
|
||||||
// css classes for form accessability
|
// css classes for form accessability
|
||||||
"kcSrOnlyClass": "sr-only",
|
"kcSrOnlyClass": "sr-only",
|
||||||
@ -93,7 +94,7 @@ export const { useGetClassName } = createUseClassName<ClassKey>({
|
|||||||
"kcAuthenticatorWebAuthnPasswordlessClass": "fa fa-key list-view-pf-icon-lg",
|
"kcAuthenticatorWebAuthnPasswordlessClass": "fa fa-key list-view-pf-icon-lg",
|
||||||
|
|
||||||
//css classes for the OTP Login Form
|
//css classes for the OTP Login Form
|
||||||
"kcSelectOTPListClass": "card-pf card-pf-view card-pf-view-select card-pf-view-single-select",
|
"kcSelectOTPListClass": "card-pf card-pf-view card-pf-view-select card-pf-view-single-select col-xs-12",
|
||||||
"kcSelectOTPListItemClass": "card-pf-body card-pf-top-element",
|
"kcSelectOTPListItemClass": "card-pf-body card-pf-top-element",
|
||||||
"kcAuthenticatorOtpCircleClass": "fa fa-mobile card-pf-icon-circle",
|
"kcAuthenticatorOtpCircleClass": "fa fa-mobile card-pf-icon-circle",
|
||||||
"kcSelectOTPItemHeadingClass": "card-pf-title text-center",
|
"kcSelectOTPItemHeadingClass": "card-pf-title text-center",
|
||||||
|
@ -37,13 +37,10 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
|
|||||||
return (
|
return (
|
||||||
<Template
|
<Template
|
||||||
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
{...{ kcContext, i18n, doUseDefaultCss, classes }}
|
||||||
displayInfo={social.displayInfo}
|
displayInfo={realm.password && realm.registrationAllowed && !registrationDisabled}
|
||||||
displayWide={realm.password && social.providers !== undefined}
|
displayWide={realm.password && social.providers !== undefined}
|
||||||
headerNode={msg("doLogIn")}
|
headerNode={msg("doLogIn")}
|
||||||
infoNode={
|
infoNode={
|
||||||
realm.password &&
|
|
||||||
realm.registrationAllowed &&
|
|
||||||
!registrationDisabled && (
|
|
||||||
<div id="kc-registration">
|
<div id="kc-registration">
|
||||||
<span>
|
<span>
|
||||||
{msg("noAccount")}
|
{msg("noAccount")}
|
||||||
@ -52,7 +49,6 @@ export default function Login(props: PageProps<Extract<KcContext, { pageId: "log
|
|||||||
</a>
|
</a>
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
)
|
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div id="kc-form" className={clsx(realm.password && social.providers !== undefined && getClassName("kcContentWrapperClass"))}>
|
<div id="kc-form" className={clsx(realm.password && social.providers !== undefined && getClassName("kcContentWrapperClass"))}>
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import { I18n } from "../i18n";
|
import { I18n } from "../i18n";
|
||||||
import { KcContext } from "../kcContext";
|
import { KcContext } from "../kcContext";
|
||||||
import { useGetClassName } from "../lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
import { PageProps } from "./PageProps";
|
import { PageProps } from "./PageProps";
|
||||||
|
|
||||||
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth2-device-verify-user-code.ftl" }>, I18n>) {
|
export default function LoginOauthGrant(props: PageProps<Extract<KcContext, { pageId: "login-oauth2-device-verify-user-code.ftl" }>, I18n>) {
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { useEffect } from "react";
|
|
||||||
import { headInsert } from "keycloakify/tools/headInsert";
|
|
||||||
import { clsx } from "keycloakify/tools/clsx";
|
import { clsx } from "keycloakify/tools/clsx";
|
||||||
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
import type { PageProps } from "keycloakify/login/pages/PageProps";
|
||||||
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
import { useGetClassName } from "keycloakify/login/lib/useGetClassName";
|
||||||
@ -18,43 +16,38 @@ export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "
|
|||||||
|
|
||||||
const { msg, msgStr } = i18n;
|
const { msg, msgStr } = i18n;
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
let isCleanedUp = false;
|
|
||||||
|
|
||||||
const { prLoaded, remove } = headInsert({
|
|
||||||
"type": "javascript",
|
|
||||||
"src": `${kcContext.url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js`
|
|
||||||
});
|
|
||||||
|
|
||||||
(async () => {
|
|
||||||
await prLoaded;
|
|
||||||
|
|
||||||
if (isCleanedUp) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
evaluateInlineScript();
|
|
||||||
})();
|
|
||||||
|
|
||||||
return () => {
|
|
||||||
isCleanedUp = true;
|
|
||||||
remove();
|
|
||||||
};
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<>
|
||||||
|
<style>
|
||||||
|
{`
|
||||||
|
input[type="radio"]:checked~label.kcSelectOTPListClass{
|
||||||
|
border: 2px solid #39a5dc;
|
||||||
|
}`}
|
||||||
|
</style>
|
||||||
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("doLogIn")}>
|
<Template {...{ kcContext, i18n, doUseDefaultCss, classes }} headerNode={msg("doLogIn")}>
|
||||||
<form id="kc-otp-login-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
|
<form id="kc-otp-login-form" className={getClassName("kcFormClass")} action={url.loginAction} method="post">
|
||||||
{otpLogin.userOtpCredentials.length > 1 && (
|
{otpLogin.userOtpCredentials.length > 1 && (
|
||||||
<div className={getClassName("kcFormGroupClass")}>
|
<div className={getClassName("kcFormGroupClass")}>
|
||||||
<div className={getClassName("kcInputWrapperClass")}>
|
<div className={getClassName("kcInputWrapperClass")}>
|
||||||
{otpLogin.userOtpCredentials.map(otpCredential => (
|
{otpLogin.userOtpCredentials.map((otpCredential, index) => (
|
||||||
<div key={otpCredential.id} className={getClassName("kcSelectOTPListClass")}>
|
<div key={otpCredential.id}>
|
||||||
<input type="hidden" value="${otpCredential.id}" />
|
<input
|
||||||
|
id={`kc-otp-credential-${index}`}
|
||||||
|
name="selectedCredentialId"
|
||||||
|
type="radio"
|
||||||
|
value={otpCredential.id}
|
||||||
|
style={{ display: "none" }}
|
||||||
|
/>
|
||||||
|
<label
|
||||||
|
htmlFor={`kc-otp-credential-${index}`}
|
||||||
|
key={otpCredential.id}
|
||||||
|
className={getClassName("kcSelectOTPListClass")}
|
||||||
|
>
|
||||||
<div className={getClassName("kcSelectOTPListItemClass")}>
|
<div className={getClassName("kcSelectOTPListItemClass")}>
|
||||||
<span className={getClassName("kcAuthenticatorOtpCircleClass")} />
|
<span className={getClassName("kcAuthenticatorOtpCircleClass")} />
|
||||||
<h2 className={getClassName("kcSelectOTPItemHeadingClass")}>{otpCredential.userLabel}</h2>
|
<h2 className={getClassName("kcSelectOTPItemHeadingClass")}>{otpCredential.userLabel}</h2>
|
||||||
</div>
|
</div>
|
||||||
|
</label>
|
||||||
</div>
|
</div>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
@ -94,29 +87,6 @@ export default function LoginOtp(props: PageProps<Extract<KcContext, { pageId: "
|
|||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
</Template>
|
</Template>
|
||||||
|
</>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
import type { LazyExoticComponent } from "react";
|
|
||||||
import type { I18n } from "keycloakify/login/i18n";
|
import type { I18n } from "keycloakify/login/i18n";
|
||||||
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
|
import { type TemplateProps, type ClassKey } from "keycloakify/login/TemplateProps";
|
||||||
|
import type { LazyOrNot } from "keycloakify/tools/LazyOrNot";
|
||||||
|
import type { KcContext } from "keycloakify/account/kcContext";
|
||||||
|
|
||||||
export type PageProps<KcContext, I18nExtended extends I18n> = {
|
export type PageProps<NarowedKcContext = KcContext, I18nExtended extends I18n = I18n> = {
|
||||||
Template: LazyExoticComponent<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
Template: LazyOrNot<(props: TemplateProps<any, any>) => JSX.Element | null>;
|
||||||
kcContext: KcContext;
|
kcContext: NarowedKcContext;
|
||||||
i18n: I18nExtended;
|
i18n: I18nExtended;
|
||||||
doUseDefaultCss: boolean;
|
doUseDefaultCss: boolean;
|
||||||
classes?: Partial<Record<ClassKey, string>>;
|
classes?: Partial<Record<ClassKey, string>>;
|
||||||
|
3
src/tools/LazyOrNot.ts
Normal file
3
src/tools/LazyOrNot.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import type { LazyExoticComponent, ComponentType } from "react";
|
||||||
|
|
||||||
|
export type LazyOrNot<Component extends ComponentType<any>> = LazyExoticComponent<Component> | Component;
|
@ -14,7 +14,7 @@
|
|||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"allowSyntheticDefaultImports": true
|
"allowSyntheticDefaultImports": true
|
||||||
},
|
},
|
||||||
"exclude": ["./bin"],
|
"exclude": ["./bin", "./vite-plugin"],
|
||||||
"references": [
|
"references": [
|
||||||
{
|
{
|
||||||
"path": "./bin"
|
"path": "./bin"
|
||||||
|
1
src/vite-plugin/index.ts
Normal file
1
src/vite-plugin/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./vite-plugin";
|
18
src/vite-plugin/tsconfig.json
Normal file
18
src/vite-plugin/tsconfig.json
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
{
|
||||||
|
"extends": "../../tsproject.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"module": "CommonJS",
|
||||||
|
"target": "ES2019",
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"lib": ["es2019", "es2020.bigint", "es2020.string", "es2020.symbol.wellknown"],
|
||||||
|
"outDir": "../../dist/vite-plugin",
|
||||||
|
"rootDir": ".",
|
||||||
|
// https://github.com/vitejs/vite/issues/15112#issuecomment-1823908010
|
||||||
|
"skipLibCheck": true
|
||||||
|
},
|
||||||
|
"references": [
|
||||||
|
{
|
||||||
|
"path": "../bin"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
192
src/vite-plugin/vite-plugin.ts
Normal file
192
src/vite-plugin/vite-plugin.ts
Normal file
@ -0,0 +1,192 @@
|
|||||||
|
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
|
||||||
|
import type { Plugin } from "vite";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import {
|
||||||
|
resolvedViteConfigJsonBasename,
|
||||||
|
nameOfTheGlobal,
|
||||||
|
basenameOfTheKeycloakifyResourcesDir,
|
||||||
|
keycloak_resources,
|
||||||
|
keycloakifyBuildOptionsForPostPostBuildScriptEnvName
|
||||||
|
} from "../bin/constants";
|
||||||
|
import type { ResolvedViteConfig } from "../bin/keycloakify/buildOptions/resolvedViteConfig";
|
||||||
|
import { getCacheDirPath } from "../bin/keycloakify/buildOptions/getCacheDirPath";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
import { rm } from "../bin/tools/fs.rm";
|
||||||
|
import { copyKeycloakResourcesToPublic } from "../bin/copy-keycloak-resources-to-public";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import type { BuildOptions } from "../bin/keycloakify/buildOptions";
|
||||||
|
import type { UserProvidedBuildOptions } from "../bin/keycloakify/buildOptions/UserProvidedBuildOptions";
|
||||||
|
import MagicString from "magic-string";
|
||||||
|
|
||||||
|
export type Params = UserProvidedBuildOptions & {
|
||||||
|
postBuild?: (buildOptions: Omit<BuildOptions, "bundler">) => Promise<void>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export function keycloakify(params?: Params) {
|
||||||
|
const { postBuild, ...userProvidedBuildOptions } = params ?? {};
|
||||||
|
|
||||||
|
let reactAppRootDirPath: string | undefined = undefined;
|
||||||
|
let urlPathname: string | undefined = undefined;
|
||||||
|
let buildDirPath: string | undefined = undefined;
|
||||||
|
let command: "build" | "serve" | undefined = undefined;
|
||||||
|
let shouldGenerateSourcemap: boolean | undefined = undefined;
|
||||||
|
|
||||||
|
const plugin = {
|
||||||
|
"name": "keycloakify" as const,
|
||||||
|
"configResolved": async resolvedConfig => {
|
||||||
|
shouldGenerateSourcemap = resolvedConfig.build.sourcemap !== false;
|
||||||
|
|
||||||
|
run_post_build_script: {
|
||||||
|
const buildOptionJson = process.env[keycloakifyBuildOptionsForPostPostBuildScriptEnvName];
|
||||||
|
|
||||||
|
if (buildOptionJson === undefined) {
|
||||||
|
break run_post_build_script;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (postBuild === undefined) {
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
const buildOptions: BuildOptions = JSON.parse(buildOptionJson);
|
||||||
|
|
||||||
|
await postBuild(buildOptions);
|
||||||
|
|
||||||
|
process.exit(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
command = resolvedConfig.command;
|
||||||
|
|
||||||
|
reactAppRootDirPath = resolvedConfig.root;
|
||||||
|
urlPathname = (() => {
|
||||||
|
let out = resolvedConfig.env.BASE_URL;
|
||||||
|
|
||||||
|
if (out.startsWith(".") && command === "build" && resolvedConfig.envPrefix?.includes("STORYBOOK_") !== true) {
|
||||||
|
throw new Error(
|
||||||
|
[
|
||||||
|
`BASE_URL=${out} is not supported By Keycloakify. Use an absolute URL instead.`,
|
||||||
|
`If this is a problem, please open an issue at https://github.com/keycloakify/keycloakify/issues/new`
|
||||||
|
].join("\n")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (out === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out.startsWith("/")) {
|
||||||
|
out = "/" + out;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!out.endsWith("/")) {
|
||||||
|
out += "/";
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
})();
|
||||||
|
|
||||||
|
buildDirPath = pathJoin(reactAppRootDirPath, resolvedConfig.build.outDir);
|
||||||
|
|
||||||
|
const { cacheDirPath } = getCacheDirPath({
|
||||||
|
reactAppRootDirPath
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!fs.existsSync(cacheDirPath)) {
|
||||||
|
fs.mkdirSync(cacheDirPath, { "recursive": true });
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
pathJoin(cacheDirPath, resolvedViteConfigJsonBasename),
|
||||||
|
Buffer.from(
|
||||||
|
JSON.stringify(
|
||||||
|
id<ResolvedViteConfig>({
|
||||||
|
"publicDir": pathRelative(reactAppRootDirPath, resolvedConfig.publicDir),
|
||||||
|
"assetsDir": resolvedConfig.build.assetsDir,
|
||||||
|
"buildDir": resolvedConfig.build.outDir,
|
||||||
|
urlPathname,
|
||||||
|
userProvidedBuildOptions
|
||||||
|
}),
|
||||||
|
null,
|
||||||
|
2
|
||||||
|
),
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
await copyKeycloakResourcesToPublic({
|
||||||
|
"processArgv": ["--project", reactAppRootDirPath]
|
||||||
|
});
|
||||||
|
},
|
||||||
|
"transform": (code, id) => {
|
||||||
|
assert(command !== undefined);
|
||||||
|
assert(shouldGenerateSourcemap !== undefined);
|
||||||
|
|
||||||
|
if (command !== "build") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(reactAppRootDirPath !== undefined);
|
||||||
|
|
||||||
|
{
|
||||||
|
const isWithinSourceDirectory = id.startsWith(pathJoin(reactAppRootDirPath, "src") + pathSep);
|
||||||
|
|
||||||
|
if (!isWithinSourceDirectory) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx");
|
||||||
|
const isTypeScriptFile = id.endsWith(".ts") || id.endsWith(".tsx");
|
||||||
|
|
||||||
|
if (!isTypeScriptFile && !isJavascriptFile) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const transformedCode = new MagicString(code);
|
||||||
|
|
||||||
|
transformedCode.replaceAll(
|
||||||
|
/import\.meta\.env(?:(?:\.BASE_URL)|(?:\["BASE_URL"\]))/g,
|
||||||
|
[
|
||||||
|
`(`,
|
||||||
|
`(window.${nameOfTheGlobal} === undefined || import.meta.env.MODE === "development")?`,
|
||||||
|
`"${urlPathname ?? "/"}":`,
|
||||||
|
`(window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/")`,
|
||||||
|
`)`
|
||||||
|
].join("")
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!transformedCode.hasChanged()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!shouldGenerateSourcemap) {
|
||||||
|
return transformedCode.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
const map = transformedCode.generateMap({
|
||||||
|
"source": id,
|
||||||
|
"includeContent": true,
|
||||||
|
"hires": true
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
"code": transformedCode.toString(),
|
||||||
|
"map": map.toString()
|
||||||
|
};
|
||||||
|
},
|
||||||
|
"closeBundle": async () => {
|
||||||
|
assert(command !== undefined);
|
||||||
|
|
||||||
|
if (command !== "build") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert(buildDirPath !== undefined);
|
||||||
|
|
||||||
|
await rm(pathJoin(buildDirPath, keycloak_resources), { "recursive": true, "force": true });
|
||||||
|
}
|
||||||
|
} satisfies Plugin;
|
||||||
|
|
||||||
|
return plugin as any;
|
||||||
|
}
|
@ -1,107 +0,0 @@
|
|||||||
import { readPaths } from "keycloakify/bin/keycloakify/generateTheme/readStaticResourcesUsage";
|
|
||||||
import { same } from "evt/tools/inDepth/same";
|
|
||||||
import { expect, it, describe } from "vitest";
|
|
||||||
|
|
||||||
describe("Ensure it's able to extract used Keycloak resources", () => {
|
|
||||||
const expectedPaths = {
|
|
||||||
"resourcesCommonFilePaths": [
|
|
||||||
"node_modules/patternfly/dist/css/patternfly.min.css",
|
|
||||||
"node_modules/patternfly/dist/css/patternfly-additions.min.css",
|
|
||||||
"lib/zocial/zocial.css",
|
|
||||||
"node_modules/jquery/dist/jquery.min.js"
|
|
||||||
]
|
|
||||||
};
|
|
||||||
|
|
||||||
it("works with coding style n°1", () => {
|
|
||||||
const paths = readPaths({
|
|
||||||
"rawSourceFile": `
|
|
||||||
const { isReady } = usePrepareTemplate({
|
|
||||||
"doFetchDefaultThemeResources": doUseDefaultCss,
|
|
||||||
"styles": [
|
|
||||||
\`\${url.resourcesCommonPath}/node_modules/patternfly/dist/css/patternfly.min.css\`,
|
|
||||||
\`\${
|
|
||||||
url.resourcesCommonPath
|
|
||||||
}/node_modules/patternfly/dist/css/patternfly-additions.min.css\`,
|
|
||||||
\`\${resourcesCommonPath }/lib/zocial/zocial.css\`,
|
|
||||||
\`\${url.resourcesPath}/css/login.css\`
|
|
||||||
],
|
|
||||||
"htmlClassName": getClassName("kcHtmlClass"),
|
|
||||||
"bodyClassName": undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
const { prLoaded, remove } = headInsert({
|
|
||||||
"type": "javascript",
|
|
||||||
"src": \`\${kcContext.url.resourcesCommonPath}/node_modules/jquery/dist/jquery.min.js\`
|
|
||||||
});
|
|
||||||
|
|
||||||
`
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(same(paths, expectedPaths)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("works with coding style n°2", () => {
|
|
||||||
const paths = readPaths({
|
|
||||||
"rawSourceFile": `
|
|
||||||
|
|
||||||
const { isReady } = usePrepareTemplate({
|
|
||||||
"doFetchDefaultThemeResources": doUseDefaultCss,
|
|
||||||
"styles": [
|
|
||||||
url.resourcesCommonPath + "/node_modules/patternfly/dist/css/patternfly.min.css",
|
|
||||||
url.resourcesCommonPath + '/node_modules/patternfly/dist/css/patternfly-additions.min.css',
|
|
||||||
url.resourcesCommonPath
|
|
||||||
+ "/lib/zocial/zocial.css",
|
|
||||||
url.resourcesPath +
|
|
||||||
'/css/login.css'
|
|
||||||
],
|
|
||||||
"htmlClassName": getClassName("kcHtmlClass"),
|
|
||||||
"bodyClassName": undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
const { prLoaded, remove } = headInsert({
|
|
||||||
"type": "javascript",
|
|
||||||
"src": kcContext.url.resourcesCommonPath + "/node_modules/jquery/dist/jquery.min.js\"
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
`
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(paths);
|
|
||||||
console.log(expectedPaths);
|
|
||||||
|
|
||||||
expect(same(paths, expectedPaths)).toBe(true);
|
|
||||||
});
|
|
||||||
|
|
||||||
it("works with coding style n°3", () => {
|
|
||||||
const paths = readPaths({
|
|
||||||
"rawSourceFile": `
|
|
||||||
|
|
||||||
const { isReady } = usePrepareTemplate({
|
|
||||||
"doFetchDefaultThemeResources": doUseDefaultCss,
|
|
||||||
"styles": [
|
|
||||||
path.join(resourcesCommonPath,"/node_modules/patternfly/dist/css/patternfly.min.css"),
|
|
||||||
path.join(url.resourcesCommonPath, '/node_modules/patternfly/dist/css/patternfly-additions.min.css'),
|
|
||||||
path.join(url.resourcesCommonPath,
|
|
||||||
"/lib/zocial/zocial.css"),
|
|
||||||
pathJoin(
|
|
||||||
url.resourcesPath,
|
|
||||||
'css/login.css'
|
|
||||||
)
|
|
||||||
],
|
|
||||||
"htmlClassName": getClassName("kcHtmlClass"),
|
|
||||||
"bodyClassName": undefined
|
|
||||||
});
|
|
||||||
|
|
||||||
const { prLoaded, remove } = headInsert({
|
|
||||||
"type": "javascript",
|
|
||||||
"src": path.join(kcContext.url.resourcesCommonPath, "/node_modules/jquery/dist/jquery.min.js")
|
|
||||||
});
|
|
||||||
|
|
||||||
|
|
||||||
`
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(same(paths, expectedPaths)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
@ -1,389 +0,0 @@
|
|||||||
import { replaceImportsFromStaticInJsCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsFromStaticInJsCode";
|
|
||||||
import { generateCssCodeToDefineGlobals, replaceImportsInCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInCssCode";
|
|
||||||
import { replaceImportsInInlineCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInInlineCssCode";
|
|
||||||
import { same } from "evt/tools/inDepth/same";
|
|
||||||
import { expect, it, describe } from "vitest";
|
|
||||||
|
|
||||||
import { isSameCode } from "../tools/isSameCode";
|
|
||||||
|
|
||||||
describe("bin/js-transforms", () => {
|
|
||||||
const jsCodeUntransformed = `
|
|
||||||
function f() {
|
|
||||||
return a.p+"static/js/" + ({}[e] || e) + "." + {
|
|
||||||
3: "0664cdc0"
|
|
||||||
}[e] + ".chunk.js"
|
|
||||||
}
|
|
||||||
|
|
||||||
function sameAsF() {
|
|
||||||
return a.p+"static/js/" + ({}[e] || e) + "." + {
|
|
||||||
3: "0664cdc0"
|
|
||||||
}[e] + ".chunk.js"
|
|
||||||
}
|
|
||||||
|
|
||||||
__webpack_require__.u=function(e){return"static/js/" + e + "." + {
|
|
||||||
147: "6c5cee76",
|
|
||||||
787: "8da10fcf",
|
|
||||||
922: "be170a73"
|
|
||||||
} [e] + ".chunk.js"
|
|
||||||
}
|
|
||||||
|
|
||||||
t.miniCssF=function(e){return"static/css/"+e+"."+{
|
|
||||||
164:"dcfd7749",
|
|
||||||
908:"67c9ed2c"
|
|
||||||
}[e]+".chunk.css"
|
|
||||||
}
|
|
||||||
|
|
||||||
n.u=e=>"static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
|
|
||||||
|
|
||||||
t.miniCssF=e=>"static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
|
|
||||||
`;
|
|
||||||
it("transforms standalone code properly", () => {
|
|
||||||
const { fixedJsCode } = replaceImportsFromStaticInJsCode({
|
|
||||||
"jsCode": jsCodeUntransformed
|
|
||||||
});
|
|
||||||
|
|
||||||
const fixedJsCodeExpected = `
|
|
||||||
function f() {
|
|
||||||
return window.kcContext.url.resourcesPath + "/build/static/js/" + ({}[e] || e) + "." + {
|
|
||||||
3: "0664cdc0"
|
|
||||||
}[e] + ".chunk.js"
|
|
||||||
}
|
|
||||||
|
|
||||||
function sameAsF() {
|
|
||||||
return window.kcContext.url.resourcesPath + "/build/static/js/" + ({}[e] || e) + "." + {
|
|
||||||
3: "0664cdc0"
|
|
||||||
}[e] + ".chunk.js"
|
|
||||||
}
|
|
||||||
|
|
||||||
__webpack_require__[(function (){
|
|
||||||
var pd = Object.getOwnPropertyDescriptor(__webpack_require__, "p");
|
|
||||||
if( pd === undefined || pd.configurable ){
|
|
||||||
Object.defineProperty(__webpack_require__, "p", {
|
|
||||||
get: function() { return window.kcContext.url.resourcesPath; },
|
|
||||||
set: function() {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return "u";
|
|
||||||
})()] = function(e) {
|
|
||||||
return "/build/static/js/" + e + "." + {
|
|
||||||
147: "6c5cee76",
|
|
||||||
787: "8da10fcf",
|
|
||||||
922: "be170a73"
|
|
||||||
} [e] + ".chunk.js"
|
|
||||||
}
|
|
||||||
|
|
||||||
t[(function (){
|
|
||||||
var pd = Object.getOwnPropertyDescriptor(t, "p");
|
|
||||||
if( pd === undefined || pd.configurable ){
|
|
||||||
Object.defineProperty(t, "p", {
|
|
||||||
get: function() { return window.kcContext.url.resourcesPath; },
|
|
||||||
set: function() {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return "miniCssF";
|
|
||||||
})()] = function(e) {
|
|
||||||
return "/build/static/css/" + e + "." + {
|
|
||||||
164:"dcfd7749",
|
|
||||||
908:"67c9ed2c"
|
|
||||||
} [e] + ".chunk.css"
|
|
||||||
}
|
|
||||||
|
|
||||||
n[(function(){
|
|
||||||
var pd = Object.getOwnPropertyDescriptor(n, "p");
|
|
||||||
if( pd === undefined || pd.configurable ){
|
|
||||||
Object.defineProperty(n, "p", {
|
|
||||||
get: function() { return window.kcContext.url.resourcesPath; },
|
|
||||||
set: function() {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return "u";
|
|
||||||
})()] = e => "/build/static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
|
|
||||||
|
|
||||||
t[(function(){
|
|
||||||
var pd = Object.getOwnPropertyDescriptor(t, "p");
|
|
||||||
if( pd === undefined || pd.configurable ){
|
|
||||||
Object.defineProperty(t, "p", {
|
|
||||||
get: function() { return window.kcContext.url.resourcesPath; },
|
|
||||||
set: function() {}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return "miniCssF";
|
|
||||||
})()] = e => "/build/static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
|
|
||||||
`;
|
|
||||||
|
|
||||||
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("bin/css-transforms", () => {
|
|
||||||
it("transforms absolute urls to css globals properly with no urlPathname", () => {
|
|
||||||
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
|
|
||||||
"cssCode": `
|
|
||||||
.my-div {
|
|
||||||
background: url(/logo192.png) no-repeat center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-div2 {
|
|
||||||
background: url(/logo192.png) no-repeat center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-div {
|
|
||||||
background-image: url(/static/media/something.svg);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
});
|
|
||||||
|
|
||||||
const fixedCssCodeExpected = `
|
|
||||||
.my-div {
|
|
||||||
background: var(--url1f9ef5a892c104c);
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-div2 {
|
|
||||||
background: var(--url1f9ef5a892c104c);
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-div {
|
|
||||||
background-image: var(--urldd75cab58377c19);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
|
||||||
|
|
||||||
const cssGlobalsToDefineExpected = {
|
|
||||||
"url1f9ef5a892c104c": "url(/logo192.png) no-repeat center center",
|
|
||||||
"urldd75cab58377c19": "url(/static/media/something.svg)"
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(same(cssGlobalsToDefine, cssGlobalsToDefineExpected)).toBe(true);
|
|
||||||
|
|
||||||
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
|
|
||||||
cssGlobalsToDefine,
|
|
||||||
"buildOptions": {
|
|
||||||
"urlPathname": undefined
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const cssCodeToPrependInHeadExpected = `
|
|
||||||
:root {
|
|
||||||
--url1f9ef5a892c104c: url(\${url.resourcesPath}/build/logo192.png) no-repeat center center;
|
|
||||||
--urldd75cab58377c19: url(\${url.resourcesPath}/build/static/media/something.svg);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
expect(isSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected)).toBe(true);
|
|
||||||
});
|
|
||||||
it("transforms absolute urls to css globals properly with custom urlPathname", () => {
|
|
||||||
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
|
|
||||||
"cssCode": `
|
|
||||||
.my-div {
|
|
||||||
background: url(/x/y/z/logo192.png) no-repeat center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-div2 {
|
|
||||||
background: url(/x/y/z/logo192.png) no-repeat center center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-div {
|
|
||||||
background-image: url(/x/y/z/static/media/something.svg);
|
|
||||||
}
|
|
||||||
`
|
|
||||||
});
|
|
||||||
|
|
||||||
const fixedCssCodeExpected = `
|
|
||||||
.my-div {
|
|
||||||
background: var(--urlf8277cddaa2be78);
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-div2 {
|
|
||||||
background: var(--urlf8277cddaa2be78);
|
|
||||||
}
|
|
||||||
|
|
||||||
.my-div {
|
|
||||||
background-image: var(--url8bdc0887b97ac9a);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
|
||||||
|
|
||||||
const cssGlobalsToDefineExpected = {
|
|
||||||
"urlf8277cddaa2be78": "url(/x/y/z/logo192.png) no-repeat center center",
|
|
||||||
"url8bdc0887b97ac9a": "url(/x/y/z/static/media/something.svg)"
|
|
||||||
};
|
|
||||||
|
|
||||||
expect(same(cssGlobalsToDefine, cssGlobalsToDefineExpected)).toBe(true);
|
|
||||||
|
|
||||||
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
|
|
||||||
cssGlobalsToDefine,
|
|
||||||
"buildOptions": {
|
|
||||||
"urlPathname": "/x/y/z/"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const cssCodeToPrependInHeadExpected = `
|
|
||||||
:root {
|
|
||||||
--urlf8277cddaa2be78: url(\${url.resourcesPath}/build/logo192.png) no-repeat center center;
|
|
||||||
--url8bdc0887b97ac9a: url(\${url.resourcesPath}/build/static/media/something.svg);
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
expect(isSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("bin/css-inline-transforms", () => {
|
|
||||||
describe("no url pathName", () => {
|
|
||||||
const cssCode = `
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
it("transforms css for standalone app properly", () => {
|
|
||||||
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
|
||||||
cssCode,
|
|
||||||
"buildOptions": {
|
|
||||||
"urlPathname": undefined
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const fixedCssCodeExpected = `
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-regular-webfont.woff2)
|
|
||||||
format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-medium-webfont.woff2)
|
|
||||||
format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-semibold-webfont.woff2)
|
|
||||||
format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-bold-webfont.woff2)
|
|
||||||
format("woff2");
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe("with url pathName", () => {
|
|
||||||
const cssCode = `
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/x/y/z/fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/x/y/z/fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/x/y/z/fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url("/x/y/z/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
it("transforms css for standalone app properly", () => {
|
|
||||||
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
|
||||||
cssCode,
|
|
||||||
"buildOptions": {
|
|
||||||
"urlPathname": "/x/y/z/"
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const fixedCssCodeExpected = `
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 400;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-regular-webfont.woff2)
|
|
||||||
format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 500;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-medium-webfont.woff2)
|
|
||||||
format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 600;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-semibold-webfont.woff2)
|
|
||||||
format("woff2");
|
|
||||||
}
|
|
||||||
@font-face {
|
|
||||||
font-family: "Work Sans";
|
|
||||||
font-style: normal;
|
|
||||||
font-weight: 700;
|
|
||||||
font-display: swap;
|
|
||||||
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-bold-webfont.woff2)
|
|
||||||
format("woff2");
|
|
||||||
}
|
|
||||||
`;
|
|
||||||
|
|
||||||
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
637
test/bin/replacers.spec.ts
Normal file
637
test/bin/replacers.spec.ts
Normal file
@ -0,0 +1,637 @@
|
|||||||
|
import { replaceImportsInJsCode_vite } from "keycloakify/bin/keycloakify/replacers/replaceImportsInJsCode/vite";
|
||||||
|
import { replaceImportsInJsCode_webpack } from "keycloakify/bin/keycloakify/replacers/replaceImportsInJsCode/webpack";
|
||||||
|
import { generateCssCodeToDefineGlobals, replaceImportsInCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInCssCode";
|
||||||
|
import { replaceImportsInInlineCssCode } from "keycloakify/bin/keycloakify/replacers/replaceImportsInInlineCssCode";
|
||||||
|
import { same } from "evt/tools/inDepth/same";
|
||||||
|
import { expect, it, describe } from "vitest";
|
||||||
|
import { isSameCode } from "../tools/isSameCode";
|
||||||
|
import { basenameOfTheKeycloakifyResourcesDir, nameOfTheGlobal } from "keycloakify/bin/constants";
|
||||||
|
|
||||||
|
describe("js replacer - vite", () => {
|
||||||
|
it("replaceImportsInJsCode_vite - 1", () => {
|
||||||
|
const before = `Uv="modulepreload",`;
|
||||||
|
const after = `,Wc={},`;
|
||||||
|
const jsCodeUntransformed = `${before}Hv=function(e){return"/foo-bar-baz/"+e}${after}`;
|
||||||
|
|
||||||
|
const { fixedJsCode } = replaceImportsInJsCode_vite({
|
||||||
|
"jsCode": jsCodeUntransformed,
|
||||||
|
"basenameOfAssetsFiles": [],
|
||||||
|
"buildOptions": {
|
||||||
|
"reactAppBuildDirPath": "/Users/someone/github/keycloakify-starter/dist/",
|
||||||
|
"assetsDirPath": "/Users/someone/github/keycloakify-starter/dist/assets/",
|
||||||
|
"urlPathname": "/foo-bar-baz/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedJsCodeExpected = `${before}Hv=function(e){return"/"+e}${after}`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaceImportsInJsCode_vite - 2", () => {
|
||||||
|
const before = `Uv="modulepreload",`;
|
||||||
|
const after = `,Wc={},`;
|
||||||
|
const jsCodeUntransformed = `${before}Hv=function(e){return"/foo/bar/baz/"+e}${after}`;
|
||||||
|
|
||||||
|
const { fixedJsCode } = replaceImportsInJsCode_vite({
|
||||||
|
"jsCode": jsCodeUntransformed,
|
||||||
|
"basenameOfAssetsFiles": [],
|
||||||
|
"buildOptions": {
|
||||||
|
"reactAppBuildDirPath": "/Users/someone/github/keycloakify-starter/dist/",
|
||||||
|
"assetsDirPath": "/Users/someone/github/keycloakify-starter/dist/assets/",
|
||||||
|
"urlPathname": "/foo/bar/baz/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedJsCodeExpected = `${before}Hv=function(e){return"/"+e}${after}`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaceImportsInJsCode_vite - 3", () => {
|
||||||
|
const jsCodeUntransformed = `
|
||||||
|
S="/assets/keycloakify-logo-mqjydaoZ.png",H=(()=>{
|
||||||
|
|
||||||
|
function __vite__mapDeps(indexes) {
|
||||||
|
if (!__vite__mapDeps.viteFileDeps) {
|
||||||
|
__vite__mapDeps.viteFileDeps = ["assets/Login-dJpPRzM4.js", "assets/index-XwzrZ5Gu.js"]
|
||||||
|
}
|
||||||
|
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (const { reactAppBuildDirPath, assetsDirPath, systemType } of [
|
||||||
|
{
|
||||||
|
"systemType": "posix",
|
||||||
|
"reactAppBuildDirPath": "/Users/someone/github/keycloakify-starter/dist",
|
||||||
|
"assetsDirPath": "/Users/someone/github/keycloakify-starter/dist/assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"systemType": "win32",
|
||||||
|
"reactAppBuildDirPath": "C:\\\\Users\\someone\\github\\keycloakify-starter\\dist",
|
||||||
|
"assetsDirPath": "C:\\\\Users\\someone\\github\\keycloakify-starter\\dist\\assets"
|
||||||
|
}
|
||||||
|
] as const) {
|
||||||
|
const { fixedJsCode } = replaceImportsInJsCode_vite({
|
||||||
|
"jsCode": jsCodeUntransformed,
|
||||||
|
"basenameOfAssetsFiles": ["Login-dJpPRzM4.js", "index-XwzrZ5Gu.js", "keycloakify-logo-mqjydaoZ.png"],
|
||||||
|
"buildOptions": {
|
||||||
|
reactAppBuildDirPath,
|
||||||
|
assetsDirPath,
|
||||||
|
"urlPathname": undefined
|
||||||
|
},
|
||||||
|
systemType
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedJsCodeExpected = `
|
||||||
|
S=(window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{
|
||||||
|
|
||||||
|
function __vite__mapDeps(indexes) {
|
||||||
|
if (!__vite__mapDeps.viteFileDeps) {
|
||||||
|
__vite__mapDeps.viteFileDeps = [
|
||||||
|
(window.${nameOfTheGlobal}.url.resourcesPath.substring(1) + "/${basenameOfTheKeycloakifyResourcesDir}/assets/Login-dJpPRzM4.js"),
|
||||||
|
(window.${nameOfTheGlobal}.url.resourcesPath.substring(1) + "/${basenameOfTheKeycloakifyResourcesDir}/assets/index-XwzrZ5Gu.js")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaceImportsInJsCode_vite - 4", () => {
|
||||||
|
const jsCodeUntransformed = `
|
||||||
|
S="/foo/bar/keycloakify-logo-mqjydaoZ.png",H=(()=>{
|
||||||
|
|
||||||
|
function __vite__mapDeps(indexes) {
|
||||||
|
if (!__vite__mapDeps.viteFileDeps) {
|
||||||
|
__vite__mapDeps.viteFileDeps = ["foo/bar/Login-dJpPRzM4.js", "foo/bar/index-XwzrZ5Gu.js"]
|
||||||
|
}
|
||||||
|
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (const { reactAppBuildDirPath, assetsDirPath, systemType } of [
|
||||||
|
{
|
||||||
|
"systemType": "posix",
|
||||||
|
"reactAppBuildDirPath": "/Users/someone/github/keycloakify-starter/dist",
|
||||||
|
"assetsDirPath": "/Users/someone/github/keycloakify-starter/dist/foo/bar"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"systemType": "win32",
|
||||||
|
"reactAppBuildDirPath": "C:\\\\Users\\someone\\github\\keycloakify-starter\\dist",
|
||||||
|
"assetsDirPath": "C:\\\\Users\\someone\\github\\keycloakify-starter\\dist\\foo\\bar"
|
||||||
|
}
|
||||||
|
] as const) {
|
||||||
|
const { fixedJsCode } = replaceImportsInJsCode_vite({
|
||||||
|
"jsCode": jsCodeUntransformed,
|
||||||
|
"basenameOfAssetsFiles": ["Login-dJpPRzM4.js", "index-XwzrZ5Gu.js", "keycloakify-logo-mqjydaoZ.png"],
|
||||||
|
"buildOptions": {
|
||||||
|
reactAppBuildDirPath,
|
||||||
|
assetsDirPath,
|
||||||
|
"urlPathname": undefined
|
||||||
|
},
|
||||||
|
systemType
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedJsCodeExpected = `
|
||||||
|
S=(window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/foo/bar/keycloakify-logo-mqjydaoZ.png"),H=(()=>{
|
||||||
|
|
||||||
|
function __vite__mapDeps(indexes) {
|
||||||
|
if (!__vite__mapDeps.viteFileDeps) {
|
||||||
|
__vite__mapDeps.viteFileDeps = [
|
||||||
|
(window.${nameOfTheGlobal}.url.resourcesPath.substring(1) + "/${basenameOfTheKeycloakifyResourcesDir}/foo/bar/Login-dJpPRzM4.js"),
|
||||||
|
(window.${nameOfTheGlobal}.url.resourcesPath.substring(1) + "/${basenameOfTheKeycloakifyResourcesDir}/foo/bar/index-XwzrZ5Gu.js")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaceImportsInJsCode_vite - 5", () => {
|
||||||
|
const jsCodeUntransformed = `
|
||||||
|
S="/foo-bar-baz/assets/keycloakify-logo-mqjydaoZ.png",H=(()=>{
|
||||||
|
|
||||||
|
function __vite__mapDeps(indexes) {
|
||||||
|
if (!__vite__mapDeps.viteFileDeps) {
|
||||||
|
__vite__mapDeps.viteFileDeps = ["assets/Login-dJpPRzM4.js", "assets/index-XwzrZ5Gu.js"]
|
||||||
|
}
|
||||||
|
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
for (const { reactAppBuildDirPath, assetsDirPath, systemType } of [
|
||||||
|
{
|
||||||
|
"systemType": "posix",
|
||||||
|
"reactAppBuildDirPath": "/Users/someone/github/keycloakify-starter/dist",
|
||||||
|
"assetsDirPath": "/Users/someone/github/keycloakify-starter/dist/assets"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"systemType": "win32",
|
||||||
|
"reactAppBuildDirPath": "C:\\\\Users\\someone\\github\\keycloakify-starter\\dist",
|
||||||
|
"assetsDirPath": "C:\\\\Users\\someone\\github\\keycloakify-starter\\dist\\assets"
|
||||||
|
}
|
||||||
|
] as const) {
|
||||||
|
const { fixedJsCode } = replaceImportsInJsCode_vite({
|
||||||
|
"jsCode": jsCodeUntransformed,
|
||||||
|
"basenameOfAssetsFiles": ["Login-dJpPRzM4.js", "index-XwzrZ5Gu.js", "keycloakify-logo-mqjydaoZ.png"],
|
||||||
|
"buildOptions": {
|
||||||
|
reactAppBuildDirPath,
|
||||||
|
assetsDirPath,
|
||||||
|
"urlPathname": "/foo-bar-baz/"
|
||||||
|
},
|
||||||
|
systemType
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedJsCodeExpected = `
|
||||||
|
S=(window.${nameOfTheGlobal}.url.resourcesPath + "/${basenameOfTheKeycloakifyResourcesDir}/assets/keycloakify-logo-mqjydaoZ.png"),H=(()=>{
|
||||||
|
|
||||||
|
function __vite__mapDeps(indexes) {
|
||||||
|
if (!__vite__mapDeps.viteFileDeps) {
|
||||||
|
__vite__mapDeps.viteFileDeps = [
|
||||||
|
(window.${nameOfTheGlobal}.url.resourcesPath.substring(1) + "/${basenameOfTheKeycloakifyResourcesDir}/assets/Login-dJpPRzM4.js"),
|
||||||
|
(window.${nameOfTheGlobal}.url.resourcesPath.substring(1) + "/${basenameOfTheKeycloakifyResourcesDir}/assets/index-XwzrZ5Gu.js")
|
||||||
|
]
|
||||||
|
}
|
||||||
|
return indexes.map((i) => __vite__mapDeps.viteFileDeps[i])
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("js replacer - webpack", () => {
|
||||||
|
it("replaceImportsInJsCode_webpack - 1", () => {
|
||||||
|
const jsCodeUntransformed = `
|
||||||
|
function f() {
|
||||||
|
return a.p+"static/js/" + ({}[e] || e) + "." + {
|
||||||
|
3: "0664cdc0"
|
||||||
|
}[e] + ".chunk.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameAsF() {
|
||||||
|
return a.p+"static/js/" + ({}[e] || e) + "." + {
|
||||||
|
3: "0664cdc0"
|
||||||
|
}[e] + ".chunk.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
__webpack_require__.u=function(e){return"static/js/" + e + "." + {
|
||||||
|
147: "6c5cee76",
|
||||||
|
787: "8da10fcf",
|
||||||
|
922: "be170a73"
|
||||||
|
} [e] + ".chunk.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
t.miniCssF=function(e){return"static/css/"+e+"."+{
|
||||||
|
164:"dcfd7749",
|
||||||
|
908:"67c9ed2c"
|
||||||
|
}[e]+".chunk.css"
|
||||||
|
}
|
||||||
|
|
||||||
|
n.u=e=>"static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
|
||||||
|
|
||||||
|
t.miniCssF=e=>"static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
|
||||||
|
`;
|
||||||
|
|
||||||
|
const { fixedJsCode } = replaceImportsInJsCode_webpack({
|
||||||
|
"jsCode": jsCodeUntransformed,
|
||||||
|
"buildOptions": {
|
||||||
|
"reactAppBuildDirPath": "/Users/someone/github/keycloakify-starter/build",
|
||||||
|
"assetsDirPath": "/Users/someone/github/keycloakify-starter/build/static",
|
||||||
|
"urlPathname": undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedJsCodeExpected = `
|
||||||
|
function f() {
|
||||||
|
return window.kcContext.url.resourcesPath + "/build/static/js/" + ({}[e] || e) + "." + {
|
||||||
|
3: "0664cdc0"
|
||||||
|
}[e] + ".chunk.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
function sameAsF() {
|
||||||
|
return window.kcContext.url.resourcesPath + "/build/static/js/" + ({}[e] || e) + "." + {
|
||||||
|
3: "0664cdc0"
|
||||||
|
}[e] + ".chunk.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
__webpack_require__[(function (){
|
||||||
|
var pd = Object.getOwnPropertyDescriptor(__webpack_require__, "p");
|
||||||
|
if( pd === undefined || pd.configurable ){
|
||||||
|
Object.defineProperty(__webpack_require__, "p", {
|
||||||
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
|
set: function() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "u";
|
||||||
|
})()] = function(e) {
|
||||||
|
return "/build/static/js/" + e + "." + {
|
||||||
|
147: "6c5cee76",
|
||||||
|
787: "8da10fcf",
|
||||||
|
922: "be170a73"
|
||||||
|
} [e] + ".chunk.js"
|
||||||
|
}
|
||||||
|
|
||||||
|
t[(function (){
|
||||||
|
var pd = Object.getOwnPropertyDescriptor(t, "p");
|
||||||
|
if( pd === undefined || pd.configurable ){
|
||||||
|
Object.defineProperty(t, "p", {
|
||||||
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
|
set: function() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "miniCssF";
|
||||||
|
})()] = function(e) {
|
||||||
|
return "/build/static/css/" + e + "." + {
|
||||||
|
164:"dcfd7749",
|
||||||
|
908:"67c9ed2c"
|
||||||
|
} [e] + ".chunk.css"
|
||||||
|
}
|
||||||
|
|
||||||
|
n[(function(){
|
||||||
|
var pd = Object.getOwnPropertyDescriptor(n, "p");
|
||||||
|
if( pd === undefined || pd.configurable ){
|
||||||
|
Object.defineProperty(n, "p", {
|
||||||
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
|
set: function() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "u";
|
||||||
|
})()] = e => "/build/static/js/"+e+"."+{69:"4f205f87",128:"49264537",453:"b2fed72e",482:"f0106901"}[e]+".chunk.js"
|
||||||
|
|
||||||
|
t[(function(){
|
||||||
|
var pd = Object.getOwnPropertyDescriptor(t, "p");
|
||||||
|
if( pd === undefined || pd.configurable ){
|
||||||
|
Object.defineProperty(t, "p", {
|
||||||
|
get: function() { return window.kcContext.url.resourcesPath; },
|
||||||
|
set: function() {}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return "miniCssF";
|
||||||
|
})()] = e => "/build/static/css/"+e+"."+{164:"dcfd7749",908:"67c9ed2c"}[e]+".chunk.css"
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaceImportsInJsCode_webpack - 2", () => {
|
||||||
|
const before = `"__esModule",{value:!0})}`;
|
||||||
|
const after = `function(){if("undefined"`;
|
||||||
|
|
||||||
|
const jsCodeUntransformed = `${before},n.p="/foo-bar/",${after}`;
|
||||||
|
|
||||||
|
const { fixedJsCode } = replaceImportsInJsCode_webpack({
|
||||||
|
"jsCode": jsCodeUntransformed,
|
||||||
|
"buildOptions": {
|
||||||
|
"reactAppBuildDirPath": "/Users/someone/github/keycloakify-starter/build",
|
||||||
|
"assetsDirPath": "/Users/someone/github/keycloakify-starter/build/static",
|
||||||
|
"urlPathname": "/foo-bar/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedJsCodeExpected = `${before},n.p="/",${after}`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it("replaceImportsInJsCode_webpack - 3", () => {
|
||||||
|
const before = `"__esModule",{value:!0})}`;
|
||||||
|
const after = `function(){if("undefined"`;
|
||||||
|
|
||||||
|
const jsCodeUntransformed = `${before},n.p="/foo/bar/",${after}`;
|
||||||
|
|
||||||
|
const { fixedJsCode } = replaceImportsInJsCode_webpack({
|
||||||
|
"jsCode": jsCodeUntransformed,
|
||||||
|
"buildOptions": {
|
||||||
|
"reactAppBuildDirPath": "/Users/someone/github/keycloakify-starter/build",
|
||||||
|
"assetsDirPath": "/Users/someone/github/keycloakify-starter/dist/build/static",
|
||||||
|
"urlPathname": "/foo/bar/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedJsCodeExpected = `${before},n.p="/",${after}`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedJsCode, fixedJsCodeExpected)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("css replacer", () => {
|
||||||
|
it("transforms absolute urls to css globals properly with no urlPathname", () => {
|
||||||
|
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
|
||||||
|
"cssCode": `
|
||||||
|
.my-div {
|
||||||
|
background: url(/logo192.png) no-repeat center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-div2 {
|
||||||
|
background: url(/logo192.png) repeat center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-div {
|
||||||
|
background-image: url(/static/media/something.svg);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedCssCodeExpected = `
|
||||||
|
.my-div {
|
||||||
|
background: var(--urla882a969fd39473) no-repeat center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-div2 {
|
||||||
|
background: var(--urla882a969fd39473) repeat center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-div {
|
||||||
|
background-image: var(--urldd75cab58377c19);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
|
|
||||||
|
const cssGlobalsToDefineExpected = {
|
||||||
|
"urla882a969fd39473": "url(/logo192.png)",
|
||||||
|
"urldd75cab58377c19": "url(/static/media/something.svg)"
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(same(cssGlobalsToDefine, cssGlobalsToDefineExpected)).toBe(true);
|
||||||
|
|
||||||
|
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
|
||||||
|
cssGlobalsToDefine,
|
||||||
|
"buildOptions": {
|
||||||
|
"urlPathname": undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cssCodeToPrependInHeadExpected = `
|
||||||
|
:root {
|
||||||
|
--urla882a969fd39473: url(\${url.resourcesPath}/build/logo192.png);
|
||||||
|
--urldd75cab58377c19: url(\${url.resourcesPath}/build/static/media/something.svg);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected)).toBe(true);
|
||||||
|
});
|
||||||
|
it("transforms absolute urls to css globals properly with custom urlPathname", () => {
|
||||||
|
const { fixedCssCode, cssGlobalsToDefine } = replaceImportsInCssCode({
|
||||||
|
"cssCode": `
|
||||||
|
.my-div {
|
||||||
|
background: url(/x/y/z/logo192.png) no-repeat center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-div2 {
|
||||||
|
background: url(/x/y/z/logo192.png) no-repeat center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-div {
|
||||||
|
background-image: url(/x/y/z/static/media/something.svg);
|
||||||
|
}
|
||||||
|
`
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedCssCodeExpected = `
|
||||||
|
.my-div {
|
||||||
|
background: var(--url749a3139386b2c8) no-repeat center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-div2 {
|
||||||
|
background: var(--url749a3139386b2c8) no-repeat center center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.my-div {
|
||||||
|
background-image: var(--url8bdc0887b97ac9a);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
|
|
||||||
|
const cssGlobalsToDefineExpected = {
|
||||||
|
"url749a3139386b2c8": "url(/x/y/z/logo192.png)",
|
||||||
|
"url8bdc0887b97ac9a": "url(/x/y/z/static/media/something.svg)"
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(same(cssGlobalsToDefine, cssGlobalsToDefineExpected)).toBe(true);
|
||||||
|
|
||||||
|
const { cssCodeToPrependInHead } = generateCssCodeToDefineGlobals({
|
||||||
|
cssGlobalsToDefine,
|
||||||
|
"buildOptions": {
|
||||||
|
"urlPathname": "/x/y/z/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const cssCodeToPrependInHeadExpected = `
|
||||||
|
:root {
|
||||||
|
--url749a3139386b2c8: url(\${url.resourcesPath}/build/logo192.png);
|
||||||
|
--url8bdc0887b97ac9a: url(\${url.resourcesPath}/build/static/media/something.svg);
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(cssCodeToPrependInHead, cssCodeToPrependInHeadExpected)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("inline css replacer", () => {
|
||||||
|
describe("no url pathName", () => {
|
||||||
|
const cssCode = `
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
it("transforms css for standalone app properly", () => {
|
||||||
|
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
||||||
|
cssCode,
|
||||||
|
"buildOptions": {
|
||||||
|
"urlPathname": undefined
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedCssCodeExpected = `
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-regular-webfont.woff2)
|
||||||
|
format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-medium-webfont.woff2)
|
||||||
|
format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-semibold-webfont.woff2)
|
||||||
|
format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-bold-webfont.woff2)
|
||||||
|
format("woff2");
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe("with url pathName", () => {
|
||||||
|
const cssCode = `
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/x/y/z/fonts/WorkSans/worksans-regular-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/x/y/z/fonts/WorkSans/worksans-medium-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/x/y/z/fonts/WorkSans/worksans-semibold-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url("/x/y/z/fonts/WorkSans/worksans-bold-webfont.woff2") format("woff2");
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
it("transforms css for standalone app properly", () => {
|
||||||
|
const { fixedCssCode } = replaceImportsInInlineCssCode({
|
||||||
|
cssCode,
|
||||||
|
"buildOptions": {
|
||||||
|
"urlPathname": "/x/y/z/"
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const fixedCssCodeExpected = `
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 400;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-regular-webfont.woff2)
|
||||||
|
format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 500;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-medium-webfont.woff2)
|
||||||
|
format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 600;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-semibold-webfont.woff2)
|
||||||
|
format("woff2");
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: "Work Sans";
|
||||||
|
font-style: normal;
|
||||||
|
font-weight: 700;
|
||||||
|
font-display: swap;
|
||||||
|
src: url(\${url.resourcesPath}/build/fonts/WorkSans/worksans-bold-webfont.woff2)
|
||||||
|
format("woff2");
|
||||||
|
}
|
||||||
|
`;
|
||||||
|
|
||||||
|
expect(isSameCode(fixedCssCode, fixedCssCodeExpected)).toBe(true);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,9 +1,16 @@
|
|||||||
import { downloadAndUnzip } from "keycloakify/bin/tools/downloadAndUnzip";
|
import { downloadAndUnzip } from "keycloakify/bin/downloadAndUnzip";
|
||||||
|
import { join as pathJoin } from "path";
|
||||||
|
import { getThisCodebaseRootDirPath } from "keycloakify/bin/tools/getThisCodebaseRootDirPath";
|
||||||
|
|
||||||
export async function setupSampleReactProject(destDirPath: string) {
|
export async function setupSampleReactProject(destDirPath: string) {
|
||||||
|
const thisCodebaseRootDirPath = getThisCodebaseRootDirPath();
|
||||||
|
|
||||||
await downloadAndUnzip({
|
await downloadAndUnzip({
|
||||||
"url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
"url": "https://github.com/keycloakify/keycloakify/releases/download/v0.0.1/sample_build_dir_and_package_json.zip",
|
||||||
"destDirPath": destDirPath,
|
"destDirPath": destDirPath,
|
||||||
"doUseCache": false
|
"buildOptions": {
|
||||||
|
"cacheDirPath": pathJoin(thisCodebaseRootDirPath, "node_modules", ".cache", "keycloakify"),
|
||||||
|
"npmWorkspaceRootDirPath": thisCodebaseRootDirPath
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -4,20 +4,17 @@
|
|||||||
"target": "es5",
|
"target": "es5",
|
||||||
"lib": ["es2015", "DOM", "ES2019.Object"],
|
"lib": ["es2015", "DOM", "ES2019.Object"],
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"declaration": true,
|
|
||||||
"outDir": "../dist_test",
|
|
||||||
"sourceMap": true,
|
|
||||||
"newLine": "LF",
|
|
||||||
"noUnusedLocals": true,
|
"noUnusedLocals": true,
|
||||||
"noUnusedParameters": true,
|
"noUnusedParameters": true,
|
||||||
"incremental": false,
|
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"downlevelIteration": true,
|
"downlevelIteration": true,
|
||||||
"jsx": "react-jsx",
|
"jsx": "react-jsx",
|
||||||
"noFallthroughCasesInSwitch": true,
|
"noFallthroughCasesInSwitch": true,
|
||||||
"paths": {
|
"paths": {
|
||||||
"keycloakify/*": ["../src/*"]
|
"keycloakify/*": ["../src/*"]
|
||||||
}
|
},
|
||||||
|
// https://github.com/vitejs/vite/issues/15112#issuecomment-1823908010
|
||||||
|
"skipLibCheck": true
|
||||||
},
|
},
|
||||||
"include": ["../src", "."]
|
"include": ["../src", "."]
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
/// <reference types="vitest" />
|
import { defineConfig } from "vitest/config";
|
||||||
import { defineConfig } from "vite";
|
import { resolve as pathResolve } from "path";
|
||||||
import path from "path";
|
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
"test": {
|
"test": {
|
||||||
"alias": {
|
"alias": {
|
||||||
"keycloakify": path.resolve(__dirname, "./src")
|
"keycloakify": pathResolve(__dirname, "./src")
|
||||||
},
|
},
|
||||||
"watchExclude": ["**/node_modules/**", "**/dist/**", "**/sample_react_project/**"]
|
"watchExclude": ["**/node_modules/**", "**/dist/**", "**/sample_react_project/**"]
|
||||||
}
|
}
|
||||||
|
275
yarn.lock
275
yarn.lock
@ -1332,116 +1332,231 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb"
|
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.3.0.tgz#ea89004119dc42db2e1dba0f97d553f7372f6fcb"
|
||||||
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==
|
integrity sha512-AHPmaAx+RYfZz0eYu6Gviiagpmiyw98ySSlQvCUhVGDRtDFe4DBS0x1bSjdF3gqUDYOczB+yYvBTtEylYSdRhg==
|
||||||
|
|
||||||
|
"@esbuild/aix-ppc64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz#d1bc06aedb6936b3b6d313bf809a5a40387d2b7f"
|
||||||
|
integrity sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==
|
||||||
|
|
||||||
"@esbuild/android-arm64@0.17.17":
|
"@esbuild/android-arm64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.17.tgz#164b054d58551f8856285f386e1a8f45d9ba3a31"
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.17.17.tgz#164b054d58551f8856285f386e1a8f45d9ba3a31"
|
||||||
integrity sha512-jaJ5IlmaDLFPNttv0ofcwy/cfeY4bh/n705Tgh+eLObbGtQBK3EPAu+CzL95JVE4nFAliyrnEu0d32Q5foavqg==
|
integrity sha512-jaJ5IlmaDLFPNttv0ofcwy/cfeY4bh/n705Tgh+eLObbGtQBK3EPAu+CzL95JVE4nFAliyrnEu0d32Q5foavqg==
|
||||||
|
|
||||||
|
"@esbuild/android-arm64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz#7ad65a36cfdb7e0d429c353e00f680d737c2aed4"
|
||||||
|
integrity sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==
|
||||||
|
|
||||||
"@esbuild/android-arm@0.17.17":
|
"@esbuild/android-arm@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.17.tgz#1b3b5a702a69b88deef342a7a80df4c894e4f065"
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.17.17.tgz#1b3b5a702a69b88deef342a7a80df4c894e4f065"
|
||||||
integrity sha512-E6VAZwN7diCa3labs0GYvhEPL2M94WLF8A+czO8hfjREXxba8Ng7nM5VxV+9ihNXIY1iQO1XxUU4P7hbqbICxg==
|
integrity sha512-E6VAZwN7diCa3labs0GYvhEPL2M94WLF8A+czO8hfjREXxba8Ng7nM5VxV+9ihNXIY1iQO1XxUU4P7hbqbICxg==
|
||||||
|
|
||||||
|
"@esbuild/android-arm@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-arm/-/android-arm-0.19.12.tgz#b0c26536f37776162ca8bde25e42040c203f2824"
|
||||||
|
integrity sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==
|
||||||
|
|
||||||
"@esbuild/android-x64@0.17.17":
|
"@esbuild/android-x64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.17.tgz#6781527e3c4ea4de532b149d18a2167f06783e7f"
|
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.17.17.tgz#6781527e3c4ea4de532b149d18a2167f06783e7f"
|
||||||
integrity sha512-446zpfJ3nioMC7ASvJB1pszHVskkw4u/9Eu8s5yvvsSDTzYh4p4ZIRj0DznSl3FBF0Z/mZfrKXTtt0QCoFmoHA==
|
integrity sha512-446zpfJ3nioMC7ASvJB1pszHVskkw4u/9Eu8s5yvvsSDTzYh4p4ZIRj0DznSl3FBF0Z/mZfrKXTtt0QCoFmoHA==
|
||||||
|
|
||||||
|
"@esbuild/android-x64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/android-x64/-/android-x64-0.19.12.tgz#cb13e2211282012194d89bf3bfe7721273473b3d"
|
||||||
|
integrity sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==
|
||||||
|
|
||||||
"@esbuild/darwin-arm64@0.17.17":
|
"@esbuild/darwin-arm64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.17.tgz#c5961ef4d3c1cc80dafe905cc145b5a71d2ac196"
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.17.17.tgz#c5961ef4d3c1cc80dafe905cc145b5a71d2ac196"
|
||||||
integrity sha512-m/gwyiBwH3jqfUabtq3GH31otL/0sE0l34XKpSIqR7NjQ/XHQ3lpmQHLHbG8AHTGCw8Ao059GvV08MS0bhFIJQ==
|
integrity sha512-m/gwyiBwH3jqfUabtq3GH31otL/0sE0l34XKpSIqR7NjQ/XHQ3lpmQHLHbG8AHTGCw8Ao059GvV08MS0bhFIJQ==
|
||||||
|
|
||||||
|
"@esbuild/darwin-arm64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz#cbee41e988020d4b516e9d9e44dd29200996275e"
|
||||||
|
integrity sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==
|
||||||
|
|
||||||
"@esbuild/darwin-x64@0.17.17":
|
"@esbuild/darwin-x64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.17.tgz#b81f3259cc349691f67ae30f7b333a53899b3c20"
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.17.17.tgz#b81f3259cc349691f67ae30f7b333a53899b3c20"
|
||||||
integrity sha512-4utIrsX9IykrqYaXR8ob9Ha2hAY2qLc6ohJ8c0CN1DR8yWeMrTgYFjgdeQ9LIoTOfLetXjuCu5TRPHT9yKYJVg==
|
integrity sha512-4utIrsX9IykrqYaXR8ob9Ha2hAY2qLc6ohJ8c0CN1DR8yWeMrTgYFjgdeQ9LIoTOfLetXjuCu5TRPHT9yKYJVg==
|
||||||
|
|
||||||
|
"@esbuild/darwin-x64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz#e37d9633246d52aecf491ee916ece709f9d5f4cd"
|
||||||
|
integrity sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==
|
||||||
|
|
||||||
"@esbuild/freebsd-arm64@0.17.17":
|
"@esbuild/freebsd-arm64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.17.tgz#db846ad16cf916fd3acdda79b85ea867cb100e87"
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.17.17.tgz#db846ad16cf916fd3acdda79b85ea867cb100e87"
|
||||||
integrity sha512-4PxjQII/9ppOrpEwzQ1b0pXCsFLqy77i0GaHodrmzH9zq2/NEhHMAMJkJ635Ns4fyJPFOlHMz4AsklIyRqFZWA==
|
integrity sha512-4PxjQII/9ppOrpEwzQ1b0pXCsFLqy77i0GaHodrmzH9zq2/NEhHMAMJkJ635Ns4fyJPFOlHMz4AsklIyRqFZWA==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-arm64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz#1ee4d8b682ed363b08af74d1ea2b2b4dbba76487"
|
||||||
|
integrity sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==
|
||||||
|
|
||||||
"@esbuild/freebsd-x64@0.17.17":
|
"@esbuild/freebsd-x64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.17.tgz#4dd99acbaaba00949d509e7c144b1b6ef9e1815b"
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.17.17.tgz#4dd99acbaaba00949d509e7c144b1b6ef9e1815b"
|
||||||
integrity sha512-lQRS+4sW5S3P1sv0z2Ym807qMDfkmdhUYX30GRBURtLTrJOPDpoU0kI6pVz1hz3U0+YQ0tXGS9YWveQjUewAJw==
|
integrity sha512-lQRS+4sW5S3P1sv0z2Ym807qMDfkmdhUYX30GRBURtLTrJOPDpoU0kI6pVz1hz3U0+YQ0tXGS9YWveQjUewAJw==
|
||||||
|
|
||||||
|
"@esbuild/freebsd-x64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz#37a693553d42ff77cd7126764b535fb6cc28a11c"
|
||||||
|
integrity sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==
|
||||||
|
|
||||||
"@esbuild/linux-arm64@0.17.17":
|
"@esbuild/linux-arm64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.17.tgz#7f9274140b2bb9f4230dbbfdf5dc2761215e30f6"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.17.17.tgz#7f9274140b2bb9f4230dbbfdf5dc2761215e30f6"
|
||||||
integrity sha512-2+pwLx0whKY1/Vqt8lyzStyda1v0qjJ5INWIe+d8+1onqQxHLLi3yr5bAa4gvbzhZqBztifYEu8hh1La5+7sUw==
|
integrity sha512-2+pwLx0whKY1/Vqt8lyzStyda1v0qjJ5INWIe+d8+1onqQxHLLi3yr5bAa4gvbzhZqBztifYEu8hh1La5+7sUw==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz#be9b145985ec6c57470e0e051d887b09dddb2d4b"
|
||||||
|
integrity sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==
|
||||||
|
|
||||||
"@esbuild/linux-arm@0.17.17":
|
"@esbuild/linux-arm@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.17.tgz#5c8e44c2af056bb2147cf9ad13840220bcb8948b"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.17.17.tgz#5c8e44c2af056bb2147cf9ad13840220bcb8948b"
|
||||||
integrity sha512-biDs7bjGdOdcmIk6xU426VgdRUpGg39Yz6sT9Xp23aq+IEHDb/u5cbmu/pAANpDB4rZpY/2USPhCA+w9t3roQg==
|
integrity sha512-biDs7bjGdOdcmIk6xU426VgdRUpGg39Yz6sT9Xp23aq+IEHDb/u5cbmu/pAANpDB4rZpY/2USPhCA+w9t3roQg==
|
||||||
|
|
||||||
|
"@esbuild/linux-arm@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz#207ecd982a8db95f7b5279207d0ff2331acf5eef"
|
||||||
|
integrity sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==
|
||||||
|
|
||||||
"@esbuild/linux-ia32@0.17.17":
|
"@esbuild/linux-ia32@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.17.tgz#18a6b3798658be7f46e9873fa0c8d4bec54c9212"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.17.17.tgz#18a6b3798658be7f46e9873fa0c8d4bec54c9212"
|
||||||
integrity sha512-IBTTv8X60dYo6P2t23sSUYym8fGfMAiuv7PzJ+0LcdAndZRzvke+wTVxJeCq4WgjppkOpndL04gMZIFvwoU34Q==
|
integrity sha512-IBTTv8X60dYo6P2t23sSUYym8fGfMAiuv7PzJ+0LcdAndZRzvke+wTVxJeCq4WgjppkOpndL04gMZIFvwoU34Q==
|
||||||
|
|
||||||
|
"@esbuild/linux-ia32@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz#d0d86b5ca1562523dc284a6723293a52d5860601"
|
||||||
|
integrity sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==
|
||||||
|
|
||||||
"@esbuild/linux-loong64@0.17.17":
|
"@esbuild/linux-loong64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.17.tgz#a8d93514a47f7b4232716c9f02aeb630bae24c40"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.17.17.tgz#a8d93514a47f7b4232716c9f02aeb630bae24c40"
|
||||||
integrity sha512-WVMBtcDpATjaGfWfp6u9dANIqmU9r37SY8wgAivuKmgKHE+bWSuv0qXEFt/p3qXQYxJIGXQQv6hHcm7iWhWjiw==
|
integrity sha512-WVMBtcDpATjaGfWfp6u9dANIqmU9r37SY8wgAivuKmgKHE+bWSuv0qXEFt/p3qXQYxJIGXQQv6hHcm7iWhWjiw==
|
||||||
|
|
||||||
|
"@esbuild/linux-loong64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz#9a37f87fec4b8408e682b528391fa22afd952299"
|
||||||
|
integrity sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==
|
||||||
|
|
||||||
"@esbuild/linux-mips64el@0.17.17":
|
"@esbuild/linux-mips64el@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.17.tgz#4784efb1c3f0eac8133695fa89253d558149ee1b"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.17.17.tgz#4784efb1c3f0eac8133695fa89253d558149ee1b"
|
||||||
integrity sha512-2kYCGh8589ZYnY031FgMLy0kmE4VoGdvfJkxLdxP4HJvWNXpyLhjOvxVsYjYZ6awqY4bgLR9tpdYyStgZZhi2A==
|
integrity sha512-2kYCGh8589ZYnY031FgMLy0kmE4VoGdvfJkxLdxP4HJvWNXpyLhjOvxVsYjYZ6awqY4bgLR9tpdYyStgZZhi2A==
|
||||||
|
|
||||||
|
"@esbuild/linux-mips64el@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz#4ddebd4e6eeba20b509d8e74c8e30d8ace0b89ec"
|
||||||
|
integrity sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==
|
||||||
|
|
||||||
"@esbuild/linux-ppc64@0.17.17":
|
"@esbuild/linux-ppc64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.17.tgz#ef6558ec5e5dd9dc16886343e0ccdb0699d70d3c"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.17.17.tgz#ef6558ec5e5dd9dc16886343e0ccdb0699d70d3c"
|
||||||
integrity sha512-KIdG5jdAEeAKogfyMTcszRxy3OPbZhq0PPsW4iKKcdlbk3YE4miKznxV2YOSmiK/hfOZ+lqHri3v8eecT2ATwQ==
|
integrity sha512-KIdG5jdAEeAKogfyMTcszRxy3OPbZhq0PPsW4iKKcdlbk3YE4miKznxV2YOSmiK/hfOZ+lqHri3v8eecT2ATwQ==
|
||||||
|
|
||||||
|
"@esbuild/linux-ppc64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz#adb67dadb73656849f63cd522f5ecb351dd8dee8"
|
||||||
|
integrity sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==
|
||||||
|
|
||||||
"@esbuild/linux-riscv64@0.17.17":
|
"@esbuild/linux-riscv64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.17.tgz#13a87fdbcb462c46809c9d16bcf79817ecf9ce6f"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.17.17.tgz#13a87fdbcb462c46809c9d16bcf79817ecf9ce6f"
|
||||||
integrity sha512-Cj6uWLBR5LWhcD/2Lkfg2NrkVsNb2sFM5aVEfumKB2vYetkA/9Uyc1jVoxLZ0a38sUhFk4JOVKH0aVdPbjZQeA==
|
integrity sha512-Cj6uWLBR5LWhcD/2Lkfg2NrkVsNb2sFM5aVEfumKB2vYetkA/9Uyc1jVoxLZ0a38sUhFk4JOVKH0aVdPbjZQeA==
|
||||||
|
|
||||||
|
"@esbuild/linux-riscv64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz#11bc0698bf0a2abf8727f1c7ace2112612c15adf"
|
||||||
|
integrity sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==
|
||||||
|
|
||||||
"@esbuild/linux-s390x@0.17.17":
|
"@esbuild/linux-s390x@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.17.tgz#83cb16d1d3ac0dca803b3f031ba3dc13f1ec7ade"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.17.17.tgz#83cb16d1d3ac0dca803b3f031ba3dc13f1ec7ade"
|
||||||
integrity sha512-lK+SffWIr0XsFf7E0srBjhpkdFVJf3HEgXCwzkm69kNbRar8MhezFpkIwpk0qo2IOQL4JE4mJPJI8AbRPLbuOQ==
|
integrity sha512-lK+SffWIr0XsFf7E0srBjhpkdFVJf3HEgXCwzkm69kNbRar8MhezFpkIwpk0qo2IOQL4JE4mJPJI8AbRPLbuOQ==
|
||||||
|
|
||||||
|
"@esbuild/linux-s390x@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz#e86fb8ffba7c5c92ba91fc3b27ed5a70196c3cc8"
|
||||||
|
integrity sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==
|
||||||
|
|
||||||
"@esbuild/linux-x64@0.17.17":
|
"@esbuild/linux-x64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.17.tgz#7bc400568690b688e20a0c94b2faabdd89ae1a79"
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.17.17.tgz#7bc400568690b688e20a0c94b2faabdd89ae1a79"
|
||||||
integrity sha512-XcSGTQcWFQS2jx3lZtQi7cQmDYLrpLRyz1Ns1DzZCtn898cWfm5Icx/DEWNcTU+T+tyPV89RQtDnI7qL2PObPg==
|
integrity sha512-XcSGTQcWFQS2jx3lZtQi7cQmDYLrpLRyz1Ns1DzZCtn898cWfm5Icx/DEWNcTU+T+tyPV89RQtDnI7qL2PObPg==
|
||||||
|
|
||||||
|
"@esbuild/linux-x64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz#5f37cfdc705aea687dfe5dfbec086a05acfe9c78"
|
||||||
|
integrity sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==
|
||||||
|
|
||||||
"@esbuild/netbsd-x64@0.17.17":
|
"@esbuild/netbsd-x64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.17.tgz#1b5dcfbc4bfba80e67a11e9148de836af5b58b6c"
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.17.17.tgz#1b5dcfbc4bfba80e67a11e9148de836af5b58b6c"
|
||||||
integrity sha512-RNLCDmLP5kCWAJR+ItLM3cHxzXRTe4N00TQyQiimq+lyqVqZWGPAvcyfUBM0isE79eEZhIuGN09rAz8EL5KdLA==
|
integrity sha512-RNLCDmLP5kCWAJR+ItLM3cHxzXRTe4N00TQyQiimq+lyqVqZWGPAvcyfUBM0isE79eEZhIuGN09rAz8EL5KdLA==
|
||||||
|
|
||||||
|
"@esbuild/netbsd-x64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz#29da566a75324e0d0dd7e47519ba2f7ef168657b"
|
||||||
|
integrity sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==
|
||||||
|
|
||||||
"@esbuild/openbsd-x64@0.17.17":
|
"@esbuild/openbsd-x64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.17.tgz#e275098902291149a5dcd012c9ea0796d6b7adff"
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.17.17.tgz#e275098902291149a5dcd012c9ea0796d6b7adff"
|
||||||
integrity sha512-PAXswI5+cQq3Pann7FNdcpSUrhrql3wKjj3gVkmuz6OHhqqYxKvi6GgRBoaHjaG22HV/ZZEgF9TlS+9ftHVigA==
|
integrity sha512-PAXswI5+cQq3Pann7FNdcpSUrhrql3wKjj3gVkmuz6OHhqqYxKvi6GgRBoaHjaG22HV/ZZEgF9TlS+9ftHVigA==
|
||||||
|
|
||||||
|
"@esbuild/openbsd-x64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz#306c0acbdb5a99c95be98bdd1d47c916e7dc3ff0"
|
||||||
|
integrity sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==
|
||||||
|
|
||||||
"@esbuild/sunos-x64@0.17.17":
|
"@esbuild/sunos-x64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.17.tgz#10603474866f64986c0370a2d4fe5a2bb7fee4f5"
|
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.17.17.tgz#10603474866f64986c0370a2d4fe5a2bb7fee4f5"
|
||||||
integrity sha512-V63egsWKnx/4V0FMYkr9NXWrKTB5qFftKGKuZKFIrAkO/7EWLFnbBZNM1CvJ6Sis+XBdPws2YQSHF1Gqf1oj/Q==
|
integrity sha512-V63egsWKnx/4V0FMYkr9NXWrKTB5qFftKGKuZKFIrAkO/7EWLFnbBZNM1CvJ6Sis+XBdPws2YQSHF1Gqf1oj/Q==
|
||||||
|
|
||||||
|
"@esbuild/sunos-x64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz#0933eaab9af8b9b2c930236f62aae3fc593faf30"
|
||||||
|
integrity sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==
|
||||||
|
|
||||||
"@esbuild/win32-arm64@0.17.17":
|
"@esbuild/win32-arm64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.17.tgz#521a6d97ee0f96b7c435930353cc4e93078f0b54"
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.17.17.tgz#521a6d97ee0f96b7c435930353cc4e93078f0b54"
|
||||||
integrity sha512-YtUXLdVnd6YBSYlZODjWzH+KzbaubV0YVd6UxSfoFfa5PtNJNaW+1i+Hcmjpg2nEe0YXUCNF5bkKy1NnBv1y7Q==
|
integrity sha512-YtUXLdVnd6YBSYlZODjWzH+KzbaubV0YVd6UxSfoFfa5PtNJNaW+1i+Hcmjpg2nEe0YXUCNF5bkKy1NnBv1y7Q==
|
||||||
|
|
||||||
|
"@esbuild/win32-arm64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz#773bdbaa1971b36db2f6560088639ccd1e6773ae"
|
||||||
|
integrity sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==
|
||||||
|
|
||||||
"@esbuild/win32-ia32@0.17.17":
|
"@esbuild/win32-ia32@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.17.tgz#56f88462ebe82dad829dc2303175c0e0ccd8e38e"
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.17.17.tgz#56f88462ebe82dad829dc2303175c0e0ccd8e38e"
|
||||||
integrity sha512-yczSLRbDdReCO74Yfc5tKG0izzm+lPMYyO1fFTcn0QNwnKmc3K+HdxZWLGKg4pZVte7XVgcFku7TIZNbWEJdeQ==
|
integrity sha512-yczSLRbDdReCO74Yfc5tKG0izzm+lPMYyO1fFTcn0QNwnKmc3K+HdxZWLGKg4pZVte7XVgcFku7TIZNbWEJdeQ==
|
||||||
|
|
||||||
|
"@esbuild/win32-ia32@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz#000516cad06354cc84a73f0943a4aa690ef6fd67"
|
||||||
|
integrity sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==
|
||||||
|
|
||||||
"@esbuild/win32-x64@0.17.17":
|
"@esbuild/win32-x64@0.17.17":
|
||||||
version "0.17.17"
|
version "0.17.17"
|
||||||
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.17.tgz#2b577b976e6844106715bbe0cdc57cd1528063f9"
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.17.17.tgz#2b577b976e6844106715bbe0cdc57cd1528063f9"
|
||||||
integrity sha512-FNZw7H3aqhF9OyRQbDDnzUApDXfC1N6fgBhkqEO2jvYCJ+DxMTfZVqg3AX0R1khg1wHTBRD5SdcibSJ+XF6bFg==
|
integrity sha512-FNZw7H3aqhF9OyRQbDDnzUApDXfC1N6fgBhkqEO2jvYCJ+DxMTfZVqg3AX0R1khg1wHTBRD5SdcibSJ+XF6bFg==
|
||||||
|
|
||||||
|
"@esbuild/win32-x64@0.19.12":
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz#c57c8afbb4054a3ab8317591a0b7320360b444ae"
|
||||||
|
integrity sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==
|
||||||
|
|
||||||
"@eslint-community/eslint-utils@^4.2.0":
|
"@eslint-community/eslint-utils@^4.2.0":
|
||||||
version "4.4.0"
|
version "4.4.0"
|
||||||
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
resolved "https://registry.yarnpkg.com/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz#a23514e8fb9af1269d5f7788aa556798d61c6b59"
|
||||||
@ -1550,7 +1665,7 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz#add4c98d341472a289190b424efbdb096991bb24"
|
||||||
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
integrity sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==
|
||||||
|
|
||||||
"@jridgewell/sourcemap-codec@^1.4.10":
|
"@jridgewell/sourcemap-codec@^1.4.10", "@jridgewell/sourcemap-codec@^1.4.15":
|
||||||
version "1.4.15"
|
version "1.4.15"
|
||||||
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
resolved "https://registry.yarnpkg.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz#d7c6e6755c78567a951e04ab52ef0fd26de59f32"
|
||||||
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
integrity sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==
|
||||||
@ -1779,6 +1894,71 @@
|
|||||||
schema-utils "^3.0.0"
|
schema-utils "^3.0.0"
|
||||||
source-map "^0.7.3"
|
source-map "^0.7.3"
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm-eabi@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.9.6.tgz#66b8d9cb2b3a474d115500f9ebaf43e2126fe496"
|
||||||
|
integrity sha512-MVNXSSYN6QXOulbHpLMKYi60ppyO13W9my1qogeiAqtjb2yR4LSmfU2+POvDkLzhjYLXz9Rf9+9a3zFHW1Lecg==
|
||||||
|
|
||||||
|
"@rollup/rollup-android-arm64@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.9.6.tgz#46327d5b86420d2307946bec1535fdf00356e47d"
|
||||||
|
integrity sha512-T14aNLpqJ5wzKNf5jEDpv5zgyIqcpn1MlwCrUXLrwoADr2RkWA0vOWP4XxbO9aiO3dvMCQICZdKeDrFl7UMClw==
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-arm64@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.9.6.tgz#166987224d2f8b1e2fd28ee90c447d52271d5e90"
|
||||||
|
integrity sha512-CqNNAyhRkTbo8VVZ5R85X73H3R5NX9ONnKbXuHisGWC0qRbTTxnF1U4V9NafzJbgGM0sHZpdO83pLPzq8uOZFw==
|
||||||
|
|
||||||
|
"@rollup/rollup-darwin-x64@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.9.6.tgz#a2e6e096f74ccea6e2f174454c26aef6bcdd1274"
|
||||||
|
integrity sha512-zRDtdJuRvA1dc9Mp6BWYqAsU5oeLixdfUvkTHuiYOHwqYuQ4YgSmi6+/lPvSsqc/I0Omw3DdICx4Tfacdzmhog==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.9.6.tgz#09fcd4c55a2d6160c5865fec708a8e5287f30515"
|
||||||
|
integrity sha512-oNk8YXDDnNyG4qlNb6is1ojTOGL/tRhbbKeE/YuccItzerEZT68Z9gHrY3ROh7axDc974+zYAPxK5SH0j/G+QQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-gnu@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.9.6.tgz#19a3c0b6315c747ca9acf86e9b710cc2440f83c9"
|
||||||
|
integrity sha512-Z3O60yxPtuCYobrtzjo0wlmvDdx2qZfeAWTyfOjEDqd08kthDKexLpV97KfAeUXPosENKd8uyJMRDfFMxcYkDQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-arm64-musl@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.9.6.tgz#94aaf95fdaf2ad9335983a4552759f98e6b2e850"
|
||||||
|
integrity sha512-gpiG0qQJNdYEVad+1iAsGAbgAnZ8j07FapmnIAQgODKcOTjLEWM9sRb+MbQyVsYCnA0Im6M6QIq6ax7liws6eQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.9.6.tgz#160510e63f4b12618af4013bddf1761cf9fc9880"
|
||||||
|
integrity sha512-+uCOcvVmFUYvVDr27aiyun9WgZk0tXe7ThuzoUTAukZJOwS5MrGbmSlNOhx1j80GdpqbOty05XqSl5w4dQvcOA==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-gnu@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.9.6.tgz#5ac5d068ce0726bd0a96ca260d5bd93721c0cb98"
|
||||||
|
integrity sha512-HUNqM32dGzfBKuaDUBqFB7tP6VMN74eLZ33Q9Y1TBqRDn+qDonkAUyKWwF9BR9unV7QUzffLnz9GrnKvMqC/fw==
|
||||||
|
|
||||||
|
"@rollup/rollup-linux-x64-musl@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.9.6.tgz#bafa759ab43e8eab9edf242a8259ffb4f2a57a5d"
|
||||||
|
integrity sha512-ch7M+9Tr5R4FK40FHQk8VnML0Szi2KRujUgHXd/HjuH9ifH72GUmw6lStZBo3c3GB82vHa0ZoUfjfcM7JiiMrQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-arm64-msvc@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.9.6.tgz#1cc3416682e5a20d8f088f26657e6e47f8db468e"
|
||||||
|
integrity sha512-VD6qnR99dhmTQ1mJhIzXsRcTBvTjbfbGGwKAHcu+52cVl15AC/kplkhxzW/uT0Xl62Y/meBKDZvoJSJN+vTeGA==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-ia32-msvc@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.9.6.tgz#7d2251e1aa5e8a1e47c86891fe4547a939503461"
|
||||||
|
integrity sha512-J9AFDq/xiRI58eR2NIDfyVmTYGyIZmRcvcAoJ48oDld/NTR8wyiPUu2X/v1navJ+N/FGg68LEbX3Ejd6l8B7MQ==
|
||||||
|
|
||||||
|
"@rollup/rollup-win32-x64-msvc@4.9.6":
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.9.6.tgz#2c1fb69e02a3f1506f52698cfdc3a8b6386df9a6"
|
||||||
|
integrity sha512-jqzNLhNDvIZOrt69Ce4UjGRpXJBzhUBzawMwnaDAwyHriki3XollsewxWzOzz+4yOFDkuJHtTsZFwMxhYJWmLQ==
|
||||||
|
|
||||||
"@storybook/addon-a11y@^6.5.16":
|
"@storybook/addon-a11y@^6.5.16":
|
||||||
version "6.5.16"
|
version "6.5.16"
|
||||||
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-6.5.16.tgz#9288a6c1d111fa4ec501d213100ffff91757d3fc"
|
resolved "https://registry.yarnpkg.com/@storybook/addon-a11y/-/addon-a11y-6.5.16.tgz#9288a6c1d111fa4ec501d213100ffff91757d3fc"
|
||||||
@ -2837,6 +3017,11 @@
|
|||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.1.tgz#aa22750962f3bf0e79d753d3cc067f010c95f194"
|
||||||
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
|
integrity sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==
|
||||||
|
|
||||||
|
"@types/estree@1.0.5":
|
||||||
|
version "1.0.5"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4"
|
||||||
|
integrity sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==
|
||||||
|
|
||||||
"@types/estree@^0.0.51":
|
"@types/estree@^0.0.51":
|
||||||
version "0.0.51"
|
version "0.0.51"
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40"
|
||||||
@ -5853,6 +6038,35 @@ esbuild@^0.17.5:
|
|||||||
"@esbuild/win32-ia32" "0.17.17"
|
"@esbuild/win32-ia32" "0.17.17"
|
||||||
"@esbuild/win32-x64" "0.17.17"
|
"@esbuild/win32-x64" "0.17.17"
|
||||||
|
|
||||||
|
esbuild@^0.19.3:
|
||||||
|
version "0.19.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.19.12.tgz#dc82ee5dc79e82f5a5c3b4323a2a641827db3e04"
|
||||||
|
integrity sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==
|
||||||
|
optionalDependencies:
|
||||||
|
"@esbuild/aix-ppc64" "0.19.12"
|
||||||
|
"@esbuild/android-arm" "0.19.12"
|
||||||
|
"@esbuild/android-arm64" "0.19.12"
|
||||||
|
"@esbuild/android-x64" "0.19.12"
|
||||||
|
"@esbuild/darwin-arm64" "0.19.12"
|
||||||
|
"@esbuild/darwin-x64" "0.19.12"
|
||||||
|
"@esbuild/freebsd-arm64" "0.19.12"
|
||||||
|
"@esbuild/freebsd-x64" "0.19.12"
|
||||||
|
"@esbuild/linux-arm" "0.19.12"
|
||||||
|
"@esbuild/linux-arm64" "0.19.12"
|
||||||
|
"@esbuild/linux-ia32" "0.19.12"
|
||||||
|
"@esbuild/linux-loong64" "0.19.12"
|
||||||
|
"@esbuild/linux-mips64el" "0.19.12"
|
||||||
|
"@esbuild/linux-ppc64" "0.19.12"
|
||||||
|
"@esbuild/linux-riscv64" "0.19.12"
|
||||||
|
"@esbuild/linux-s390x" "0.19.12"
|
||||||
|
"@esbuild/linux-x64" "0.19.12"
|
||||||
|
"@esbuild/netbsd-x64" "0.19.12"
|
||||||
|
"@esbuild/openbsd-x64" "0.19.12"
|
||||||
|
"@esbuild/sunos-x64" "0.19.12"
|
||||||
|
"@esbuild/win32-arm64" "0.19.12"
|
||||||
|
"@esbuild/win32-ia32" "0.19.12"
|
||||||
|
"@esbuild/win32-x64" "0.19.12"
|
||||||
|
|
||||||
escalade@^3.1.1:
|
escalade@^3.1.1:
|
||||||
version "3.1.1"
|
version "3.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
|
||||||
@ -6477,6 +6691,11 @@ fsevents@^2.1.2, fsevents@~2.3.2:
|
|||||||
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.2.tgz#8a526f78b8fdf4623b709e0b975c52c24c02fd1a"
|
||||||
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
integrity sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==
|
||||||
|
|
||||||
|
fsevents@~2.3.3:
|
||||||
|
version "2.3.3"
|
||||||
|
resolved "https://registry.yarnpkg.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6"
|
||||||
|
integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==
|
||||||
|
|
||||||
function-bind@^1.1.1:
|
function-bind@^1.1.1:
|
||||||
version "1.1.1"
|
version "1.1.1"
|
||||||
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
resolved "https://registry.yarnpkg.com/function-bind/-/function-bind-1.1.1.tgz#a56899d3ea3c9bab874bb9773b7c5ede92f4895d"
|
||||||
@ -8133,6 +8352,13 @@ lz-string@^1.4.4:
|
|||||||
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
resolved "https://registry.yarnpkg.com/lz-string/-/lz-string-1.5.0.tgz#c1ab50f77887b712621201ba9fd4e3a6ed099941"
|
||||||
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
integrity sha512-h5bgJWpxJNswbU7qCrV0tIKQCaS3blPDrqKWx+QxzuzL1zGUzij9XCWLrSLsJPu5t+eWA/ycetzYAO5IOMcWAQ==
|
||||||
|
|
||||||
|
magic-string@^0.30.7:
|
||||||
|
version "0.30.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/magic-string/-/magic-string-0.30.7.tgz#0cecd0527d473298679da95a2d7aeb8c64048505"
|
||||||
|
integrity sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==
|
||||||
|
dependencies:
|
||||||
|
"@jridgewell/sourcemap-codec" "^1.4.15"
|
||||||
|
|
||||||
make-dir@^2.0.0, make-dir@^2.1.0:
|
make-dir@^2.0.0, make-dir@^2.1.0:
|
||||||
version "2.1.0"
|
version "2.1.0"
|
||||||
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
resolved "https://registry.yarnpkg.com/make-dir/-/make-dir-2.1.0.tgz#5f0310e18b8be898cc07009295a30ae41e91e6f5"
|
||||||
@ -8661,6 +8887,11 @@ nanoid@^3.3.1, nanoid@^3.3.6:
|
|||||||
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.6.tgz#443380c856d6e9f9824267d960b4236ad583ea4c"
|
||||||
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
integrity sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==
|
||||||
|
|
||||||
|
nanoid@^3.3.7:
|
||||||
|
version "3.3.7"
|
||||||
|
resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8"
|
||||||
|
integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==
|
||||||
|
|
||||||
nanomatch@^1.2.9:
|
nanomatch@^1.2.9:
|
||||||
version "1.2.13"
|
version "1.2.13"
|
||||||
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
resolved "https://registry.yarnpkg.com/nanomatch/-/nanomatch-1.2.13.tgz#b87a8aa4fc0de8fe6be88895b38983ff265bd119"
|
||||||
@ -9545,6 +9776,15 @@ postcss@^8.2.15, postcss@^8.4.21:
|
|||||||
picocolors "^1.0.0"
|
picocolors "^1.0.0"
|
||||||
source-map-js "^1.0.2"
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
|
postcss@^8.4.32:
|
||||||
|
version "8.4.33"
|
||||||
|
resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.33.tgz#1378e859c9f69bf6f638b990a0212f43e2aaa742"
|
||||||
|
integrity sha512-Kkpbhhdjw2qQs2O2DGX+8m5OVqEcbB9HRBvuYM9pgrjEFUg30A9LmXNlTAUj4S9kgtGyrMbTzVjH7E+s5Re2yg==
|
||||||
|
dependencies:
|
||||||
|
nanoid "^3.3.7"
|
||||||
|
picocolors "^1.0.0"
|
||||||
|
source-map-js "^1.0.2"
|
||||||
|
|
||||||
powerhooks@^0.26.7:
|
powerhooks@^0.26.7:
|
||||||
version "0.26.7"
|
version "0.26.7"
|
||||||
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.26.7.tgz#3c9709a6012207e073aa268a775b352905ea46f5"
|
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.26.7.tgz#3c9709a6012207e073aa268a775b352905ea46f5"
|
||||||
@ -10305,6 +10545,28 @@ rollup@^3.18.0:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
rollup@^4.2.0:
|
||||||
|
version "4.9.6"
|
||||||
|
resolved "https://registry.yarnpkg.com/rollup/-/rollup-4.9.6.tgz#4515facb0318ecca254a2ee1315e22e09efc50a0"
|
||||||
|
integrity sha512-05lzkCS2uASX0CiLFybYfVkwNbKZG5NFQ6Go0VWyogFTXXbR039UVsegViTntkk4OglHBdF54ccApXRRuXRbsg==
|
||||||
|
dependencies:
|
||||||
|
"@types/estree" "1.0.5"
|
||||||
|
optionalDependencies:
|
||||||
|
"@rollup/rollup-android-arm-eabi" "4.9.6"
|
||||||
|
"@rollup/rollup-android-arm64" "4.9.6"
|
||||||
|
"@rollup/rollup-darwin-arm64" "4.9.6"
|
||||||
|
"@rollup/rollup-darwin-x64" "4.9.6"
|
||||||
|
"@rollup/rollup-linux-arm-gnueabihf" "4.9.6"
|
||||||
|
"@rollup/rollup-linux-arm64-gnu" "4.9.6"
|
||||||
|
"@rollup/rollup-linux-arm64-musl" "4.9.6"
|
||||||
|
"@rollup/rollup-linux-riscv64-gnu" "4.9.6"
|
||||||
|
"@rollup/rollup-linux-x64-gnu" "4.9.6"
|
||||||
|
"@rollup/rollup-linux-x64-musl" "4.9.6"
|
||||||
|
"@rollup/rollup-win32-arm64-msvc" "4.9.6"
|
||||||
|
"@rollup/rollup-win32-ia32-msvc" "4.9.6"
|
||||||
|
"@rollup/rollup-win32-x64-msvc" "4.9.6"
|
||||||
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
rsvp@^4.8.4:
|
rsvp@^4.8.4:
|
||||||
version "4.8.5"
|
version "4.8.5"
|
||||||
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
resolved "https://registry.yarnpkg.com/rsvp/-/rsvp-4.8.5.tgz#c8f155311d167f68f21e168df71ec5b083113734"
|
||||||
@ -11951,6 +12213,17 @@ vite-node@0.29.8:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
fsevents "~2.3.2"
|
fsevents "~2.3.2"
|
||||||
|
|
||||||
|
vite@^5.0.12:
|
||||||
|
version "5.0.12"
|
||||||
|
resolved "https://registry.yarnpkg.com/vite/-/vite-5.0.12.tgz#8a2ffd4da36c132aec4adafe05d7adde38333c47"
|
||||||
|
integrity sha512-4hsnEkG3q0N4Tzf1+t6NdN9dg/L3BM+q8SWgbSPnJvrgH2kgdyzfVJwbR1ic69/4uMJJ/3dqDZZE5/WwqW8U1w==
|
||||||
|
dependencies:
|
||||||
|
esbuild "^0.19.3"
|
||||||
|
postcss "^8.4.32"
|
||||||
|
rollup "^4.2.0"
|
||||||
|
optionalDependencies:
|
||||||
|
fsevents "~2.3.3"
|
||||||
|
|
||||||
vitest@^0.29.8:
|
vitest@^0.29.8:
|
||||||
version "0.29.8"
|
version "0.29.8"
|
||||||
resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.29.8.tgz#9c13cfa007c3511e86c26e1fe9a686bb4dbaec80"
|
resolved "https://registry.yarnpkg.com/vitest/-/vitest-0.29.8.tgz#9c13cfa007c3511e86c26e1fe9a686bb4dbaec80"
|
||||||
|
Reference in New Issue
Block a user