Migrate to extention model for Account SPA
This commit is contained in:
parent
1ac678a368
commit
488dd2c6b9
@ -11,12 +11,7 @@ import {
|
|||||||
} from "./shared/constants";
|
} from "./shared/constants";
|
||||||
import { capitalize } from "tsafe/capitalize";
|
import { capitalize } from "tsafe/capitalize";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import {
|
import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path";
|
||||||
join as pathJoin,
|
|
||||||
relative as pathRelative,
|
|
||||||
dirname as pathDirname,
|
|
||||||
basename as pathBasename
|
|
||||||
} from "path";
|
|
||||||
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
import { kebabCaseToCamelCase } from "./tools/kebabCaseToSnakeCase";
|
||||||
import { assert, Equals } from "tsafe/assert";
|
import { assert, Equals } from "tsafe/assert";
|
||||||
import type { BuildContext } from "./shared/buildContext";
|
import type { BuildContext } from "./shared/buildContext";
|
||||||
@ -77,85 +72,16 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
(assert(buildContext.implementedThemeTypes.account.isImplemented),
|
(assert(buildContext.implementedThemeTypes.account.isImplemented),
|
||||||
buildContext.implementedThemeTypes.account.type === "Single-Page")
|
buildContext.implementedThemeTypes.account.type === "Single-Page")
|
||||||
) {
|
) {
|
||||||
const srcDirPath = pathJoin(
|
|
||||||
pathDirname(buildContext.packageJsonFilePath),
|
|
||||||
"node_modules",
|
|
||||||
"@keycloakify",
|
|
||||||
`keycloak-account-ui`,
|
|
||||||
"src"
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
console.log(
|
||||||
[
|
chalk.yellow(
|
||||||
`There isn't an interactive CLI to eject components of the Account SPA UI.`,
|
[
|
||||||
`You can however copy paste into your codebase the any file or directory from the following source directory:`,
|
"You are implementing a Single-Page Account theme.",
|
||||||
``,
|
"The eject-page command isn't applicable in this context"
|
||||||
`${chalk.bold(pathJoin(pathRelative(process.cwd(), srcDirPath)))}`,
|
].join("\n")
|
||||||
``
|
)
|
||||||
].join("\n")
|
|
||||||
);
|
);
|
||||||
|
|
||||||
eject_entrypoint: {
|
process.exit(1);
|
||||||
const kcUiTsxFileRelativePath = `KcAccountUi.tsx` as const;
|
|
||||||
|
|
||||||
const themeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account");
|
|
||||||
|
|
||||||
const targetFilePath = pathJoin(themeSrcDirPath, kcUiTsxFileRelativePath);
|
|
||||||
|
|
||||||
if (fs.existsSync(targetFilePath)) {
|
|
||||||
break eject_entrypoint;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.cpSync(pathJoin(srcDirPath, kcUiTsxFileRelativePath), targetFilePath);
|
|
||||||
|
|
||||||
{
|
|
||||||
const kcPageTsxFilePath = pathJoin(themeSrcDirPath, "KcPage.tsx");
|
|
||||||
|
|
||||||
const kcPageTsxCode = fs.readFileSync(kcPageTsxFilePath).toString("utf8");
|
|
||||||
|
|
||||||
const componentName = pathBasename(kcUiTsxFileRelativePath).replace(
|
|
||||||
/.tsx$/,
|
|
||||||
""
|
|
||||||
);
|
|
||||||
|
|
||||||
let modifiedKcPageTsxCode = kcPageTsxCode.replace(
|
|
||||||
`@keycloakify/keycloak-account-ui/${componentName}`,
|
|
||||||
`./${componentName}`
|
|
||||||
);
|
|
||||||
|
|
||||||
run_prettier: {
|
|
||||||
if (!(await getIsPrettierAvailable())) {
|
|
||||||
break run_prettier;
|
|
||||||
}
|
|
||||||
|
|
||||||
modifiedKcPageTsxCode = await runPrettier({
|
|
||||||
filePath: kcPageTsxFilePath,
|
|
||||||
sourceCode: modifiedKcPageTsxCode
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
kcPageTsxFilePath,
|
|
||||||
Buffer.from(modifiedKcPageTsxCode, "utf8")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
const routesTsxFilePath = pathRelative(
|
|
||||||
process.cwd(),
|
|
||||||
pathJoin(srcDirPath, "routes.tsx")
|
|
||||||
);
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
[
|
|
||||||
`To help you get started ${chalk.bold(pathRelative(process.cwd(), targetFilePath))} has been copied into your project.`,
|
|
||||||
`The next step is usually to eject ${chalk.bold(routesTsxFilePath)}`,
|
|
||||||
`with \`cp ${routesTsxFilePath} ${pathRelative(process.cwd(), themeSrcDirPath)}\``,
|
|
||||||
`then update the import of routes in ${kcUiTsxFileRelativePath}.`
|
|
||||||
].join("\n")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
process.exit(0);
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,32 +0,0 @@
|
|||||||
import * as fs from "fs";
|
|
||||||
import { join as pathJoin } from "path";
|
|
||||||
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
|
|
||||||
import { assert, type Equals } from "tsafe/assert";
|
|
||||||
|
|
||||||
export function copyBoilerplate(params: {
|
|
||||||
accountThemeType: "Single-Page" | "Multi-Page";
|
|
||||||
accountThemeSrcDirPath: string;
|
|
||||||
}) {
|
|
||||||
const { accountThemeType, accountThemeSrcDirPath } = params;
|
|
||||||
|
|
||||||
fs.cpSync(
|
|
||||||
pathJoin(
|
|
||||||
getThisCodebaseRootDirPath(),
|
|
||||||
"src",
|
|
||||||
"bin",
|
|
||||||
"initialize-account-theme",
|
|
||||||
"src",
|
|
||||||
(() => {
|
|
||||||
switch (accountThemeType) {
|
|
||||||
case "Single-Page":
|
|
||||||
return "single-page";
|
|
||||||
case "Multi-Page":
|
|
||||||
return "multi-page";
|
|
||||||
}
|
|
||||||
assert<Equals<typeof accountThemeType, never>>(false);
|
|
||||||
})()
|
|
||||||
),
|
|
||||||
accountThemeSrcDirPath,
|
|
||||||
{ recursive: true }
|
|
||||||
);
|
|
||||||
}
|
|
@ -7,6 +7,7 @@ import { updateAccountThemeImplementationInConfig } from "./updateAccountThemeIm
|
|||||||
import { command as updateKcGenCommand } from "../update-kc-gen";
|
import { command as updateKcGenCommand } from "../update-kc-gen";
|
||||||
import { maybeDelegateCommandToCustomHandler } from "../shared/customHandler_delegate";
|
import { maybeDelegateCommandToCustomHandler } from "../shared/customHandler_delegate";
|
||||||
import { exitIfUncommittedChanges } from "../shared/exitIfUncommittedChanges";
|
import { exitIfUncommittedChanges } from "../shared/exitIfUncommittedChanges";
|
||||||
|
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
|
||||||
|
|
||||||
export async function command(params: { buildContext: BuildContext }) {
|
export async function command(params: { buildContext: BuildContext }) {
|
||||||
const { buildContext } = params;
|
const { buildContext } = params;
|
||||||
@ -50,24 +51,24 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
|
|
||||||
switch (accountThemeType) {
|
switch (accountThemeType) {
|
||||||
case "Multi-Page":
|
case "Multi-Page":
|
||||||
{
|
fs.cpSync(
|
||||||
const { initializeAccountTheme_multiPage } = await import(
|
pathJoin(
|
||||||
"./initializeAccountTheme_multiPage"
|
getThisCodebaseRootDirPath(),
|
||||||
);
|
"src",
|
||||||
|
"bin",
|
||||||
await initializeAccountTheme_multiPage({
|
"initialize-account-theme",
|
||||||
accountThemeSrcDirPath
|
"multi-page-boilerplate"
|
||||||
});
|
),
|
||||||
}
|
accountThemeSrcDirPath,
|
||||||
|
{ recursive: true }
|
||||||
|
);
|
||||||
break;
|
break;
|
||||||
case "Single-Page":
|
case "Single-Page":
|
||||||
{
|
{
|
||||||
const { initializeAccountTheme_singlePage } = await import(
|
const { initializeSpa } = await import("../shared/initializeSpa");
|
||||||
"./initializeAccountTheme_singlePage"
|
|
||||||
);
|
|
||||||
|
|
||||||
await initializeAccountTheme_singlePage({
|
await initializeSpa({
|
||||||
accountThemeSrcDirPath,
|
themeType: "account",
|
||||||
buildContext
|
buildContext
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,21 +0,0 @@
|
|||||||
import { relative as pathRelative } from "path";
|
|
||||||
import chalk from "chalk";
|
|
||||||
import { copyBoilerplate } from "./copyBoilerplate";
|
|
||||||
|
|
||||||
export async function initializeAccountTheme_multiPage(params: {
|
|
||||||
accountThemeSrcDirPath: string;
|
|
||||||
}) {
|
|
||||||
const { accountThemeSrcDirPath } = params;
|
|
||||||
|
|
||||||
copyBoilerplate({
|
|
||||||
accountThemeType: "Multi-Page",
|
|
||||||
accountThemeSrcDirPath
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
[
|
|
||||||
chalk.green("The Multi-Page account theme has been initialized."),
|
|
||||||
`Directory created: ${chalk.bold(pathRelative(process.cwd(), accountThemeSrcDirPath))}`
|
|
||||||
].join("\n")
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,129 +0,0 @@
|
|||||||
import { relative as pathRelative, dirname as pathDirname } from "path";
|
|
||||||
import type { BuildContext } from "../shared/buildContext";
|
|
||||||
import * as fs from "fs";
|
|
||||||
import chalk from "chalk";
|
|
||||||
import fetch from "make-fetch-happen";
|
|
||||||
import { z } from "zod";
|
|
||||||
import { assert, type Equals, is } from "tsafe/assert";
|
|
||||||
import { id } from "tsafe/id";
|
|
||||||
import { npmInstall } from "../tools/npmInstall";
|
|
||||||
import { copyBoilerplate } from "./copyBoilerplate";
|
|
||||||
|
|
||||||
type BuildContextLike = {
|
|
||||||
fetchOptions: BuildContext["fetchOptions"];
|
|
||||||
packageJsonFilePath: string;
|
|
||||||
};
|
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
|
||||||
|
|
||||||
export async function initializeAccountTheme_singlePage(params: {
|
|
||||||
accountThemeSrcDirPath: string;
|
|
||||||
buildContext: BuildContextLike;
|
|
||||||
}) {
|
|
||||||
const { accountThemeSrcDirPath, buildContext } = params;
|
|
||||||
|
|
||||||
const OWNER = "keycloakify";
|
|
||||||
const REPO = "keycloak-account-ui";
|
|
||||||
|
|
||||||
const version = "26.0.6-rc.1";
|
|
||||||
|
|
||||||
const dependencies = await fetch(
|
|
||||||
`https://raw.githubusercontent.com/${OWNER}/${REPO}/v${version}/dependencies.gen.json`,
|
|
||||||
buildContext.fetchOptions
|
|
||||||
)
|
|
||||||
.then(r => r.json())
|
|
||||||
.then(
|
|
||||||
(() => {
|
|
||||||
type Dependencies = {
|
|
||||||
dependencies: Record<string, string>;
|
|
||||||
devDependencies?: Record<string, string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const zDependencies = (() => {
|
|
||||||
type TargetType = Dependencies;
|
|
||||||
|
|
||||||
const zTargetType = z.object({
|
|
||||||
dependencies: z.record(z.string()),
|
|
||||||
devDependencies: z.record(z.string()).optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
|
||||||
|
|
||||||
return id<z.ZodType<TargetType>>(zTargetType);
|
|
||||||
})();
|
|
||||||
|
|
||||||
return o => zDependencies.parse(o);
|
|
||||||
})()
|
|
||||||
);
|
|
||||||
|
|
||||||
dependencies.dependencies["@keycloakify/keycloak-account-ui"] = version;
|
|
||||||
|
|
||||||
const parsedPackageJson = (() => {
|
|
||||||
type ParsedPackageJson = {
|
|
||||||
dependencies?: Record<string, string>;
|
|
||||||
devDependencies?: Record<string, string>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const zParsedPackageJson = (() => {
|
|
||||||
type TargetType = ParsedPackageJson;
|
|
||||||
|
|
||||||
const zTargetType = z.object({
|
|
||||||
dependencies: z.record(z.string()).optional(),
|
|
||||||
devDependencies: z.record(z.string()).optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
|
|
||||||
|
|
||||||
return id<z.ZodType<TargetType>>(zTargetType);
|
|
||||||
})();
|
|
||||||
const parsedPackageJson = JSON.parse(
|
|
||||||
fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8")
|
|
||||||
);
|
|
||||||
|
|
||||||
zParsedPackageJson.parse(parsedPackageJson);
|
|
||||||
|
|
||||||
assert(is<ParsedPackageJson>(parsedPackageJson));
|
|
||||||
|
|
||||||
return parsedPackageJson;
|
|
||||||
})();
|
|
||||||
|
|
||||||
parsedPackageJson.dependencies = {
|
|
||||||
...parsedPackageJson.dependencies,
|
|
||||||
...dependencies.dependencies
|
|
||||||
};
|
|
||||||
|
|
||||||
parsedPackageJson.devDependencies = {
|
|
||||||
...parsedPackageJson.devDependencies,
|
|
||||||
...dependencies.devDependencies
|
|
||||||
};
|
|
||||||
|
|
||||||
if (Object.keys(parsedPackageJson.devDependencies).length === 0) {
|
|
||||||
delete parsedPackageJson.devDependencies;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
buildContext.packageJsonFilePath,
|
|
||||||
JSON.stringify(parsedPackageJson, undefined, 4)
|
|
||||||
);
|
|
||||||
|
|
||||||
await npmInstall({
|
|
||||||
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
|
|
||||||
});
|
|
||||||
|
|
||||||
copyBoilerplate({
|
|
||||||
accountThemeType: "Single-Page",
|
|
||||||
accountThemeSrcDirPath
|
|
||||||
});
|
|
||||||
|
|
||||||
console.log(
|
|
||||||
[
|
|
||||||
chalk.green(
|
|
||||||
"The Single-Page account theme has been successfully initialized."
|
|
||||||
),
|
|
||||||
`Using Account UI of Keycloak version: ${chalk.bold(version.split("-")[0])}`,
|
|
||||||
`Directory created: ${chalk.bold(pathRelative(process.cwd(), accountThemeSrcDirPath))}`,
|
|
||||||
`Dependencies added to your project's package.json: `,
|
|
||||||
chalk.bold(JSON.stringify(dependencies, null, 2))
|
|
||||||
].join("\n")
|
|
||||||
);
|
|
||||||
}
|
|
@ -1,7 +0,0 @@
|
|||||||
import type { KcContextLike } from "@keycloakify/keycloak-account-ui";
|
|
||||||
import type { KcEnvName } from "../kc.gen";
|
|
||||||
|
|
||||||
export type KcContext = KcContextLike & {
|
|
||||||
themeType: "account";
|
|
||||||
properties: Record<KcEnvName, string>;
|
|
||||||
};
|
|
@ -1,11 +0,0 @@
|
|||||||
import { lazy } from "react";
|
|
||||||
import { KcAccountUiLoader } from "@keycloakify/keycloak-account-ui";
|
|
||||||
import type { KcContext } from "./KcContext";
|
|
||||||
|
|
||||||
const KcAccountUi = lazy(() => import("@keycloakify/keycloak-account-ui/KcAccountUi"));
|
|
||||||
|
|
||||||
export default function KcPage(props: { kcContext: KcContext }) {
|
|
||||||
const { kcContext } = props;
|
|
||||||
|
|
||||||
return <KcAccountUiLoader kcContext={kcContext} KcAccountUi={KcAccountUi} />;
|
|
||||||
}
|
|
@ -1,15 +1,8 @@
|
|||||||
import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path";
|
|
||||||
import type { BuildContext } from "./shared/buildContext";
|
import type { BuildContext } from "./shared/buildContext";
|
||||||
import * as fs from "fs";
|
|
||||||
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
|
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
|
||||||
import { assert, is, type Equals } from "tsafe/assert";
|
import { initializeSpa } from "./shared/initializeSpa";
|
||||||
import { id } from "tsafe/id";
|
import { exitIfUncommittedChanges } from "./shared/exitIfUncommittedChanges";
|
||||||
import { addSyncExtensionsToPostinstallScript } from "./shared/addSyncExtensionsToPostinstallScript";
|
import { command as updateKcGenCommand } from "./update-kc-gen";
|
||||||
import { getIsPrettierAvailable, runPrettier } from "./tools/runPrettier";
|
|
||||||
import { npmInstall } from "./tools/npmInstall";
|
|
||||||
import * as child_process from "child_process";
|
|
||||||
import { z } from "zod";
|
|
||||||
import chalk from "chalk";
|
|
||||||
|
|
||||||
export async function command(params: { buildContext: BuildContext }) {
|
export async function command(params: { buildContext: BuildContext }) {
|
||||||
const { buildContext } = params;
|
const { buildContext } = params;
|
||||||
@ -23,124 +16,24 @@ export async function command(params: { buildContext: BuildContext }) {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
exitIfUncommittedChanges({
|
||||||
const adminThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "admin");
|
projectDirPath: buildContext.projectDirPath
|
||||||
|
});
|
||||||
|
|
||||||
if (
|
await initializeSpa({
|
||||||
fs.existsSync(adminThemeSrcDirPath) &&
|
themeType: "admin",
|
||||||
fs.readdirSync(adminThemeSrcDirPath).length > 0
|
|
||||||
) {
|
|
||||||
console.warn(
|
|
||||||
chalk.red(
|
|
||||||
`There is already a ${pathRelative(
|
|
||||||
process.cwd(),
|
|
||||||
adminThemeSrcDirPath
|
|
||||||
)} directory in your project. Aborting.`
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
process.exit(-1);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const parsedPackageJson = (() => {
|
|
||||||
type ParsedPackageJson = {
|
|
||||||
scripts?: Record<string, string | undefined>;
|
|
||||||
dependencies?: Record<string, string | undefined>;
|
|
||||||
devDependencies?: Record<string, string | undefined>;
|
|
||||||
};
|
|
||||||
|
|
||||||
const zParsedPackageJson = (() => {
|
|
||||||
type TargetType = ParsedPackageJson;
|
|
||||||
|
|
||||||
const zTargetType = z.object({
|
|
||||||
scripts: z.record(z.union([z.string(), z.undefined()])).optional(),
|
|
||||||
dependencies: z.record(z.union([z.string(), z.undefined()])).optional(),
|
|
||||||
devDependencies: z.record(z.union([z.string(), z.undefined()])).optional()
|
|
||||||
});
|
|
||||||
|
|
||||||
assert<Equals<z.infer<typeof zTargetType>, TargetType>>;
|
|
||||||
|
|
||||||
return id<z.ZodType<TargetType>>(zTargetType);
|
|
||||||
})();
|
|
||||||
const parsedPackageJson = JSON.parse(
|
|
||||||
fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8")
|
|
||||||
);
|
|
||||||
|
|
||||||
zParsedPackageJson.parse(parsedPackageJson);
|
|
||||||
|
|
||||||
assert(is<ParsedPackageJson>(parsedPackageJson));
|
|
||||||
|
|
||||||
return parsedPackageJson;
|
|
||||||
})();
|
|
||||||
|
|
||||||
addSyncExtensionsToPostinstallScript({
|
|
||||||
parsedPackageJson,
|
|
||||||
buildContext
|
buildContext
|
||||||
});
|
});
|
||||||
|
|
||||||
const uiSharedMajor = (() => {
|
await updateKcGenCommand({
|
||||||
const dependencies = {
|
buildContext: {
|
||||||
...parsedPackageJson.devDependencies,
|
...buildContext,
|
||||||
...parsedPackageJson.dependencies
|
implementedThemeTypes: {
|
||||||
};
|
...buildContext.implementedThemeTypes,
|
||||||
|
admin: {
|
||||||
const version = dependencies["@keycloakify/keycloak-ui-shared"];
|
isImplemented: true
|
||||||
|
}
|
||||||
if (version === undefined) {
|
}
|
||||||
return undefined;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const match = version.match(/^[^~]?(\d+)\./);
|
|
||||||
|
|
||||||
if (match === null) {
|
|
||||||
return undefined;
|
|
||||||
}
|
|
||||||
|
|
||||||
return match[1];
|
|
||||||
})();
|
|
||||||
|
|
||||||
const moduleName = "@keycloakify/keycloak-admin-ui";
|
|
||||||
|
|
||||||
const version = (
|
|
||||||
JSON.parse(
|
|
||||||
child_process
|
|
||||||
.execSync(`npm show ${moduleName} versions --json`)
|
|
||||||
.toString("utf8")
|
|
||||||
.trim()
|
|
||||||
) as string[]
|
|
||||||
)
|
|
||||||
.reverse()
|
|
||||||
.filter(version => !version.includes("-"))
|
|
||||||
.find(version =>
|
|
||||||
uiSharedMajor === undefined ? true : version.startsWith(`${uiSharedMajor}.`)
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(version !== undefined);
|
|
||||||
|
|
||||||
(parsedPackageJson.dependencies ??= {})[moduleName] = `~${version}`;
|
|
||||||
|
|
||||||
if (parsedPackageJson.devDependencies !== undefined) {
|
|
||||||
delete parsedPackageJson.devDependencies[moduleName];
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let sourceCode = JSON.stringify(parsedPackageJson, undefined, 2);
|
|
||||||
|
|
||||||
if (await getIsPrettierAvailable()) {
|
|
||||||
sourceCode = await runPrettier({
|
|
||||||
sourceCode,
|
|
||||||
filePath: buildContext.packageJsonFilePath
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
fs.writeFileSync(
|
|
||||||
buildContext.packageJsonFilePath,
|
|
||||||
Buffer.from(sourceCode, "utf8")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
await npmInstall({
|
|
||||||
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import { dirname as pathDirname, relative as pathRelative, sep as pathSep } from "path";
|
import { dirname as pathDirname, relative as pathRelative, sep as pathSep } from "path";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import type { BuildContext } from "./buildContext";
|
import type { BuildContext } from "../buildContext";
|
||||||
|
|
||||||
export type BuildContextLike = {
|
export type BuildContextLike = {
|
||||||
projectDirPath: string;
|
projectDirPath: string;
|
1
src/bin/shared/initializeSpa/index.ts
Normal file
1
src/bin/shared/initializeSpa/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from "./initializeSpa";
|
149
src/bin/shared/initializeSpa/initializeSpa.ts
Normal file
149
src/bin/shared/initializeSpa/initializeSpa.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path";
|
||||||
|
import type { BuildContext } from "../buildContext";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { assert, is, type Equals } from "tsafe/assert";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
import {
|
||||||
|
addSyncExtensionsToPostinstallScript,
|
||||||
|
type BuildContextLike as BuildContextLike_addSyncExtensionsToPostinstallScript
|
||||||
|
} from "./addSyncExtensionsToPostinstallScript";
|
||||||
|
import { getIsPrettierAvailable, runPrettier } from "../../tools/runPrettier";
|
||||||
|
import { npmInstall } from "../../tools/npmInstall";
|
||||||
|
import * as child_process from "child_process";
|
||||||
|
import { z } from "zod";
|
||||||
|
import chalk from "chalk";
|
||||||
|
|
||||||
|
export type BuildContextLike = BuildContextLike_addSyncExtensionsToPostinstallScript & {
|
||||||
|
themeSrcDirPath: string;
|
||||||
|
packageJsonFilePath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
|
||||||
|
export async function initializeSpa(params: {
|
||||||
|
themeType: "account" | "admin";
|
||||||
|
buildContext: BuildContextLike;
|
||||||
|
}) {
|
||||||
|
const { themeType, buildContext } = params;
|
||||||
|
|
||||||
|
{
|
||||||
|
const themeTypeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, themeType);
|
||||||
|
|
||||||
|
if (
|
||||||
|
fs.existsSync(themeTypeSrcDirPath) &&
|
||||||
|
fs.readdirSync(themeTypeSrcDirPath).length > 0
|
||||||
|
) {
|
||||||
|
console.warn(
|
||||||
|
chalk.red(
|
||||||
|
`There is already a ${pathRelative(
|
||||||
|
process.cwd(),
|
||||||
|
themeTypeSrcDirPath
|
||||||
|
)} directory in your project. Aborting.`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedPackageJson = (() => {
|
||||||
|
type ParsedPackageJson = {
|
||||||
|
scripts?: Record<string, string | undefined>;
|
||||||
|
dependencies?: Record<string, string | undefined>;
|
||||||
|
devDependencies?: Record<string, string | undefined>;
|
||||||
|
};
|
||||||
|
|
||||||
|
const zParsedPackageJson = (() => {
|
||||||
|
type TargetType = ParsedPackageJson;
|
||||||
|
|
||||||
|
const zTargetType = z.object({
|
||||||
|
scripts: z.record(z.union([z.string(), z.undefined()])).optional(),
|
||||||
|
dependencies: z.record(z.union([z.string(), z.undefined()])).optional(),
|
||||||
|
devDependencies: z.record(z.union([z.string(), z.undefined()])).optional()
|
||||||
|
});
|
||||||
|
|
||||||
|
assert<Equals<z.infer<typeof zTargetType>, TargetType>>;
|
||||||
|
|
||||||
|
return id<z.ZodType<TargetType>>(zTargetType);
|
||||||
|
})();
|
||||||
|
const parsedPackageJson = JSON.parse(
|
||||||
|
fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8")
|
||||||
|
);
|
||||||
|
|
||||||
|
zParsedPackageJson.parse(parsedPackageJson);
|
||||||
|
|
||||||
|
assert(is<ParsedPackageJson>(parsedPackageJson));
|
||||||
|
|
||||||
|
return parsedPackageJson;
|
||||||
|
})();
|
||||||
|
|
||||||
|
addSyncExtensionsToPostinstallScript({
|
||||||
|
parsedPackageJson,
|
||||||
|
buildContext
|
||||||
|
});
|
||||||
|
|
||||||
|
const uiSharedMajor = (() => {
|
||||||
|
const dependencies = {
|
||||||
|
...parsedPackageJson.devDependencies,
|
||||||
|
...parsedPackageJson.dependencies
|
||||||
|
};
|
||||||
|
|
||||||
|
const version = dependencies["@keycloakify/keycloak-ui-shared"];
|
||||||
|
|
||||||
|
if (version === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const match = version.match(/^[^~]?(\d+)\./);
|
||||||
|
|
||||||
|
if (match === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return match[1];
|
||||||
|
})();
|
||||||
|
|
||||||
|
const moduleName = `@keycloakify/keycloak-${themeType}-ui`;
|
||||||
|
|
||||||
|
const version = (
|
||||||
|
JSON.parse(
|
||||||
|
child_process
|
||||||
|
.execSync(`npm show ${moduleName} versions --json`)
|
||||||
|
.toString("utf8")
|
||||||
|
.trim()
|
||||||
|
) as string[]
|
||||||
|
)
|
||||||
|
.reverse()
|
||||||
|
.filter(version => !version.includes("-"))
|
||||||
|
.find(version =>
|
||||||
|
uiSharedMajor === undefined ? true : version.startsWith(`${uiSharedMajor}.`)
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(version !== undefined);
|
||||||
|
|
||||||
|
(parsedPackageJson.dependencies ??= {})[moduleName] = `~${version}`;
|
||||||
|
|
||||||
|
if (parsedPackageJson.devDependencies !== undefined) {
|
||||||
|
delete parsedPackageJson.devDependencies[moduleName];
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let sourceCode = JSON.stringify(parsedPackageJson, undefined, 2);
|
||||||
|
|
||||||
|
if (await getIsPrettierAvailable()) {
|
||||||
|
sourceCode = await runPrettier({
|
||||||
|
sourceCode,
|
||||||
|
filePath: buildContext.packageJsonFilePath
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.writeFileSync(
|
||||||
|
buildContext.packageJsonFilePath,
|
||||||
|
Buffer.from(sourceCode, "utf8")
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
await npmInstall({
|
||||||
|
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
|
||||||
|
});
|
||||||
|
}
|
@ -10,5 +10,5 @@
|
|||||||
"rootDir": "."
|
"rootDir": "."
|
||||||
},
|
},
|
||||||
"include": ["**/*.ts", "**/*.tsx"],
|
"include": ["**/*.ts", "**/*.tsx"],
|
||||||
"exclude": ["initialize-account-theme/src"]
|
"exclude": ["initialize-account-theme/multi-page-boilerplate"]
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user