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)
|
||||
);
|
||||
|
||||
npmInstall({ packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath) });
|
||||
await npmInstall({
|
||||
packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath)
|
||||
});
|
||||
|
||||
copyBoilerplate({
|
||||
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
|
||||
.command({
|
||||
name: "initialize-account-theme",
|
||||
description: "Initialize the account theme."
|
||||
description: "Initialize an Account Single-Page or Multi-Page custom Account UI."
|
||||
})
|
||||
.task({
|
||||
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
|
||||
.command({
|
||||
name: "copy-keycloak-resources-to-public",
|
||||
|
@ -149,7 +149,7 @@ export async function installUiModulesPeerDependencies(params: {
|
||||
|
||||
await fsPr.writeFile(buildContext.packageJsonFilePath, packageJsonContentStr);
|
||||
|
||||
npmInstall({
|
||||
await npmInstall({
|
||||
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"
|
||||
| "initialize-account-theme"
|
||||
| "initialize-admin-theme"
|
||||
| "initialize-admin-theme"
|
||||
| "initialize-email-theme"
|
||||
| "copy-keycloak-resources-to-public";
|
||||
|
||||
|
@ -9,8 +9,9 @@ import { objectKeys } from "tsafe/objectKeys";
|
||||
import { getAbsoluteAndInOsFormatPath } from "./getAbsoluteAndInOsFormatPath";
|
||||
import { exclude } from "tsafe/exclude";
|
||||
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 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..."));
|
||||
|
||||
installWithoutBreakingLinks({
|
||||
await installWithoutBreakingLinks({
|
||||
packageJsonDirPath,
|
||||
garronejLinkInfos
|
||||
});
|
||||
@ -77,9 +78,9 @@ export function npmInstall(params: { packageJsonDirPath: string }) {
|
||||
}
|
||||
|
||||
try {
|
||||
child_process.execSync(`${packageManagerBinName} install`, {
|
||||
cwd: packageJsonDirPath,
|
||||
stdio: "inherit"
|
||||
await runPackageManagerInstall({
|
||||
packageManagerBinName,
|
||||
cwd: packageJsonDirPath
|
||||
});
|
||||
} catch {
|
||||
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: {
|
||||
packageJsonDirPath: string;
|
||||
}): { linkedModuleNames: string[]; yarnHomeDirPath: string } | undefined {
|
||||
@ -180,7 +217,7 @@ function getGarronejLinkInfos(params: {
|
||||
return { linkedModuleNames, yarnHomeDirPath };
|
||||
}
|
||||
|
||||
function installWithoutBreakingLinks(params: {
|
||||
async function installWithoutBreakingLinks(params: {
|
||||
packageJsonDirPath: string;
|
||||
garronejLinkInfos: Exclude<ReturnType<typeof getGarronejLinkInfos>, undefined>;
|
||||
}) {
|
||||
@ -261,9 +298,9 @@ function installWithoutBreakingLinks(params: {
|
||||
pathJoin(tmpProjectDirPath, YARN_LOCK)
|
||||
);
|
||||
|
||||
child_process.execSync(`yarn install`, {
|
||||
cwd: tmpProjectDirPath,
|
||||
stdio: "inherit"
|
||||
await runPackageManagerInstall({
|
||||
packageManagerBinName: "yarn",
|
||||
cwd: tmpProjectDirPath
|
||||
});
|
||||
|
||||
// NOTE: Moving the modules from the tmp project to the actual project
|
||||
|
Loading…
x
Reference in New Issue
Block a user