Compare commits
42 Commits
v11.2.1
...
v11.3.0-rc
Author | SHA1 | Date | |
---|---|---|---|
0fe49e3d6e | |||
881386a123 | |||
7b9aec4ed0 | |||
cf18f9d06c | |||
052936f769 | |||
590de7a67b | |||
7f608ad8ad | |||
35b012b937 | |||
e3bd7f3bc5 | |||
e14f187fc0 | |||
da495b90ae | |||
8d9b80f549 | |||
2e9da33622 | |||
6f416ad335 | |||
4e982ee898 | |||
bcb514ae9c | |||
cfdad8d71d | |||
39ad1eb8d1 | |||
3d1d2e316b | |||
dd217e8a46 | |||
1339a96ea4 | |||
616e834c90 | |||
80eaa77acc | |||
ce3135c83b | |||
09abc73068 | |||
037d623550 | |||
8c8d2fd6a8 | |||
153a99d63f | |||
939e3ca7ea | |||
a0dc7eeb7c | |||
c21d072231 | |||
2e10ec8073 | |||
1177d6770c | |||
d492a393fe | |||
77952337c5 | |||
6716fcb881 | |||
302fe8d7cd | |||
2ea5e34e81 | |||
d7103b1ad9 | |||
9f8a36fe93 | |||
47ca811878 | |||
8cacb21f1b |
@ -269,6 +269,27 @@
|
||||
"test",
|
||||
"code"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "kathari00",
|
||||
"name": "Katharina Eiserfey",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/42547712?v=4",
|
||||
"profile": "https://github.com/kathari00",
|
||||
"contributions": [
|
||||
"code",
|
||||
"test",
|
||||
"doc"
|
||||
]
|
||||
},
|
||||
{
|
||||
"login": "luca-peruzzo",
|
||||
"name": "Luca Peruzzo",
|
||||
"avatar_url": "https://avatars.githubusercontent.com/u/69015314?v=4",
|
||||
"profile": "https://github.com/luca-peruzzo",
|
||||
"contributions": [
|
||||
"code",
|
||||
"test"
|
||||
]
|
||||
}
|
||||
],
|
||||
"contributorsPerLine": 7,
|
||||
|
2
.github/workflows/ci.yaml
vendored
2
.github/workflows/ci.yaml
vendored
@ -112,7 +112,7 @@ jobs:
|
||||
registry-url: https://registry.npmjs.org/
|
||||
- uses: bahmutov/npm-install@v1
|
||||
- run: npm run build
|
||||
- run: npx -y -p denoify@1.6.12 enable_short_npm_import_path
|
||||
- run: npx -y -p denoify@1.6.13 enable_short_npm_import_path
|
||||
env:
|
||||
DRY_RUN: "0"
|
||||
- uses: garronej/ts-ci@v2.1.2
|
||||
|
@ -1,3 +1,3 @@
|
||||
Looking to contribute? Thank you! PR are more than welcome.
|
||||
|
||||
Please refers to [this documentation page](https://docs.keycloakify.dev/contributing) that will help you get started.
|
||||
Please refers to [this documentation page](https://docs.keycloakify.dev/faq-and-help/contributing) that will help you get started.
|
||||
|
@ -41,7 +41,7 @@
|
||||
<img width="400" src="https://github.com/user-attachments/assets/6bf3bef9-00b0-4460-97b9-0d2da8500798">
|
||||
</p>
|
||||
|
||||
Keycloakify is fully compatible with Keycloak from version 11 to 25...[and beyond](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)
|
||||
Keycloakify is fully compatible with Keycloak from version 11 to 26...[and beyond](https://github.com/keycloakify/keycloakify/discussions/346#discussioncomment-5889791)
|
||||
|
||||
## Sponsors
|
||||
|
||||
@ -134,6 +134,8 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
|
||||
</tr>
|
||||
<tr>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://www.linkedin.com/in/oes-rioniz/"><img src="https://avatars.githubusercontent.com/u/5172296?v=4?s=100" width="100px;" alt="Omid"/><br /><sub><b>Omid</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=uchar" title="Tests">⚠️</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=uchar" title="Code">💻</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/kathari00"><img src="https://avatars.githubusercontent.com/u/42547712?v=4?s=100" width="100px;" alt="Katharina Eiserfey"/><br /><sub><b>Katharina Eiserfey</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=kathari00" title="Code">💻</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=kathari00" title="Tests">⚠️</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=kathari00" title="Documentation">📖</a></td>
|
||||
<td align="center" valign="top" width="14.28%"><a href="https://github.com/luca-peruzzo"><img src="https://avatars.githubusercontent.com/u/69015314?v=4?s=100" width="100px;" alt="Luca Peruzzo"/><br /><sub><b>Luca Peruzzo</b></sub></a><br /><a href="https://github.com/keycloakify/keycloakify/commits?author=luca-peruzzo" title="Code">💻</a> <a href="https://github.com/keycloakify/keycloakify/commits?author=luca-peruzzo" title="Tests">⚠️</a></td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
10
package.json
10
package.json
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "keycloakify",
|
||||
"version": "11.2.1",
|
||||
"version": "11.3.0-rc.3",
|
||||
"description": "Framework to create custom Keycloak UIs",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -38,12 +38,14 @@
|
||||
"dist/",
|
||||
"!dist/tsconfig.tsbuildinfo",
|
||||
"!dist/bin/",
|
||||
"dist/bin/**/*.d.ts",
|
||||
"dist/bin/main.js",
|
||||
"dist/bin/*.index.js",
|
||||
"dist/bin/*.node",
|
||||
"dist/bin/shared/constants.js",
|
||||
"dist/bin/shared/*.d.ts",
|
||||
"dist/bin/shared/*.js.map",
|
||||
"dist/bin/shared/constants.js.map",
|
||||
"dist/bin/shared/customHandler.js",
|
||||
"dist/bin/shared/customHandler.js.map",
|
||||
"!dist/vite-plugin/",
|
||||
"dist/vite-plugin/index.js",
|
||||
"dist/vite-plugin/index.d.ts",
|
||||
@ -62,7 +64,7 @@
|
||||
],
|
||||
"homepage": "https://www.keycloakify.dev",
|
||||
"dependencies": {
|
||||
"tsafe": "^1.6.6"
|
||||
"tsafe": "^1.7.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@babel/core": "^7.24.5",
|
||||
|
@ -13,16 +13,11 @@ import * as fs from "fs";
|
||||
import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path";
|
||||
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
||||
import { assert, Equals } from "tsafe/assert";
|
||||
import type { CliCommandOptions } from "./main";
|
||||
import { getBuildContext } from "./shared/buildContext";
|
||||
import type { BuildContext } from "./shared/buildContext";
|
||||
import chalk from "chalk";
|
||||
|
||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const buildContext = getBuildContext({
|
||||
cliCommandOptions
|
||||
});
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
|
||||
console.log(chalk.cyan("Theme type:"));
|
||||
|
||||
|
@ -1,11 +1,8 @@
|
||||
import { copyKeycloakResourcesToPublic } from "./shared/copyKeycloakResourcesToPublic";
|
||||
import { getBuildContext } from "./shared/buildContext";
|
||||
import type { CliCommandOptions } from "./main";
|
||||
import type { BuildContext } from "./shared/buildContext";
|
||||
|
||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const buildContext = getBuildContext({ cliCommandOptions });
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
|
||||
copyKeycloakResourcesToPublic({
|
||||
buildContext
|
||||
|
@ -20,16 +20,11 @@ import {
|
||||
} from "path";
|
||||
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
||||
import { assert, Equals } from "tsafe/assert";
|
||||
import type { CliCommandOptions } from "./main";
|
||||
import { getBuildContext } from "./shared/buildContext";
|
||||
import type { BuildContext } from "./shared/buildContext";
|
||||
import chalk from "chalk";
|
||||
|
||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const buildContext = getBuildContext({
|
||||
cliCommandOptions
|
||||
});
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
|
||||
console.log(chalk.cyan("Theme type:"));
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { getBuildContext } from "../shared/buildContext";
|
||||
import type { CliCommandOptions } from "../main";
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import cliSelect from "cli-select";
|
||||
import child_process from "child_process";
|
||||
import chalk from "chalk";
|
||||
@ -8,10 +7,8 @@ import * as fs from "fs";
|
||||
import { updateAccountThemeImplementationInConfig } from "./updateAccountThemeImplementationInConfig";
|
||||
import { generateKcGenTs } from "../shared/generateKcGenTs";
|
||||
|
||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const buildContext = getBuildContext({ cliCommandOptions });
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
|
||||
const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account");
|
||||
|
||||
|
@ -1,15 +1,12 @@
|
||||
import { join as pathJoin, relative as pathRelative } from "path";
|
||||
import { transformCodebase } from "./tools/transformCodebase";
|
||||
import { promptKeycloakVersion } from "./shared/promptKeycloakVersion";
|
||||
import { getBuildContext } from "./shared/buildContext";
|
||||
import type { BuildContext } from "./shared/buildContext";
|
||||
import * as fs from "fs";
|
||||
import type { CliCommandOptions } from "./main";
|
||||
import { downloadAndExtractArchive } from "./tools/downloadAndExtractArchive";
|
||||
|
||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const buildContext = getBuildContext({ cliCommandOptions });
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
|
||||
const emailThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "email");
|
||||
|
||||
|
@ -197,7 +197,7 @@ export async function buildJar(params: {
|
||||
|
||||
await new Promise<void>((resolve, reject) =>
|
||||
child_process.exec(
|
||||
`mvn install -Dmaven.repo.local="${pathJoin(keycloakifyBuildCacheDirPath, ".m2")}"`,
|
||||
`mvn clean install -Dmaven.repo.local="${pathJoin(keycloakifyBuildCacheDirPath, ".m2")}"`,
|
||||
{ cwd: keycloakifyBuildCacheDirPath },
|
||||
error => {
|
||||
if (error !== null) {
|
||||
|
@ -52,9 +52,9 @@ export function getKeycloakVersionRangeForJar(params: {
|
||||
case "0.6":
|
||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||
case null:
|
||||
return undefined;
|
||||
return "26-and-above" as const;
|
||||
case "1.1.5":
|
||||
return "25-and-above" as const;
|
||||
return "25" as const;
|
||||
}
|
||||
}
|
||||
assert<Equals<typeof keycloakAccountV1Version, never>>(false);
|
||||
@ -75,9 +75,9 @@ export function getKeycloakVersionRangeForJar(params: {
|
||||
}
|
||||
switch (keycloakThemeAdditionalInfoExtensionVersion) {
|
||||
case null:
|
||||
return "21-and-below";
|
||||
return "all-other-versions";
|
||||
case "1.1.5":
|
||||
return "22-and-above";
|
||||
return "22-to-25";
|
||||
}
|
||||
assert<Equals<typeof keycloakThemeAdditionalInfoExtensionVersion, never>>(
|
||||
false
|
||||
|
@ -166,7 +166,7 @@ function decodeHtmlEntities(htmlStr){
|
||||
areSamePath(path, []) &&
|
||||
["login-idp-link-confirm.ftl", "login-idp-link-email.ftl" ]?seq_contains(xKeycloakify.pageId)
|
||||
) || (
|
||||
["masterAdminClient", "delegateForUpdate", "defaultRole"]?seq_contains(key) &&
|
||||
["masterAdminClient", "delegateForUpdate", "defaultRole", "smtpConfig"]?seq_contains(key) &&
|
||||
areSamePath(path, ["realm"])
|
||||
) || (
|
||||
xKeycloakify.pageId == "error.ftl" &&
|
||||
@ -235,6 +235,9 @@ function decodeHtmlEntities(htmlStr){
|
||||
"identityFederationEnabled",
|
||||
"userManagedAccessAllowed"
|
||||
]?seq_contains(key)
|
||||
) || (
|
||||
["flowContext", "session", "realm"]?seq_contains(key) &&
|
||||
areSamePath(path, ["social"])
|
||||
)
|
||||
>
|
||||
<#-- <#local outSeq += ["/*" + path?join(".") + "." + key + " excluded*/"]> -->
|
||||
|
@ -2,19 +2,16 @@ import { generateResources } from "./generateResources";
|
||||
import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path";
|
||||
import * as child_process from "child_process";
|
||||
import * as fs from "fs";
|
||||
import { getBuildContext } from "../shared/buildContext";
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import { VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES } from "../shared/constants";
|
||||
import { buildJars } from "./buildJars";
|
||||
import type { CliCommandOptions } from "../main";
|
||||
import chalk from "chalk";
|
||||
import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion";
|
||||
import * as os from "os";
|
||||
import { rmSync } from "../tools/fs.rmSync";
|
||||
|
||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const buildContext = getBuildContext({ cliCommandOptions });
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
|
||||
exit_if_maven_not_installed: {
|
||||
let commandOutput: Buffer | undefined = undefined;
|
||||
|
301
src/bin/main.ts
301
src/bin/main.ts
@ -4,8 +4,10 @@ import { termost } from "termost";
|
||||
import { readThisNpmPackageVersion } from "./tools/readThisNpmPackageVersion";
|
||||
import * as child_process from "child_process";
|
||||
import { assertNoPnpmDlx } from "./tools/assertNoPnpmDlx";
|
||||
import { callHandlerIfAny } from "./shared/customHandler_caller";
|
||||
import { getBuildContext } from "./shared/buildContext";
|
||||
|
||||
export type CliCommandOptions = {
|
||||
type CliCommandOptions = {
|
||||
projectDirPath: string | undefined;
|
||||
};
|
||||
|
||||
@ -69,115 +71,158 @@ program
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async cliCommandOptions => {
|
||||
handler: async ({ projectDirPath }) => {
|
||||
const buildContext = getBuildContext({ projectDirPath });
|
||||
|
||||
const { command } = await import("./keycloakify");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
await command({ buildContext });
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command<{
|
||||
port: number | undefined;
|
||||
keycloakVersion: string | undefined;
|
||||
realmJsonFilePath: string | undefined;
|
||||
}>({
|
||||
name: "start-keycloak",
|
||||
description:
|
||||
"Spin up a pre configured Docker image of Keycloak to test your theme."
|
||||
})
|
||||
.option({
|
||||
key: "port",
|
||||
name: (() => {
|
||||
const name = "port";
|
||||
{
|
||||
const commandName = "start-keycloak";
|
||||
|
||||
optionsKeys.push(name);
|
||||
program
|
||||
.command<{
|
||||
port: number | undefined;
|
||||
keycloakVersion: string | undefined;
|
||||
realmJsonFilePath: string | undefined;
|
||||
}>({
|
||||
name: commandName,
|
||||
description:
|
||||
"Spin up a pre configured Docker image of Keycloak to test your theme."
|
||||
})
|
||||
.option({
|
||||
key: "port",
|
||||
name: (() => {
|
||||
const name = "port";
|
||||
|
||||
return name;
|
||||
})(),
|
||||
description: ["Keycloak server port.", "Example `--port 8085`"].join(" "),
|
||||
defaultValue: undefined
|
||||
})
|
||||
.option({
|
||||
key: "keycloakVersion",
|
||||
name: (() => {
|
||||
const name = "keycloak-version";
|
||||
optionsKeys.push(name);
|
||||
|
||||
optionsKeys.push(name);
|
||||
return name;
|
||||
})(),
|
||||
description: ["Keycloak server port.", "Example `--port 8085`"].join(" "),
|
||||
defaultValue: undefined
|
||||
})
|
||||
.option({
|
||||
key: "keycloakVersion",
|
||||
name: (() => {
|
||||
const name = "keycloak-version";
|
||||
|
||||
return name;
|
||||
})(),
|
||||
description: [
|
||||
"Use a specific version of Keycloak.",
|
||||
"Example `--keycloak-version 21.1.1`"
|
||||
].join(" "),
|
||||
defaultValue: undefined
|
||||
})
|
||||
.option({
|
||||
key: "realmJsonFilePath",
|
||||
name: (() => {
|
||||
const name = "import";
|
||||
optionsKeys.push(name);
|
||||
|
||||
optionsKeys.push(name);
|
||||
return name;
|
||||
})(),
|
||||
description: [
|
||||
"Use a specific version of Keycloak.",
|
||||
"Example `--keycloak-version 21.1.1`"
|
||||
].join(" "),
|
||||
defaultValue: undefined
|
||||
})
|
||||
.option({
|
||||
key: "realmJsonFilePath",
|
||||
name: (() => {
|
||||
const name = "import";
|
||||
|
||||
return name;
|
||||
})(),
|
||||
defaultValue: undefined,
|
||||
description: [
|
||||
"Import your own realm configuration file",
|
||||
"Example `--import path/to/myrealm-realm.json`"
|
||||
].join(" ")
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./start-keycloak");
|
||||
optionsKeys.push(name);
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
}
|
||||
});
|
||||
return name;
|
||||
})(),
|
||||
defaultValue: undefined,
|
||||
description: [
|
||||
"Import your own realm configuration file",
|
||||
"Example `--import path/to/myrealm-realm.json`"
|
||||
].join(" ")
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async ({
|
||||
projectDirPath,
|
||||
keycloakVersion,
|
||||
port,
|
||||
realmJsonFilePath
|
||||
}) => {
|
||||
const buildContext = getBuildContext({ projectDirPath });
|
||||
|
||||
program
|
||||
.command({
|
||||
name: "eject-page",
|
||||
description: "Eject a Keycloak page."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./eject-page");
|
||||
const { command } = await import("./start-keycloak");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
}
|
||||
});
|
||||
await command({
|
||||
buildContext,
|
||||
cliCommandOptions: { keycloakVersion, port, realmJsonFilePath }
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
program
|
||||
.command({
|
||||
name: "add-story",
|
||||
description: "Add *.stories.tsx file for a specific page to in your Storybook."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./add-story");
|
||||
{
|
||||
const commandName = "eject-page";
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
}
|
||||
});
|
||||
program
|
||||
.command({
|
||||
name: commandName,
|
||||
description: "Eject a Keycloak page."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async ({ projectDirPath }) => {
|
||||
const buildContext = getBuildContext({ projectDirPath });
|
||||
|
||||
program
|
||||
.command({
|
||||
name: "initialize-email-theme",
|
||||
description: "Initialize an email theme."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./initialize-email-theme");
|
||||
console.log("before callHandlerIfAny");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
}
|
||||
});
|
||||
callHandlerIfAny({ buildContext, commandName });
|
||||
|
||||
console.log("after callHandlerIfAny");
|
||||
|
||||
const { command } = await import("./eject-page");
|
||||
|
||||
await command({ buildContext });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const commandName = "add-story";
|
||||
|
||||
program
|
||||
.command({
|
||||
name: commandName,
|
||||
description:
|
||||
"Add *.stories.tsx file for a specific page to in your Storybook."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async ({ projectDirPath }) => {
|
||||
const buildContext = getBuildContext({ projectDirPath });
|
||||
|
||||
callHandlerIfAny({ buildContext, commandName });
|
||||
|
||||
const { command } = await import("./add-story");
|
||||
|
||||
await command({ buildContext });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const comandName = "initialize-login-theme";
|
||||
|
||||
program
|
||||
.command({
|
||||
name: comandName,
|
||||
description: "Initialize an email theme."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async ({ projectDirPath }) => {
|
||||
const buildContext = getBuildContext({ projectDirPath });
|
||||
|
||||
const { command } = await import("./initialize-email-theme");
|
||||
|
||||
await command({ buildContext });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
program
|
||||
.command({
|
||||
@ -186,42 +231,58 @@ program
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async cliCommandOptions => {
|
||||
handler: async ({ projectDirPath }) => {
|
||||
const buildContext = getBuildContext({ projectDirPath });
|
||||
|
||||
const { command } = await import("./initialize-account-theme");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
await command({ buildContext });
|
||||
}
|
||||
});
|
||||
|
||||
program
|
||||
.command({
|
||||
name: "copy-keycloak-resources-to-public",
|
||||
description:
|
||||
"(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./copy-keycloak-resources-to-public");
|
||||
{
|
||||
const commandName = "copy-keycloak-resources-to-public";
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
}
|
||||
});
|
||||
program
|
||||
.command({
|
||||
name: commandName,
|
||||
description:
|
||||
"(Webpack/Create-React-App only) Copy Keycloak default theme resources to the public directory."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async ({ projectDirPath }) => {
|
||||
const buildContext = getBuildContext({ projectDirPath });
|
||||
|
||||
program
|
||||
.command({
|
||||
name: "update-kc-gen",
|
||||
description:
|
||||
"(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async cliCommandOptions => {
|
||||
const { command } = await import("./update-kc-gen");
|
||||
const { command } = await import("./copy-keycloak-resources-to-public");
|
||||
|
||||
await command({ cliCommandOptions });
|
||||
}
|
||||
});
|
||||
await command({ buildContext });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
{
|
||||
const commandName = "update-kc-gen";
|
||||
|
||||
program
|
||||
.command({
|
||||
name: commandName,
|
||||
description:
|
||||
"(Webpack/Create-React-App only) Create/update the kc.gen.ts file in your project."
|
||||
})
|
||||
.task({
|
||||
skip,
|
||||
handler: async ({ projectDirPath }) => {
|
||||
const buildContext = getBuildContext({ projectDirPath });
|
||||
|
||||
callHandlerIfAny({ buildContext, commandName });
|
||||
|
||||
const { command } = await import("./update-kc-gen");
|
||||
|
||||
await command({ buildContext });
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Fallback to build command if no command is provided
|
||||
{
|
||||
|
@ -3,7 +3,7 @@ export type KeycloakVersionRange =
|
||||
| KeycloakVersionRange.WithoutAccountV1Theme;
|
||||
|
||||
export namespace KeycloakVersionRange {
|
||||
export type WithoutAccountV1Theme = "21-and-below" | "22-and-above";
|
||||
export type WithoutAccountV1Theme = "22-to-25" | "all-other-versions";
|
||||
|
||||
export type WithAccountV1Theme = "21-and-below" | "23" | "24" | "25-and-above";
|
||||
export type WithAccountV1Theme = "21-and-below" | "23" | "24" | "25" | "26-and-above";
|
||||
}
|
||||
|
@ -7,7 +7,6 @@ import {
|
||||
dirname as pathDirname
|
||||
} from "path";
|
||||
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
|
||||
import type { CliCommandOptions } from "../main";
|
||||
import { z } from "zod";
|
||||
import * as fs from "fs";
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
@ -24,7 +23,8 @@ import { objectEntries } from "tsafe/objectEntries";
|
||||
import { type ThemeType } from "./constants";
|
||||
import { id } from "tsafe/id";
|
||||
import chalk from "chalk";
|
||||
import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions";
|
||||
import { getProxyFetchOptions, type FetchOptionsLike } from "../tools/fetchProxyOptions";
|
||||
import { is } from "tsafe/is";
|
||||
|
||||
export type BuildContext = {
|
||||
themeVersion: string;
|
||||
@ -42,7 +42,7 @@ export type BuildContext = {
|
||||
* In this case the urlPathname will be "/my-app/" */
|
||||
urlPathname: string | undefined;
|
||||
assetsDirPath: string;
|
||||
fetchOptions: ProxyFetchOptions;
|
||||
fetchOptions: FetchOptionsLike;
|
||||
kcContextExclusionsFtlCode: string | undefined;
|
||||
environmentVariables: { name: string; default: string }[];
|
||||
themeSrcDirPath: string;
|
||||
@ -128,14 +128,12 @@ export type ResolvedViteConfig = {
|
||||
};
|
||||
|
||||
export function getBuildContext(params: {
|
||||
cliCommandOptions: CliCommandOptions;
|
||||
projectDirPath: string | undefined;
|
||||
}): BuildContext {
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const projectDirPath =
|
||||
cliCommandOptions.projectDirPath !== undefined
|
||||
params.projectDirPath !== undefined
|
||||
? getAbsoluteAndInOsFormatPath({
|
||||
pathIsh: cliCommandOptions.projectDirPath,
|
||||
pathIsh: params.projectDirPath,
|
||||
cwd: process.cwd()
|
||||
})
|
||||
: process.cwd();
|
||||
@ -276,7 +274,8 @@ export function getBuildContext(params: {
|
||||
"21-and-below": z.union([z.boolean(), z.string()]),
|
||||
"23": z.union([z.boolean(), z.string()]),
|
||||
"24": z.union([z.boolean(), z.string()]),
|
||||
"25-and-above": z.union([z.boolean(), z.string()])
|
||||
"25": z.union([z.boolean(), z.string()]),
|
||||
"26-and-above": z.union([z.boolean(), z.string()])
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
@ -297,8 +296,8 @@ export function getBuildContext(params: {
|
||||
]),
|
||||
keycloakVersionTargets: z
|
||||
.object({
|
||||
"21-and-below": z.union([z.boolean(), z.string()]),
|
||||
"22-and-above": z.union([z.boolean(), z.string()])
|
||||
"22-to-25": z.union([z.boolean(), z.string()]),
|
||||
"all-other-versions": z.union([z.boolean(), z.string()])
|
||||
})
|
||||
.optional()
|
||||
});
|
||||
@ -766,7 +765,11 @@ export function getBuildContext(params: {
|
||||
return "24" as const;
|
||||
}
|
||||
|
||||
return "25-and-above" as const;
|
||||
if (buildForKeycloakMajorVersionNumber === 25) {
|
||||
return "25" as const;
|
||||
}
|
||||
|
||||
return "26-and-above" as const;
|
||||
})();
|
||||
|
||||
assert<
|
||||
@ -779,11 +782,14 @@ export function getBuildContext(params: {
|
||||
return keycloakVersionRange;
|
||||
} else {
|
||||
const keycloakVersionRange = (() => {
|
||||
if (buildForKeycloakMajorVersionNumber <= 21) {
|
||||
return "21-and-below" as const;
|
||||
if (
|
||||
buildForKeycloakMajorVersionNumber <= 21 ||
|
||||
buildForKeycloakMajorVersionNumber >= 26
|
||||
) {
|
||||
return "all-other-versions" as const;
|
||||
}
|
||||
|
||||
return "22-and-above" as const;
|
||||
return "22-to-25" as const;
|
||||
})();
|
||||
|
||||
assert<
|
||||
@ -801,6 +807,12 @@ export function getBuildContext(params: {
|
||||
use_custom_jar_basename: {
|
||||
const { keycloakVersionTargets } = buildOptions;
|
||||
|
||||
assert(
|
||||
is<Record<KeycloakVersionRange, string | boolean>>(
|
||||
keycloakVersionTargets
|
||||
)
|
||||
);
|
||||
|
||||
if (keycloakVersionTargets === undefined) {
|
||||
break use_custom_jar_basename;
|
||||
}
|
||||
@ -845,7 +857,8 @@ export function getBuildContext(params: {
|
||||
"21-and-below",
|
||||
"23",
|
||||
"24",
|
||||
"25-and-above"
|
||||
"25",
|
||||
"26-and-above"
|
||||
] as const) {
|
||||
assert<
|
||||
Equals<
|
||||
@ -861,8 +874,8 @@ export function getBuildContext(params: {
|
||||
}
|
||||
} else {
|
||||
for (const keycloakVersionRange of [
|
||||
"21-and-below",
|
||||
"22-and-above"
|
||||
"22-to-25",
|
||||
"all-other-versions"
|
||||
] as const) {
|
||||
assert<
|
||||
Equals<
|
||||
@ -888,7 +901,17 @@ export function getBuildContext(params: {
|
||||
const jarTargets: BuildContext["jarTargets"] = [];
|
||||
|
||||
for (const [keycloakVersionRange, jarNameOrBoolean] of objectEntries(
|
||||
buildOptions.keycloakVersionTargets
|
||||
(() => {
|
||||
const { keycloakVersionTargets } = buildOptions;
|
||||
|
||||
assert(
|
||||
is<Record<KeycloakVersionRange, string | boolean>>(
|
||||
keycloakVersionTargets
|
||||
)
|
||||
);
|
||||
|
||||
return keycloakVersionTargets;
|
||||
})()
|
||||
)) {
|
||||
if (jarNameOrBoolean === false) {
|
||||
continue;
|
||||
|
@ -71,3 +71,8 @@ export type AccountThemePageId = (typeof ACCOUNT_THEME_PAGE_IDS)[number];
|
||||
export const CONTAINER_NAME = "keycloak-keycloakify";
|
||||
|
||||
export const FALLBACK_LANGUAGE_TAG = "en";
|
||||
|
||||
export const CUSTOM_HANDLER_ENV_NAMES = {
|
||||
COMMAND_NAME: "KEYCLOAKIFY_COMMAND_NAME",
|
||||
BUILD_CONTEXT: "KEYCLOAKIFY_BUILD_CONTEXT"
|
||||
};
|
||||
|
35
src/bin/shared/customHandler.ts
Normal file
35
src/bin/shared/customHandler.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { BuildContext } from "./buildContext";
|
||||
import { CUSTOM_HANDLER_ENV_NAMES } from "./constants";
|
||||
|
||||
export const BIN_NAME = "_keycloakify-custom-handler";
|
||||
|
||||
export const NOT_IMPLEMENTED_EXIT_CODE = 78;
|
||||
|
||||
export type CommandName = "update-kc-gen" | "eject-page" | "add-story";
|
||||
|
||||
export type ApiVersion = "v1";
|
||||
|
||||
export function readParams(params: { apiVersion: ApiVersion }) {
|
||||
const { apiVersion } = params;
|
||||
|
||||
assert(apiVersion === "v1");
|
||||
|
||||
const commandName = (() => {
|
||||
const envValue = process.env[CUSTOM_HANDLER_ENV_NAMES.COMMAND_NAME];
|
||||
|
||||
assert(envValue !== undefined);
|
||||
|
||||
return envValue as CommandName;
|
||||
})();
|
||||
|
||||
const buildContext = (() => {
|
||||
const envValue = process.env[CUSTOM_HANDLER_ENV_NAMES.BUILD_CONTEXT];
|
||||
|
||||
assert(envValue !== undefined);
|
||||
|
||||
return JSON.parse(envValue) as BuildContext;
|
||||
})();
|
||||
|
||||
return { commandName, buildContext };
|
||||
}
|
50
src/bin/shared/customHandler_caller.ts
Normal file
50
src/bin/shared/customHandler_caller.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { assert, type Equals } from "tsafe/assert";
|
||||
import type { BuildContext } from "./buildContext";
|
||||
import { CUSTOM_HANDLER_ENV_NAMES } from "./constants";
|
||||
import {
|
||||
NOT_IMPLEMENTED_EXIT_CODE,
|
||||
type CommandName,
|
||||
BIN_NAME,
|
||||
ApiVersion
|
||||
} from "./customHandler";
|
||||
import * as child_process from "child_process";
|
||||
import { is } from "tsafe/is";
|
||||
import { dirname as pathDirname } from "path";
|
||||
import * as fs from "fs";
|
||||
|
||||
assert<Equals<ApiVersion, "v1">>();
|
||||
|
||||
export function callHandlerIfAny(params: {
|
||||
commandName: CommandName;
|
||||
buildContext: BuildContext;
|
||||
}) {
|
||||
const { commandName, buildContext } = params;
|
||||
|
||||
if (!fs.readdirSync(pathDirname(process.argv[1])).includes(BIN_NAME)) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
child_process.execSync(`npx ${BIN_NAME}`, {
|
||||
stdio: "inherit",
|
||||
env: {
|
||||
...process.env,
|
||||
[CUSTOM_HANDLER_ENV_NAMES.COMMAND_NAME]: commandName,
|
||||
[CUSTOM_HANDLER_ENV_NAMES.BUILD_CONTEXT]: JSON.stringify(buildContext)
|
||||
}
|
||||
});
|
||||
} catch (error: any) {
|
||||
console.log(error.message);
|
||||
console.log(error.status);
|
||||
|
||||
assert(is<child_process.ExecException>(error));
|
||||
|
||||
if (error.code === NOT_IMPLEMENTED_EXIT_CODE) {
|
||||
return;
|
||||
}
|
||||
|
||||
process.exit(error.code);
|
||||
}
|
||||
|
||||
process.exit(0);
|
||||
}
|
2400
src/bin/start-keycloak/myrealm-realm-26.json
Normal file
2400
src/bin/start-keycloak/myrealm-realm-26.json
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,6 +1,5 @@
|
||||
import { getBuildContext } from "../shared/buildContext";
|
||||
import type { BuildContext } from "../shared/buildContext";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
import type { CliCommandOptions as CliCommandOptions_common } from "../main";
|
||||
import { promptKeycloakVersion } from "../shared/promptKeycloakVersion";
|
||||
import { CONTAINER_NAME } from "../shared/constants";
|
||||
import { SemVer } from "../tools/SemVer";
|
||||
@ -29,13 +28,14 @@ import { existsAsync } from "../tools/fs.existsAsync";
|
||||
import { rm } from "../tools/fs.rm";
|
||||
import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive";
|
||||
|
||||
export type CliCommandOptions = CliCommandOptions_common & {
|
||||
port: number | undefined;
|
||||
keycloakVersion: string | undefined;
|
||||
realmJsonFilePath: string | undefined;
|
||||
};
|
||||
|
||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||
export async function command(params: {
|
||||
buildContext: BuildContext;
|
||||
cliCommandOptions: {
|
||||
port: number | undefined;
|
||||
keycloakVersion: string | undefined;
|
||||
realmJsonFilePath: string | undefined;
|
||||
};
|
||||
}) {
|
||||
exit_if_docker_not_installed: {
|
||||
let commandOutput: string | undefined = undefined;
|
||||
|
||||
@ -88,9 +88,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const buildContext = getBuildContext({ cliCommandOptions });
|
||||
const { cliCommandOptions, buildContext } = params;
|
||||
|
||||
const { dockerImageTag } = await (async () => {
|
||||
if (cliCommandOptions.keycloakVersion !== undefined) {
|
||||
|
@ -1,16 +1,18 @@
|
||||
import { type FetchOptions } from "make-fetch-happen";
|
||||
import * as child_process from "child_process";
|
||||
import * as fs from "fs";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
|
||||
export type ProxyFetchOptions = Pick<
|
||||
FetchOptions,
|
||||
"proxy" | "noProxy" | "strictSSL" | "cert" | "ca"
|
||||
>;
|
||||
export type FetchOptionsLike = {
|
||||
proxy: string | undefined;
|
||||
noProxy: string | string[];
|
||||
strictSSL: boolean;
|
||||
cert: string | string[] | undefined;
|
||||
ca: string[] | undefined;
|
||||
};
|
||||
|
||||
export function getProxyFetchOptions(params: {
|
||||
npmConfigGetCwd: string;
|
||||
}): ProxyFetchOptions {
|
||||
}): FetchOptionsLike {
|
||||
const { npmConfigGetCwd } = params;
|
||||
|
||||
const cfg = (() => {
|
||||
|
@ -1,13 +1,8 @@
|
||||
import type { CliCommandOptions } from "./main";
|
||||
import { getBuildContext } from "./shared/buildContext";
|
||||
import type { BuildContext } from "./shared/buildContext";
|
||||
import { generateKcGenTs } from "./shared/generateKcGenTs";
|
||||
|
||||
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
|
||||
const { cliCommandOptions } = params;
|
||||
|
||||
const buildContext = getBuildContext({
|
||||
cliCommandOptions
|
||||
});
|
||||
export async function command(params: { buildContext: BuildContext }) {
|
||||
const { buildContext } = params;
|
||||
|
||||
await generateKcGenTs({ buildContext });
|
||||
}
|
||||
|
@ -2,7 +2,7 @@ import type { ThemeType, LoginThemePageId } from "keycloakify/bin/shared/constan
|
||||
import type { ValueOf } from "keycloakify/tools/ValueOf";
|
||||
import { assert } from "tsafe/assert";
|
||||
import type { Equals } from "tsafe";
|
||||
import type { ClassKey } from "keycloakify/login/TemplateProps";
|
||||
import type { ClassKey } from "keycloakify/login/lib/kcClsx";
|
||||
|
||||
export type ExtendKcContext<
|
||||
KcContextExtension extends { properties?: Record<string, string | undefined> },
|
||||
|
@ -434,9 +434,7 @@ function AddRemoveButtonsMultiValuedAttribute(props: {
|
||||
}
|
||||
|
||||
function InputTagSelects(props: InputFieldByTypeProps) {
|
||||
const { attribute, dispatchFormAction, kcClsx, valueOrValues } = props;
|
||||
|
||||
const { advancedMsg } = props.i18n;
|
||||
const { attribute, dispatchFormAction, kcClsx, i18n, valueOrValues } = props;
|
||||
|
||||
const { classDiv, classInput, classLabel, inputType } = (() => {
|
||||
const { inputType } = attribute.annotations;
|
||||
@ -533,7 +531,7 @@ function InputTagSelects(props: InputFieldByTypeProps) {
|
||||
htmlFor={`${attribute.name}-${option}`}
|
||||
className={`${classLabel}${attribute.readOnly ? ` ${kcClsx("kcInputClassRadioCheckboxLabelDisabled")}` : ""}`}
|
||||
>
|
||||
{advancedMsg(option)}
|
||||
{inputLabel(i18n, attribute, option)}
|
||||
</label>
|
||||
</div>
|
||||
))}
|
||||
@ -580,8 +578,6 @@ function TextareaTag(props: InputFieldByTypeProps) {
|
||||
function SelectTag(props: InputFieldByTypeProps) {
|
||||
const { attribute, dispatchFormAction, kcClsx, displayableErrors, i18n, valueOrValues } = props;
|
||||
|
||||
const { advancedMsgStr } = i18n;
|
||||
|
||||
const isMultiple = attribute.annotations.inputType === "multiselect";
|
||||
|
||||
return (
|
||||
@ -645,22 +641,26 @@ function SelectTag(props: InputFieldByTypeProps) {
|
||||
|
||||
return options.map(option => (
|
||||
<option key={option} value={option}>
|
||||
{(() => {
|
||||
if (attribute.annotations.inputOptionLabels !== undefined) {
|
||||
const { inputOptionLabels } = attribute.annotations;
|
||||
|
||||
return advancedMsgStr(inputOptionLabels[option] ?? option);
|
||||
}
|
||||
|
||||
if (attribute.annotations.inputOptionLabelsI18nPrefix !== undefined) {
|
||||
return advancedMsgStr(`${attribute.annotations.inputOptionLabelsI18nPrefix}.${option}`);
|
||||
}
|
||||
|
||||
return option;
|
||||
})()}
|
||||
{inputLabel(i18n, attribute, option)}
|
||||
</option>
|
||||
));
|
||||
})()}
|
||||
</select>
|
||||
);
|
||||
}
|
||||
|
||||
function inputLabel(i18n: I18n, attribute: Attribute, option: string) {
|
||||
const { advancedMsg } = i18n;
|
||||
|
||||
if (attribute.annotations.inputOptionLabels !== undefined) {
|
||||
const { inputOptionLabels } = attribute.annotations;
|
||||
|
||||
return advancedMsg(inputOptionLabels[option] ?? option);
|
||||
}
|
||||
|
||||
if (attribute.annotations.inputOptionLabelsI18nPrefix !== undefined) {
|
||||
return advancedMsg(`${attribute.annotations.inputOptionLabelsI18nPrefix}.${option}`);
|
||||
}
|
||||
|
||||
return option;
|
||||
}
|
||||
|
@ -122,9 +122,7 @@ export function keycloakify(params: keycloakify.Params) {
|
||||
}
|
||||
|
||||
const buildContext = getBuildContext({
|
||||
cliCommandOptions: {
|
||||
projectDirPath
|
||||
}
|
||||
projectDirPath
|
||||
});
|
||||
|
||||
copyKeycloakResourcesToPublic({ buildContext }),
|
||||
@ -166,7 +164,7 @@ export function keycloakify(params: keycloakify.Params) {
|
||||
[
|
||||
`(`,
|
||||
`(window.kcContext === undefined || import.meta.env.MODE === "development")?`,
|
||||
`"${urlPathname ?? "/"}":`,
|
||||
`import.meta.env.BASE_URL:`,
|
||||
`(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/")`,
|
||||
`)`
|
||||
].join("")
|
||||
|
@ -115,6 +115,38 @@ export const WithFavoritePet: Story = {
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
export const WithNewsletter: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
kcContext={{
|
||||
profile: {
|
||||
attributesByName: {
|
||||
newsletter: {
|
||||
name: "newsletter",
|
||||
displayName: "Sign up to the newsletter",
|
||||
validators: {
|
||||
options: {
|
||||
options: ["yes"]
|
||||
}
|
||||
},
|
||||
annotations: {
|
||||
inputOptionLabels: {
|
||||
"yes": "I want my email inbox filled with spam"
|
||||
},
|
||||
inputType: "multiselect-checkboxes"
|
||||
},
|
||||
required: false,
|
||||
readOnly: false
|
||||
} satisfies Attribute
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
)
|
||||
};
|
||||
|
||||
|
||||
export const WithEmailAsUsername: Story = {
|
||||
render: () => (
|
||||
<KcPageStory
|
||||
|
@ -12579,6 +12579,11 @@ tsafe@^1.6.6:
|
||||
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.6.6.tgz#fd93e64d6eb13ef83ed1650669cc24bad4f5df9f"
|
||||
integrity sha512-gzkapsdbMNwBnTIjgO758GujLCj031IgHK/PKr2mrmkCSJMhSOR5FeOuSxKLMUoYc0vAA4RGEYYbjt/v6afD3g==
|
||||
|
||||
tsafe@^1.7.5:
|
||||
version "1.7.5"
|
||||
resolved "https://registry.yarnpkg.com/tsafe/-/tsafe-1.7.5.tgz#0d3a31202b5ef87c7ba997e66e03fd80801278ef"
|
||||
integrity sha512-tbNyyBSbwfbilFfiuXkSOj82a6++ovgANwcoqBAcO9/REPoZMEQoE8kWPeO0dy5A2D/2Lajr8Ohue5T0ifIvLQ==
|
||||
|
||||
tsc-alias@^1.8.10:
|
||||
version "1.8.10"
|
||||
resolved "https://registry.yarnpkg.com/tsc-alias/-/tsc-alias-1.8.10.tgz#279f9bf0dd8bc10fb27820393d4881db5a303938"
|
||||
|
Reference in New Issue
Block a user