diff --git a/.eslintignore b/.eslintignore deleted file mode 100644 index eeeefeec..00000000 --- a/.eslintignore +++ /dev/null @@ -1,7 +0,0 @@ -node_modules/ -dist/ -CHANGELOG.md -.yarn_home/ -src/test/apps/ -src/test/types/ -src/tools/types/ \ No newline at end of file diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index c5dae94f..00000000 --- a/.eslintrc.js +++ /dev/null @@ -1,16 +0,0 @@ -module.exports = { - "root": true, - "parser": "@typescript-eslint/parser", - "plugins": [ - "@typescript-eslint", - ], - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/recommended", - "prettier", - ], - "rules": { - "no-extra-boolean-cast": "off", - "@typescript-eslint/explicit-module-boundary-types": "off", - }, -}; \ No newline at end of file diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 10c9b0b7..0cb975d3 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -9,14 +9,13 @@ on: jobs: - test_lint: + test_formatting: runs-on: ubuntu-latest - if: ${{ !github.event.created && github.repository != 'garronej/ts_ci' }} steps: - uses: actions/checkout@v2.3.4 - uses: actions/setup-node@v2.1.3 - uses: bahmutov/npm-install@v1 - - name: If this step fails run 'npm run lint' and 'npm run format' then commit again. + - name: If this step fails run 'yarn format' then commit again. run: | PACKAGE_MANAGER=npm if [ -f "./yarn.lock" ]; then @@ -27,7 +26,7 @@ jobs: test: runs-on: macos-10.15 - needs: test_lint + needs: test_formatting strategy: matrix: node: [ '15', '14', '13' ] diff --git a/README.md b/README.md index ab7dac6e..93090b6a 100644 --- a/README.md +++ b/README.md @@ -20,17 +20,19 @@

-**NEW in v2** -- It's now possible to implement custom `.ftl` pages. -- Support for Keycloak plugins that introduce non standard ftl values. - (Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) that define `authorizedMailDomains` in `register.ftl`). +**NEW in v2** + +- It's now possible to implement custom `.ftl` pages. +- Support for Keycloak plugins that introduce non standard ftl values. + (Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) that define `authorizedMailDomains` in `register.ftl`). + # Motivations Keycloak provides [theme support](https://www.keycloak.org/docs/latest/server_development/#_themes) for web pages. This allows customizing the look and feel of end-user facing pages so they can be integrated with your applications. It involves, however, a lot of raw JS/CSS/[FTL]() hacking, and bundling the theme is not exactly straightforward. Beyond that, if you use Keycloak for a specific app you want your login page to be tightly integrated with it. -Ideally, you don't want the user to notice when he is being redirected away. +Ideally, you don't want the user to notice when he is being redirected away. Trying to reproduce the look and feel of a specific app in another stack is not an easy task not to mention the cheer amount of maintenance that it involves. @@ -47,69 +49,71 @@ Here is `keycloakify` for you 🍸 With keycloakify:
-

+

-**TL;DR**: [Here](https://github.com/garronej/keycloakify-demo-app) is a Hello World React project with Keycloakify set up. +**TL;DR**: [Here](https://github.com/garronej/keycloakify-demo-app) is a Hello World React project with Keycloakify set up. If you already have a Keycloak custom theme, it can be easily ported to Keycloakify. --- +- [Motivations](#motivations) +- [Requirements](#requirements) + - [My framework doesn’t seem to be supported, what can I do?](#my-framework-doesnt-seem-to-be-supported-what-can-i-do) +- [How to use](#how-to-use) + - [Setting up the build tool](#setting-up-the-build-tool) + - [Changing just the look of the default Keycloak theme](#changing-just-the-look-of-the-default-keycloak-theme) + - [Advanced pages configuration](#advanced-pages-configuration) + - [Hot reload](#hot-reload) + - [Enable loading in a blink of an eye of login pages ⚡ (--external-assets)](#enable-loading-in-a-blink-of-an-eye-of-login-pages----external-assets) +- [Support for Terms and conditions](#support-for-terms-and-conditions) +- [Some pages still have the default theme. Why?](#some-pages-still-have-the-default-theme-why) +- [GitHub Actions](#github-actions) +- [Limitations](#limitations) + - [`process.env.PUBLIC_URL` not supported.](#processenvpublic_url-not-supported) + - [`@font-face` importing fonts from the `src/` dir](#font-face-importing-fonts-from-thesrc-dir) + - [Example of setup that **won't** work](#example-of-setup-that-wont-work) + - [Possible workarounds](#possible-workarounds) +- [Implement context persistence (optional)](#implement-context-persistence-optional) +- [Kickstart video](#kickstart-video) +- [About the errors related to `objectToJson` in Keycloak logs.](#about-the-errors-related-to-objecttojson-in-keycloak-logs) +- [Email domain whitelist](#email-domain-whitelist) -- [Motivations](#motivations) -- [Requirements](#requirements) - - [My framework doesn’t seem to be supported, what can I do?](#my-framework-doesnt-seem-to-be-supported-what-can-i-do) -- [How to use](#how-to-use) - - [Setting up the build tool](#setting-up-the-build-tool) - - [Changing just the look of the default Keycloak theme](#changing-just-the-look-of-the-default-keycloak-theme) - - [Advanced pages configuration](#advanced-pages-configuration) - - [Hot reload](#hot-reload) - - [Enable loading in a blink of an eye of login pages ⚡ (--external-assets)](#enable-loading-in-a-blink-of-an-eye-of-login-pages----external-assets) -- [Support for Terms and conditions](#support-for-terms-and-conditions) -- [Some pages still have the default theme. Why?](#some-pages-still-have-the-default-theme-why) -- [GitHub Actions](#github-actions) -- [Limitations](#limitations) - - [`process.env.PUBLIC_URL` not supported.](#processenvpublic_url-not-supported) - - [`@font-face` importing fonts from the `src/` dir](#font-face-importing-fonts-from-thesrc-dir) - - [Example of setup that **won't** work](#example-of-setup-that-wont-work) - - [Possible workarounds](#possible-workarounds) -- [Implement context persistence (optional)](#implement-context-persistence-optional) -- [Kickstart video](#kickstart-video) -- [About the errors related to `objectToJson` in Keycloak logs.](#about-the-errors-related-to-objecttojson-in-keycloak-logs) -- [Email domain whitelist](#email-domain-whitelist) +# Requirements -# Requirements +Tested with the following Keycloak versions: -Tested with the following Keycloak versions: -- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore) -- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore) -- Tests ongoing with [14.0.0](https://hub.docker.com/layers/jboss/keycloak/14.0.0/images/sha256-ca713e87ad163da71ab329010de2464a41ff030a25ae0aef15c1c290252f3d7f?context=explore) +- [11.0.3](https://hub.docker.com/layers/jboss/keycloak/11.0.3/images/sha256-4438f1e51c1369371cb807dffa526e1208086b3ebb9cab009830a178de949782?context=explore) +- [12.0.4](https://hub.docker.com/layers/jboss/keycloak/12.0.4/images/sha256-67e0c88e69bd0c7aef972c40bdeb558a974013a28b3668ca790ed63a04d70584?context=explore) +- Tests ongoing with [14.0.0](https://hub.docker.com/layers/jboss/keycloak/14.0.0/images/sha256-ca713e87ad163da71ab329010de2464a41ff030a25ae0aef15c1c290252f3d7f?context=explore) -This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get +This tool will be maintained to stay compatible with Keycloak v11 and up, however, the default pages you will get (before you customize it) will always be the ones of Keycloak v11. This tool assumes you are bundling your app with Webpack (tested with 4.44.2) . It assumes there is a `build/` directory at the root of your react project directory containing a `index.html` file -and a `build/static/` directory generated by webpack. +and a `build/static/` directory generated by webpack. For more information see [this issue](https://github.com/InseeFrLab/keycloakify/issues/5#issuecomment-832296432) **All this is defaults with [`create-react-app`](https://create-react-app.dev)** (tested with 4.0.3) -- `mvn` ([Maven](https://maven.apache.org/)), `rm`, `mkdir`, `wget`, `unzip` are assumed to be available. -- `docker` must be up and running when running `yarn keycloak`. +- `mvn` ([Maven](https://maven.apache.org/)), `rm`, `mkdir`, `wget`, `unzip` are assumed to be available. +- `docker` must be up and running when running `yarn keycloak`. On Windows you'll have to use [WSL](https://docs.microsoft.com/en-us/windows/wsl/install-win10). -## My framework doesn’t seem to be supported, what can I do? +## My framework doesn’t seem to be supported, what can I do? Currently Keycloakify is only compatible with `create-react-app` apps. -It doesn’t mean that you can't use Keycloakify if you are using Next.js, Express or any other +It doesn’t mean that you can't use Keycloakify if you are using Next.js, Express or any other framework that involves SSR but your Keycloak theme will need to be a standalone project. -Find specific instructions about how to get started [**here**](https://github.com/garronej/keycloakify-demo-app#keycloak-theme-only). +Find specific instructions about how to get started [**here**](https://github.com/garronej/keycloakify-demo-app#keycloak-theme-only). To share your styles between your main app and your login pages you will need to externalize your design system by making it a separate module. Checkout [ts_ci](https://github.com/garronej/ts_ci), it can help with that. + # How to use + ## Setting up the build tool ```bash @@ -117,6 +121,7 @@ yarn add keycloakify ``` [`package.json`](https://github.com/garronej/keycloakify-demo-app/blob/main/package.json) + ```json "scripts": { "keycloak": "yarn build && build-keycloak-theme", @@ -135,14 +140,15 @@ The first approach is to only customize the style of the default Keycloak login your own class names. If you have created a new React project specifically to create a Keycloak theme and nothing else then -your index should look something like: +your index should look something like: `src/index.tsx` + ```tsx import { App } from ".//App"; -import { - KcApp, - defaultKcProps, +import { + KcApp, + defaultKcProps, getKcContext } from "keycloakify"; import { css } from "tss-react/@emotion/css"; @@ -152,59 +158,57 @@ const { kcContext } = getKcContext(); const myClassName = css({ "color": "red" }); reactDom.render( - document.getElementById("root") ); ``` If you share a unique project for your app and the Keycloak theme, your index should look -more like this: +more like this: `src/index.tsx` + ```tsx -import { App } from ".//App"; -import { - KcApp, - defaultKcProps, - getKcContext -} from "keycloakify"; -import { css } from "tss-react/@emotion/css"; +import { App } from ".//App"; +import { KcApp, defaultKcProps, getKcContext } from "keycloakify"; +import { css } from "tss-react/@emotion/css"; const { kcContext } = getKcContext(); const myClassName = css({ "color": "red" }); reactDom.render( - // Unless the app is currently being served by Keycloak + // Unless the app is currently being served by Keycloak // kcContext is undefined. - kcContext !== undefined ? - : - , // Your actual app - document.getElementById("root") + "kcHeaderWrapperClass": myClassName, + }} + /> + ) : ( + + ), // Your actual app + document.getElementById("root"), ); ``` -

result:

-Example of a customization using only CSS: [here](https://github.com/InseeFrLab/onyxia-ui/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/components/KcApp.tsx) +Example of a customization using only CSS: [here](https://github.com/InseeFrLab/onyxia-ui/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/components/KcApp.tsx) (the [index.tsx](https://github.com/InseeFrLab/onyxia-ui/blob/012639d62327a9a56be80c46e32c32c9497b82db/src/app/index.tsx#L89-L94) ) -and the result you can expect: +and the result you can expect:

Customization using only CSS: @@ -214,36 +218,39 @@ and the result you can expect: ### Advanced pages configuration -If you want to go beyond only customizing the CSS you can re-implement some of the -pages or even add new ones. +If you want to go beyond only customizing the CSS you can re-implement some of the +pages or even add new ones. If you want to go this way checkout the demo setup provided [here](https://github.com/garronej/keycloakify-demo-app/tree/look_and_feel). -If you prefer a real life example you can checkout [onyxia-web's source](https://github.com/InseeFrLab/onyxia-web/tree/main/src/app/components/KcApp). +If you prefer a real life example you can checkout [onyxia-web's source](https://github.com/InseeFrLab/onyxia-web/tree/main/src/app/components/KcApp). The web app is in production [here](https://datalab.sspcloud.fr). Main takeaways are: -- You must declare your custom pages in the package.json. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/package.json#L17-L22) -- (TS only) You must declare theses page in the type argument of the getter - function for the `kcContext` in order to have the correct typings. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L16-L21) -- (TS only) If you use Keycloak plugins that defines non standard `.ftl` values - (Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) - that define `authorizedMailDomains` in `register.ftl`) you should - declare theses value to get the type. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L6-L13) -- You should provide sample data for all the non standard value if you want to be able - to debug the page outside of keycloak. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L28-L43) + +- You must declare your custom pages in the package.json. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/package.json#L17-L22) +- (TS only) You must declare theses page in the type argument of the getter + function for the `kcContext` in order to have the correct typings. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L16-L21) +- (TS only) If you use Keycloak plugins that defines non standard `.ftl` values + (Like for example [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) + that define `authorizedMailDomains` in `register.ftl`) you should + declare theses value to get the type. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L6-L13) +- You should provide sample data for all the non standard value if you want to be able + to debug the page outside of keycloak. [example](https://github.com/garronej/keycloakify-demo-app/blob/4eb2a9f63e9823e653b2d439495bda55e5ecc134/src/KcApp/kcContext.ts#L28-L43) WARNING: If you chose to go this way use: + ```json "dependencies": { "keycloakify": "~X.Y.Z" } ``` + in your `package.json` instead of `^X.Y.Z`. A minor update of Keycloakify might break your app. ### Hot reload Rebuild the theme each time you make a change to see the result is not practical. -If you want to test your login screens outside of Keycloak you can mock a given `kcContext`: +If you want to test your login screens outside of Keycloak you can mock a given `kcContext`: ```tsx import { @@ -257,9 +264,9 @@ const { kcContext } = getKcContext({ }); reactDom.render( - document.getElementById("root") ); @@ -274,15 +281,15 @@ Checkout [this concrete example](https://github.com/garronej/keycloakify-demo-ap By default the theme generated is standalone. Meaning that when your users reach the login pages all scripts, images and stylesheet are downloaded from the Keycloak server. If you are specifically building a theme to integrate with an app or a website that allows users -to first browse unauthenticated before logging in, you will get a significant +to first browse unauthenticated before logging in, you will get a significant performance boost if you jump through those hoops: -- Provide the url of your app in the `homepage` field of package.json. [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/package.json#L2) -- Build the theme using `npx build-keycloak-theme --external-assets` [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/.github/workflows/ci.yaml#L21) -- Enable [long-term assets caching](https://create-react-app.dev/docs/production-build/#static-file-caching) on the server hosting your app. -- Make sure not to build your app and the keycloak theme separately - and remember to update the Keycloak theme every time you update your app. -- Be mindful that if your app is down your login pages are down as well. +- Provide the url of your app in the `homepage` field of package.json. [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/package.json#L2) +- Build the theme using `npx build-keycloak-theme --external-assets` [ex](https://github.com/garronej/keycloakify-demo-app/blob/7847cc70ef374ab26a6cc7953461cf25603e9a6d/.github/workflows/ci.yaml#L21) +- Enable [long-term assets caching](https://create-react-app.dev/docs/production-build/#static-file-caching) on the server hosting your app. +- Make sure not to build your app and the keycloak theme separately + and remember to update the Keycloak theme every time you update your app. +- Be mindful that if your app is down your login pages are down as well. Checkout a complete setup [here](https://github.com/garronej/keycloakify-demo-app#about-keycloakify) @@ -308,51 +315,51 @@ If you need to customize pages that are not supported yet or if you need to impl [Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate the building and publishing of the theme (the .jar file). + # Limitations + ## `process.env.PUBLIC_URL` not supported. -You won't be able to [import things from your public directory **in your JavaScript code**](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system). +You won't be able to [import things from your public directory **in your JavaScript code**](https://create-react-app.dev/docs/using-the-public-folder/#adding-assets-outside-of-the-module-system). (This isn't recommended anyway). - - ## `@font-face` importing fonts from the `src/` dir -If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-) +If you are building the theme with [--external-assets](#enable-loading-in-a-blink-of-a-eye-of-login-pages-) this limitation doesn't apply, you can import fonts however you see fit. -### Example of setup that **won't** work +### Example of setup that **won't** work -- We have a `fonts/` directory in `src/` -- We import the font like this [`src: url("/fonts/my-font.woff2") format("woff2");`](https://github.com/garronej/keycloakify-demo-app/blob/07d54a3012ef354ee12b1374c6f7ad1cb125d56b/src/fonts.scss#L4) in a `.scss` a file. +- We have a `fonts/` directory in `src/` +- We import the font like this [`src: url("/fonts/my-font.woff2") format("woff2");`](https://github.com/garronej/keycloakify-demo-app/blob/07d54a3012ef354ee12b1374c6f7ad1cb125d56b/src/fonts.scss#L4) in a `.scss` a file. -### Possible workarounds +### Possible workarounds -- Use [`--external-assets`](#enable-loading-in-a-blink-of-a-eye-of-login-pages-). -- If it is possible, use Google Fonts or any other font provider. -- If you want to host your font recommended approach is to move your fonts into the `public` -directory and to place your `@font-face` statements in the `public/index.html`. -Example [here](https://github.com/InseeFrLab/onyxia-ui/blob/0e3a04610cfe872ca71dad59e05ced8f785dee4b/public/index.html#L6-L51). -- You can also [use non relative url](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/src/fonts.scss#L16) but don't forget [`Access-Control-Allow-Origin`](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/nginx.conf#L17-L19). +- Use [`--external-assets`](#enable-loading-in-a-blink-of-a-eye-of-login-pages-). +- If it is possible, use Google Fonts or any other font provider. +- If you want to host your font recommended approach is to move your fonts into the `public` + directory and to place your `@font-face` statements in the `public/index.html`. + Example [here](https://github.com/InseeFrLab/onyxia-ui/blob/0e3a04610cfe872ca71dad59e05ced8f785dee4b/public/index.html#L6-L51). +- You can also [use non relative url](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/src/fonts.scss#L16) but don't forget [`Access-Control-Allow-Origin`](https://github.com/garronej/keycloakify-demo-app/blob/2de8a9eb6f5de9c94f9cd3991faad0377e63268c/nginx.conf#L17-L19). # Implement context persistence (optional) -If, before logging in, a user has selected a specific language +If, before logging in, a user has selected a specific language you don't want it to be reset to default when the user gets redirected to -the login or register pages. - +the login or register pages. + Same goes for the dark mode, you don't want, if the user had it enabled -to show the login page with light themes. - +to show the login page with light themes. + The problem is that you are probably using `localStorage` to persist theses values across reload but, as the Keycloak pages are not served on the same domain that the rest of your -app you won't be able to carry over states using `localStorage`. +app you won't be able to carry over states using `localStorage`. The only reliable solution is to inject parameters into the URL before -redirecting to Keycloak. We integrate with -[`keycloak-js`](https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/javascript-adapter.adoc), +redirecting to Keycloak. We integrate with +[`keycloak-js`](https://github.com/keycloak/keycloak-documentation/blob/master/securing_apps/topics/oidc/javascript-adapter.adoc), by providing you a way to tell `keycloak-js` that you would like to inject -some search parameters before redirecting. +some search parameters before redirecting. The method also works with [`@react-keycloak/web`](https://www.npmjs.com/package/@react-keycloak/web) (use the `initOptions`). @@ -368,23 +375,24 @@ Note that the states are automatically restored on the other side by `powerhooks ```typescript import keycloak_js from "keycloak-js"; import { injectGlobalStatesInSearchParams } from "powerhooks/useGlobalState"; -import { createKeycloakAdapter } from "keycloakify"; +import { createKeycloakAdapter } from "keycloakify"; //... const keycloakInstance = keycloak_js({ "url": "http://keycloak-server/auth", "realm": "myrealm", - "clientId": "myapp" + "clientId": "myapp", }); keycloakInstance.init({ - "onLoad": 'check-sso', - "silentCheckSsoRedirectUri": window.location.origin + "/silent-check-sso.html", + "onLoad": "check-sso", + "silentCheckSsoRedirectUri": + window.location.origin + "/silent-check-sso.html", "adapter": createKeycloakAdapter({ "transformUrlBeforeRedirect": injectGlobalStatesInSearchParams, - keycloakInstance - }) + keycloakInstance, + }), }); //... @@ -396,12 +404,13 @@ flash of the blank html before the js bundle have been evaluated # Kickstart video -*NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded* +_NOTE: keycloak-react-theming was renamed keycloakify since this video was recorded_ [![kickstart_video](https://user-images.githubusercontent.com/6702424/108877866-f146ee80-75ff-11eb-8120-003b3c5f6dd8.png)](https://youtu.be/xTz0Rj7i2v8) -# About the errors related to `objectToJson` in Keycloak logs. +# About the errors related to `objectToJson` in Keycloak logs. + +The logs of your keycloak server will always show this kind of errors every time a client request a page: -The logs of your keycloak server will always show this kind of errors every time a client request a page: ```log FTL stack trace ("~" means nesting-related): - Failed at: #local value = object[key] [in template "login.ftl" in macro "objectToJson" at line 70, column 21] @@ -410,13 +419,14 @@ FTL stack trace ("~" means nesting-related): - Reached through: @compress [in template "login.ftl" in macro "objectToJson" at line 36, column 5] - Reached through: @objectToJson object=(.data_model) de... [in template "login.ftl" at line 163, column 43] ``` -Theses are expected and can be safely ignored. -To [converts the `.ftl` values into a JavaScript object](https://github.com/InseeFrLab/keycloakify/blob/main/src/bin/build-keycloak-theme/generateFtl/common.ftl) +Theses are expected and can be safely ignored. + +To [converts the `.ftl` values into a JavaScript object](https://github.com/InseeFrLab/keycloakify/blob/main/src/bin/build-keycloak-theme/generateFtl/common.ftl) without making assumptions on the `.data_model` we have to do things that throws. It's all-right though because every statement that can fail is inside an `<#attempt><#recorver>` block but it results in errors being printed to the logs. # Email domain whitelist -If you want to restrict the emails domain that can register, you can use [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) +If you want to restrict the emails domain that can register, you can use [this plugin](https://github.com/micedre/keycloak-mail-whitelisting) and `kcRegisterContext["authorizedMailDomains"]` to validate on. diff --git a/package.json b/package.json index 4a12451f..0b29f31e 100755 --- a/package.json +++ b/package.json @@ -16,8 +16,6 @@ "copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/", "generate-messages": "node dist/bin/generate-i18n-messages.js", "link_in_test_app": "node dist/bin/link_in_test_app.js", - "lint:check": "eslint . --ext .ts,.tsx", - "lint": "yarn lint:check --fix", "_format": "prettier '**/*.{ts,tsx,json,md}'", "format": "yarn _format --write", "format:check": "yarn _format --list-different", @@ -28,9 +26,6 @@ "download-builtin-keycloak-theme": "dist/bin/download-builtin-keycloak-theme.js" }, "lint-staged": { - "*.{ts,tsx}": [ - "eslint --fix" - ], "*.{ts,tsx,json,md}": [ "prettier --write" ] @@ -68,12 +63,9 @@ "react": "^17.0.1", "rimraf": "^3.0.2", "typescript": "^4.2.3", - "@typescript-eslint/eslint-plugin": "^4.24.0", - "@typescript-eslint/parser": "^4.24.0", - "eslint": "^7.26.0", - "eslint-config-prettier": "^8.3.0", "husky": "^4.3.8", - "lint-staged": "^11.0.0" + "lint-staged": "^11.0.0", + "prettier": "^2.3.0" }, "dependencies": { "@emotion/react": "^11.4.1", diff --git a/src/bin/KeycloakVersion.ts b/src/bin/KeycloakVersion.ts index 825595cf..6b0c17cb 100644 --- a/src/bin/KeycloakVersion.ts +++ b/src/bin/KeycloakVersion.ts @@ -1,5 +1,3 @@ - export const keycloakVersions = ["11.0.3", "15.0.2"] as const; export type KeycloakVersion = typeof keycloakVersions[number]; - diff --git a/src/bin/build-keycloak-theme/build-keycloak-theme.ts b/src/bin/build-keycloak-theme/build-keycloak-theme.ts index 4ce76dc8..eac73d65 100644 --- a/src/bin/build-keycloak-theme/build-keycloak-theme.ts +++ b/src/bin/build-keycloak-theme/build-keycloak-theme.ts @@ -1,8 +1,15 @@ import { generateKeycloakThemeResources } from "./generateKeycloakThemeResources"; import { generateJavaStackFiles } from "./generateJavaStackFiles"; -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 * as child_process from "child_process"; -import { generateDebugFiles, containerLaunchScriptBasename } from "./generateDebugFiles"; +import { + generateDebugFiles, + containerLaunchScriptBasename, +} from "./generateDebugFiles"; import { URL } from "url"; type ParsedPackageJson = { @@ -13,21 +20,34 @@ type ParsedPackageJson = { const reactProjectDirPath = process.cwd(); -const doUseExternalAssets = process.argv[2]?.toLowerCase() === "--external-assets"; +const doUseExternalAssets = + process.argv[2]?.toLowerCase() === "--external-assets"; -const parsedPackageJson: ParsedPackageJson = require(pathJoin(reactProjectDirPath, "package.json")); +const parsedPackageJson: ParsedPackageJson = require(pathJoin( + reactProjectDirPath, + "package.json", +)); -export const keycloakThemeBuildingDirPath = pathJoin(reactProjectDirPath, "build_keycloak"); +export const keycloakThemeBuildingDirPath = pathJoin( + reactProjectDirPath, + "build_keycloak", +); function sanitizeThemeName(name: string) { - return name.replace(/^@(.*)/, '$1').split('/').join('-'); + return name + .replace(/^@(.*)/, "$1") + .split("/") + .join("-"); } export function main() { console.log("🔏 Building the keycloak theme...⌚"); - const extraPagesId: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraPages"] ?? []; - const extraThemeProperties: string[] = (parsedPackageJson as any)["keycloakify"]?.["extraThemeProperties"] ?? []; + const extraPagesId: string[] = + (parsedPackageJson as any)["keycloakify"]?.["extraPages"] ?? []; + const extraThemeProperties: string[] = + (parsedPackageJson as any)["keycloakify"]?.["extraThemeProperties"] ?? + []; const themeName = sanitizeThemeName(parsedPackageJson.name); generateKeycloakThemeResources({ @@ -35,105 +55,111 @@ export function main() { "reactAppBuildDirPath": pathJoin(reactProjectDirPath, "build"), themeName, ...(() => { - const url = (() => { - const { homepage } = parsedPackageJson; - return homepage === undefined ? - undefined : - new URL(homepage); - + return homepage === undefined ? undefined : new URL(homepage); })(); return { "urlPathname": - url === undefined ? - "/" : - url.pathname.replace(/([^/])$/, "$1/"), - "urlOrigin": !doUseExternalAssets ? undefined : (() => { - - if (url === undefined) { - console.error("ERROR: You must specify 'homepage' in your package.json"); - process.exit(-1); - } - - return url.origin; - - })() + url === undefined + ? "/" + : url.pathname.replace(/([^/])$/, "$1/"), + "urlOrigin": !doUseExternalAssets + ? undefined + : (() => { + if (url === undefined) { + console.error( + "ERROR: You must specify 'homepage' in your package.json", + ); + process.exit(-1); + } + return url.origin; + })(), }; - })(), extraPagesId, extraThemeProperties, - //We have to leave it at that otherwise we break our default theme. + //We have to leave it at that otherwise we break our default theme. //Problem is that we can't guarantee that the the old resources common - //will still be available on the newer keycloak version. - "keycloakVersion": "11.0.3" + //will still be available on the newer keycloak version. + "keycloakVersion": "11.0.3", }); const { jarFilePath } = generateJavaStackFiles({ version: parsedPackageJson.version, themeName, homepage: parsedPackageJson.homepage, - keycloakThemeBuildingDirPath + keycloakThemeBuildingDirPath, }); - child_process.execSync( - "mvn package", - { "cwd": keycloakThemeBuildingDirPath } - ); + child_process.execSync("mvn package", { + "cwd": keycloakThemeBuildingDirPath, + }); generateDebugFiles({ keycloakThemeBuildingDirPath, themeName, - "keycloakVersion": "15.0.2" + "keycloakVersion": "15.0.2", }); - console.log([ - '', - `✅ Your keycloak theme has been generated and bundled into ./${pathRelative(reactProjectDirPath, jarFilePath)} 🚀`, - `It is to be placed in "/opt/jboss/keycloak/standalone/deployments" in the container running a jboss/keycloak Docker image.`, - '', - 'Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:', - '', - 'value.yaml: ', - ' extraInitContainers: |', - ' - name: realm-ext-provider', - ' image: curlimages/curl', - ' imagePullPolicy: IfNotPresent', - ' command:', - ' - sh', - ' args:', - ' - -c', - ` - curl -L -f -S -o /extensions/${pathBasename(jarFilePath)} https://AN.URL.FOR/${pathBasename(jarFilePath)}`, - ' volumeMounts:', - ' - name: extensions', - ' mountPath: /extensions', - ' ', - ' extraVolumeMounts: |', - ' - name: extensions', - ' mountPath: /opt/jboss/keycloak/standalone/deployments', - ' extraEnv: |', - ' - name: KEYCLOAK_USER', - ' value: admin', - ' - name: KEYCLOAK_PASSWORD', - ' value: xxxxxxxxx', - ' - name: JAVA_OPTS', - ' value: -Dkeycloak.profile=preview', - '', - '', - 'To test your theme locally, with hot reloading, you can spin up a Keycloak container image with the theme loaded by running:', - '', - `👉 $ ./${pathRelative(reactProjectDirPath, pathJoin(keycloakThemeBuildingDirPath, containerLaunchScriptBasename))} 👈`, - '', - 'To enable the theme within keycloak log into the admin console ( 👉 http://localhost:8080 username: admin, password: admin 👈), create a realm (called "myrealm" for example),', - `go to your realm settings, click on the theme tab then select ${themeName}.`, - `More details: https://www.keycloak.org/getting-started/getting-started-docker`, - '', - 'Once your container is up and configured 👉 http://localhost:8080/auth/realms/myrealm/account 👈', - '', - ].join("\n")); - + console.log( + [ + "", + `✅ Your keycloak theme has been generated and bundled into ./${pathRelative( + reactProjectDirPath, + jarFilePath, + )} 🚀`, + `It is to be placed in "/opt/jboss/keycloak/standalone/deployments" in the container running a jboss/keycloak Docker image.`, + "", + "Using Helm (https://github.com/codecentric/helm-charts), edit to reflect:", + "", + "value.yaml: ", + " extraInitContainers: |", + " - name: realm-ext-provider", + " image: curlimages/curl", + " imagePullPolicy: IfNotPresent", + " command:", + " - sh", + " args:", + " - -c", + ` - curl -L -f -S -o /extensions/${pathBasename( + jarFilePath, + )} https://AN.URL.FOR/${pathBasename(jarFilePath)}`, + " volumeMounts:", + " - name: extensions", + " mountPath: /extensions", + " ", + " extraVolumeMounts: |", + " - name: extensions", + " mountPath: /opt/jboss/keycloak/standalone/deployments", + " extraEnv: |", + " - name: KEYCLOAK_USER", + " value: admin", + " - name: KEYCLOAK_PASSWORD", + " value: xxxxxxxxx", + " - name: JAVA_OPTS", + " value: -Dkeycloak.profile=preview", + "", + "", + "To test your theme locally, with hot reloading, you can spin up a Keycloak container image with the theme loaded by running:", + "", + `👉 $ ./${pathRelative( + reactProjectDirPath, + pathJoin( + keycloakThemeBuildingDirPath, + containerLaunchScriptBasename, + ), + )} 👈`, + "", + 'To enable the theme within keycloak log into the admin console ( 👉 http://localhost:8080 username: admin, password: admin 👈), create a realm (called "myrealm" for example),', + `go to your realm settings, click on the theme tab then select ${themeName}.`, + `More details: https://www.keycloak.org/getting-started/getting-started-docker`, + "", + "Once your container is up and configured 👉 http://localhost:8080/auth/realms/myrealm/account 👈", + "", + ].join("\n"), + ); } diff --git a/src/bin/build-keycloak-theme/ftlValuesGlobalName.ts b/src/bin/build-keycloak-theme/ftlValuesGlobalName.ts index 305f63c7..eb63e562 100644 --- a/src/bin/build-keycloak-theme/ftlValuesGlobalName.ts +++ b/src/bin/build-keycloak-theme/ftlValuesGlobalName.ts @@ -1,2 +1 @@ - -export const ftlValuesGlobalName = "kcContext"; \ No newline at end of file +export const ftlValuesGlobalName = "kcContext"; diff --git a/src/bin/build-keycloak-theme/generateDebugFiles/generateDebugFiles.ts b/src/bin/build-keycloak-theme/generateDebugFiles/generateDebugFiles.ts index a24fc0a7..b0e7591b 100644 --- a/src/bin/build-keycloak-theme/generateDebugFiles/generateDebugFiles.ts +++ b/src/bin/build-keycloak-theme/generateDebugFiles/generateDebugFiles.ts @@ -1,18 +1,15 @@ - import * as fs from "fs"; -import { join as pathJoin, dirname as pathDirname, } from "path"; +import { join as pathJoin, dirname as pathDirname } from "path"; -export const containerLaunchScriptBasename = "start_keycloak_testing_container.sh"; +export const containerLaunchScriptBasename = + "start_keycloak_testing_container.sh"; /** Files for being able to run a hot reload keycloak container */ -export function generateDebugFiles( - params: { - keycloakVersion: "11.0.3" | "15.0.2"; - themeName: string; - keycloakThemeBuildingDirPath: string; - } -) { - +export function generateDebugFiles(params: { + keycloakVersion: "11.0.3" | "15.0.2"; + themeName: string; + keycloakThemeBuildingDirPath: string; +}) { const { themeName, keycloakThemeBuildingDirPath, keycloakVersion } = params; fs.writeFileSync( @@ -29,8 +26,8 @@ export function generateDebugFiles( "", 'ENTRYPOINT [ "/opt/jboss/tools/docker-entrypoint.sh" ]', ].join("\n"), - "utf8" - ) + "utf8", + ), ); const dockerImage = `${themeName}/keycloak-hot-reload`; @@ -54,42 +51,53 @@ export function generateDebugFiles( " -e KEYCLOAK_USER=admin \\", " -e KEYCLOAK_PASSWORD=admin \\", " -e JAVA_OPTS=-Dkeycloak.profile=preview \\", - ` -v ${pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName) - }:/opt/jboss/keycloak/themes/${themeName}:rw \\`, + ` -v ${pathJoin( + keycloakThemeBuildingDirPath, + "src", + "main", + "resources", + "theme", + themeName, + )}:/opt/jboss/keycloak/themes/${themeName}:rw \\`, ` -it ${dockerImage}:latest`, - "" + "", ].join("\n"), - "utf8" + "utf8", ), - { "mode": 0o755 } + { "mode": 0o755 }, ); - const standaloneHaFilePath = pathJoin(keycloakThemeBuildingDirPath, "configuration", `standalone-ha.xml`); + const standaloneHaFilePath = pathJoin( + keycloakThemeBuildingDirPath, + "configuration", + `standalone-ha.xml`, + ); - try { fs.mkdirSync(pathDirname(standaloneHaFilePath)); } catch { } + try { + fs.mkdirSync(pathDirname(standaloneHaFilePath)); + } catch {} fs.writeFileSync( standaloneHaFilePath, - fs.readFileSync( - pathJoin( - __dirname, - `standalone-ha_${keycloakVersion}.xml` + fs + .readFileSync( + pathJoin(__dirname, `standalone-ha_${keycloakVersion}.xml`), ) - ) .toString("utf8") .replace( - new RegExp([ - "2592000", - "true", - "true" - ].join("\\s*"), "g" + new RegExp( + [ + "2592000", + "true", + "true", + ].join("\\s*"), + "g", ), [ "-1", "false", - "false" - ].join("\n") - ) + "false", + ].join("\n"), + ), ); - -} \ No newline at end of file +} diff --git a/src/bin/build-keycloak-theme/generateDebugFiles/index.ts b/src/bin/build-keycloak-theme/generateDebugFiles/index.ts index 3115f415..acfa69a9 100644 --- a/src/bin/build-keycloak-theme/generateDebugFiles/index.ts +++ b/src/bin/build-keycloak-theme/generateDebugFiles/index.ts @@ -1 +1 @@ -export * from "./generateDebugFiles"; \ No newline at end of file +export * from "./generateDebugFiles"; diff --git a/src/bin/build-keycloak-theme/generateFtl/generateFtl.ts b/src/bin/build-keycloak-theme/generateFtl/generateFtl.ts index f6cbd866..7b509736 100644 --- a/src/bin/build-keycloak-theme/generateFtl/generateFtl.ts +++ b/src/bin/build-keycloak-theme/generateFtl/generateFtl.ts @@ -2,7 +2,7 @@ import cheerio from "cheerio"; import { replaceImportsFromStaticInJsCode, replaceImportsInInlineCssCode, - generateCssCodeToDefineGlobals + generateCssCodeToDefineGlobals, } from "../replaceImportFromStatic"; import fs from "fs"; import { join as pathJoin } from "path"; @@ -10,62 +10,62 @@ import { objectKeys } from "tsafe/objectKeys"; import { ftlValuesGlobalName } from "../ftlValuesGlobalName"; export const pageIds = [ - "login.ftl", "register.ftl", "register-user-profile.ftl", - "info.ftl", "error.ftl", "login-reset-password.ftl", - "login-verify-email.ftl", "terms.ftl", "login-otp.ftl", - "login-update-profile.ftl", "login-idp-link-confirm.ftl" + "login.ftl", + "register.ftl", + "register-user-profile.ftl", + "info.ftl", + "error.ftl", + "login-reset-password.ftl", + "login-verify-email.ftl", + "terms.ftl", + "login-otp.ftl", + "login-update-profile.ftl", + "login-idp-link-confirm.ftl", ] as const; export type PageId = typeof pageIds[number]; function loadAdjacentFile(fileBasename: string) { - return fs.readFileSync(pathJoin(__dirname, fileBasename)) - .toString("utf8"); -}; + return fs.readFileSync(pathJoin(__dirname, fileBasename)).toString("utf8"); +} - -export function generateFtlFilesCodeFactory( - params: { - cssGlobalsToDefine: Record; - indexHtmlCode: string; - urlPathname: string; - urlOrigin: undefined | string; - } -) { - - const { cssGlobalsToDefine, indexHtmlCode, urlPathname, urlOrigin } = params; +export function generateFtlFilesCodeFactory(params: { + cssGlobalsToDefine: Record; + indexHtmlCode: string; + urlPathname: string; + urlOrigin: undefined | string; +}) { + const { cssGlobalsToDefine, indexHtmlCode, urlPathname, urlOrigin } = + params; const $ = cheerio.load(indexHtmlCode); $("script:not([src])").each((...[, element]) => { - const { fixedJsCode } = replaceImportsFromStaticInJsCode({ "jsCode": $(element).html()!, - urlOrigin + urlOrigin, }); $(element).text(fixedJsCode); - }); $("style").each((...[, element]) => { - const { fixedCssCode } = replaceImportsInInlineCssCode({ "cssCode": $(element).html()!, "urlPathname": params.urlPathname, - urlOrigin + urlOrigin, }); $(element).text(fixedCssCode); - }); - ([ - ["link", "href"], - ["script", "src"], - ] as const).forEach(([selector, attrName]) => + ( + [ + ["link", "href"], + ["script", "src"], + ] as const + ).forEach(([selector, attrName]) => $(selector).each((...[, element]) => { - const href = $(element).attr(attrName); if (href === undefined) { @@ -74,94 +74,90 @@ export function generateFtlFilesCodeFactory( $(element).attr( attrName, - urlOrigin !== undefined ? - href.replace(/^\//, `${urlOrigin}/`) : - href.replace( - new RegExp(`^${urlPathname.replace(/\//g, "\\/")}`), - "${url.resourcesPath}/build/" - ) + urlOrigin !== undefined + ? href.replace(/^\//, `${urlOrigin}/`) + : href.replace( + new RegExp(`^${urlPathname.replace(/\//g, "\\/")}`), + "${url.resourcesPath}/build/", + ), ); - - }) + }), ); //FTL is no valid html, we can't insert with cheerio, we put placeholder for injecting later. const ftlPlaceholders = { - '{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadAdjacentFile("common.ftl") - .match(/^', - ' ', - '' - ].join("\n") + '{ "x": "vIdLqMeOed9sdLdIdOxdK0d" }': loadAdjacentFile( + "common.ftl", + ).match(/^', + " ", + "", + ].join("\n"), }; - const pageSpecificCodePlaceholder = ""; + const pageSpecificCodePlaceholder = + ""; $("head").prepend( [ - ...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [ - '', - '', - '' - ]), + ...(Object.keys(cssGlobalsToDefine).length === 0 + ? [] + : [ + "", + "", + "", + ]), "", - '', - '', + " );", + "", + "", pageSpecificCodePlaceholder, - '', - objectKeys(ftlPlaceholders)[1] + "", + objectKeys(ftlPlaceholders)[1], ].join("\n"), ); const partiallyFixedIndexHtmlCode = $.html(); - function generateFtlFilesCode( - params: { - pageId: string; - } - ): { ftlCode: string; } { - + function generateFtlFilesCode(params: { pageId: string }): { + ftlCode: string; + } { const { pageId } = params; const $ = cheerio.load(partiallyFixedIndexHtmlCode); - let ftlCode = $.html() - .replace( - pageSpecificCodePlaceholder, - [ - '' - ].join("\n") - ); + let ftlCode = $.html().replace( + pageSpecificCodePlaceholder, + [ + "", + ].join("\n"), + ); - objectKeys(ftlPlaceholders) - .forEach(id => ftlCode = ftlCode.replace(id, ftlPlaceholders[id])); + objectKeys(ftlPlaceholders).forEach( + id => (ftlCode = ftlCode.replace(id, ftlPlaceholders[id])), + ); return { ftlCode }; - } return { generateFtlFilesCode }; - - -} \ No newline at end of file +} diff --git a/src/bin/build-keycloak-theme/generateFtl/index.ts b/src/bin/build-keycloak-theme/generateFtl/index.ts index de891f55..3a6d8e56 100644 --- a/src/bin/build-keycloak-theme/generateFtl/index.ts +++ b/src/bin/build-keycloak-theme/generateFtl/index.ts @@ -1 +1 @@ -export * from "./generateFtl"; \ No newline at end of file +export * from "./generateFtl"; diff --git a/src/bin/build-keycloak-theme/generateJavaStackFiles.ts b/src/bin/build-keycloak-theme/generateJavaStackFiles.ts index 7111dd7c..40b11156 100644 --- a/src/bin/build-keycloak-theme/generateJavaStackFiles.ts +++ b/src/bin/build-keycloak-theme/generateJavaStackFiles.ts @@ -1,39 +1,33 @@ - import * as url from "url"; import * as fs from "fs"; import { join as pathJoin, dirname as pathDirname } from "path"; - -export function generateJavaStackFiles( - params: { - version: string; - themeName: string; - homepage?: string; - keycloakThemeBuildingDirPath: string; - } -): { jarFilePath: string; } { - - const { - themeName, - version, - homepage, - keycloakThemeBuildingDirPath - } = params; +export function generateJavaStackFiles(params: { + version: string; + themeName: string; + homepage?: string; + keycloakThemeBuildingDirPath: string; +}): { jarFilePath: string } { + const { themeName, version, homepage, keycloakThemeBuildingDirPath } = + params; { - - const { pomFileCode } = (function generatePomFileCode(): { pomFileCode: string; } { - - + const { pomFileCode } = (function generatePomFileCode(): { + pomFileCode: string; + } { const groupId = (() => { - const fallbackGroupId = `there.was.no.homepage.field.in.the.package.json.${themeName}`; - return (!homepage ? - fallbackGroupId : - url.parse(homepage).host?.replace(/:[0-9]+$/,"")?.split(".").reverse().join(".") ?? fallbackGroupId - ) + ".keycloak"; - + return ( + (!homepage + ? fallbackGroupId + : url + .parse(homepage) + .host?.replace(/:[0-9]+$/, "") + ?.split(".") + .reverse() + .join(".") ?? fallbackGroupId) + ".keycloak" + ); })(); const artefactId = `${themeName}-keycloak-theme`; @@ -49,51 +43,57 @@ export function generateJavaStackFiles( ` ${version}`, ` ${artefactId}`, ` `, - `` + ``, ].join("\n"); return { pomFileCode }; - })(); fs.writeFileSync( pathJoin(keycloakThemeBuildingDirPath, "pom.xml"), - Buffer.from(pomFileCode, "utf8") + Buffer.from(pomFileCode, "utf8"), ); - } { - const themeManifestFilePath = pathJoin( - keycloakThemeBuildingDirPath, "src", "main", - "resources", "META-INF", "keycloak-themes.json" + keycloakThemeBuildingDirPath, + "src", + "main", + "resources", + "META-INF", + "keycloak-themes.json", ); try { - fs.mkdirSync(pathDirname(themeManifestFilePath)); - - } catch { } + } catch {} fs.writeFileSync( themeManifestFilePath, Buffer.from( - JSON.stringify({ - "themes": [ - { - "name": themeName, - "types": ["login"] - } - ] - }, null, 2), - "utf8" - ) + JSON.stringify( + { + "themes": [ + { + "name": themeName, + "types": ["login"], + }, + ], + }, + null, + 2, + ), + "utf8", + ), ); - } - return { "jarFilePath": pathJoin(keycloakThemeBuildingDirPath, "target", `${themeName}-${version}.jar`) }; - + return { + "jarFilePath": pathJoin( + keycloakThemeBuildingDirPath, + "target", + `${themeName}-${version}.jar`, + ), + }; } - diff --git a/src/bin/build-keycloak-theme/generateKeycloakThemeResources.ts b/src/bin/build-keycloak-theme/generateKeycloakThemeResources.ts index 95d201be..b61eebf7 100644 --- a/src/bin/build-keycloak-theme/generateKeycloakThemeResources.ts +++ b/src/bin/build-keycloak-theme/generateKeycloakThemeResources.ts @@ -1,178 +1,197 @@ - import { transformCodebase } from "../tools/transformCodebase"; import * as fs from "fs"; import { join as pathJoin } from "path"; import { replaceImportsInCssCode, - replaceImportsFromStaticInJsCode + replaceImportsFromStaticInJsCode, } from "./replaceImportFromStatic"; import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl"; import { downloadBuiltinKeycloakTheme } from "../download-builtin-keycloak-theme"; import * as child_process from "child_process"; -import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/getKcContext/kcContextMocks/urlResourcesPath"; +import { + resourcesCommonPath, + resourcesPath, + subDirOfPublicDirBasename, +} from "../../lib/getKcContext/kcContextMocks/urlResourcesPath"; import { isInside } from "../tools/isInside"; - -export function generateKeycloakThemeResources( - params: { - themeName: string; - reactAppBuildDirPath: string; - keycloakThemeBuildingDirPath: string; - urlPathname: string; - //If urlOrigin is not undefined then it means --externals-assets - urlOrigin: undefined | string; - extraPagesId: string[]; - extraThemeProperties: string[]; - keycloakVersion: "11.0.3" | "15.0.2" - } -) { - - const { - themeName, reactAppBuildDirPath, keycloakThemeBuildingDirPath, - urlPathname, urlOrigin, extraPagesId, extraThemeProperties, - keycloakVersion +export function generateKeycloakThemeResources(params: { + themeName: string; + reactAppBuildDirPath: string; + keycloakThemeBuildingDirPath: string; + urlPathname: string; + //If urlOrigin is not undefined then it means --externals-assets + urlOrigin: undefined | string; + extraPagesId: string[]; + extraThemeProperties: string[]; + keycloakVersion: "11.0.3" | "15.0.2"; +}) { + const { + themeName, + reactAppBuildDirPath, + keycloakThemeBuildingDirPath, + urlPathname, + urlOrigin, + extraPagesId, + extraThemeProperties, + keycloakVersion, } = params; - const themeDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme", themeName, "login"); + const themeDirPath = pathJoin( + keycloakThemeBuildingDirPath, + "src", + "main", + "resources", + "theme", + themeName, + "login", + ); let allCssGlobalsToDefine: Record = {}; transformCodebase({ "destDirPath": - urlOrigin === undefined ? - pathJoin(themeDirPath, "resources", "build") : - reactAppBuildDirPath, + urlOrigin === undefined + ? pathJoin(themeDirPath, "resources", "build") + : reactAppBuildDirPath, "srcDirPath": reactAppBuildDirPath, "transformSourceCode": ({ filePath, sourceCode }) => { - //NOTE: Prevent cycles, excludes the folder we generated for debug in public/ if ( urlOrigin === undefined && isInside({ - "dirPath": pathJoin(reactAppBuildDirPath, subDirOfPublicDirBasename), - filePath + "dirPath": pathJoin( + reactAppBuildDirPath, + subDirOfPublicDirBasename, + ), + filePath, }) ) { return undefined; } if (urlOrigin === undefined && /\.css?$/i.test(filePath)) { - - const { cssGlobalsToDefine, fixedCssCode } = replaceImportsInCssCode( - { "cssCode": sourceCode.toString("utf8") } - ); + const { cssGlobalsToDefine, fixedCssCode } = + replaceImportsInCssCode({ + "cssCode": sourceCode.toString("utf8"), + }); allCssGlobalsToDefine = { ...allCssGlobalsToDefine, - ...cssGlobalsToDefine + ...cssGlobalsToDefine, }; - return { "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8") }; - + return { + "modifiedSourceCode": Buffer.from(fixedCssCode, "utf8"), + }; } if (/\.js?$/i.test(filePath)) { - const { fixedJsCode } = replaceImportsFromStaticInJsCode({ "jsCode": sourceCode.toString("utf8"), - urlOrigin + urlOrigin, }); - return { "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8") }; - + return { + "modifiedSourceCode": Buffer.from(fixedJsCode, "utf8"), + }; } - return urlOrigin === undefined ? - { "modifiedSourceCode": sourceCode } : - undefined; - - } + return urlOrigin === undefined + ? { "modifiedSourceCode": sourceCode } + : undefined; + }, }); const { generateFtlFilesCode } = generateFtlFilesCodeFactory({ "cssGlobalsToDefine": allCssGlobalsToDefine, - "indexHtmlCode": fs.readFileSync( - pathJoin(reactAppBuildDirPath, "index.html") - ).toString("utf8"), + "indexHtmlCode": fs + .readFileSync(pathJoin(reactAppBuildDirPath, "index.html")) + .toString("utf8"), urlPathname, - urlOrigin + urlOrigin, }); [...pageIds, ...extraPagesId].forEach(pageId => { - const { ftlCode } = generateFtlFilesCode({ pageId }); fs.mkdirSync(themeDirPath, { "recursive": true }); fs.writeFileSync( pathJoin(themeDirPath, pageId), - Buffer.from(ftlCode, "utf8") + Buffer.from(ftlCode, "utf8"), ); - }); { - const tmpDirPath = pathJoin(themeDirPath, "..", "tmp_xxKdLpdIdLd"); downloadBuiltinKeycloakTheme({ keycloakVersion, - "destDirPath": tmpDirPath + "destDirPath": tmpDirPath, }); const themeResourcesDirPath = pathJoin(themeDirPath, "resources"); transformCodebase({ - "srcDirPath": pathJoin(tmpDirPath, "keycloak", "login", "resources"), - "destDirPath": themeResourcesDirPath + "srcDirPath": pathJoin( + tmpDirPath, + "keycloak", + "login", + "resources", + ), + "destDirPath": themeResourcesDirPath, }); - const reactAppPublicDirPath = pathJoin(reactAppBuildDirPath, "..", "public"); + const reactAppPublicDirPath = pathJoin( + reactAppBuildDirPath, + "..", + "public", + ); transformCodebase({ "srcDirPath": themeResourcesDirPath, - "destDirPath": pathJoin( - reactAppPublicDirPath, - resourcesPath - ) + "destDirPath": pathJoin(reactAppPublicDirPath, resourcesPath), }); transformCodebase({ - "srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"), - "destDirPath": pathJoin( - reactAppPublicDirPath, - resourcesCommonPath - ) + "srcDirPath": pathJoin( + tmpDirPath, + "keycloak", + "common", + "resources", + ), + "destDirPath": pathJoin(reactAppPublicDirPath, resourcesCommonPath), }); - const keycloakResourcesWithinPublicDirPath = - pathJoin(reactAppPublicDirPath, subDirOfPublicDirBasename); - + const keycloakResourcesWithinPublicDirPath = pathJoin( + reactAppPublicDirPath, + subDirOfPublicDirBasename, + ); fs.writeFileSync( pathJoin(keycloakResourcesWithinPublicDirPath, "README.txt"), - Buffer.from([ - "This is just a test folder that helps develop", - "the login and register page without having to yarn build" - ].join(" ")) + Buffer.from( + [ + "This is just a test folder that helps develop", + "the login and register page without having to yarn build", + ].join(" "), + ), ); fs.writeFileSync( pathJoin(keycloakResourcesWithinPublicDirPath, ".gitignore"), - Buffer.from("*", "utf8") + Buffer.from("*", "utf8"), ); child_process.execSync(`rm -r ${tmpDirPath}`); - } fs.writeFileSync( pathJoin(themeDirPath, "theme.properties"), Buffer.from( "parent=keycloak".concat("\n\n", extraThemeProperties.join("\n\n")), - "utf8" - ) + "utf8", + ), ); - } - diff --git a/src/bin/build-keycloak-theme/index.ts b/src/bin/build-keycloak-theme/index.ts index 1e8f42a9..f2283be3 100644 --- a/src/bin/build-keycloak-theme/index.ts +++ b/src/bin/build-keycloak-theme/index.ts @@ -4,7 +4,5 @@ export * from "./build-keycloak-theme"; import { main } from "./build-keycloak-theme"; if (require.main === module) { - - main(); - -} \ No newline at end of file + main(); +} diff --git a/src/bin/build-keycloak-theme/replaceImportFromStatic.ts b/src/bin/build-keycloak-theme/replaceImportFromStatic.ts index bde269af..3e444d29 100644 --- a/src/bin/build-keycloak-theme/replaceImportFromStatic.ts +++ b/src/bin/build-keycloak-theme/replaceImportFromStatic.ts @@ -1,14 +1,10 @@ - import * as crypto from "crypto"; import { ftlValuesGlobalName } from "./ftlValuesGlobalName"; -export function replaceImportsFromStaticInJsCode( - params: { - jsCode: string; - urlOrigin: undefined | string; - } -): { fixedJsCode: string; } { - +export function replaceImportsFromStaticInJsCode(params: { + jsCode: string; + urlOrigin: undefined | string; +}): { fixedJsCode: string } { /* NOTE: @@ -23,114 +19,104 @@ export function replaceImportsFromStaticInJsCode( const { jsCode, urlOrigin } = params; - const fixedJsCode = - jsCode - .replace( - /([a-z]+\.[a-z]+)\+"static\//g, - (...[, group]) => - urlOrigin === undefined ? - `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/` : - `("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group} + "static/` - ) - .replace( - /".chunk.css",([a-z])+=([a-z]+\.[a-z]+)\+([a-z]+),/, - (...[, group1, group2, group3]) => - urlOrigin === undefined ? - `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},` : - `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group2} + ${group3},` - ); + const fixedJsCode = jsCode + .replace(/([a-z]+\.[a-z]+)\+"static\//g, (...[, group]) => + urlOrigin === undefined + ? `window.${ftlValuesGlobalName}.url.resourcesPath + "/build/static/` + : `("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group} + "static/`, + ) + .replace( + /".chunk.css",([a-z])+=([a-z]+\.[a-z]+)\+([a-z]+),/, + (...[, group1, group2, group3]) => + urlOrigin === undefined + ? `".chunk.css",${group1} = window.${ftlValuesGlobalName}.url.resourcesPath + "/build/" + ${group3},` + : `".chunk.css",${group1} = ("${ftlValuesGlobalName}" in window ? "${urlOrigin}" : "") + ${group2} + ${group3},`, + ); return { fixedJsCode }; - } -export function replaceImportsInInlineCssCode( - params: { - cssCode: string; - urlPathname: string; - urlOrigin: undefined | string; - } -): { fixedCssCode: string; } { - +export function replaceImportsInInlineCssCode(params: { + cssCode: string; + urlPathname: string; + urlOrigin: undefined | string; +}): { fixedCssCode: string } { const { cssCode, urlPathname, urlOrigin } = params; const fixedCssCode = cssCode.replace( - urlPathname === "/" ? - /url\(\/([^/][^)]+)\)/g : - new RegExp(`url\\(${urlPathname}([^)]+)\\)`, "g"), - (...[, group]) => `url(${urlOrigin === undefined ? - "${url.resourcesPath}/build/" + group : - params.urlOrigin + urlPathname + group})` + urlPathname === "/" + ? /url\(\/([^/][^)]+)\)/g + : new RegExp(`url\\(${urlPathname}([^)]+)\\)`, "g"), + (...[, group]) => + `url(${ + urlOrigin === undefined + ? "${url.resourcesPath}/build/" + group + : params.urlOrigin + urlPathname + group + })`, ); return { fixedCssCode }; - } -export function replaceImportsInCssCode( - params: { - cssCode: string; - } -): { +export function replaceImportsInCssCode(params: { cssCode: string }): { fixedCssCode: string; cssGlobalsToDefine: Record; } { - const { cssCode } = params; const cssGlobalsToDefine: Record = {}; - new Set(cssCode.match(/url\(\/[^/][^)]+\)[^;}]*/g) ?? []) - .forEach(match => - cssGlobalsToDefine[ - "url" + crypto - .createHash("sha256") - .update(match) - .digest("hex") - .substring(0, 15) - ] = match - ); + new Set(cssCode.match(/url\(\/[^/][^)]+\)[^;}]*/g) ?? []).forEach( + match => + (cssGlobalsToDefine[ + "url" + + crypto + .createHash("sha256") + .update(match) + .digest("hex") + .substring(0, 15) + ] = match), + ); let fixedCssCode = cssCode; Object.keys(cssGlobalsToDefine).forEach( cssVariableName => //NOTE: split/join pattern ~ replace all - fixedCssCode = - fixedCssCode.split(cssGlobalsToDefine[cssVariableName]) - .join(`var(--${cssVariableName})`) + (fixedCssCode = fixedCssCode + .split(cssGlobalsToDefine[cssVariableName]) + .join(`var(--${cssVariableName})`)), ); return { fixedCssCode, cssGlobalsToDefine }; - } -export function generateCssCodeToDefineGlobals( - params: { - cssGlobalsToDefine: Record; - urlPathname: string; - } -): { +export function generateCssCodeToDefineGlobals(params: { + cssGlobalsToDefine: Record; + urlPathname: string; +}): { cssCodeToPrependInHead: string; } { - const { cssGlobalsToDefine, urlPathname } = params; return { "cssCodeToPrependInHead": [ ":root {", ...Object.keys(cssGlobalsToDefine) - .map(cssVariableName => [ - `--${cssVariableName}:`, - cssGlobalsToDefine[cssVariableName] - .replace(new RegExp(`url\\(${urlPathname.replace(/\//g, "\\/")}`, "g"), "url(${url.resourcesPath}/build/") - ].join(" ")) + .map(cssVariableName => + [ + `--${cssVariableName}:`, + cssGlobalsToDefine[cssVariableName].replace( + new RegExp( + `url\\(${urlPathname.replace(/\//g, "\\/")}`, + "g", + ), + "url(${url.resourcesPath}/build/", + ), + ].join(" "), + ) .map(line => ` ${line};`), - "}" - ].join("\n") + "}", + ].join("\n"), }; - } - - - diff --git a/src/bin/download-builtin-keycloak-theme.ts b/src/bin/download-builtin-keycloak-theme.ts index 511b295a..24b1a663 100644 --- a/src/bin/download-builtin-keycloak-theme.ts +++ b/src/bin/download-builtin-keycloak-theme.ts @@ -2,52 +2,49 @@ import { keycloakThemeBuildingDirPath } from "./build-keycloak-theme"; import { join as pathJoin } from "path"; -import { downloadAndUnzip } from "./tools/downloadAndUnzip" +import { downloadAndUnzip } from "./tools/downloadAndUnzip"; import type { KeycloakVersion } from "./KeycloakVersion"; -export function downloadBuiltinKeycloakTheme( - params: { - keycloakVersion: KeycloakVersion; - destDirPath: string; - } -) { - +export function downloadBuiltinKeycloakTheme(params: { + keycloakVersion: KeycloakVersion; + destDirPath: string; +}) { const { keycloakVersion, destDirPath } = params; for (const ext of ["", "-community"]) { - downloadAndUnzip({ "destDirPath": destDirPath, "url": `https://github.com/keycloak/keycloak/archive/refs/tags/${keycloakVersion}.zip`, - "pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme` + "pathOfDirToExtractInArchive": `keycloak-${keycloakVersion}/themes/src/main/resources${ext}/theme`, }); - } - } if (require.main === module) { - const keycloakVersion = (() => { - - const keycloakVersion = process.argv[2] as (KeycloakVersion | undefined); + const keycloakVersion = process.argv[2] as KeycloakVersion | undefined; if (keycloakVersion === undefined) { return "15.0.2"; } return keycloakVersion; - })(); - const destDirPath = pathJoin(keycloakThemeBuildingDirPath, "src", "main", "resources", "theme"); + const destDirPath = pathJoin( + keycloakThemeBuildingDirPath, + "src", + "main", + "resources", + "theme", + ); - console.log(`Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`); + console.log( + `Downloading builtins theme of Keycloak ${keycloakVersion} here ${destDirPath}`, + ); downloadBuiltinKeycloakTheme({ keycloakVersion, - destDirPath + destDirPath, }); - } - diff --git a/src/bin/generate-i18n-messages.ts b/src/bin/generate-i18n-messages.ts index 1d399c0f..5868cc20 100644 --- a/src/bin/generate-i18n-messages.ts +++ b/src/bin/generate-i18n-messages.ts @@ -11,7 +11,6 @@ import { keycloakVersions } from "./KeycloakVersion"; const propertiesParser = require("properties-parser"); for (const keycloakVersion of keycloakVersions) { - console.log({ keycloakVersion }); const tmpDirPath = pathJoin(getProjectRoot(), "tmp_xImOef9dOd44"); @@ -20,20 +19,21 @@ for (const keycloakVersion of keycloakVersions) { downloadBuiltinKeycloakTheme({ keycloakVersion, - "destDirPath": tmpDirPath + "destDirPath": tmpDirPath, }); type Dictionary = { [idiomId: string]: string }; - const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = {}; + const record: { [typeOfPage: string]: { [language: string]: Dictionary } } = + {}; { - const baseThemeDirPath = pathJoin(tmpDirPath, "base"); crawl(baseThemeDirPath).forEach(filePath => { - - const match = filePath.match(/^([^/]+)\/messages\/messages_([^.]+)\.properties$/); + const match = filePath.match( + /^([^/]+)\/messages\/messages_([^.]+)\.properties$/, + ); if (match === null) { return; @@ -45,43 +45,58 @@ for (const keycloakVersion of keycloakVersions) { Object.fromEntries( Object.entries( propertiesParser.parse( - fs.readFileSync( - pathJoin(baseThemeDirPath, filePath) - ) - .toString("utf8") - ) - ).map(([key, value]: any) => [key, value.replace(/''/g, "'")]) + fs + .readFileSync( + pathJoin(baseThemeDirPath, filePath), + ) + .toString("utf8"), + ), + ).map(([key, value]: any) => [ + key, + value.replace(/''/g, "'"), + ]), ); - }); - } rm_r(tmpDirPath); - const targetDirPath = pathJoin(getProjectRoot(), "src", "lib", "i18n", "generated_kcMessages", keycloakVersion); + const targetDirPath = pathJoin( + getProjectRoot(), + "src", + "lib", + "i18n", + "generated_kcMessages", + keycloakVersion, + ); fs.mkdirSync(targetDirPath, { "recursive": true }); Object.keys(record).forEach(pageType => { - const filePath = pathJoin(targetDirPath, `${pageType}.ts`); fs.writeFileSync( filePath, Buffer.from( [ - `//This code was automatically generated by running ${pathRelative(getProjectRoot(), __filename)}`, - '//PLEASE DO NOT EDIT MANUALLY', - '', - '/* spell-checker: disable */', - `export const kcMessages= ${JSON.stringify(record[pageType], null, 2)};`, - '/* spell-checker: enable */' - ].join("\n"), "utf8") + `//This code was automatically generated by running ${pathRelative( + getProjectRoot(), + __filename, + )}`, + "//PLEASE DO NOT EDIT MANUALLY", + "", + "/* spell-checker: disable */", + `export const kcMessages= ${JSON.stringify( + record[pageType], + null, + 2, + )};`, + "/* spell-checker: enable */", + ].join("\n"), + "utf8", + ), ); console.log(`${filePath} wrote`); - }); - } diff --git a/src/bin/link_in_test_app.ts b/src/bin/link_in_test_app.ts index 38177f0d..bec4a8ca 100644 --- a/src/bin/link_in_test_app.ts +++ b/src/bin/link_in_test_app.ts @@ -1,4 +1,3 @@ - import { execSync } from "child_process"; import { join as pathJoin, relative as pathRelative } from "path"; import * as fs from "fs"; @@ -12,7 +11,9 @@ fs.writeFileSync( (() => { const packageJsonParsed = JSON.parse( fs - .readFileSync(pathJoin(keycloakifyDirPath, "package.json")) + .readFileSync( + pathJoin(keycloakifyDirPath, "package.json"), + ) .toString("utf8"), ); @@ -31,7 +32,13 @@ fs.writeFileSync( const commonThirdPartyDeps = (() => { const namespaceModuleNames = ["@emotion"]; - const standaloneModuleNames = ["react", "@types/react", "powerhooks", "tss-react", "evt"]; + const standaloneModuleNames = [ + "react", + "@types/react", + "powerhooks", + "tss-react", + "evt", + ]; return [ ...namespaceModuleNames @@ -69,7 +76,9 @@ const execYarnLink = (params: { targetModuleName?: string; cwd: string }) => { ...(targetModuleName !== undefined ? [targetModuleName] : []), ].join(" "); - console.log(`$ cd ${pathRelative(keycloakifyDirPath, cwd) || "."} && ${cmd}`); + console.log( + `$ cd ${pathRelative(keycloakifyDirPath, cwd) || "."} && ${cmd}`, + ); execSync(cmd, { cwd, @@ -128,4 +137,4 @@ testAppNames.forEach(testAppName => "cwd": getTestAppPath(testAppName), "targetModuleName": "keycloakify", }), -); \ No newline at end of file +); diff --git a/src/bin/tools/crawl.ts b/src/bin/tools/crawl.ts index 14da285c..4882c5e6 100644 --- a/src/bin/tools/crawl.ts +++ b/src/bin/tools/crawl.ts @@ -3,35 +3,25 @@ import * as path from "path"; /** List all files in a given directory return paths relative to the dir_path */ export const crawl = (() => { - const crawlRec = (dir_path: string, paths: string[]) => { - for (const file_name of fs.readdirSync(dir_path)) { - const file_path = path.join(dir_path, file_name); if (fs.lstatSync(file_path).isDirectory()) { - crawlRec(file_path, paths); continue; - } paths.push(file_path); - } - }; return function crawl(dir_path: string): string[] { - const paths: string[] = []; crawlRec(dir_path, paths); return paths.map(file_path => path.relative(dir_path, file_path)); - - } - -})(); \ No newline at end of file + }; +})(); diff --git a/src/bin/tools/downloadAndUnzip.ts b/src/bin/tools/downloadAndUnzip.ts index 9816657a..ed0739bb 100644 --- a/src/bin/tools/downloadAndUnzip.ts +++ b/src/bin/tools/downloadAndUnzip.ts @@ -1,4 +1,3 @@ - import { basename as pathBasename, join as pathJoin } from "path"; import { execSync } from "child_process"; import fs from "fs"; @@ -6,14 +5,11 @@ import { transformCodebase } from "../tools/transformCodebase"; import { rm_rf, rm, rm_r } from "./rm"; /** assert url ends with .zip */ -export function downloadAndUnzip( - params: { - url: string; - destDirPath: string; - pathOfDirToExtractInArchive?: string; - } -) { - +export function downloadAndUnzip(params: { + url: string; + destDirPath: string; + pathOfDirToExtractInArchive?: string; +}) { const { url, destDirPath, pathOfDirToExtractInArchive } = params; const tmpDirPath = pathJoin(destDirPath, "..", "tmp_xxKdOxnEdx"); @@ -25,23 +21,23 @@ export function downloadAndUnzip( execSync(`wget ${url}`, { "cwd": tmpDirPath }); execSync( - `unzip ${pathBasename(url) - }${pathOfDirToExtractInArchive === undefined ? - "" : ` "${pathOfDirToExtractInArchive}/*"` + `unzip ${pathBasename(url)}${ + pathOfDirToExtractInArchive === undefined + ? "" + : ` "${pathOfDirToExtractInArchive}/*"` }`, - { "cwd": tmpDirPath } + { "cwd": tmpDirPath }, ); rm(pathBasename(url), { "cwd": tmpDirPath }); transformCodebase({ - "srcDirPath": pathOfDirToExtractInArchive === undefined ? - tmpDirPath : - pathJoin(tmpDirPath, pathOfDirToExtractInArchive) - , + "srcDirPath": + pathOfDirToExtractInArchive === undefined + ? tmpDirPath + : pathJoin(tmpDirPath, pathOfDirToExtractInArchive), destDirPath, }); rm_r(tmpDirPath); - -} \ No newline at end of file +} diff --git a/src/bin/tools/getProjectRoot.ts b/src/bin/tools/getProjectRoot.ts index 05e3252f..4d4ea107 100644 --- a/src/bin/tools/getProjectRoot.ts +++ b/src/bin/tools/getProjectRoot.ts @@ -16,4 +16,4 @@ export function getProjectRoot(): string { } return (result = getProjectRootRec(__dirname)); -} \ No newline at end of file +} diff --git a/src/bin/tools/grant-exec-perms.ts b/src/bin/tools/grant-exec-perms.ts index f2295f7e..1e9776c7 100644 --- a/src/bin/tools/grant-exec-perms.ts +++ b/src/bin/tools/grant-exec-perms.ts @@ -1,8 +1,11 @@ +import { getProjectRoot } from "./getProjectRoot"; +import { join as pathJoin } from "path"; +import child_process from "child_process"; -import { getProjectRoot } from "./getProjectRoot"; -import { join as pathJoin } from "path"; -import child_process from "child_process"; - -Object.entries(require(pathJoin(getProjectRoot(), "package.json"))["bin"]) - .forEach(([, scriptPath]) => child_process.execSync(`chmod +x ${scriptPath}`, { "cwd": getProjectRoot() })); - +Object.entries( + require(pathJoin(getProjectRoot(), "package.json"))["bin"], +).forEach(([, scriptPath]) => + child_process.execSync(`chmod +x ${scriptPath}`, { + "cwd": getProjectRoot(), + }), +); diff --git a/src/bin/tools/isInside.ts b/src/bin/tools/isInside.ts index ce2e6b88..cc33ec4d 100644 --- a/src/bin/tools/isInside.ts +++ b/src/bin/tools/isInside.ts @@ -1,14 +1,7 @@ import { relative as pathRelative } from "path"; -export function isInside( - params: { - dirPath: string; - filePath: string; - } -) { - +export function isInside(params: { dirPath: string; filePath: string }) { const { dirPath, filePath } = params; return !pathRelative(dirPath, filePath).startsWith(".."); - -} \ No newline at end of file +} diff --git a/src/bin/tools/rm.ts b/src/bin/tools/rm.ts index 18136e60..26d8b389 100644 --- a/src/bin/tools/rm.ts +++ b/src/bin/tools/rm.ts @@ -1,42 +1,38 @@ - import { execSync } from "child_process"; -function rmInternal( - params: { - pathToRemove: string; - args: string | undefined; - cwd: string | undefined; - } -) { +function rmInternal(params: { + pathToRemove: string; + args: string | undefined; + cwd: string | undefined; +}) { + const { pathToRemove, args, cwd } = params; - const { pathToRemove, args, cwd } = params; - - execSync( - `rm ${args ? `-${args} ` : ""}${pathToRemove.replace(/\ /g, "\\ ")}`, - cwd !== undefined ? { cwd } : undefined - ); + execSync( + `rm ${args ? `-${args} ` : ""}${pathToRemove.replace(/ /g, "\\ ")}`, + cwd !== undefined ? { cwd } : undefined, + ); } -export function rm(pathToRemove: string, options?: { cwd: string; }) { - rmInternal({ - pathToRemove, - "args": undefined, - "cwd": options?.cwd, - }); +export function rm(pathToRemove: string, options?: { cwd: string }) { + rmInternal({ + pathToRemove, + "args": undefined, + "cwd": options?.cwd, + }); } -export function rm_r(pathToRemove: string, options?: { cwd: string; }) { - rmInternal({ - pathToRemove, - "args": "r", - "cwd": options?.cwd, - }); +export function rm_r(pathToRemove: string, options?: { cwd: string }) { + rmInternal({ + pathToRemove, + "args": "r", + "cwd": options?.cwd, + }); } -export function rm_rf(pathToRemove: string, options?: { cwd: string; }) { - rmInternal({ - pathToRemove, - "args": "rf", - "cwd": options?.cwd, - }); +export function rm_rf(pathToRemove: string, options?: { cwd: string }) { + rmInternal({ + pathToRemove, + "args": "rf", + "cwd": options?.cwd, + }); } diff --git a/src/bin/tools/transformCodebase.ts b/src/bin/tools/transformCodebase.ts index bb5d1bd0..c7777887 100644 --- a/src/bin/tools/transformCodebase.ts +++ b/src/bin/tools/transformCodebase.ts @@ -1,69 +1,56 @@ - - import * as fs from "fs"; import * as path from "path"; import { crawl } from "./crawl"; -import { id } from "tsafe/id"; +import { id } from "tsafe/id"; -type TransformSourceCode = - (params: { - sourceCode: Buffer; - filePath: string; - }) => { - modifiedSourceCode: Buffer; - newFileName?: string; - } | undefined; +type TransformSourceCode = (params: { + sourceCode: Buffer; + filePath: string; +}) => + | { + modifiedSourceCode: Buffer; + newFileName?: string; + } + | undefined; /** Apply a transformation function to every file of directory */ -export function transformCodebase( - params: { - srcDirPath: string; - destDirPath: string; - transformSourceCode?: TransformSourceCode; - } -) { - - const { - srcDirPath, - destDirPath, - transformSourceCode = id(({ sourceCode }) => ({ "modifiedSourceCode": sourceCode })) +export function transformCodebase(params: { + srcDirPath: string; + destDirPath: string; + transformSourceCode?: TransformSourceCode; +}) { + const { + srcDirPath, + destDirPath, + transformSourceCode = id(({ sourceCode }) => ({ + "modifiedSourceCode": sourceCode, + })), } = params; for (const file_relative_path of crawl(srcDirPath)) { - const filePath = path.join(srcDirPath, file_relative_path); const transformSourceCodeResult = transformSourceCode({ "sourceCode": fs.readFileSync(filePath), - "filePath": path.join(srcDirPath, file_relative_path) + "filePath": path.join(srcDirPath, file_relative_path), }); if (transformSourceCodeResult === undefined) { continue; } - fs.mkdirSync( - path.dirname( - path.join( - destDirPath, - file_relative_path - ) - ), - { "recursive": true } - ); + fs.mkdirSync(path.dirname(path.join(destDirPath, file_relative_path)), { + "recursive": true, + }); const { newFileName, modifiedSourceCode } = transformSourceCodeResult; fs.writeFileSync( path.join( path.dirname(path.join(destDirPath, file_relative_path)), - newFileName ?? path.basename(file_relative_path) + newFileName ?? path.basename(file_relative_path), ), - modifiedSourceCode + modifiedSourceCode, ); - } - - } - diff --git a/src/lib/components/Error.tsx b/src/lib/components/Error.tsx index 8a79bbf5..06638b2b 100644 --- a/src/lib/components/Error.tsx +++ b/src/lib/components/Error.tsx @@ -4,33 +4,31 @@ import type { KcProps } from "./KcProps"; import type { KcContextBase } from "../getKcContext/KcContextBase"; import { useKcMessage } from "../i18n/useKcMessage"; -export const Error = memo(({ kcContext, ...props }: { kcContext: KcContextBase.Error; } & KcProps) => { - - const { msg } = useKcMessage(); - - const { message, client } = kcContext; - - return ( -