Implement initialize-admin-theme command
This commit is contained in:
parent
94b7d2b85b
commit
13c21e8910
@ -119,7 +119,9 @@ export async function initializeAccountTheme_singlePage(params: {
|
|||||||
JSON.stringify(parsedPackageJson, undefined, 4)
|
JSON.stringify(parsedPackageJson, undefined, 4)
|
||||||
);
|
);
|
||||||
|
|
||||||
npmInstall({ packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath) });
|
await npmInstall({
|
||||||
|
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
|
||||||
|
});
|
||||||
|
|
||||||
copyBoilerplate({
|
copyBoilerplate({
|
||||||
accountThemeType: "Single-Page",
|
accountThemeType: "Single-Page",
|
||||||
|
143
src/bin/initialize-admin-theme.ts
Normal file
143
src/bin/initialize-admin-theme.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { dirname as pathDirname, join as pathJoin, relative as pathRelative } from "path";
|
||||||
|
import type { BuildContext } from "./shared/buildContext";
|
||||||
|
import * as fs from "fs";
|
||||||
|
import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate";
|
||||||
|
import { assert, is, type Equals } from "tsafe/assert";
|
||||||
|
import { id } from "tsafe/id";
|
||||||
|
import { addPostinstallScriptIfNotPresent } from "./shared/addPostinstallScriptIfNotPresent";
|
||||||
|
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 }) {
|
||||||
|
const { buildContext } = params;
|
||||||
|
|
||||||
|
const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({
|
||||||
|
commandName: "initialize-admin-theme",
|
||||||
|
buildContext
|
||||||
|
});
|
||||||
|
|
||||||
|
if (hasBeenHandled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
const adminThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "admin");
|
||||||
|
|
||||||
|
if (
|
||||||
|
fs.existsSync(adminThemeSrcDirPath) &&
|
||||||
|
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;
|
||||||
|
})();
|
||||||
|
|
||||||
|
addPostinstallScriptIfNotPresent({
|
||||||
|
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-admin-ui";
|
||||||
|
|
||||||
|
const version = (
|
||||||
|
JSON.parse(
|
||||||
|
child_process
|
||||||
|
.execSync(`npm show ${moduleName} versions --json`)
|
||||||
|
.toString("utf8")
|
||||||
|
.trim()
|
||||||
|
) as string[]
|
||||||
|
).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)
|
||||||
|
});
|
||||||
|
}
|
@ -191,7 +191,7 @@ program
|
|||||||
program
|
program
|
||||||
.command({
|
.command({
|
||||||
name: "initialize-account-theme",
|
name: "initialize-account-theme",
|
||||||
description: "Initialize the account theme."
|
description: "Initialize an Account Single-Page or Multi-Page custom Account UI."
|
||||||
})
|
})
|
||||||
.task({
|
.task({
|
||||||
skip,
|
skip,
|
||||||
@ -202,6 +202,20 @@ program
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
program
|
||||||
|
.command({
|
||||||
|
name: "initialize-admin-theme",
|
||||||
|
description: "Initialize an Admin Console custom UI."
|
||||||
|
})
|
||||||
|
.task({
|
||||||
|
skip,
|
||||||
|
handler: async ({ projectDirPath }) => {
|
||||||
|
const { command } = await import("./initialize-admin-theme");
|
||||||
|
|
||||||
|
await command({ buildContext: getBuildContext({ projectDirPath }) });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
program
|
program
|
||||||
.command({
|
.command({
|
||||||
name: "copy-keycloak-resources-to-public",
|
name: "copy-keycloak-resources-to-public",
|
||||||
|
@ -149,7 +149,7 @@ export async function installUiModulesPeerDependencies(params: {
|
|||||||
|
|
||||||
await fsPr.writeFile(buildContext.packageJsonFilePath, packageJsonContentStr);
|
await fsPr.writeFile(buildContext.packageJsonFilePath, packageJsonContentStr);
|
||||||
|
|
||||||
npmInstall({
|
await npmInstall({
|
||||||
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
|
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
65
src/bin/shared/addPostinstallScriptIfNotPresent.ts
Normal file
65
src/bin/shared/addPostinstallScriptIfNotPresent.ts
Normal file
@ -0,0 +1,65 @@
|
|||||||
|
import { dirname as pathDirname, relative as pathRelative, sep as pathSep } from "path";
|
||||||
|
import { assert } from "tsafe/assert";
|
||||||
|
import type { BuildContext } from "./buildContext";
|
||||||
|
|
||||||
|
export type BuildContextLike = {
|
||||||
|
projectDirPath: string;
|
||||||
|
packageJsonFilePath: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
|
||||||
|
export function addPostinstallScriptIfNotPresent(params: {
|
||||||
|
parsedPackageJson: { scripts?: Record<string, string | undefined> };
|
||||||
|
buildContext: BuildContextLike;
|
||||||
|
}) {
|
||||||
|
const { parsedPackageJson, buildContext } = params;
|
||||||
|
|
||||||
|
const scripts = (parsedPackageJson.scripts ??= {});
|
||||||
|
|
||||||
|
const cmd_base = "keycloakify postinstall";
|
||||||
|
|
||||||
|
const projectCliOptionValue = (() => {
|
||||||
|
const packageJsonDirPath = pathDirname(buildContext.packageJsonFilePath);
|
||||||
|
|
||||||
|
const relativePath = pathRelative(
|
||||||
|
packageJsonDirPath,
|
||||||
|
buildContext.projectDirPath
|
||||||
|
);
|
||||||
|
|
||||||
|
if (relativePath === "") {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return relativePath.split(pathSep).join("/");
|
||||||
|
})();
|
||||||
|
|
||||||
|
const generateCmd = (params: { cmd_preexisting: string | undefined }) => {
|
||||||
|
const { cmd_preexisting } = params;
|
||||||
|
|
||||||
|
let cmd = cmd_preexisting === undefined ? "" : `${cmd_preexisting} && `;
|
||||||
|
|
||||||
|
cmd += cmd_base;
|
||||||
|
|
||||||
|
if (projectCliOptionValue !== undefined) {
|
||||||
|
cmd += ` -p ${projectCliOptionValue}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
return cmd;
|
||||||
|
};
|
||||||
|
|
||||||
|
for (const scriptName of ["postinstall", "prepare"]) {
|
||||||
|
const cmd_preexisting = scripts[scriptName];
|
||||||
|
|
||||||
|
if (cmd_preexisting === undefined) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cmd_preexisting.includes(cmd_base)) {
|
||||||
|
scripts[scriptName] = generateCmd({ cmd_preexisting });
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
scripts["postinstall"] = generateCmd({ cmd_preexisting: scripts["postinstall"] });
|
||||||
|
}
|
@ -12,6 +12,7 @@ export type CommandName =
|
|||||||
| "add-story"
|
| "add-story"
|
||||||
| "initialize-account-theme"
|
| "initialize-account-theme"
|
||||||
| "initialize-admin-theme"
|
| "initialize-admin-theme"
|
||||||
|
| "initialize-admin-theme"
|
||||||
| "initialize-email-theme"
|
| "initialize-email-theme"
|
||||||
| "copy-keycloak-resources-to-public";
|
| "copy-keycloak-resources-to-public";
|
||||||
|
|
||||||
|
@ -9,8 +9,9 @@ import { objectKeys } from "tsafe/objectKeys";
|
|||||||
import { getAbsoluteAndInOsFormatPath } from "./getAbsoluteAndInOsFormatPath";
|
import { getAbsoluteAndInOsFormatPath } from "./getAbsoluteAndInOsFormatPath";
|
||||||
import { exclude } from "tsafe/exclude";
|
import { exclude } from "tsafe/exclude";
|
||||||
import { rmSync } from "./fs.rmSync";
|
import { rmSync } from "./fs.rmSync";
|
||||||
|
import { Deferred } from "evt/tools/Deferred";
|
||||||
|
|
||||||
export function npmInstall(params: { packageJsonDirPath: string }) {
|
export async function npmInstall(params: { packageJsonDirPath: string }) {
|
||||||
const { packageJsonDirPath } = params;
|
const { packageJsonDirPath } = params;
|
||||||
|
|
||||||
const packageManagerBinName = (() => {
|
const packageManagerBinName = (() => {
|
||||||
@ -68,7 +69,7 @@ export function npmInstall(params: { packageJsonDirPath: string }) {
|
|||||||
|
|
||||||
console.log(chalk.green("Installing in a way that won't break the links..."));
|
console.log(chalk.green("Installing in a way that won't break the links..."));
|
||||||
|
|
||||||
installWithoutBreakingLinks({
|
await installWithoutBreakingLinks({
|
||||||
packageJsonDirPath,
|
packageJsonDirPath,
|
||||||
garronejLinkInfos
|
garronejLinkInfos
|
||||||
});
|
});
|
||||||
@ -77,9 +78,9 @@ export function npmInstall(params: { packageJsonDirPath: string }) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
child_process.execSync(`${packageManagerBinName} install`, {
|
await runPackageManagerInstall({
|
||||||
cwd: packageJsonDirPath,
|
packageManagerBinName,
|
||||||
stdio: "inherit"
|
cwd: packageJsonDirPath
|
||||||
});
|
});
|
||||||
} catch {
|
} catch {
|
||||||
console.log(
|
console.log(
|
||||||
@ -90,6 +91,42 @@ export function npmInstall(params: { packageJsonDirPath: string }) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function runPackageManagerInstall(params: {
|
||||||
|
packageManagerBinName: string;
|
||||||
|
cwd: string;
|
||||||
|
}) {
|
||||||
|
const { packageManagerBinName, cwd } = params;
|
||||||
|
|
||||||
|
const dCompleted = new Deferred<void>();
|
||||||
|
|
||||||
|
const child = child_process.spawn(packageManagerBinName, ["install"], {
|
||||||
|
cwd,
|
||||||
|
env: process.env,
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stdout.on("data", data => {
|
||||||
|
if (data.toString("utf8").includes("has unmet peer dependency")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(data);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on("data", data => process.stderr.write(data));
|
||||||
|
|
||||||
|
child.on("exit", code => {
|
||||||
|
if (code !== 0) {
|
||||||
|
dCompleted.reject(new Error(`Failed with code ${code}`));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dCompleted.resolve();
|
||||||
|
});
|
||||||
|
|
||||||
|
await dCompleted.pr;
|
||||||
|
}
|
||||||
|
|
||||||
function getGarronejLinkInfos(params: {
|
function getGarronejLinkInfos(params: {
|
||||||
packageJsonDirPath: string;
|
packageJsonDirPath: string;
|
||||||
}): { linkedModuleNames: string[]; yarnHomeDirPath: string } | undefined {
|
}): { linkedModuleNames: string[]; yarnHomeDirPath: string } | undefined {
|
||||||
@ -180,7 +217,7 @@ function getGarronejLinkInfos(params: {
|
|||||||
return { linkedModuleNames, yarnHomeDirPath };
|
return { linkedModuleNames, yarnHomeDirPath };
|
||||||
}
|
}
|
||||||
|
|
||||||
function installWithoutBreakingLinks(params: {
|
async function installWithoutBreakingLinks(params: {
|
||||||
packageJsonDirPath: string;
|
packageJsonDirPath: string;
|
||||||
garronejLinkInfos: Exclude<ReturnType<typeof getGarronejLinkInfos>, undefined>;
|
garronejLinkInfos: Exclude<ReturnType<typeof getGarronejLinkInfos>, undefined>;
|
||||||
}) {
|
}) {
|
||||||
@ -261,9 +298,9 @@ function installWithoutBreakingLinks(params: {
|
|||||||
pathJoin(tmpProjectDirPath, YARN_LOCK)
|
pathJoin(tmpProjectDirPath, YARN_LOCK)
|
||||||
);
|
);
|
||||||
|
|
||||||
child_process.execSync(`yarn install`, {
|
await runPackageManagerInstall({
|
||||||
cwd: tmpProjectDirPath,
|
packageManagerBinName: "yarn",
|
||||||
stdio: "inherit"
|
cwd: tmpProjectDirPath
|
||||||
});
|
});
|
||||||
|
|
||||||
// NOTE: Moving the modules from the tmp project to the actual project
|
// NOTE: Moving the modules from the tmp project to the actual project
|
||||||
|
Loading…
x
Reference in New Issue
Block a user