Compare commits

...

80 Commits

Author SHA1 Message Date
f4414e1249 Update changelog v0.2.10 2021-03-19 23:19:03 +00:00
b72971f4ce Merge branch 'main' of https://github.com/InseeFrLab/keycloakify into main 2021-03-20 00:17:06 +01:00
b9af4e6804 Bump version (changelog ingnore) 2021-03-20 00:16:58 +01:00
2fd1d42d1e Remove dependency to denoify 2021-03-20 00:16:34 +01:00
3cfc7d7fa9 Update changelog v0.2.9 2021-03-19 23:14:27 +00:00
b5d9055fcf Bump version (changelog ignore) 2021-03-20 00:09:37 +01:00
63d644d95f Update deps and CI workflow 2021-03-20 00:09:12 +01:00
e16192b416 Update changelog v0.2.8 2021-03-19 21:42:31 +00:00
505e018448 Bump version (changelog ignore) 2021-03-19 22:39:48 +01:00
5ced0e2809 Bugfix: keycloak_build that grow and grow in size 2021-03-19 22:39:32 +01:00
0e1d919f7e Add disclaimer about maitainment strategy 2021-03-15 16:22:13 +01:00
a009db998e Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-15 16:05:28 +01:00
d6c6bd933b Add a note for tested version support 2021-03-15 16:05:21 +01:00
859cc03f35 Update changelog v0.2.7 2021-03-13 11:56:59 +00:00
1a3b8ae3b8 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-13 12:51:41 +01:00
863a08abf3 Bump version 2021-03-13 12:51:34 +01:00
fd9c6afa5e Update README.md 2021-03-11 00:10:34 +01:00
8f3797407b Update README.md 2021-03-10 23:55:37 +01:00
7eedb23285 Update changelog v0.2.6 2021-03-10 22:04:25 +00:00
e4a2c95dd8 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-10 23:02:30 +01:00
9429228b71 Bump version (changelog ignore) 2021-03-10 23:02:25 +01:00
aafbc60f12 Fix generated gitignore 2021-03-10 23:02:03 +01:00
7170611791 Update changelog v0.2.5 2021-03-10 21:51:07 +00:00
59e57f3dd5 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-10 22:46:06 +01:00
fd0d25b081 Bump version (changelog ignore) 2021-03-10 22:46:01 +01:00
fa529c911a Fix generated .gitignore 2021-03-10 22:45:09 +01:00
dc997b7ef4 Update changelog v0.2.4 2021-03-10 20:33:36 +00:00
0168d32f96 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-10 21:27:48 +01:00
d6fc0c779c Bump version (changelog ignore) 2021-03-10 21:27:43 +01:00
08be36edfa Update deps (changelog ignore) 2021-03-10 21:27:23 +01:00
990d287953 Update README.md 2021-03-09 05:12:45 +01:00
a629d4ab45 Update readme 2021-03-09 04:56:45 +01:00
78b48af886 Update changelog v0.2.3 2021-03-09 02:07:07 +00:00
72d267853c fix gitignore generation 2021-03-09 03:04:59 +01:00
66a218c2ec Update changelog v0.2.2 2021-03-08 02:02:53 +00:00
d4f3ec2245 Add table of content 2021-03-08 02:59:22 +01:00
7bdc19bf4b Update README.md 2021-03-08 02:55:41 +01:00
103ef788fb Update README.md 2021-03-08 02:54:43 +01:00
b6f6d1f3cc Update README.md 2021-03-08 02:52:58 +01:00
ef3d2e4e04 Update publish.yaml 2021-03-08 02:51:50 +01:00
8ec8b91ead Update changelog v0.2.1 2021-03-08 01:49:47 +00:00
819a1d473d Update ci.yaml 2021-03-08 02:48:02 +01:00
c930337255 Update readme 2021-03-08 02:40:25 +01:00
57bb4a9d96 Bump version (changelog ignore) 2021-03-08 02:36:21 +01:00
1776341242 Update readme 2021-03-08 02:35:58 +01:00
983eec6941 Bump version (changelog ignore) 2021-03-08 02:34:31 +01:00
86d390ee1a update deps 2021-03-08 02:33:52 +01:00
91703409d9 Update readme 2021-03-08 02:29:54 +01:00
3322d0e4a5 Add all mocks for testing 2021-03-08 01:02:06 +01:00
d09038fde2 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-08 00:10:57 +01:00
71966deaac Bump version (changelog ignore) 2021-03-08 00:10:51 +01:00
12e83c9468 many small fixes 2021-03-08 00:09:52 +01:00
fe27357dbb Update changelog v0.1.6 2021-03-07 16:09:08 +00:00
b93003e76d Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 17:07:20 +01:00
d6c0e9f783 Bump version (changelog ignore) 2021-03-07 17:05:28 +01:00
18a1baae59 Fix Turkish 2021-03-07 17:05:10 +01:00
2330788995 Update changelog v0.1.5 2021-03-07 15:43:11 +00:00
40c146022a Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 16:41:32 +01:00
9844e7554f Bump version changelog ignore 2021-03-07 16:41:27 +01:00
b064b8cbe6 Fix getKcLanguageLabel 2021-03-07 16:41:07 +01:00
997941fbf7 Update changelog v0.1.4 2021-03-07 14:46:12 +00:00
8d5e080bd6 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 15:44:38 +01:00
1c1ca25287 Bump version (changelog ignore) 2021-03-07 15:44:33 +01:00
5195b3bc1e Update changelog v0.1.3 2021-03-07 14:39:29 +00:00
3ff56cfea7 Bump version (changelog ignore) 2021-03-07 15:37:58 +01:00
adc6d69201 Implement LoginVerifyEmail 2021-03-07 15:37:37 +01:00
438ca4595f Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 14:57:59 +01:00
740d9b7af5 Implement login-reset-password.ftl 2021-03-07 14:57:53 +01:00
919a6947bc Update changelog v0.1.2 2021-03-07 01:17:32 +00:00
85fdaa2f22 Bump version (changelog version) 2021-03-07 02:15:51 +01:00
6ade3d6375 Fix build 2021-03-07 02:15:21 +01:00
73e9ebbe40 Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-07 01:47:10 +01:00
ad78221025 Fix build 2021-03-07 01:47:03 +01:00
714fec0bf3 Update changelog v0.1.1 2021-03-06 22:06:48 +00:00
de312c60b1 Bump version (changelog ignore) 2021-03-06 23:05:15 +01:00
1d07fd7675 Implement Error page 2021-03-06 23:03:03 +01:00
307650aaea rename pageBasename by pageId 2021-03-06 22:41:36 +01:00
6eccd313b6 Implement reactive programing for language switching 2021-03-06 15:16:21 +01:00
b8751d67db Merge branch 'develop' of https://github.com/garronej/keycloakify into develop 2021-03-06 14:43:03 +01:00
25d9d3dc26 Add Info page, refactor 2021-03-06 14:42:56 +01:00
33 changed files with 1543 additions and 982 deletions

View File

@ -2,87 +2,135 @@ name: ci
on:
push:
branches:
- develop
- main
pull_request:
branches:
- develop
- main
jobs:
test_node:
runs-on: ubuntu-latest
test:
runs-on: macos-10.15
strategy:
matrix:
node: [ '14', '13', '12' ]
name: Test with Node v${{ matrix.node }}
steps:
- name: Tell if project is using npm or yarn
id: _1
uses: garronej/github_actions_toolkit@v1.11
id: step1
uses: garronej/github_actions_toolkit@v2.2
with:
action_name: tell_if_project_uses_npm_or_yarn
owner: ${{github.repository_owner}}
repo: ${{github.event.repository.name}}
branch: ${{github.ref}}
- uses: actions/checkout@v2
- uses: actions/setup-node@v1
- uses: actions/checkout@v2.3.4
- uses: actions/setup-node@v2.1.3
with:
node-version: ${{ matrix.node }}
- if: steps._1.outputs.npm_or_yarn == 'yarn'
- if: steps.step1.outputs.npm_or_yarn == 'yarn'
run: |
yarn install --frozen-lockfile
yarn run build
yarn run test
- if: steps._1.outputs.npm_or_yarn == 'npm'
yarn build
yarn test
- if: steps.step1.outputs.npm_or_yarn == 'npm'
run: |
npm ci
npm run build
npm run test
trigger_publish:
name: Trigger publish.yaml workflow if package.json version updated ( and secrets.PAT is set ).
npm test
check_if_version_upgraded:
name: Check if version upgrade
if: github.event_name == 'push'
runs-on: ubuntu-latest
env:
PAT: ${{secrets.PAT}}
if: github.event_name == 'push' && github.event.head_commit.author.name != 'ts_ci'
needs: test_node
needs: test
outputs:
from_version: ${{ steps.step1.outputs.from_version }}
to_version: ${{ steps.step1.outputs.to_version }}
is_upgraded_version: ${{steps.step1.outputs.is_upgraded_version }}
steps:
- name: Get version on latest
id: v_latest
uses: garronej/github_actions_toolkit@v1.11
- uses: garronej/github_actions_toolkit@v2.2
id: step1
with:
action_name: get_package_json_version
owner: ${{github.repository_owner}}
repo: ${{github.event.repository.name}}
branch: latest
compare_to_version: '0.0.0'
action_name: is_package_json_version_upgraded
- name: Get version on develop
id: v_develop
uses: garronej/github_actions_toolkit@v1.11
with:
action_name: get_package_json_version
owner: ${{github.repository_owner}}
repo: ${{github.event.repository.name}}
branch: ${{ github.sha }}
compare_to_version: ${{steps.v_latest.outputs.version || '0.0.0'}}
- name: 'Trigger the ''publish'' workflow'
if: ${{ !!env.PAT && steps.v_develop.outputs.compare_result == '1' }}
uses: garronej/github_actions_toolkit@v1.11
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
update_changelog:
runs-on: ubuntu-latest
needs: check_if_version_upgraded
if: needs.check_if_version_upgraded.outputs.is_upgraded_version == 'true'
steps:
- uses: garronej/github_actions_toolkit@v2.2
with:
action_name: dispatch_event
owner: ${{github.repository_owner}}
repo: ${{github.event.repository.name}}
event_type: publish
client_payload_json: |
${{
format(
'{{"from_version":"{0}","to_version":"{1}","repo":"{2}"}}',
steps.v_latest.outputs.version,
steps.v_develop.outputs.version,
github.event.repository.name
)
}}
action_name: update_changelog
branch: ${{ github.ref }}
commit_author_email: ts_ci@github.com
create_github_release:
runs-on: ubuntu-latest
needs:
- update_changelog
- check_if_version_upgraded
steps:
- uses: actions/checkout@v2
with:
ref: ${{ github.ref }}
- name: Build GitHub release body
id: step1
run: |
if [ "$FROM_VERSION" = "0.0.0" ]; then
echo "::set-output name=body::🚀"
else
echo "::set-output name=body::📋 [CHANGELOG](https://github.com/$GITHUB_REPOSITORY/blob/v$TO_VERSION/CHANGELOG.md)"
fi
env:
FROM_VERSION: ${{ needs.check_if_version_upgraded.outputs.from_version }}
TO_VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}
- uses: garronej/action-gh-release@v0.2.0
with:
name: Release v${{ needs.check_if_version_upgraded.outputs.to_version }}
tag_name: v${{ needs.check_if_version_upgraded.outputs.to_version }}
target_commitish: ${{ github.ref }}
body: ${{ steps.step1.outputs.body }}
draft: false
prerelease: false
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
publish_on_npm:
runs-on: macos-10.15
needs:
- update_changelog
- check_if_version_upgraded
steps:
- uses: actions/checkout@v2.3.4
with:
ref: ${{ github.ref }}
- uses: actions/setup-node@v2.1.3
with:
node-version: '15'
- run: |
PACKAGE_MANAGER=npm
if [ -f "./yarn.lock" ]; then
PACKAGE_MANAGER=yarn
fi
if [ "$PACKAGE_MANAGER" = "yarn" ]; then
yarn install --frozen-lockfile
else
npm ci
fi
$PACKAGE_MANAGER run build
- run: npx -y -p denoify@0.6.5 denoify_enable_short_npm_import_path
env:
DRY_RUN: "0"
- name: Publishing on NPM
run: |
if [ "$(npm show . version)" = "$VERSION" ]; then
echo "This version is already published"
exit 0
fi
if [ "$NPM_TOKEN" = "" ]; then
echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
false
fi
echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc
npm publish
rm .npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
VERSION: ${{ needs.check_if_version_upgraded.outputs.to_version }}

View File

@ -1,116 +0,0 @@
on:
repository_dispatch:
types: publish
jobs:
update_changelog_and_sync_package_lock_version:
name: Update CHANGELOG.md and make sure package.json and package-lock.json versions matches.
runs-on: ubuntu-latest
steps:
- name: Synchronize package.json and package-lock.json version if needed.
uses: garronej/github_actions_toolkit@v1.11
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
with:
action_name: sync_package_and_package_lock_version
owner: ${{github.repository_owner}}
repo: ${{github.event.client_payload.repo}}
branch: develop
commit_author_email: ts_ci@github.com
- name: Update CHANGELOG.md
if: ${{ !!github.event.client_payload.from_version }}
uses: garronej/github_actions_toolkit@v1.11
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
with:
action_name: update_changelog
owner: ${{github.repository_owner}}
repo: ${{github.event.client_payload.repo}}
branch_behind: latest
branch_ahead: develop
commit_author_email: ts_ci@github.com
exclude_commit_from_author_names_json: '["ts_ci"]'
publish:
runs-on: ubuntu-latest
needs: update_changelog_and_sync_package_lock_version
steps:
- uses: actions/checkout@v2
with:
fetch-depth: 0
ref: develop
- name: Remove .github directory, useless on 'latest' branch
run: rm -r .github
- name: Remove branch 'latest'
continue-on-error: true
run: |
git branch -d latest || true
git push origin :latest
- name: Create the new 'latest' branch
run: |
git branch latest
git checkout latest
- uses: actions/setup-node@v1
- run: |
if [ -f "./yarn.lock" ]; then
yarn install --frozen-lockfile
else
npm ci
fi
- run: |
PACKAGE_MANAGER=npm
if [ -f "./yarn.lock" ]; then
PACKAGE_MANAGER=yarn
fi
$PACKAGE_MANAGER run enable_short_import_path
env:
DRY_RUN: "0"
- name: (DEBUG) Show how the files have been moved to enable short import
run: ls -lR
- name: Publishing on NPM
run: |
if [ "$(npm show . version)" = "$VERSION" ]; then
echo "This version is already published"
exit 0
fi
if [ "$NPM_TOKEN" = "" ]; then
echo "Can't publish on NPM, You must first create a secret called NPM_TOKEN that contains your NPM auth token. https://help.github.com/en/actions/automating-your-workflow-with-github-actions/creating-and-using-encrypted-secrets"
false
fi
echo '//registry.npmjs.org/:_authToken=${NPM_TOKEN}' > .npmrc
npm publish
rm .npmrc
env:
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
VERSION: ${{ github.event.client_payload.to_version }}
- name: Commit changes
run: |
git config --local user.email "ts_ci@github.com"
git config --local user.name "ts_ci"
git add -A
git commit -am "Enabling shorter import paths [automatic]"
- run: git push origin latest
- name: Release body
id: id_rb
run: |
if [ "$FROM_VERSION" = "" ]; then
echo "::set-output name=body::🚀"
else
echo "::set-output name=body::📋 [CHANGELOG](https://github.com/$OWNER/$REPO/blob/$REF/CHANGELOG.md)"
fi
env:
FROM_VERSION: ${{ github.event.client_payload.from_version }}
OWNER: ${{github.repository_owner}}
REPO: ${{github.event.client_payload.repo}}
REF: v${{github.event.client_payload.to_version}}
- name: Create Release
uses: garronej/create-release@master
env:
GITHUB_TOKEN: ${{ secrets.PAT }}
with:
tag_name: v${{ github.event.client_payload.to_version }}
release_name: Release v${{ github.event.client_payload.to_version }}
branch: latest
draft: false
prerelease: false
body: ${{ steps.id_rb.outputs.body }}

View File

@ -1,3 +1,84 @@
### **0.2.10** (2021-03-19)
- Remove dependency to denoify
### **0.2.9** (2021-03-19)
- Update deps and CI workflow
### **0.2.8** (2021-03-19)
- Bugfix: keycloak_build that grow and grow in size
- Add disclaimer about maitainment strategy
- Add a note for tested version support
### **0.2.7** (2021-03-13)
- Bump version
- Update README.md
- Update README.md
### **0.2.6** (2021-03-10)
- Fix generated gitignore
### **0.2.5** (2021-03-10)
- Fix generated .gitignore
### **0.2.4** (2021-03-10)
- Update README.md
### **0.2.3** (2021-03-09)
- fix gitignore generation
### **0.2.2** (2021-03-08)
- Add table of content
- Update README.md
- Update README.md
## **0.2.1** (2021-03-08)
- Update ci.yaml
- Update readme
- Update readme
- update deps
- Update readme
- Add all mocks for testing
- many small fixes
### **0.1.6** (2021-03-07)
- Fix Turkish
### **0.1.5** (2021-03-07)
- Fix getKcLanguageLabel
### **0.1.4** (2021-03-07)
### **0.1.3** (2021-03-07)
- Implement LoginVerifyEmail
- Implement login-reset-password.ftl
### **0.1.2** (2021-03-07)
- Fix build
- Fix build
### **0.1.1** (2021-03-06)
- Implement Error page
- rename pageBasename by pageId
- Implement reactive programing for language switching
- Add Info page, refactor
## **0.1.0** (2021-03-05)
- Rename keycloakify

159
README.md
View File

@ -2,7 +2,7 @@
<img src="https://user-images.githubusercontent.com/6702424/109387840-eba11f80-7903-11eb-9050-db1dad883f78.png">
</p>
<p align="center">
<i>🔏 Keycloak theme generator for Reacts app 🔏</i>
<i>🔏 Customize key cloak's pages as if they were part of your App 🔏</i>
<br>
<br>
<img src="https://github.com/garronej/keycloakify/workflows/ci/badge.svg?branch=develop">
@ -11,9 +11,10 @@
<img src="https://img.shields.io/npm/l/keycloakify">
</p>
**!!! This module is under active developement. It is not usable yet!!!**
<p align="center">
<i>Ultimately this build tool Generates a Keycloak theme</i>
<img src="https://user-images.githubusercontent.com/6702424/110260457-a1c3d380-7fac-11eb-853a-80459b65626b.png">
</p>
# Motivations
@ -22,20 +23,38 @@ The problem:
![keycloak_before](https://user-images.githubusercontent.com/6702424/108838381-dbbbcf80-75d3-11eb-8ae8-db41563ef9db.gif)
When we redirected to Keycloak the user suffers from a harsh context switch.
On je login/register pages the language is set back to default and the theme is different that the one on the app.
Keycloak does offer a way to customize theses pages but it requires a lot of raw HTML/CSS hacking
to reproduce the look and feel of a specific app. Not mentioning the maintenance cost of such an endeavour.
Wouldn't it be great if we could just design the login and register pages as if they where part of our app while
still letting Keycloak handle the heavy lifting of actually authenticating the users?
Wouldn't it be great if we could just design the login and register pages as if they where part of our app?
Here is `yarn add keycloakify` for you 🍸
TODO: Insert video after.
# How to use
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)
**Disclaimer**: 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 the Keycloak v11.
- [Motivations](#motivations)
- [How to use](#how-to-use)
- [Setting up the build tool](#setting-up-the-build-tool)
- [Developing your login and register pages in your React app](#developing-your-login-and-register-pages-in-your-react-app)
- [Just changing the look](#just-changing-the-look)
- [Changing the look **and** feel](#changing-the-look-and-feel)
- [Hot reload](#hot-reload)
- [How to implement context persistance](#how-to-implement-context-persistance)
- [If your keycloak is a subdomain of your app.](#if-your-keycloak-is-a-subdomain-of-your-app)
- [Else](#else)
- [GitHub Actions](#github-actions)
- [REQUIREMENTS](#requirements)
- [API Reference](#api-reference)
- [The build tool](#the-build-tool)
- [The fronted lib ( imported into your react app )](#the-fronted-lib--imported-into-your-react-app-)
# How to use
## Setting up the build tool
Add `keycloakify` to the dev dependencies of your project `npm install --save-dev keycloakify` or `yarn add --dev keycloakify`
@ -43,31 +62,124 @@ then configure your `package.json` build's script to build the keycloak's theme
Typically you will get:
`package.json`:
`package.json`
```json
"devDependencies": {
"devDependencies": {
"keycloakify": "^0.0.10"
},
"scripts": {
"build": "react-scripts build && build-keycloak-theme"
},
},
"scripts": {
"keycloak": "yarn build && build-keycloak-theme",
},
```
Then build your app with `yarn run build` or `npm run build`, you will be provided with instructions
Then run `yarn keycloak` or `npm run keycloak`, you will be provided with instructions
about how to load the theme into Keycloak.
[![kickstart_video](https://user-images.githubusercontent.com/6702424/108877866-f146ee80-75ff-11eb-8120-003b3c5f6dd8.png)](https://youtu.be/xTz0Rj7i2v8)
## Developing your login and register pages in your React app
TODO
### Just changing the look
The fist approach is to only arr/replace the default class names by your
own.
```tsx
import { App } from "./<wherever>/App";
import {
KcApp,
defaultKcProps,
kcContext
} from "keycloakify";
import { css } from "tss-react";
const myClassName = css({ "color": "red" });
reactDom.render(
// Unless the app is currently being served by Keycloak
// kcContext is undefined.
kcContext !== undefined ?
<KcApp
kcContext={kcContext}
{...{
...defaultKcProps,
"kcHeaderWrapperClass": myClassName
}}
/> :
<App />, // Your actual app
document.getElementById("root")
);
```
<i>result:</i>
<p align="center">
<img src="https://user-images.githubusercontent.com/6702424/110261408-688d6280-7fb0-11eb-9822-7003d268b459.png">
</p>
### Changing the look **and** feel
If you want to really re-implement the pages the best approach is to
create you own version of the [`<KcApp />`](https://github.com/garronej/keycloakify/blob/develop/src/lib/components/KcApp.tsx).
Copy/past some of [the components](https://github.com/garronej/keycloakify/tree/develop/src/lib/components) provided by this module and start hacking around.
### Hot reload
By default, in order to see your changes you will have to wait for
`yarn build` to complete which can takes sevrall minute.
If you want to test your login screens outside of Keycloak, in [storybook](https://storybook.js.org/)
for example you can use `kcContextMocks`.
```tsx
import {
KcApp,
defaultKcProps,
kcContextMocks
} from "keycloakify";
reactDom.render(
kcContext !== undefined ?
<KcApp
kcContext={kcContextMocks.kcLoginContext}
{...defaultKcProps}
/>
document.getElementById("root")
);
```
then `yarn start` ...
Checkout [this concrete example](https://github.com/garronej/keycloakify-demo-app/blob/main/src/index.tsx)
*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)
# How to implement context persistance
If you want dark mode preference, language and others users preferences your can do so
very easily by using [`powerhooks/useGlobalState`](https://github.com/garronej/powerhooks)
If you want dark mode preference, language and others users preferences
to persist within the page served by keycloak here are the methods you can
adopt.
WARNING: `powerhooks` is still a work in progress.
## If your keycloak is a subdomain of your app.
E.g: Your app url is `my-app.com` and your keycloak url is `auth.my-app.com`.
In this case there is a very straightforward approach and it is to use [`powerhooks/useGlobalState`](https://github.com/garronej/powerhooks).
Instead of `{ "persistance": "localStorage" }` use `{ "persistance": "cookie" }`.
## Else
You will have to use URL parameters to passes states when you redirect to
the login page.
TOTO: Provide a clean way, as abstracted as possible, way to do that.
# GitHub Actions
![image](https://user-images.githubusercontent.com/6702424/110708305-c44b2c00-81fa-11eb-8152-eeaaac0883d6.png)
[Here is a demo repo](https://github.com/garronej/keycloakify-demo-app) to show how to automate
the building and publishing of the theme (the .jar file).
# REQUIREMENTS
@ -97,3 +209,4 @@ Part of the lib that runs with node, at build time.
Part of the lib that you import in your react project and runs on the browser.
**TODO**

View File

@ -1,6 +1,6 @@
{
"name": "keycloakify",
"version": "0.1.0",
"version": "0.2.10",
"description": "Keycloak theme generator for Reacts app",
"repository": {
"type": "git",
@ -13,8 +13,7 @@
"build": "yarn clean && tsc && yarn grant-exec-perms && yarn copy-files",
"grant-exec-perms": "node dist/bin/tools/grant-exec-perms.js",
"test": "node dist/test",
"enable_short_import_path": "yarn build && denoify_enable_short_npm_import_path",
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml dist/",
"copy-files": "copyfiles -u 1 src/**/*.ftl src/**/*.xml src/**/*.js dist/",
"generate-messages": "node dist/bin/generate-i18n-messages.js",
"watch": "tsc -w"
},
@ -45,18 +44,18 @@
"@types/node": "^10.0.0",
"@types/react": "^17.0.0",
"copyfiles": "^2.4.1",
"denoify": "^0.6.5",
"properties-parser": "^0.3.1",
"react": "^17.0.1",
"rimraf": "^3.0.2",
"typescript": "^4.1.5"
"typescript": "^4.2.3"
},
"dependencies": {
"scripting-tools": "^0.19.13",
"cheerio": "^1.0.0-rc.5",
"evt": "2.0.0-beta.15",
"minimal-polyfills": "^2.1.6",
"powerhooks": "^0.0.17",
"tss-react": "^0.0.9"
"path": "^0.12.7",
"powerhooks": "^0.0.27",
"scripting-tools": "^0.19.13",
"tss-react": "^0.0.11"
}
}

View File

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

View File

@ -0,0 +1,26 @@
var es = /&(?:amp|#38|lt|#60|gt|#62|apos|#39|quot|#34);/g;
var unes = {
'&amp;': '&',
'&#38;': '&',
'&lt;': '<',
'&#60;': '<',
'&gt;': '>',
'&#62;': '>',
'&apos;': "'",
'&#39;': "'",
'&quot;': '"',
'&#34;': '"'
};
var cape = function (m) { return unes[m]; };
Object.defineProperty(
String,
"htmlUnescape",
{
"value": function (un) {
return String.prototype.replace.call(un, es, cape);
}
}
);

View File

@ -0,0 +1,15 @@
<script>const _=
{
"client": (function (){
<#if client??>
return {
"baseUrl": "${(client.baseUrl!'')?no_esc}" || undefined
};
</#if>
return undefined;
})()
}
</script>

View File

@ -9,12 +9,25 @@ import fs from "fs";
import { join as pathJoin } from "path";
import { objectKeys } from "evt/tools/typeSafety/objectKeys";
function loadFtlFile(ftlFileBasename: "template.ftl" | "login.ftl" | "register.ftl") {
return fs.readFileSync(pathJoin(__dirname, ftlFileBasename))
.toString("utf8")
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1];
}
export const pageIds = ["login.ftl", "register.ftl", "info.ftl", "error.ftl", "login-reset-password.ftl", "login-verify-email.ftl"] as const;
export type PageId = typeof pageIds[number];
function loadAdjacentFile(fileBasename: string){
return fs.readFileSync(pathJoin(__dirname, fileBasename))
.toString("utf8");
};
function loadFtlFile(ftlFileBasename: PageId | "template.ftl") {
try {
return loadAdjacentFile(ftlFileBasename)
.match(/^<script>const _=((?:.|\n)+)<\/script>[\n]?$/)![1];
} catch {
return "{}";
}
}
export function generateFtlFilesCodeFactory(
params: {
@ -39,7 +52,6 @@ export function generateFtlFilesCodeFactory(
});
([
["link", "href"],
["script", "src"],
@ -66,10 +78,12 @@ export function generateFtlFilesCodeFactory(
' <#list scripts as script>',
' <script src="${script}" type="text/javascript"></script>',
' </#list>',
'</#if>',
'</#if>'
].join("\n")
};
const pageSpecificCodePlaceholder = "<!-- dIddLqMeOedErIdLsPdNdI9dSl42sw -->";
$("head").prepend(
[
...(Object.keys(cssGlobalsToDefine).length === 0 ? [] : [
@ -81,80 +95,59 @@ export function generateFtlFilesCodeFactory(
'</style>',
''
]),
...["Object.deepAssign.js", "String.htmlUnescape.js"].map(
fileBasename => [
"<script>",
loadAdjacentFile(fileBasename),
"</script>"
].join("\n")
),
'<script>',
' Object.deepAssign(',
` window.${ftlValuesGlobalName},`,
` window.${ftlValuesGlobalName}= Object.assign(`,
` {},`,
` ${objectKeys(ftlCommonPlaceholders)[0]}`,
' );',
'</script>',
'',
objectKeys(ftlCommonPlaceholders)[1],
''
pageSpecificCodePlaceholder,
'',
objectKeys(ftlCommonPlaceholders)[1]
].join("\n"),
);
const partiallyFixedIndexHtmlCode = $.html();
function generateFtlFilesCode(
params: {
pageBasename: "login.ftl" | "register.ftl"
pageId: PageId;
}
): { ftlCode: string; } {
const { pageBasename } = params;
const { pageId } = params;
const $ = cheerio.load(partiallyFixedIndexHtmlCode);
const ftlPlaceholders = {
'{ "x": "kxOlLqMeOed9sdLdIdOxd444" }': loadFtlFile(pageBasename),
'{ "x": "kxOlLqMeOed9sdLdIdOxd444" }': loadFtlFile(pageId),
...ftlCommonPlaceholders
};
$("head").prepend(
[
'',
'<script>',
'',
` window.${ftlValuesGlobalName} = Object.assign(`,
` { "pageBasename": "${pageBasename}" },`,
` ${objectKeys(ftlPlaceholders)[0]}`,
' );',
'',
' Object.defineProperty(',
' Object,',
' "deepAssign",',
' {',
' "value": function callee(target, source) {',
' Object.keys(source).forEach(function (key) {',
' var value = source[key];',
' if( target[key] === undefined ){',
' target[key]= value;',
' return;',
' }',
' if( value instanceof Object ){',
' if( value instanceof Array ){',
' value.forEach(function (entry){',
' target[key].push(entry);',
' });',
' return;',
' }',
' callee(target[key], value);',
' return;',
' }',
' target[key]= value;',
' });',
' return target;',
' }',
' }',
' );',
'',
'</script>',
''
].join("\n")
);
let ftlCode = $.html();
let ftlCode = $.html()
.replace(
pageSpecificCodePlaceholder,
[
'<script>',
` Object.deepAssign(`,
` window.${ftlValuesGlobalName},`,
` { "pageId": "${pageId}" }`,
' );',
` Object.deepAssign(`,
` window.${ftlValuesGlobalName},`,
` ${objectKeys(ftlPlaceholders)[0]}`,
' );',
'</script>'
].join("\n")
);
objectKeys(ftlPlaceholders)
.forEach(id => ftlCode = ftlCode.replace(id, ftlPlaceholders[id]));

View File

@ -0,0 +1,37 @@
<script>const _=
{
"messageHeader": "${messageHeader!''}" || undefined,
"requiredActions": (function (){
<#if requiredActions??>
var out =[];
<#list requiredActions>
<#items as reqActionItem>
out.push("${reqActionItem}");
</#items></b>
</#list>
return out;
<#else>
return undefined;
})(),
"skipLink": (function (){
<#if skipLink??>
return true;
</#if>
return false;
})(),
"pageRedirectUri": "${(pageRedirectUri!'')?no_esc}" || undefined,
"actionUri": "${(actionUri!'')?no_esc}" || undefined,
"client": {
"baseUrl": "${(client.baseUrl!'')?no_esc}" || undefined
}
}
</script>

View File

@ -0,0 +1,7 @@
<script>const _=
{
"realm": {
"loginWithEmailAllowed": ${realm.loginWithEmailAllowed?c}
},
}
</script>

View File

@ -7,6 +7,7 @@
"realm": {
"loginWithEmailAllowed": ${realm.loginWithEmailAllowed?c},
"rememberMe": ${realm.rememberMe?c},
"password": ${realm.password?c},
"resetPasswordAllowed": ${realm.resetPasswordAllowed?c},
"registrationAllowed": ${realm.registrationAllowed?c}
},

View File

@ -11,7 +11,6 @@
"displayName": "${realm.displayName!''}" || undefined,
"displayNameHtml": "${realm.displayNameHtml!''}" || undefined,
"internationalizationEnabled": ${realm.internationalizationEnabled?c},
"password": ${realm.password?c},
"registrationEmailAsUsername": ${realm.registrationEmailAsUsername?c},
},
"locale": (function (){
@ -95,7 +94,7 @@
return { 
"type": "${message.type}",
"summary": "${kcSanitize(message.summary)?no_esc}"
"summary": String.htmlUnescape("${message.summary}")
};
</#if>

View File

@ -6,11 +6,13 @@ import {
replaceImportFromStaticInCssCode,
replaceImportFromStaticInJsCode
} from "./replaceImportFromStatic";
import { generateFtlFilesCodeFactory } from "./generateFtl";
import { generateFtlFilesCodeFactory, pageIds } from "./generateFtl";
import { builtinThemesUrl } from "../install-builtin-keycloak-themes";
import { downloadAndUnzip } from "../tools/downloadAndUnzip";
import * as child_process from "child_process";
import { ftlValuesGlobalName } from "./ftlValuesGlobalName";
import { resourcesCommonPath, resourcesPath, subDirOfPublicDirBasename } from "../../lib/kcContextMocks/urlResourcesPath";
import { isInside } from "../tools/isInside";
export function generateKeycloakThemeResources(
params: {
@ -31,6 +33,17 @@ export function generateKeycloakThemeResources(
"srcDirPath": reactAppBuildDirPath,
"transformSourceCode": ({ filePath, sourceCode }) => {
//NOTE: Prevent cycles, excludes the folder we generated for debug in public/
if (
isInside({
"dirPath": pathJoin(reactAppBuildDirPath, subDirOfPublicDirBasename),
filePath
})
) {
return undefined;
}
if (/\.css?$/i.test(filePath)) {
const { cssGlobalsToDefine, fixedCssCode } = replaceImportFromStaticInCssCode(
@ -70,14 +83,14 @@ export function generateKeycloakThemeResources(
).toString("utf8")
});
(["login.ftl", "register.ftl"] as const).forEach(pageBasename => {
pageIds.forEach(pageId => {
const { ftlCode } = generateFtlFilesCode({ pageBasename });
const { ftlCode } = generateFtlFilesCode({ pageId });
fs.writeFileSync(
pathJoin(themeDirPath, pageBasename),
pathJoin(themeDirPath, pageId),
Buffer.from(ftlCode, "utf8")
)
);
});
@ -90,11 +103,48 @@ export function generateKeycloakThemeResources(
"destDirPath": tmpDirPath
});
const themeResourcesDirPath = pathJoin(themeDirPath, "resources");
transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "login", "resources"),
"destDirPath": pathJoin(themeDirPath, "resources")
"destDirPath": themeResourcesDirPath
});
const reactAppPublicDirPath = pathJoin(reactAppBuildDirPath, "..", "public");
transformCodebase({
"srcDirPath": themeResourcesDirPath,
"destDirPath": pathJoin(
reactAppPublicDirPath,
resourcesPath
)
});
transformCodebase({
"srcDirPath": pathJoin(tmpDirPath, "keycloak", "common", "resources"),
"destDirPath": pathJoin(
reactAppPublicDirPath,
resourcesCommonPath
)
});
const keycloakResourcesWithinPublicDirPath =
pathJoin(reactAppPublicDirPath, subDirOfPublicDirBasename);
fs.writeFileSync(
pathJoin(keycloakResourcesWithinPublicDirPath, "README.txt"),
Buffer.from([
"This is just a test folder that helps develop",
"the login and register page without having to yarn build"
].join(" "))
);
fs.writeFileSync(
pathJoin(keycloakResourcesWithinPublicDirPath, ".gitignore"),
Buffer.from("*", "utf8")
);
child_process.execSync(`rm -r ${tmpDirPath}`);
}

14
src/bin/tools/isInside.ts Normal file
View File

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

View File

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

View File

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

View File

@ -1,24 +1,21 @@
import { memo } from "react";
import { kcContext } from "../kcContext";
import { assert } from "../tools/assert";
import type { KcPagesProperties } from "./KcProperties";
import type { KcContext } from "../KcContext";
import type { KcProps } from "./KcProps";
import { Login } from "./Login";
import { Register } from "./Register";
import { Info } from "./Info";
import { Error } from "./Error";
import { LoginResetPassword } from "./LoginResetPassword";
import { LoginVerifyEmail } from "./LoginVerifyEmail";
export type KcAppProps = {
kcProperties?: KcPagesProperties;
};
export const KcApp = memo((props: KcAppProps) => {
const { kcProperties } = props;
assert(kcContext !== undefined, "App is not currently served by a Keycloak server");
switch (kcContext.pageBasename) {
case "login.ftl": return <Login kcProperties={kcProperties} />;
case "register.ftl": return <Register kcProperties={kcProperties} />;
export const KcApp = memo(({ kcContext, ...props }: { kcContext: KcContext; } & KcProps ) => {
switch (kcContext.pageId) {
case "login.ftl": return <Login {...{ kcContext, ...props }} />;
case "register.ftl": return <Register {...{ kcContext, ...props }} />;
case "info.ftl": return <Info {...{ kcContext, ...props }} />;
case "error.ftl": return <Error {...{ kcContext, ...props }} />;
case "login-reset-password.ftl": return <LoginResetPassword {...{ kcContext, ...props }} />;
case "login-verify-email.ftl": return <LoginVerifyEmail {...{ kcContext, ...props }} />;
}
});

View File

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

View File

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

View File

@ -1,40 +1,21 @@
import { useState, memo } from "react";
import { Template } from "./Template";
import type { KcPagesProperties } from "./KcProperties";
import { defaultKcPagesProperties } from "./KcProperties";
import { assert } from "../tools/assert";
import { kcContext } from "../kcContext";
import { useKcTranslation } from "../i18n/useKcTranslation";
import type { KcProps } from "./KcProps";
import type { KcContext } from "../KcContext";
import { useKcMessage } from "../i18n/useKcMessage";
import { cx } from "tss-react";
import { useConstCallback } from "powerhooks";
export type LoginProps = {
kcProperties?: KcPagesProperties;
};
export const Login = memo(({ kcContext, ...props }: { kcContext: KcContext.Login; } & KcProps) => {
export const Login = memo((props: LoginProps) => {
const { msg, msgStr } = useKcMessage();
const { kcProperties = {} } = props;
const { t, tStr } = useKcTranslation();
Object.assign(kcProperties, defaultKcPagesProperties);
const [{
const {
social, realm, url,
usernameEditDisabled, login,
auth, registrationDisabled
}] = useState(() => {
assert(
kcContext !== undefined &&
kcContext.pageBasename === "login.ftl"
);
return kcContext;
});
} = kcContext;
const [isLoginButtonDisabled, setIsLoginButtonDisabled] = useState(false);
@ -44,78 +25,78 @@ export const Login = memo((props: LoginProps) => {
return (
<Template
{...{ kcContext, ...props }}
displayInfo={social.displayInfo}
displayWide={realm.password && social.providers !== undefined}
kcProperties={kcProperties}
headerNode={t("doLogIn")}
headerNode={msg("doLogIn")}
formNode={
<div
id="kc-form"
className={cx(realm.password && social.providers !== undefined && kcProperties.kcContentWrapperClass)}
className={cx(realm.password && social.providers !== undefined && props.kcContentWrapperClass)}
>
<div
id="kc-form-wrapper"
className={cx(realm.password && social.providers && [kcProperties.kcFormSocialAccountContentClass, kcProperties.kcFormSocialAccountClass])}
className={cx(realm.password && social.providers && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])}
>
{
realm.password &&
(
<form id="kc-form-login" onSubmit={onSubmit} action={url.loginAction} method="post">
<div className={cx(kcProperties.kcFormGroupClass)}>
<label htmlFor="username" className={cx(kcProperties.kcLabelClass)}>
<div className={cx(props.kcFormGroupClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>
{
!realm.loginWithEmailAllowed ?
t("username")
msg("username")
:
(
!realm.registrationEmailAsUsername ?
t("usernameOrEmail") :
t("email")
msg("usernameOrEmail") :
msg("email")
)
}
</label>
<input
tabIndex={1}
id="username"
className={cx(kcProperties.kcInputClass)}
className={cx(props.kcInputClass)}
name="username"
defaultValue={login.username ?? ''}
type="text"
{...(usernameEditDisabled ? { "disabled": true } : { "autoFocus": true, "autocomplete": "off" })}
{...(usernameEditDisabled ? { "disabled": true } : { "autoFocus": true, "autoComplete": "off" })}
/>
</div>
<div className={cx(kcProperties.kcFormGroupClass)}>
<label htmlFor="password" className={cx(kcProperties.kcLabelClass)}>
{t("password")}
<div className={cx(props.kcFormGroupClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}>
{msg("password")}
</label>
<input tabIndex={2} id="password" className={cx(kcProperties.kcInputClass)} name="password" type="password" autoComplete="off" />
<input tabIndex={2} id="password" className={cx(props.kcInputClass)} name="password" type="password" autoComplete="off" />
</div>
<div className={cx(kcProperties.kcFormGroupClass, kcProperties.kcFormSettingClass)}>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-options">
{
(
realm.rememberMe &&
(
realm.rememberMe &&
!usernameEditDisabled
) &&
<div className="checkbox">
<label>
<input tabIndex={3} id="rememberMe" name="rememberMe" type="checkbox" {...(login.rememberMe ? { "checked": true } : {})}/>
{t("rememberMe")}
<input tabIndex={3} id="rememberMe" name="rememberMe" type="checkbox" {...(login.rememberMe ? { "checked": true } : {})} />
{msg("rememberMe")}
</label>
</div>
}
</div>
<div className={cx(kcProperties.kcFormOptionsWrapperClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
{
realm.resetPasswordAllowed &&
<span>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>{t("doForgotPassword")}</a>
<a tabIndex={5} href={url.loginResetCredentialsUrl}>{msg("doForgotPassword")}</a>
</span>
}
</div>
</div>
<div id="kc-form-buttons" className={cx(kcProperties.kcFormGroupClass)}>
<div id="kc-form-buttons" className={cx(props.kcFormGroupClass)}>
<input
type="hidden"
id="id-hidden-input"
@ -124,8 +105,8 @@ export const Login = memo((props: LoginProps) => {
/>
<input
tabIndex={4}
className={cx(kcProperties.kcButtonClass, kcProperties.kcButtonPrimaryClass, kcProperties.kcButtonBlockClass, kcProperties.kcButtonLargeClass)} name="login" id="kc-login" type="submit"
value={tStr("doLogIn")}
className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} name="login" id="kc-login" type="submit"
value={msgStr("doLogIn")}
disabled={isLoginButtonDisabled}
/>
</div>
@ -135,11 +116,11 @@ export const Login = memo((props: LoginProps) => {
</div>
{
(realm.password && social.providers !== undefined) &&
<div id="kc-social-providers" className={cx(kcProperties.kcFormSocialAccountContentClass, kcProperties.kcFormSocialAccountClass)}>
<ul className={cx(kcProperties.kcFormSocialAccountListClass, social.providers.length > 4 && kcProperties.kcFormSocialAccountDoubleListClass)}>
<div id="kc-social-providers" className={cx(props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass)}>
<ul className={cx(props.kcFormSocialAccountListClass, social.providers.length > 4 && props.kcFormSocialAccountDoubleListClass)}>
{
social.providers.map(p =>
<li className={cx(kcProperties.kcFormSocialAccountListLinkClass)}>
<li className={cx(props.kcFormSocialAccountListLinkClass)}>
<a href={p.loginUrl} id={`zocial-${p.alias}`} className={cx("zocial", p.providerId)}>
<span>{p.displayName}</span>
</a>
@ -151,7 +132,7 @@ export const Login = memo((props: LoginProps) => {
}
</div>
}
displayInfoNode={
infoNode={
(
realm.password &&
realm.registrationAllowed &&
@ -159,9 +140,9 @@ export const Login = memo((props: LoginProps) => {
) &&
<div id="kc-registration">
<span>
{t("noAccount")}
{msg("noAccount")}
<a tabIndex={6} href={url.registrationUrl}>
{t("doRegister")}
{msg("doRegister")}
</a>
</span>
</div>

View File

@ -0,0 +1,80 @@
import { memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContext } from "../KcContext";
import { useKcMessage } from "../i18n/useKcMessage";
import { cx } from "tss-react";
export const LoginResetPassword = memo(({ kcContext, ...props }: { kcContext: KcContext.LoginResetPassword; } & KcProps) => {
const { msg, msgStr } = useKcMessage();
const {
url,
realm,
auth
} = kcContext;
return (
<Template
{...{ kcContext, ...props }}
displayMessage={false}
headerNode={msg("emailForgotTitle")}
formNode={
<form id="kc-reset-password-form" className={cx(props.kcFormClass)} action={url.loginAction} method="post">
<div className={cx(props.kcFormGroupClass)}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>
{
!realm.loginWithEmailAllowed ?
msg("username")
:
!realm.registrationEmailAsUsername ?
msg("usernameOrEmail") :
msg("email")
}
</label>
</div>
<div className={cx(props.kcInputWrapperClass)}>
<input
type="text"
id="username"
name="username"
className={cx(props.kcInputClass)}
autoFocus
defaultValue={
auth !== undefined && auth.showUsername ?
auth.attemptedUsername : undefined
}
/>
</div>
</div>
<div className={cx(props.kcFormGroupClass, props.kcFormSettingClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span>
<a href={url.loginUrl}>{msg("backToLogin")}</a>
</span>
</div>
</div>
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input
className={cx(
props.kcButtonClass, props.kcButtonPrimaryClass,
props.kcButtonBlockClass, props.kcButtonLargeClass
)}
type="submit"
defaultValue={msgStr("doSubmit")}
/>
</div>
</div>
</form>
}
infoNode={msg("emailInstruction")}
/>
);
});

View File

@ -0,0 +1,38 @@
import { memo } from "react";
import { Template } from "./Template";
import type { KcProps } from "./KcProps";
import type { KcContext } from "../KcContext";
import { useKcMessage } from "../i18n/useKcMessage";
export const LoginVerifyEmail = memo(({ kcContext, ...props }: { kcContext: KcContext.LoginVerifyEmail; } & KcProps) => {
const { msg } = useKcMessage();
const {
url
} = kcContext;
return (
<Template
{...{ kcContext, ...props }}
displayMessage={false}
headerNode={msg("emailVerifyTitle")}
formNode={
<>
<p className="instruction">
{msg("emailVerifyInstruction1")}
</p>
<p className="instruction">
{msg("emailVerifyInstruction2")}
<a href={url.loginAction}>{msg("doClickHere")}</a>
{msg("emailVerifyInstruction3")}
</p>
</>
}
/>
);
});

View File

@ -1,27 +1,15 @@
import { useState, memo } from "react";
import { memo } from "react";
import { Template } from "./Template";
import type { KcPagesProperties } from "./KcProperties";
import { defaultKcPagesProperties } from "./KcProperties";
import { assert } from "../tools/assert";
import { kcContext } from "../kcContext";
import { useKcTranslation } from "../i18n/useKcTranslation";
import type { KcProps } from "./KcProps";
import type { KcContext } from "../KcContext";
import { useKcMessage } from "../i18n/useKcMessage";
import { cx } from "tss-react";
export type RegisterPageProps = {
kcProperties?: KcPagesProperties;
};
export const Register = memo(({ kcContext, ...props }: { kcContext: KcContext.Register; } & KcProps) => {
export const Register = memo((props: RegisterPageProps) => {
const { msg, msgStr } = useKcMessage();
const { kcProperties = {} } = props;
const { t, tStr } = useKcTranslation();
Object.assign(kcProperties, defaultKcPagesProperties);
const [{
const {
url,
messagesPerField,
register,
@ -29,91 +17,80 @@ export const Register = memo((props: RegisterPageProps) => {
passwordRequired,
recaptchaRequired,
recaptchaSiteKey
}] = useState(() => {
assert(
kcContext !== undefined &&
kcContext.pageBasename === "register.ftl"
);
return kcContext;
});
} = kcContext;
return (
<Template
kcProperties={kcProperties}
headerNode={t("registerTitle")}
{...{ kcContext, ...props }}
headerNode={msg("registerTitle")}
formNode={
<form id="kc-register-form" className={cx(kcProperties.kcFormClass)} action={url.registrationAction} method="post">
<form id="kc-register-form" className={cx(props.kcFormClass)} action={url.registrationAction} method="post">
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists('firstName', kcProperties.kcFormGroupErrorClass))}>
<div className={cx(kcProperties.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(kcProperties.kcLabelClass)}>{t("firstName")}</label>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('firstName', props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="firstName" className={cx(props.kcLabelClass)}>{msg("firstName")}</label>
</div>
<div className={cx(kcProperties.kcInputWrapperClass)}>
<input type="text" id="firstName" className={cx(kcProperties.kcInputClass)} name="firstName"
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="firstName" className={cx(props.kcInputClass)} name="firstName"
defaultValue={register.formData.firstName ?? ""}
/>
</div>
</div>
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists("lastName", kcProperties.kcFormGroupErrorClass))}>
<div className={cx(kcProperties.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(kcProperties.kcLabelClass)}>{t("lastName")}</label>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("lastName", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="lastName" className={cx(props.kcLabelClass)}>{msg("lastName")}</label>
</div>
<div className={cx(kcProperties.kcInputWrapperClass)}>
<input type="text" id="lastName" className={cx(kcProperties.kcInputClass)} name="lastName"
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="lastName" className={cx(props.kcInputClass)} name="lastName"
defaultValue={register.formData.lastName ?? ""}
/>
</div>
</div>
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists('email', kcProperties.kcFormGroupErrorClass))}>
<div className={cx(kcProperties.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(kcProperties.kcLabelClass)}>{t("email")}</label>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('email', props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="email" className={cx(props.kcLabelClass)}>{msg("email")}</label>
</div>
<div className={cx(kcProperties.kcInputWrapperClass)}>
<input type="text" id="email" className={cx(kcProperties.kcInputClass)} name="email"
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="email" className={cx(props.kcInputClass)} name="email"
defaultValue={register.formData.email ?? ""} autoComplete="email"
/>
</div>
</div>
{
!realm.registrationEmailAsUsername &&
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists('username', kcProperties.kcFormGroupErrorClass))}>
<div className={cx(kcProperties.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(kcProperties.kcLabelClass)}>{t("username")}</label>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists('username', props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="username" className={cx(props.kcLabelClass)}>{msg("username")}</label>
</div>
<div className={cx(kcProperties.kcInputWrapperClass)}>
<input type="text" id="username" className={cx(kcProperties.kcInputClass)} name="username"
<div className={cx(props.kcInputWrapperClass)}>
<input type="text" id="username" className={cx(props.kcInputClass)} name="username"
defaultValue={register.formData.username ?? ""} autoComplete="username" />
</div>
</div >
}
{
passwordRequired &&
<>
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists("password", kcProperties.kcFormGroupErrorClass))}>
<div className={cx(kcProperties.kcLabelWrapperClass)}>
<label htmlFor="password" className={cx(kcProperties.kcLabelClass)}>{t("password")}</label>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password" className={cx(props.kcLabelClass)}>{msg("password")}</label>
</div>
<div className={cx(kcProperties.kcInputWrapperClass)}>
<input type="password" id="password" className={cx(kcProperties.kcInputClass)} name="password" autoComplete="new-password" />
<div className={cx(props.kcInputWrapperClass)}>
<input type="password" id="password" className={cx(props.kcInputClass)} name="password" autoComplete="new-password" />
</div>
</div>
<div className={cx(kcProperties.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", kcProperties.kcFormGroupErrorClass))}>
<div className={cx(kcProperties.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={cx(kcProperties.kcLabelClass)}>{t("passwordConfirm")}</label>
<div className={cx(props.kcFormGroupClass, messagesPerField.printIfExists("password-confirm", props.kcFormGroupErrorClass))}>
<div className={cx(props.kcLabelWrapperClass)}>
<label htmlFor="password-confirm" className={cx(props.kcLabelClass)}>{msg("passwordConfirm")}</label>
</div>
<div className={cx(kcProperties.kcInputWrapperClass)}>
<input type="password" id="password-confirm" className={cx(kcProperties.kcInputClass)} name="password-confirm" />
<div className={cx(props.kcInputWrapperClass)}>
<input type="password" id="password-confirm" className={cx(props.kcInputClass)} name="password-confirm" />
</div>
</div>
</>
@ -122,24 +99,21 @@ export const Register = memo((props: RegisterPageProps) => {
{
recaptchaRequired &&
<div className="form-group">
<div className={cx(kcProperties.kcInputWrapperClass)}>
<div className={cx(props.kcInputWrapperClass)}>
<div className="g-recaptcha" data-size="compact" data-sitekey={recaptchaSiteKey}></div>
</div>
</div>
}
<div className={cx(kcProperties.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(kcProperties.kcFormOptionsClass)}>
<div className={cx(kcProperties.kcFormOptionsWrapperClass)}>
<span><a href={url.loginUrl}>{t("backToLogin")}</a></span>
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-form-options" className={cx(props.kcFormOptionsClass)}>
<div className={cx(props.kcFormOptionsWrapperClass)}>
<span><a href={url.loginUrl}>{msg("backToLogin")}</a></span>
</div>
</div>
<div id="kc-form-buttons" className={cx(kcProperties.kcFormButtonsClass)}>
<input className={cx(kcProperties.kcButtonClass, kcProperties.kcButtonPrimaryClass, kcProperties.kcButtonBlockClass, kcProperties.kcButtonLargeClass)} type="submit"
defaultValue={tStr("doRegister")} />
<div id="kc-form-buttons" className={cx(props.kcFormButtonsClass)}>
<input className={cx(props.kcButtonClass, props.kcButtonPrimaryClass, props.kcButtonBlockClass, props.kcButtonLargeClass)} type="submit"
defaultValue={msgStr("doRegister")} />
</div>
</div>
</form >
@ -147,10 +121,3 @@ export const Register = memo((props: RegisterPageProps) => {
/>
);
});
// JSX.IntrinsicElements.input: React.DetailedHTMLProps<React.InputHTMLAttributes<HTMLInputElement>, HTMLInputElement>

View File

@ -1,22 +1,21 @@
import { useState, useReducer ,useEffect, memo } from "react";
import { useReducer, useEffect, memo } from "react";
import type { ReactNode } from "react";
import { useKcTranslation } from "../i18n/useKcTranslation";
import { kcContext } from "../kcContext";
import { useKcMessage } from "../i18n/useKcMessage";
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
import type { KcContext } from "../KcContext";
import { assert } from "../tools/assert";
import { cx } from "tss-react";
import { useKcLanguageTag } from "../i18n/useKcLanguageTag";
import type { KcLanguageTag } from "../i18n/KcLanguageTag";
import { getBestMatchAmongKcLanguageTag } from "../i18n/KcLanguageTag";
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
import { useCallbackFactory } from "powerhooks";
import { appendHead } from "../tools/appendHead";
import { join as pathJoin } from "path";
import { useConstCallback } from "powerhooks";
import type { KcTemplateProperties } from "./KcProperties";
import { defaultKcTemplateProperties } from "./KcProperties";
import type { KcTemplateProps } from "./KcProps";
export type TemplateProps = {
kcProperties: KcTemplateProperties;
displayInfo?: boolean;
displayMessage?: boolean;
displayRequiredFields?: boolean;
@ -25,8 +24,8 @@ export type TemplateProps = {
headerNode: ReactNode;
showUsernameNode?: ReactNode;
formNode: ReactNode;
displayInfoNode?: ReactNode;
};
infoNode?: ReactNode;
} & { kcContext: KcContext.Template; } & KcTemplateProps;
export const Template = memo((props: TemplateProps) => {
@ -37,38 +36,50 @@ export const Template = memo((props: TemplateProps) => {
displayRequiredFields = false,
displayWide = false,
showAnotherWayIfPresent = true,
kcProperties = {},
headerNode,
showUsernameNode = null,
formNode,
displayInfoNode = null
infoNode = null,
kcContext
} = props;
useEffect(()=> { console.log("Rendering this page with react using keycloakify") },[]);
useEffect(() => { console.log("Rendering this page with react using keycloakify") }, []);
const { t } = useKcTranslation();
Object.assign(kcProperties, defaultKcTemplateProperties);
const { msg } = useKcMessage();
const { kcLanguageTag, setKcLanguageTag } = useKcLanguageTag();
const onChangeLanguageClickFactory = useCallbackFactory(
([languageTag]: [KcLanguageTag]) =>
([languageTag]: [KcLanguageTag]) =>
setKcLanguageTag(languageTag)
);
const onTryAnotherWayClick = useConstCallback(() => {
const onTryAnotherWayClick = useConstCallback(() =>
(document.forms["kc-select-try-another-way-form" as never].submit(), false)
);
document.forms["kc-select-try-another-way-form" as never].submit();
const {
realm, locale, auth,
url, message, isAppInitiatedAction
}= kcContext;
return false;
useEffect(()=>{
});
if( !realm.internationalizationEnabled ){
return;
}
const [{ realm, locale, auth, url, message, isAppInitiatedAction }] = useState(() => (
assert(kcContext !== undefined, "App is not currently being served by KeyCloak"),
kcContext
));
assert( locale !== undefined );
if( kcLanguageTag === getBestMatchAmongKcLanguageTag(locale.current) ){
return;
}
window.location.href =
locale.supported.find(({ languageTag }) => languageTag === kcLanguageTag)!.url;
},[kcLanguageTag]);
const [isExtraCssLoaded, setExtraCssLoaded] = useReducer(() => true, false);
@ -76,10 +87,13 @@ export const Template = memo((props: TemplateProps) => {
let isUnmounted = false;
const toArr = (x: string | readonly string[] | undefined) =>
typeof x === "string" ? x.split(" ") : x ?? [];
Promise.all(
[
...(kcProperties.stylesCommon ?? []).map(relativePath => pathJoin(url.resourcesCommonPath, relativePath)),
...(kcProperties.styles ?? []).map(relativePath => pathJoin(url.resourcesPath, relativePath))
...toArr(props.stylesCommon).map(relativePath => pathJoin(url.resourcesCommonPath, relativePath)),
...toArr(props.styles).map(relativePath => pathJoin(url.resourcesPath, relativePath))
].map(href => appendHead({
"type": "css",
href
@ -93,7 +107,7 @@ export const Template = memo((props: TemplateProps) => {
});
kcProperties.scripts?.forEach(
toArr(props.scripts).forEach(
relativePath => appendHead({
"type": "javascript",
"src": pathJoin(url.resourcesPath, relativePath)
@ -102,7 +116,7 @@ export const Template = memo((props: TemplateProps) => {
document.getElementsByTagName("html")[0]
.classList
.add(cx(kcProperties.kcHtmlClass));
.add(cx(props.kcHtmlClass));
return () => { isUnmounted = true; };
@ -113,16 +127,16 @@ export const Template = memo((props: TemplateProps) => {
}
return (
<div className={cx(kcProperties.kcLoginClass)}>
<div className={cx(props.kcLoginClass)}>
<div id="kc-header" className={cx(kcProperties.kcHeaderClass)}>
<div id="kc-header-wrapper" className={cx(kcProperties.kcHeaderWrapperClass)}>
{t("loginTitleHtml", realm.displayNameHtml)}
<div id="kc-header" className={cx(props.kcHeaderClass)}>
<div id="kc-header-wrapper" className={cx(props.kcHeaderWrapperClass)}>
{msg("loginTitleHtml", realm.displayNameHtml)}
</div>
</div>
<div className={cx(kcProperties.kcFormCardClass, displayWide && kcProperties.kcFormCardAccountClass)}>
<header className={cx(kcProperties.kcFormHeaderClass)}>
<div className={cx(props.kcFormCardClass, displayWide && props.kcFormCardAccountClass)}>
<header className={cx(props.kcFormHeaderClass)}>
{
(
realm.internationalizationEnabled &&
@ -130,7 +144,7 @@ export const Template = memo((props: TemplateProps) => {
locale.supported.length > 1
) &&
<div id="kc-locale">
<div id="kc-locale-wrapper" className={cx(kcProperties.kcLocaleWrapperClass)}>
<div id="kc-locale-wrapper" className={cx(props.kcLocaleWrapperClass)}>
<div className="kc-dropdown" id="kc-locale-dropdown">
<a href="#" id="kc-current-locale-link">
{getKcLanguageTagLabel(kcLanguageTag)}
@ -138,15 +152,14 @@ export const Template = memo((props: TemplateProps) => {
<ul>
{
locale.supported.map(
({ languageTag, url }) =>
<li className="kc-dropdown-item">
<a href={url} onClick={onChangeLanguageClickFactory(languageTag)}>
({ languageTag }) =>
<li key={languageTag} className="kc-dropdown-item">
<a href="#" onClick={onChangeLanguageClickFactory(languageTag)}>
{getKcLanguageTagLabel(languageTag)}
</a>
</li>
)
}
</ul>
</div>
@ -164,11 +177,11 @@ export const Template = memo((props: TemplateProps) => {
displayRequiredFields ?
(
<div className={cx(kcProperties.kcContentWrapperClass)}>
<div className={cx(kcProperties.kcLabelWrapperClass, "subtitle")}>
<div className={cx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle">
<span className="required">*</span>
{t("requiredFields")}
{msg("requiredFields")}
</span>
</div>
<div className="col-md-10">
@ -185,19 +198,19 @@ export const Template = memo((props: TemplateProps) => {
)
) : (
displayRequiredFields ? (
<div className={cx(kcProperties.kcContentWrapperClass)}>
<div className={cx(kcProperties.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle"><span className="required">*</span> {t("requiredFields")}</span>
<div className={cx(props.kcContentWrapperClass)}>
<div className={cx(props.kcLabelWrapperClass, "subtitle")}>
<span className="subtitle"><span className="required">*</span> {msg("requiredFields")}</span>
</div>
<div className="col-md-10">
{showUsernameNode}
<div className={cx(kcProperties.kcFormGroupClass)}>
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-username">
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
<a id="reset-login" href={url.loginRestartFlowUrl}>
<div className="kc-login-tooltip">
<i className={cx(kcProperties.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{t("restartLoginTooltip")}</span>
<i className={cx(props.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
</div>
</a>
</div>
@ -207,13 +220,13 @@ export const Template = memo((props: TemplateProps) => {
) : (
<>
{showUsernameNode}
<div className={cx(kcProperties.kcFormGroupClass)}>
<div className={cx(props.kcFormGroupClass)}>
<div id="kc-username">
<label id="kc-attempted-username">{auth?.attemptedUsername}</label>
<a id="reset-login" href={url.loginRestartFlowUrl}>
<div className="kc-login-tooltip">
<i className={cx(kcProperties.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{t("restartLoginTooltip")}</span>
<i className={cx(props.kcResetFlowIcon)}></i>
<span className="kc-tooltip-text">{msg("restartLoginTooltip")}</span>
</div>
</a>
</div>
@ -236,10 +249,10 @@ export const Template = memo((props: TemplateProps) => {
)
) &&
<div className={cx("alert", `alert-${message.type}`)}>
{message.type === "success" && <span className={cx(kcProperties.kcFeedbackSuccessIcon)}></span>}
{message.type === "warning" && <span className={cx(kcProperties.kcFeedbackWarningIcon)}></span>}
{message.type === "error" && <span className={cx(kcProperties.kcFeedbackErrorIcon)}></span>}
{message.type === "info" && <span className={cx(kcProperties.kcFeedbackInfoIcon)}></span>}
{message.type === "success" && <span className={cx(props.kcFeedbackSuccessIcon)}></span>}
{message.type === "warning" && <span className={cx(props.kcFeedbackWarningIcon)}></span>}
{message.type === "error" && <span className={cx(props.kcFeedbackErrorIcon)}></span>}
{message.type === "info" && <span className={cx(props.kcFeedbackInfoIcon)}></span>}
<span className="kc-feedback-text">{message.summary}</span>
</div>
}
@ -251,11 +264,11 @@ export const Template = memo((props: TemplateProps) => {
showAnotherWayIfPresent
) &&
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post" className={cx(displayWide && kcProperties.kcContentWrapperClass)} >
<div className={cx(displayWide && [kcProperties.kcFormSocialAccountContentClass, kcProperties.kcFormSocialAccountClass])} >
<div className={cx(kcProperties.kcFormGroupClass)}>
<form id="kc-select-try-another-way-form" action={url.loginAction} method="post" className={cx(displayWide && props.kcContentWrapperClass)} >
<div className={cx(displayWide && [props.kcFormSocialAccountContentClass, props.kcFormSocialAccountClass])} >
<div className={cx(props.kcFormGroupClass)}>
<input type="hidden" name="tryAnotherWay" value="on" />
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>{t("doTryAnotherWay")}</a>
<a href="#" id="try-another-way" onClick={onTryAnotherWayClick}>{msg("doTryAnotherWay")}</a>
</div>
</div >
</form>
@ -263,9 +276,9 @@ export const Template = memo((props: TemplateProps) => {
{
displayInfo &&
<div id="kc-info" className={cx(kcProperties.kcSignUpClass)}>
<div id="kc-info-wrapper" className={cx(kcProperties.kcInfoAreaWrapperClass)}>
{displayInfoNode}
<div id="kc-info" className={cx(props.kcSignUpClass)}>
<div id="kc-info-wrapper" className={cx(props.kcInfoAreaWrapperClass)}>
{infoNode}
</div>
</div>
}

View File

@ -8,7 +8,7 @@ export type LanguageLabel =
/* spell-checker: disable */
"Deutsch" | "Norsk" | "Русский" | "Svenska" | "Português (Brasil)" | "Lietuvių" |
"English" | "Italiano" | "Français" | "中文简体" | "Español" | "Čeština" | "日本語" |
"Slovenčina" | "Polish" | "Català" | "Nederlands" | "tr";
"Slovenčina" | "Polski" | "Català" | "Nederlands" | "Türkçe";
/* spell-checker: enable */
export function getKcLanguageTagLabel(language: KcLanguageTag): LanguageLabel {
@ -24,16 +24,15 @@ export function getKcLanguageTagLabel(language: KcLanguageTag): LanguageLabel {
case "no": return "Norsk";
case "pt-BR": return "Português (Brasil)";
case "ru": return "Русский";
case "sk":
case "sv": return "Slovenčina";
case "sk": return "Slovenčina";
case "ja": return "日本語";
case "pl": return "Polish";
case "pl": return "Polski";
case "zh-CN": return "中文简体"
case "sv": return "Svenska";
case "lt": return "Lietuvių";
case "cs": return "Čeština";
case "nl": return "Nederlands";
case "tr": return "tr"
case "tr": return "Türkçe";
/* spell-checker: enable */
}

View File

@ -1,13 +1,23 @@
import { createUseGlobalState } from "powerhooks";
import { kcContext } from "../kcContext";
import { kcContext } from "../KcContext";
import { getBestMatchAmongKcLanguageTag } from "./KcLanguageTag";
export const { useKcLanguageTag } = createUseGlobalState(
//export const { useKcLanguageTag, evtKcLanguageTag } = createUseGlobalState(
const wrap = createUseGlobalState(
"kcLanguageTag",
() => getBestMatchAmongKcLanguageTag(
kcContext?.locale?.["current" as never] ??
kcContext?.locale?.current ??
navigator.language
),
{ "persistance": "cookie" }
);
export const { useKcLanguageTag } = wrap;
export function getEvtKcLanguage() {
return wrap.evtKcLanguageTag;
}

View File

@ -7,11 +7,11 @@ import { id } from "evt/tools/typeSafety/id";
export type MessageKey = keyof typeof messages["en"];
export function useKcTranslation() {
export function useKcMessage() {
const { kcLanguageTag } = useKcLanguageTag();
const tStr = useConstCallback(
const msgStr = useConstCallback(
(key: MessageKey, ...args: (string | undefined)[]): string => {
let str: string = messages[kcLanguageTag as any as "en"][key] ?? messages["en"][key];
@ -31,13 +31,13 @@ export function useKcTranslation() {
}
);
const t = useConstCallback(
id<(...args: Parameters<typeof tStr>) => ReactNode>(
const msg = useConstCallback(
id<(...args: Parameters<typeof msgStr>) => ReactNode>(
(key, ...args) =>
<span className={key} dangerouslySetInnerHTML={{ "__html": tStr(key, ...args) }} />
<span className={key} dangerouslySetInnerHTML={{ "__html": msgStr(key, ...args) }} />
)
);
return { t, tStr };
return { msg, msgStr };
}

View File

@ -1,10 +1,18 @@
export * from "./kcContext";
export * from "./KcContext";
export * from "./i18n/KcLanguageTag";
export * from "./i18n/useKcLanguageTag";
export * from "./i18n/useKcTranslation";
export * from "./i18n/useKcMessage";
export * from "./components/KcProperties";
export * from "./components/KcProps";
export * from "./components/Login";
export * from "./components/Template";
export * from "./components/KcApp";
export * from "./components/KcApp";
export * from "./components/Info";
export * from "./components/Error";
export * from "./components/LoginResetPassword";
export * from "./components/LoginVerifyEmail";
export * from "./tools/assert";
export * as kcContextMocks from "./kcContextMocks";

View File

@ -1,12 +1,18 @@
import { ftlValuesGlobalName } from "../bin/build-keycloak-theme/ftlValuesGlobalName";
import type { generateFtlFilesCodeFactory } from "../bin/build-keycloak-theme/generateFtl";
import type { PageId } from "../bin/build-keycloak-theme/generateFtl";
import { id } from "evt/tools/typeSafety/id";
import type { KcLanguageTag } from "./i18n/KcLanguageTag";
import { doExtends } from "evt/tools/typeSafety/doExtends";
import type { MessageKey } from "./i18n/useKcMessage";
import type { LanguageLabel } from "./i18n/KcLanguageTag";
export type KcContext = KcContext.Login | KcContext.Register;
type ExtractAfterStartingWith<Prefix extends string, StrEnum> =
StrEnum extends `${Prefix}${infer U}` ? U : never;
export type KcContext =
KcContext.Login | KcContext.Register | KcContext.Info |
KcContext.Error | KcContext.LoginResetPassword | KcContext.LoginVerifyEmail;
export declare namespace KcContext {
export type Template = {
@ -21,8 +27,7 @@ export declare namespace KcContext {
displayName?: string;
displayNameHtml?: string;
internationalizationEnabled: boolean;
password: boolean;
registrationEmailAsUsername: boolean;
registrationEmailAsUsername: boolean; //<---
};
/** Undefined if !realm.internationalizationEnabled */
locale?: {
@ -34,15 +39,13 @@ export declare namespace KcContext {
*/
//label: LanguageLabel;
}[];
//NOTE: We do not expose this because the language is managed
//client side. We use this value however to set the default.
//current: LanguageLabel;
current: LanguageLabel;
},
auth?: {
showUsername: boolean;
showResetCredentials: boolean;
showTryAnotherWayLink: boolean;
attemptedUsername?: boolean;
attemptedUsername?: string;
};
scripts: string[];
message?: {
@ -53,7 +56,7 @@ export declare namespace KcContext {
};
export type Login = Template & {
pageBasename: "login.ftl";
pageId: "login.ftl";
url: {
loginResetCredentialsUrl: string;
registrationUrl: string;
@ -61,6 +64,7 @@ export declare namespace KcContext {
realm: {
loginWithEmailAllowed: boolean;
rememberMe: boolean;
password: boolean;
resetPasswordAllowed: boolean;
registrationAllowed: boolean;
};
@ -85,7 +89,7 @@ export declare namespace KcContext {
};
export type Register = Template & {
pageBasename: "register.ftl";
pageId: "register.ftl";
url: {
registrationAction: string;
};
@ -113,18 +117,44 @@ export declare namespace KcContext {
};
passwordRequired: boolean;
recaptchaRequired: boolean;
/** undefined if !recaptchaRequired */
recaptchaSiteKey?: string;
};
export type Info = Template & {
pageId: "info.ftl";
messageHeader?: string;
requiredActions?: ExtractAfterStartingWith<"requiredAction.", MessageKey>[];
skipLink: boolean;
pageRedirectUri?: string;
actionUri?: string;
client: {
baseUrl?: string;
}
};
export type Error = Template & {
pageId: "error.ftl";
client?: {
baseUrl?: string;
}
};
export type LoginResetPassword = Template & {
pageId: "login-reset-password.ftl";
realm: {
loginWithEmailAllowed: boolean;
}
};
export type LoginVerifyEmail = Template & {
pageId: "login-verify-email.ftl";
};
}
{
type T = KcContext["pageBasename"];
type U = Parameters<ReturnType<typeof generateFtlFilesCodeFactory>["generateFtlFilesCode"]>[0]["pageBasename"];
doExtends<T, U>();
doExtends<U, T>();
}
doExtends<KcContext["pageId"], PageId>();
doExtends<PageId, KcContext["pageId"]>();
export const kcContext = id<KcContext | undefined>((window as any)[ftlValuesGlobalName]);

View File

@ -0,0 +1,201 @@
import type { KcContext } from "../KcContext";
import { getEvtKcLanguage } from "../i18n/useKcLanguageTag";
import { getKcLanguageTagLabel } from "../i18n/KcLanguageTag";
//NOTE: Aside because we want to be able to import them from node
import { resourcesCommonPath, resourcesPath } from "./urlResourcesPath";
export const kcTemplateContext: KcContext.Template = {
"url": {
"loginAction": "#",
"resourcesPath": "/" + resourcesPath,
"resourcesCommonPath": "/" + resourcesCommonPath,
"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",
},
"realm": {
"displayName": "myrealm",
"displayNameHtml": "myrealm",
"internationalizationEnabled": true,
"registrationEmailAsUsername": true,
},
"locale": {
"supported": [
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=de",
"languageTag": "de"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=no",
"languageTag": "no"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ru",
"languageTag": "ru"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sv",
"languageTag": "sv"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pt-BR",
"languageTag": "pt-BR"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=lt",
"languageTag": "lt"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=en",
"languageTag": "en"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=it",
"languageTag": "it"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=fr",
"languageTag": "fr"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=zh-CN",
"languageTag": "zh-CN"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=es",
"languageTag": "es"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=cs",
"languageTag": "cs"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ja",
"languageTag": "ja"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=sk",
"languageTag": "sk"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=pl",
"languageTag": "pl"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=ca",
"languageTag": "ca"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=nl",
"languageTag": "nl"
},
{
"url": "/auth/realms/myrealm/login-actions/authenticate?client_id=account&tab_id=HoAx28ja4xg&execution=ee6c2834-46a4-4a20-a1b6-f6d6f6451b36&kc_locale=tr",
"languageTag": "tr"
}
],
"current": null as any
},
"auth": {
"showUsername": false,
"showResetCredentials": false,
"showTryAnotherWayLink": false
},
"scripts": [],
"message": {
"type": "success",
"summary": "This is a test message"
},
"isAppInitiatedAction": false,
};
Object.defineProperty(
kcTemplateContext.locale!,
"current",
{
"get": () => getKcLanguageTagLabel(getEvtKcLanguage().state),
"enumerable": true
}
);
export const kcLoginContext: KcContext.Login = {
...kcTemplateContext,
"pageId": "login.ftl",
"url": {
...kcTemplateContext.url,
"loginResetCredentialsUrl": "/auth/realms/myrealm/login-actions/reset-credentials?client_id=account&tab_id=HoAx28ja4xg",
"registrationUrl": "/auth/realms/myrealm/login-actions/registration?client_id=account&tab_id=HoAx28ja4xg"
},
"realm": {
...kcTemplateContext.realm,
"loginWithEmailAllowed": true,
"rememberMe": true,
"password": true,
"resetPasswordAllowed": true,
"registrationAllowed": true
},
"auth": kcTemplateContext.auth!,
"social": {
"displayInfo": true
},
"usernameEditDisabled": false,
"login": {
"rememberMe": false
},
"registrationDisabled": false,
};
export const kcRegisterContext: KcContext.Register = {
...kcTemplateContext,
"url": {
...kcLoginContext.url,
"registrationAction": "http://localhost:8080/auth/realms/myrealm/login-actions/registration?session_code=gwZdUeO7pbYpFTRxiIxRg_QtzMbtFTKrNu6XW_f8asM&execution=12146ce0-b139-4bbd-b25b-0eccfee6577e&client_id=account&tab_id=uS8lYfebLa0"
},
"messagesPerField": {
"printIfExists": (...[,x]) => x
},
"scripts": [],
"isAppInitiatedAction": false,
"pageId": "register.ftl",
"register": {
"formData": {}
},
"passwordRequired": true,
"recaptchaRequired": false
};
export const kcInfoContext: KcContext.Info ={
...kcTemplateContext,
"pageId": "info.ftl",
"messageHeader": "<Message header>",
"requiredActions": undefined,
"skipLink": false,
"actionUri": "#",
"client": {
"baseUrl": "#"
}
};
export const kcErrorContext: KcContext.Error = {
...kcTemplateContext,
"pageId": "error.ftl",
"client": {
"baseUrl": "#"
}
};
export const kcLoginResetPasswordContext: KcContext.LoginResetPassword = {
...kcTemplateContext,
"pageId": "login-reset-password.ftl",
"realm":{
...kcTemplateContext.realm,
"loginWithEmailAllowed": false
}
};
export const kcLoginVerifyEmailContext: KcContext.LoginVerifyEmail = {
...kcTemplateContext,
"pageId": "login-verify-email.ftl"
};

View File

@ -0,0 +1,6 @@
import { join as pathJoin } from "path";
export const subDirOfPublicDirBasename = "keycloak_static";
export const resourcesPath = pathJoin(subDirOfPublicDirBasename, "/resources");
export const resourcesCommonPath = pathJoin(subDirOfPublicDirBasename, "/resources_common");

338
yarn.lock
View File

@ -27,9 +27,9 @@
integrity sha512-np/lG3uARFybkoHokJUmf1QfEvRVCPbmQeUQpKow5cQ3xWrV9i3rUHodKDJPQfTVX61qKi+UdYk8kik84n7XOw==
"@babel/highlight@^7.12.13":
version "7.13.8"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.8.tgz#10b2dac78526424dfc1f47650d0e415dfd9dc481"
integrity sha512-4vrIhfJyfNf+lCtXC2ck1rKSzDwciqF7IWFhXXrSOUC2O5DrVp+w4c6ed4AllTxhTkUP5x2tYj41VaxdVMMRDw==
version "7.13.10"
resolved "https://registry.yarnpkg.com/@babel/highlight/-/highlight-7.13.10.tgz#a8b2a66148f5b27d666b15d81774347a731d52d1"
integrity sha512-5aPpe5XQPzflQrFwL1/QoeHkP2MsA4JCntcXHRhEsdsfPVkvPi2w7Qix4iV7t5S/oC9OodGrggd8aco1g3SZFg==
dependencies:
"@babel/helper-validator-identifier" "^7.12.11"
chalk "^2.0.0"
@ -43,9 +43,9 @@
"@babel/helper-plugin-utils" "^7.12.13"
"@babel/runtime@^7.7.2":
version "7.13.9"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.9.tgz#97dbe2116e2630c489f22e0656decd60aaa1fcee"
integrity sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==
version "7.13.10"
resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
dependencies:
regenerator-runtime "^0.13.4"
@ -109,9 +109,9 @@
integrity sha512-igX9a37DR2ZPGYtV6suZ6whr8pTFtyHL3K/oLUotxpSVO2ASaprmAe2Dkq7tBo7CRY7MMDrAa9nuQP9/YG8FxQ==
"@emotion/serialize@^1.0.0":
version "1.0.0"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.0.tgz#1a61f4f037cf39995c97fc80ebe99abc7b191ca9"
integrity sha512-zt1gm4rhdo5Sry8QpCOpopIUIKU+mUSpV9WNmFILUraatm5dttNEaYzUWWSboSMUE6PtN2j1cAsuvcugfdI3mw==
version "1.0.1"
resolved "https://registry.yarnpkg.com/@emotion/serialize/-/serialize-1.0.1.tgz#322cdebfdbb5a88946f17006548191859b9b0855"
integrity sha512-TXlKs5sgUKhFlszp/rg4lIAZd7UUSmJpwaf9/lAEFcUh2vPi32i7x4wk7O8TN8L8v2Ol8k0CxnhRBY0zQalTxA==
dependencies:
"@emotion/hash" "^0.8.0"
"@emotion/memoize" "^0.7.4"
@ -139,117 +139,10 @@
resolved "https://registry.yarnpkg.com/@emotion/weak-memoize/-/weak-memoize-0.2.5.tgz#8eed982e2ee6f7f4e44c253e12962980791efd46"
integrity sha512-6U71C2Wp7r5XtFtQzYrW5iKFT67OixrSxjI4MptCHzdSVlgabczzqLe0ZSgnub/5Kp4hSbpDB1tMytZY9pwxxA==
"@octokit/auth-token@^2.4.4":
version "2.4.5"
resolved "https://registry.yarnpkg.com/@octokit/auth-token/-/auth-token-2.4.5.tgz#568ccfb8cb46f36441fac094ce34f7a875b197f3"
integrity sha512-BpGYsPgJt05M7/L/5FoE1PiAbdxXFZkX/3kDYcsvd1v6UhlnE5e96dTDr0ezX/EFwciQxf3cNV0loipsURU+WA==
dependencies:
"@octokit/types" "^6.0.3"
"@octokit/core@^3.2.3":
version "3.2.5"
resolved "https://registry.yarnpkg.com/@octokit/core/-/core-3.2.5.tgz#57becbd5fd789b0592b915840855f3a5f233d554"
integrity sha512-+DCtPykGnvXKWWQI0E1XD+CCeWSBhB6kwItXqfFmNBlIlhczuDPbg+P6BtLnVBaRJDAjv+1mrUJuRsFSjktopg==
dependencies:
"@octokit/auth-token" "^2.4.4"
"@octokit/graphql" "^4.5.8"
"@octokit/request" "^5.4.12"
"@octokit/types" "^6.0.3"
before-after-hook "^2.1.0"
universal-user-agent "^6.0.0"
"@octokit/endpoint@^6.0.1":
version "6.0.11"
resolved "https://registry.yarnpkg.com/@octokit/endpoint/-/endpoint-6.0.11.tgz#082adc2aebca6dcefa1fb383f5efb3ed081949d1"
integrity sha512-fUIPpx+pZyoLW4GCs3yMnlj2LfoXTWDUVPTC4V3MUEKZm48W+XYpeWSZCv+vYF1ZABUm2CqnDVf1sFtIYrj7KQ==
dependencies:
"@octokit/types" "^6.0.3"
is-plain-object "^5.0.0"
universal-user-agent "^6.0.0"
"@octokit/graphql@^4.5.8":
version "4.6.0"
resolved "https://registry.yarnpkg.com/@octokit/graphql/-/graphql-4.6.0.tgz#f9abca55f82183964a33439d5264674c701c3327"
integrity sha512-CJ6n7izLFXLvPZaWzCQDjU/RP+vHiZmWdOunaCS87v+2jxMsW9FB5ktfIxybRBxZjxuJGRnxk7xJecWTVxFUYQ==
dependencies:
"@octokit/request" "^5.3.0"
"@octokit/types" "^6.0.3"
universal-user-agent "^6.0.0"
"@octokit/openapi-types@^5.3.0":
version "5.3.0"
resolved "https://registry.yarnpkg.com/@octokit/openapi-types/-/openapi-types-5.3.0.tgz#29e3faa119da90082dc653ea74c8bb345d197bf7"
integrity sha512-5q2qBz4iZ0xS/DEJ0ROusFbN4cVlbJE9GvOByen+mv7artuGXfVhONqcuRd7jYN2glTmCnzcZw+X6LrjRVqs0A==
"@octokit/plugin-paginate-rest@^2.6.2":
version "2.11.0"
resolved "https://registry.yarnpkg.com/@octokit/plugin-paginate-rest/-/plugin-paginate-rest-2.11.0.tgz#3568c43896a3355f4a0bbb3a64f443b2abdc760d"
integrity sha512-7L9xQank2G3r1dGqrVPo1z62V5utbykOUzlmNHPz87Pww/JpZQ9KyG5CHtUzgmB4n5iDRKYNK/86A8D98HP0yA==
dependencies:
"@octokit/types" "^6.11.0"
"@octokit/plugin-request-log@^1.0.2":
version "1.0.3"
resolved "https://registry.yarnpkg.com/@octokit/plugin-request-log/-/plugin-request-log-1.0.3.tgz#70a62be213e1edc04bb8897ee48c311482f9700d"
integrity sha512-4RFU4li238jMJAzLgAwkBAw+4Loile5haQMQr+uhFq27BmyJXcXSKvoQKqh0agsZEiUlW6iSv3FAgvmGkur7OQ==
"@octokit/plugin-rest-endpoint-methods@4.13.3":
version "4.13.3"
resolved "https://registry.yarnpkg.com/@octokit/plugin-rest-endpoint-methods/-/plugin-rest-endpoint-methods-4.13.3.tgz#17ba3a24adce0933129cfefa6d44e7da2eeac04b"
integrity sha512-nMGS2osFcWXRfHkDR0d+lB1zpMPTZJ0NjysPUfs7BT5/juNG/Q0+5UB6nC1f62jPzun154qekzwOb7Q5oahCXQ==
dependencies:
"@octokit/types" "^6.12.0"
deprecation "^2.3.1"
"@octokit/request-error@^2.0.0":
version "2.0.5"
resolved "https://registry.yarnpkg.com/@octokit/request-error/-/request-error-2.0.5.tgz#72cc91edc870281ad583a42619256b380c600143"
integrity sha512-T/2wcCFyM7SkXzNoyVNWjyVlUwBvW3igM3Btr/eKYiPmucXTtkxt2RBsf6gn3LTzaLSLTQtNmvg+dGsOxQrjZg==
dependencies:
"@octokit/types" "^6.0.3"
deprecation "^2.0.0"
once "^1.4.0"
"@octokit/request@^5.3.0", "@octokit/request@^5.4.12":
version "5.4.14"
resolved "https://registry.yarnpkg.com/@octokit/request/-/request-5.4.14.tgz#ec5f96f78333bb2af390afa5ff66f114b063bc96"
integrity sha512-VkmtacOIQp9daSnBmDI92xNIeLuSRDOIuplp/CJomkvzt7M18NXgG044Cx/LFKLgjKt9T2tZR6AtJayba9GTSA==
dependencies:
"@octokit/endpoint" "^6.0.1"
"@octokit/request-error" "^2.0.0"
"@octokit/types" "^6.7.1"
deprecation "^2.0.0"
is-plain-object "^5.0.0"
node-fetch "^2.6.1"
once "^1.4.0"
universal-user-agent "^6.0.0"
"@octokit/rest@^18.0.0":
version "18.3.3"
resolved "https://registry.yarnpkg.com/@octokit/rest/-/rest-18.3.3.tgz#a3b0f24bf5c42cacd93dbc48576b5d4d4e032a38"
integrity sha512-OxElwBanZn1AShCaIrRTLM9PwhGE5/busMke/go30OWAQ+eJMD7Us/67mtapE77EYY4FM2tvb4Eg25rZaA/NPA==
dependencies:
"@octokit/core" "^3.2.3"
"@octokit/plugin-paginate-rest" "^2.6.2"
"@octokit/plugin-request-log" "^1.0.2"
"@octokit/plugin-rest-endpoint-methods" "4.13.3"
"@octokit/types@^6.0.3", "@octokit/types@^6.11.0", "@octokit/types@^6.12.0", "@octokit/types@^6.7.1":
version "6.12.0"
resolved "https://registry.yarnpkg.com/@octokit/types/-/types-6.12.0.tgz#8376fd60edfd5d1eebfeedb994c6bcb5b862c7a1"
integrity sha512-KwOf16soD7aDEEi/PgNeJlHzjZPfrmmNy+7WezSdrpnqZ7YImBJcNnX9+5RUHt1MnA4h8oISRHTqaZDGsX9DRQ==
dependencies:
"@octokit/openapi-types" "^5.3.0"
"@types/comment-json@^1.1.1":
version "1.1.1"
resolved "https://registry.yarnpkg.com/@types/comment-json/-/comment-json-1.1.1.tgz#b4ae889912a93e64619f97989aecaff8ce889dca"
integrity sha512-U70oEqvnkeSSp8BIJwJclERtT13rd9ejK7XkIzMCQQePZe3VW1b7iQggXyW4ZvfGtGeXD0pZw24q5iWNe++HqQ==
"@types/node@^10.0.0":
version "10.17.54"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.54.tgz#a737488631aca3ec7bd9f6229d77f1079e444793"
integrity sha512-c8Lm7+hXdSPmWH4B9z/P/xIXhFK3mCQin4yCYMd2p1qpMG5AfgyJuYZ+3q2dT7qLiMMMGMd5dnkFpdqJARlvtQ==
version "10.17.55"
resolved "https://registry.yarnpkg.com/@types/node/-/node-10.17.55.tgz#a147f282edec679b894d4694edb5abeb595fecbd"
integrity sha512-koZJ89uLZufDvToeWO5BrC4CR4OUfHnUz2qoPs/daQH6qq3IN62QFxCTZ+bKaCE0xaoCAJYE4AXre8AbghCrhg==
"@types/parse-json@^4.0.0":
version "4.0.0"
@ -262,13 +155,19 @@
integrity sha512-KfRL3PuHmqQLOG+2tGpRO26Ctg+Cq1E01D2DMriKEATHgWLfeNDmq9e29Q9WIky0dQ3NPkd1mzYH8Lm936Z9qw==
"@types/react@^17.0.0":
version "17.0.2"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.2.tgz#3de24c4efef902dd9795a49c75f760cbe4f7a5a8"
integrity sha512-Xt40xQsrkdvjn1EyWe1Bc0dJLcil/9x2vAuW7ya+PuQip4UYUaXyhzWmAbwRsdMgwOFHpfp7/FFZebDU6Y8VHA==
version "17.0.3"
resolved "https://registry.yarnpkg.com/@types/react/-/react-17.0.3.tgz#ba6e215368501ac3826951eef2904574c262cc79"
integrity sha512-wYOUxIgs2HZZ0ACNiIayItyluADNbONl7kt8lkLjVK8IitMH5QMyAh75Fwhmo37r1m7L2JaFj03sIfxBVDvRAg==
dependencies:
"@types/prop-types" "*"
"@types/scheduler" "*"
csstype "^3.0.2"
"@types/scheduler@*":
version "0.16.1"
resolved "https://registry.yarnpkg.com/@types/scheduler/-/scheduler-0.16.1.tgz#18845205e86ff0038517aab7a18a62a6b9f71275"
integrity sha512-EaCxbanVeyxDRTQBkdLb3Bvl/HK7PBK6UJjsSixB0iHKoWxE5uu2Q/DgtpOhPIojN0Zl1whvOd7PoHs2P0s5eA==
ansi-regex@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.0.tgz#388539f55179bf39339c81af30a654d69f87cb75"
@ -302,11 +201,6 @@ balanced-match@^1.0.0:
resolved "https://registry.yarnpkg.com/balanced-match/-/balanced-match-1.0.0.tgz#89b4d199ab2bee49de164ea02b89ce462d71b767"
integrity sha1-ibTRmasr7kneFk6gK4nORi1xt2c=
before-after-hook@^2.1.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/before-after-hook/-/before-after-hook-2.1.1.tgz#99ae36992b5cfab4a83f6bee74ab27835f28f405"
integrity sha512-5ekuQOvO04MDj7kYZJaMab2S8SPjGJbotVNyv7QYFCOAwrGZs/YnoDNlh1U+m5hl7H2D/+n0taaAV/tfyd3KMA==
boolbase@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
@ -391,21 +285,6 @@ color-name@~1.1.4:
resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2"
integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==
commander@^4.1.1:
version "4.1.1"
resolved "https://registry.yarnpkg.com/commander/-/commander-4.1.1.tgz#9fd602bd936294e9e9ef46a3f4d6964044b18068"
integrity sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==
comment-json@^3.0.2:
version "3.0.3"
resolved "https://registry.yarnpkg.com/comment-json/-/comment-json-3.0.3.tgz#0cadacd6278602b57b8c51b1814dc5d311d228c4"
integrity sha512-P7XwYkC3qjIK45EAa9c5Y3lR7SMXhJqwFdWg3niAIAcbk3zlpKDdajV8Hyz/Y3sGNn3l+YNMl8A2N/OubSArHg==
dependencies:
core-util-is "^1.0.2"
esprima "^4.0.1"
has-own-prop "^2.0.0"
repeat-string "^1.6.1"
concat-map@0.0.1:
version "0.0.1"
resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b"
@ -431,7 +310,7 @@ copyfiles@^2.4.1:
untildify "^4.0.0"
yargs "^16.1.0"
core-util-is@^1.0.2, core-util-is@~1.0.0:
core-util-is@~1.0.0:
version "1.0.2"
resolved "https://registry.yarnpkg.com/core-util-is/-/core-util-is-1.0.2.tgz#b5fd54220aa2bc5ab57aab7140c940754503c1a7"
integrity sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=
@ -476,29 +355,6 @@ d@1, d@^1.0.1:
es5-ext "^0.10.50"
type "^1.0.1"
denoify@^0.6.5:
version "0.6.5"
resolved "https://registry.yarnpkg.com/denoify/-/denoify-0.6.5.tgz#06512be38026ec0f2033db9566d07428e63d56bf"
integrity sha512-AhKBlvO91QdP5T/fRcxiMj3b6cDe5ucr+7YVlePXCuV/kImbiR0V1qjGTCfmVuC3wvRn5Xf6r34qJJRbo86AqA==
dependencies:
"@octokit/rest" "^18.0.0"
"@types/comment-json" "^1.1.1"
commander "^4.1.1"
comment-json "^3.0.2"
evt "^1.9.2"
get-github-default-branch-name "^0.0.4"
gitignore-parser "0.0.2"
glob "^7.1.6"
node-fetch "^2.6.0"
path-depth "^1.0.0"
scripting-tools "^0.19.13"
url-join "^4.0.1"
deprecation@^2.0.0, deprecation@^2.3.1:
version "2.3.1"
resolved "https://registry.yarnpkg.com/deprecation/-/deprecation-2.3.1.tgz#6368cbdb40abf3373b525ac87e4a260c3a700919"
integrity sha512-xmHIy4F3scKVwMsQ4WnVaS8bHOx0DmVwRywosKhaILI0ywMDWPtBSku2HNxRvF7jtwDRsoEwYQSfbxj8b7RlJQ==
dom-serializer@^1.0.1, dom-serializer@~1.2.0:
version "1.2.0"
resolved "https://registry.yarnpkg.com/dom-serializer/-/dom-serializer-1.2.0.tgz#3433d9136aeb3c627981daa385fc7f32d27c48f1"
@ -521,9 +377,9 @@ domhandler@^4.0.0:
domelementtype "^2.1.0"
domutils@^2.4.3, domutils@^2.4.4:
version "2.4.4"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.4.4.tgz#282739c4b150d022d34699797369aad8d19bbbd3"
integrity sha512-jBC0vOsECI4OMdD0GC9mGn7NXPLb+Qt6KW1YDQzeQYRUFKmNG8lh7mO5HiELfr+lLQE7loDVI4QcAxV80HS+RA==
version "2.5.0"
resolved "https://registry.yarnpkg.com/domutils/-/domutils-2.5.0.tgz#42f49cffdabb92ad243278b331fd761c1c2d3039"
integrity sha512-Ho16rzNMOFk2fPwChGh3D2D9OEHAfG19HgmRR2l+WLSsIstNsAYBzePH412bL0y5T44ejABIVfTHQ8nqi/tBCg==
dependencies:
dom-serializer "^1.0.1"
domelementtype "^2.0.1"
@ -602,11 +458,6 @@ escape-string-regexp@^4.0.0:
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34"
integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==
esprima@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/esprima/-/esprima-4.0.1.tgz#13b04cdb3e6c5d19df91ab6987a8695619b0aa71"
integrity sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==
event-emitter@^0.3.5:
version "0.3.5"
resolved "https://registry.yarnpkg.com/event-emitter/-/event-emitter-0.3.5.tgz#df8c69eef1647923c7157b9ce83840610b02cc39"
@ -615,14 +466,6 @@ event-emitter@^0.3.5:
d "1"
es5-ext "~0.10.14"
evt@2.0.0-beta.13:
version "2.0.0-beta.13"
resolved "https://registry.yarnpkg.com/evt/-/evt-2.0.0-beta.13.tgz#a84828cc15b535fabdeaf551982d0705ed428a6f"
integrity sha512-Y71l8BU1r/hs7MOyaNb5PTf8Jp+5h8Vt6T8ixvJSqDmKhwCjjCVKP2b1E3UE+Qlr1bZnCOWnVTucTeTeJghWEQ==
dependencies:
minimal-polyfills "^2.1.5"
run-exclusive "^2.2.14"
evt@2.0.0-beta.15:
version "2.0.0-beta.15"
resolved "https://registry.yarnpkg.com/evt/-/evt-2.0.0-beta.15.tgz#a34ef224c827152c06f29157a9af5a41644e1a36"
@ -631,14 +474,6 @@ evt@2.0.0-beta.15:
minimal-polyfills "^2.1.5"
run-exclusive "^2.2.14"
evt@^1.9.2:
version "1.9.12"
resolved "https://registry.yarnpkg.com/evt/-/evt-1.9.12.tgz#8d06177259cbcb09ef936e18945bf7ddd087170c"
integrity sha512-u8wC4Xif2pcDJ9cEm0wzWCIQb+Y214m1eUgsgm2hVIuXuvC6LToryA0Ecl1O8Slii2E9l6USLsyxXWntjlnIbw==
dependencies:
minimal-polyfills "^2.1.5"
run-exclusive "^2.2.14"
ext@^1.1.2:
version "1.4.0"
resolved "https://registry.yarnpkg.com/ext/-/ext-1.4.0.tgz#89ae7a07158f79d35517882904324077e4379244"
@ -666,20 +501,7 @@ get-caller-file@^2.0.5:
resolved "https://registry.yarnpkg.com/get-caller-file/-/get-caller-file-2.0.5.tgz#4f94412a82db32f36e3b0b9741f8a97feb031f7e"
integrity sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==
get-github-default-branch-name@^0.0.4:
version "0.0.4"
resolved "https://registry.yarnpkg.com/get-github-default-branch-name/-/get-github-default-branch-name-0.0.4.tgz#9c0c6606ba606edb136d2fd26e4d515b69f2de90"
integrity sha512-ltOGC9Jk0k8boe48Gk7SkJErwxt7MhwXtbNrBUyNCZcwcXSmGRdkKb2u0YO250PGvPsUtdqRjg7lVuIk1VtpCg==
dependencies:
"@octokit/rest" "^18.0.0"
scripting-tools "^0.19.12"
gitignore-parser@0.0.2:
version "0.0.2"
resolved "https://registry.yarnpkg.com/gitignore-parser/-/gitignore-parser-0.0.2.tgz#f61259b985dd91414b9a7168faef9171c2eec5df"
integrity sha1-9hJZuYXdkUFLmnFo+u+RccLuxd8=
glob@^7.0.5, glob@^7.1.3, glob@^7.1.6:
glob@^7.0.5, glob@^7.1.3:
version "7.1.6"
resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.6.tgz#141f33b81a7c2492e125594307480c46679278a6"
integrity sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==
@ -696,11 +518,6 @@ has-flag@^3.0.0:
resolved "https://registry.yarnpkg.com/has-flag/-/has-flag-3.0.0.tgz#b5d454dc2199ae225699f3467e5a07f3b955bafd"
integrity sha1-tdRU3CGZriJWmfNGfloH87lVuv0=
has-own-prop@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/has-own-prop/-/has-own-prop-2.0.0.tgz#f0f95d58f65804f5d218db32563bb85b8e0417af"
integrity sha512-Pq0h+hvsVm6dDEa8x82GnLSYHOzNDt7f0ddFa3FqcQlgzEiptPqL+XrOJNavjOzSYiYWIrgeVYYgGlLmnxwilQ==
has@^1.0.3:
version "1.0.3"
resolved "https://registry.yarnpkg.com/has/-/has-1.0.3.tgz#722d7cbfc1f6aa8241f16dd814e011e1f41e8796"
@ -709,9 +526,9 @@ has@^1.0.3:
function-bind "^1.1.1"
htmlparser2@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.0.tgz#c2da005030390908ca4c91e5629e418e0665ac01"
integrity sha512-numTQtDZMoh78zJpaNdJ9MXb2cv5G3jwUoe3dMQODubZvLoGvTE/Ofp6sHvH8OGKcN/8A47pGLi/k58xHP/Tfw==
version "6.0.1"
resolved "https://registry.yarnpkg.com/htmlparser2/-/htmlparser2-6.0.1.tgz#422521231ef6d42e56bd411da8ba40aa36e91446"
integrity sha512-GDKPd+vk4jvSuvCbyuzx/unmXkk090Azec7LovXP8as1Hn8q9p3hbjmDGbUqqhknw0ajwit6LiiWqfiTUPMK7w==
dependencies:
domelementtype "^2.0.1"
domhandler "^4.0.0"
@ -739,6 +556,11 @@ inherits@2, inherits@^2.0.1, inherits@~2.0.1, inherits@~2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.4.tgz#0fa2c64f932917c3433a0ded55363aae37416b7c"
integrity sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==
inherits@2.0.3:
version "2.0.3"
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
is-arrayish@^0.2.1:
version "0.2.1"
resolved "https://registry.yarnpkg.com/is-arrayish/-/is-arrayish-0.2.1.tgz#77c99840527aa8ecb1a8ba697b80645a7a926a9d"
@ -756,11 +578,6 @@ is-fullwidth-code-point@^3.0.0:
resolved "https://registry.yarnpkg.com/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz#f116f8064fe90b3f7844a38997c0b75051269f1d"
integrity sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==
is-plain-object@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/is-plain-object/-/is-plain-object-5.0.0.tgz#4427f50ab3429e9025ea7d52e9043a9ef4159344"
integrity sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==
is-promise@^2.2.2:
version "2.2.2"
resolved "https://registry.yarnpkg.com/is-promise/-/is-promise-2.2.2.tgz#39ab959ccbf9a774cf079f7b40c7a26f763135f1"
@ -851,11 +668,6 @@ next-tick@~1.0.0:
resolved "https://registry.yarnpkg.com/next-tick/-/next-tick-1.0.0.tgz#ca86d1fe8828169b0120208e3dc8424b9db8342c"
integrity sha1-yobR/ogoFpsBICCOPchCS524NCw=
node-fetch@^2.6.0, node-fetch@^2.6.1:
version "2.6.1"
resolved "https://registry.yarnpkg.com/node-fetch/-/node-fetch-2.6.1.tgz#045bd323631f76ed2e2b55573394416b639a0052"
integrity sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw==
noms@0.0.0:
version "0.0.0"
resolved "https://registry.yarnpkg.com/noms/-/noms-0.0.0.tgz#da8ebd9f3af9d6760919b27d9cdc8092a7332859"
@ -876,7 +688,7 @@ object-assign@^4.1.1:
resolved "https://registry.yarnpkg.com/object-assign/-/object-assign-4.1.1.tgz#2109adc7965887cfc05cbbd442cac8bfbb360863"
integrity sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=
once@^1.3.0, once@^1.4.0:
once@^1.3.0:
version "1.4.0"
resolved "https://registry.yarnpkg.com/once/-/once-1.4.0.tgz#583b1aa775961d4b113ac17d9c50baef9dd76bd1"
integrity sha1-WDsap3WWHUsROsF9nFC6753Xa9E=
@ -912,11 +724,6 @@ parse5@^6.0.0, parse5@^6.0.1:
resolved "https://registry.yarnpkg.com/parse5/-/parse5-6.0.1.tgz#e1a1c085c569b3dc08321184f19a39cc27f7c30b"
integrity sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==
path-depth@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/path-depth/-/path-depth-1.0.0.tgz#88cf881097e171b8b54d450d2167ea76063d3086"
integrity sha512-dEiwdXAQyLvOi6ktLqhFhjVelJiVsdp2xBX3BaUtYCCkMRZTwUiq7cha+A0myvAVXRHbXfjhfTf4mNoAWzm2iA==
path-is-absolute@^1.0.0:
version "1.0.1"
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
@ -932,12 +739,20 @@ path-type@^4.0.0:
resolved "https://registry.yarnpkg.com/path-type/-/path-type-4.0.0.tgz#84ed01c0a7ba380afe09d90a8c180dcd9d03043b"
integrity sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==
powerhooks@^0.0.17:
version "0.0.17"
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.0.17.tgz#840295271d989c27b83047fad44973434c509ba7"
integrity sha512-9z0C5pnVJI3SRKgcoAjbOxVa1lf/e58N+YdByThue1C4DUqfp8umx9XvrRJJ3dGwl6A0RRrJlyPM1KXjVChQMQ==
path@^0.12.7:
version "0.12.7"
resolved "https://registry.yarnpkg.com/path/-/path-0.12.7.tgz#d4dc2a506c4ce2197eb481ebfcd5b36c0140b10f"
integrity sha1-1NwqUGxM4hl+tIHr/NWzbAFAsQ8=
dependencies:
evt "2.0.0-beta.13"
process "^0.11.1"
util "^0.10.3"
powerhooks@^0.0.27:
version "0.0.27"
resolved "https://registry.yarnpkg.com/powerhooks/-/powerhooks-0.0.27.tgz#e9dc29258860d2f6bf32b249d9cba07c6f53f393"
integrity sha512-ohayWhtIEdLqiC2th/GEhaRfOhqekFg2uFo0JZ8Dn7oTnAZybs618QJeq5ag9oy3lFVzl+kbROpVa8Ch5zrkaA==
dependencies:
evt "2.0.0-beta.15"
memoizee "^0.4.15"
resize-observer-polyfill "^1.5.1"
@ -946,6 +761,11 @@ process-nextick-args@~2.0.0:
resolved "https://registry.yarnpkg.com/process-nextick-args/-/process-nextick-args-2.0.1.tgz#7820d9b16120cc55ca9ae7792680ae7dba6d7fe2"
integrity sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==
process@^0.11.1:
version "0.11.10"
resolved "https://registry.yarnpkg.com/process/-/process-0.11.10.tgz#7332300e840161bda3e69a1d1d91a7d4bc16f182"
integrity sha1-czIwDoQBYb2j5podHZGn1LwW8YI=
properties-parser@^0.3.1:
version "0.3.1"
resolved "https://registry.yarnpkg.com/properties-parser/-/properties-parser-0.3.1.tgz#1316e9539ffbfd93845e369b211022abd478771a"
@ -989,11 +809,6 @@ regenerator-runtime@^0.13.4:
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz#cac2dacc8a1ea675feaabaeb8ae833898ae46f55"
integrity sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==
repeat-string@^1.6.1:
version "1.6.1"
resolved "https://registry.yarnpkg.com/repeat-string/-/repeat-string-1.6.1.tgz#8dcae470e1c88abc2d600fff4a776286da75e637"
integrity sha1-jcrkcOHIirwtYA//Sndihtp15jc=
require-directory@^2.1.1:
version "2.1.1"
resolved "https://registry.yarnpkg.com/require-directory/-/require-directory-2.1.1.tgz#8c64ad5fd30dab1c976e2344ffe7f792a6a6df42"
@ -1036,7 +851,7 @@ safe-buffer@~5.1.0, safe-buffer@~5.1.1:
resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d"
integrity sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==
scripting-tools@^0.19.12, scripting-tools@^0.19.13:
scripting-tools@^0.19.13:
version "0.19.13"
resolved "https://registry.yarnpkg.com/scripting-tools/-/scripting-tools-0.19.13.tgz#836df7d9c2ec99aea91984d1d5b2bd110670afec"
integrity sha512-d09H8vzSVa8p4XUTJqHZDbjKDyl5TG3SyPfNPUUkfyOwjwykStmfK8AXyWq7VRWjcgzTpkTiJ9uMk1NytMQY7w==
@ -1112,10 +927,10 @@ to-fast-properties@^2.0.0:
resolved "https://registry.yarnpkg.com/to-fast-properties/-/to-fast-properties-2.0.0.tgz#dc5e698cbd079265bc73e0377681a4e4e83f616e"
integrity sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=
tss-react@^0.0.9:
version "0.0.9"
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-0.0.9.tgz#4f07be32ad8a9c7db2881236b80fd4715384fe92"
integrity sha512-1Vo1UQrj18RaQIla/pes717iBzuYwVpQq/BdK/jREpm6kVS4HpoYXQlM0A0wp2ToG3kvFdop5STqowW3sviCfw==
tss-react@^0.0.11:
version "0.0.11"
resolved "https://registry.yarnpkg.com/tss-react/-/tss-react-0.0.11.tgz#1cd061927744cd4fc9b7346e2fd1cfcf896a18d5"
integrity sha512-j8CDpHHIl6S6/mX+AmK08v7waPqwgNA7urHOD3qknCgbY79LRlS7he5DF4NUNE/5B4/Btc3F25w+KqgChNbyGw==
dependencies:
"@emotion/css" "^11.1.3"
@ -1125,35 +940,32 @@ type@^1.0.1:
integrity sha512-+5nt5AAniqsCnu2cEQQdpzCAh33kVx8n0VoFidKpB1dVVLAN/F+bgVOqOJqOnEnrhp222clB5p3vUlD+1QAnfg==
type@^2.0.0:
version "2.3.0"
resolved "https://registry.yarnpkg.com/type/-/type-2.3.0.tgz#ada7c045f07ead08abf9e2edd29be1a0c0661132"
integrity sha512-rgPIqOdfK/4J9FhiVrZ3cveAjRRo5rsQBAIhnylX874y1DX/kEKSVdLsnuHB6l1KTjHyU01VjiMBHgU2adejyg==
version "2.5.0"
resolved "https://registry.yarnpkg.com/type/-/type-2.5.0.tgz#0a2e78c2e77907b252abe5f298c1b01c63f0db3d"
integrity sha512-180WMDQaIMm3+7hGXWf12GtdniDEy7nYcyFMKJn/eZz/6tSLXrUN9V0wKSbMjej0I1WHWbpREDEKHtqPQa9NNw==
typescript@^4.1.5:
typescript@^4.2.3:
version "4.2.3"
resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.2.3.tgz#39062d8019912d43726298f09493d598048c1ce3"
integrity sha512-qOcYwxaByStAWrBf4x0fibwZvMRG+r4cQoTjbPtUlrWjBHbmCAww1i448U0GJ+3cNNEtebDteo/cHOR3xJ4wEw==
universal-user-agent@^6.0.0:
version "6.0.0"
resolved "https://registry.yarnpkg.com/universal-user-agent/-/universal-user-agent-6.0.0.tgz#3381f8503b251c0d9cd21bc1de939ec9df5480ee"
integrity sha512-isyNax3wXoKaulPDZWHQqbmIx1k2tb9fb3GGDBRxCscfYV2Ch7WxPArBsFEG8s/safwXTT7H4QGhaIkTp9447w==
untildify@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/untildify/-/untildify-4.0.0.tgz#2bc947b953652487e4600949fb091e3ae8cd919b"
integrity sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==
url-join@^4.0.1:
version "4.0.1"
resolved "https://registry.yarnpkg.com/url-join/-/url-join-4.0.1.tgz#b642e21a2646808ffa178c4c5fda39844e12cde7"
integrity sha512-jk1+QP6ZJqyOiuEI9AEWQfju/nB2Pw466kbA0LEZljHwKeMgd9WrAEgEGxjPDD2+TNbbb37rTyhEfrCXfuKXnA==
util-deprecate@~1.0.1:
version "1.0.2"
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=
util@^0.10.3:
version "0.10.4"
resolved "https://registry.yarnpkg.com/util/-/util-0.10.4.tgz#3aa0125bfe668a4672de58857d3ace27ecb76901"
integrity sha512-0Pm9hTQ3se5ll1XihRic3FDIku70C+iHUdT/W926rSgHV5QgXsYbKZN8MSC3tJtSkhuROzvsQjAaFENRXr+19A==
dependencies:
inherits "2.0.3"
wrap-ansi@^7.0.0:
version "7.0.0"
resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43"
@ -1179,14 +991,14 @@ y18n@^5.0.5:
integrity sha512-hsRUr4FFrvhhRH12wOdfs38Gy7k2FFzB9qgN9v3aLykRq0dRcdcpz5C9FxdS2NuhOrI/628b/KSTJ3rwHysYSg==
yaml@^1.7.2:
version "1.10.0"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.0.tgz#3b593add944876077d4d683fee01081bd9fff31e"
integrity sha512-yr2icI4glYaNG+KWONODapy2/jDdMSDnrONSjblABjD9B4Z5LgiircSt8m8sRZFNi08kG9Sm0uSHtEmP3zaEGg==
version "1.10.2"
resolved "https://registry.yarnpkg.com/yaml/-/yaml-1.10.2.tgz#2301c5ffbf12b467de8da2333a459e29e7920e4b"
integrity sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==
yargs-parser@^20.2.2:
version "20.2.6"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.6.tgz#69f920addf61aafc0b8b89002f5d66e28f2d8b20"
integrity sha512-AP1+fQIWSM/sMiET8fyayjx/J+JmTPt2Mr0FkrgqB4todtfa53sOsrSAcIrJRD5XS20bKUwaDIuMkWKCEiQLKA==
version "20.2.7"
resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.7.tgz#61df85c113edfb5a7a4e36eb8aa60ef423cbc90a"
integrity sha512-FiNkvbeHzB/syOjIUxFDCnhSfzAL8R5vs40MgLFBorXACCOAEaWu0gRZl14vG8MR9AOJIZbmkjhusqBYZ3HTHw==
yargs@^16.1.0:
version "16.2.0"