diff --git a/src/bin/initialize-admin-theme/copyBoilerplate.ts b/src/bin/initialize-admin-theme/copyBoilerplate.ts deleted file mode 100644 index f2e6e7ca..00000000 --- a/src/bin/initialize-admin-theme/copyBoilerplate.ts +++ /dev/null @@ -1,19 +0,0 @@ -import * as fs from "fs"; -import { join as pathJoin } from "path"; -import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath"; - -export function copyBoilerplate(params: { adminThemeSrcDirPath: string }) { - const { adminThemeSrcDirPath } = params; - - fs.cpSync( - pathJoin( - getThisCodebaseRootDirPath(), - "src", - "bin", - "initialize-admin-theme", - "src" - ), - adminThemeSrcDirPath, - { recursive: true } - ); -} diff --git a/src/bin/initialize-admin-theme/index.ts b/src/bin/initialize-admin-theme/index.ts deleted file mode 100644 index b229b5f4..00000000 --- a/src/bin/initialize-admin-theme/index.ts +++ /dev/null @@ -1 +0,0 @@ -export * from "./initialize-admin-theme"; diff --git a/src/bin/initialize-admin-theme/initialize-admin-theme.ts b/src/bin/initialize-admin-theme/initialize-admin-theme.ts deleted file mode 100644 index e6ba503b..00000000 --- a/src/bin/initialize-admin-theme/initialize-admin-theme.ts +++ /dev/null @@ -1,60 +0,0 @@ -import type { BuildContext } from "../shared/buildContext"; -import chalk from "chalk"; -import { join as pathJoin, relative as pathRelative } from "path"; -import * as fs from "fs"; -import { command as updateKcGenCommand } from "../update-kc-gen"; -import { maybeDelegateCommandToCustomHandler } from "../shared/customHandler_delegate"; -import { exitIfUncommittedChanges } from "../shared/exitIfUncommittedChanges"; -import { initializeAdminTheme } from "./initializeAdminTheme"; - -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); - } - - exitIfUncommittedChanges({ - projectDirPath: buildContext.projectDirPath - }); - - await initializeAdminTheme({ - adminThemeSrcDirPath, - buildContext - }); - - await updateKcGenCommand({ - buildContext: { - ...buildContext, - implementedThemeTypes: { - ...buildContext.implementedThemeTypes, - admin: { - isImplemented: true - } - } - } - }); -} diff --git a/src/bin/initialize-admin-theme/initializeAdminTheme.ts b/src/bin/initialize-admin-theme/initializeAdminTheme.ts deleted file mode 100644 index 8498eefa..00000000 --- a/src/bin/initialize-admin-theme/initializeAdminTheme.ts +++ /dev/null @@ -1,150 +0,0 @@ -import { join as pathJoin, relative as pathRelative, dirname as pathDirname } from "path"; -import type { BuildContext } from "../shared/buildContext"; -import * as fs from "fs"; -import chalk from "chalk"; -import { - getLatestsSemVersionedTag, - type BuildContextLike as BuildContextLike_getLatestsSemVersionedTag -} from "../shared/getLatestsSemVersionedTag"; -import { SemVer } from "../tools/SemVer"; -import fetch from "make-fetch-happen"; -import { z } from "zod"; -import { assert, type Equals } from "tsafe/assert"; -import { is } from "tsafe/is"; -import { id } from "tsafe/id"; -import { npmInstall } from "../tools/npmInstall"; -import { copyBoilerplate } from "./copyBoilerplate"; -import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath"; - -type BuildContextLike = BuildContextLike_getLatestsSemVersionedTag & { - fetchOptions: BuildContext["fetchOptions"]; - packageJsonFilePath: string; -}; - -assert(); - -export async function initializeAdminTheme(params: { - adminThemeSrcDirPath: string; - buildContext: BuildContextLike; -}) { - const { adminThemeSrcDirPath, buildContext } = params; - - const OWNER = "keycloakify"; - const REPO = "keycloak-admin-ui"; - - const [semVersionedTag] = await getLatestsSemVersionedTag({ - owner: OWNER, - repo: REPO, - count: 1, - doIgnoreReleaseCandidates: false, - buildContext - }); - - const dependencies = await fetch( - `https://raw.githubusercontent.com/${OWNER}/${REPO}/${semVersionedTag.tag}/dependencies.gen.json`, - buildContext.fetchOptions - ) - .then(r => r.json()) - .then( - (() => { - type Dependencies = { - dependencies: Record; - devDependencies?: Record; - }; - - const zDependencies = (() => { - type TargetType = Dependencies; - - const zTargetType = z.object({ - dependencies: z.record(z.string()), - devDependencies: z.record(z.string()).optional() - }); - - assert, TargetType>>(); - - return id>(zTargetType); - })(); - - return o => zDependencies.parse(o); - })() - ); - - dependencies.dependencies["@keycloakify/keycloak-admin-ui"] = SemVer.stringify( - semVersionedTag.version - ); - - const parsedPackageJson = (() => { - type ParsedPackageJson = { - dependencies?: Record; - devDependencies?: Record; - }; - - const zParsedPackageJson = (() => { - type TargetType = ParsedPackageJson; - - const zTargetType = z.object({ - dependencies: z.record(z.string()).optional(), - devDependencies: z.record(z.string()).optional() - }); - - assert, TargetType>>(); - - return id>(zTargetType); - })(); - const parsedPackageJson = JSON.parse( - fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8") - ); - - zParsedPackageJson.parse(parsedPackageJson); - - assert(is(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) - ); - - run_npm_install: { - if ( - JSON.parse( - fs - .readFileSync(pathJoin(getThisCodebaseRootDirPath(), "package.json")) - .toString("utf8") - )["version"] === "0.0.0" - ) { - //NOTE: Linked version - break run_npm_install; - } - - npmInstall({ packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath) }); - } - - copyBoilerplate({ adminThemeSrcDirPath }); - - console.log( - [ - chalk.green("The Admin theme has been successfully initialized."), - `Using Admin UI of Keycloak version: ${chalk.bold(semVersionedTag.tag.split("-")[0])}`, - `Directory created: ${chalk.bold(pathRelative(process.cwd(), adminThemeSrcDirPath))}`, - `Dependencies added to your project's package.json: `, - chalk.bold(JSON.stringify(dependencies, null, 2)) - ].join("\n") - ); -} diff --git a/src/bin/initialize-admin-theme/src/KcContext.ts b/src/bin/initialize-admin-theme/src/KcContext.ts deleted file mode 100644 index e86dc980..00000000 --- a/src/bin/initialize-admin-theme/src/KcContext.ts +++ /dev/null @@ -1,7 +0,0 @@ -import type { KcContextLike } from "@keycloakify/keycloak-admin-ui"; -import type { KcEnvName } from "../kc.gen"; - -export type KcContext = KcContextLike & { - themeType: "admin"; - properties: Record; -}; diff --git a/src/bin/initialize-admin-theme/src/KcPage.tsx b/src/bin/initialize-admin-theme/src/KcPage.tsx deleted file mode 100644 index f5f08f1f..00000000 --- a/src/bin/initialize-admin-theme/src/KcPage.tsx +++ /dev/null @@ -1,11 +0,0 @@ -import { lazy } from "react"; -import { KcAdminUiLoader } from "@keycloakify/keycloak-admin-ui"; -import type { KcContext } from "./KcContext"; - -const KcAdminUi = lazy(() => import("@keycloakify/keycloak-admin-ui/KcAdminUi")); - -export default function KcPage(props: { kcContext: KcContext }) { - const { kcContext } = props; - - return ; -} diff --git a/src/bin/sync-ui-modules/installUiModulesPeerDependencies.ts b/src/bin/sync-ui-modules/installUiModulesPeerDependencies.ts new file mode 100644 index 00000000..60dbb946 --- /dev/null +++ b/src/bin/sync-ui-modules/installUiModulesPeerDependencies.ts @@ -0,0 +1,157 @@ +import { assert, type Equals } from "tsafe/assert"; +import { is } from "tsafe/is"; +import type { BuildContext } from "../shared/buildContext"; +import type { UiModuleMeta } from "./uiModuleMeta"; +import { z } from "zod"; +import { id } from "tsafe/id"; +import * as fsPr from "fs/promises"; +import { SemVer } from "../tools/SemVer"; +import { same } from "evt/tools/inDepth/same"; +import { runPrettier, getIsPrettierAvailable } from "../tools/runPrettier"; +import { npmInstall } from "../tools/npmInstall"; + +export type BuildContextLike = { + packageJsonFilePath: string; +}; + +assert(); + +export type UiModuleMetaLike = { + moduleName: string; + peerDependencies: Record; +}; + +assert(); + +export async function installUiModulesPeerDependencies(params: { + buildContext: BuildContextLike; + uiModuleMetas: UiModuleMetaLike[]; +}): Promise { + const { buildContext, uiModuleMetas } = params; + + const { uiModulesPerDependencies } = (() => { + const uiModulesPerDependencies: Record = {}; + + for (const { peerDependencies } of uiModuleMetas) { + for (const [peerDependencyName, versionRange_candidate] of Object.entries( + peerDependencies + )) { + const versionRange = (() => { + const versionRange_current = + uiModulesPerDependencies[peerDependencyName]; + + if (versionRange_current === undefined) { + return versionRange_candidate; + } + + if (versionRange_current === "*") { + return versionRange_candidate; + } + + if (versionRange_candidate === "*") { + return versionRange_current; + } + + const { versionRange } = [ + versionRange_current, + versionRange_candidate + ] + .map(versionRange => ({ + versionRange, + semVer: SemVer.parse( + (() => { + if ( + versionRange.startsWith("^") || + versionRange.startsWith("~") + ) { + return versionRange.slice(1); + } + + return versionRange; + })() + ) + })) + .sort((a, b) => SemVer.compare(b.semVer, a.semVer))[0]; + + return versionRange; + })(); + + uiModulesPerDependencies[peerDependencyName] = versionRange; + } + } + + return { uiModulesPerDependencies }; + })(); + + const parsedPackageJson = await (async () => { + type ParsedPackageJson = { + dependencies?: Record; + devDependencies?: Record; + }; + + const zParsedPackageJson = (() => { + type TargetType = ParsedPackageJson; + + const zParsedPackageJson = z.object({ + dependencies: z.record(z.string()).optional(), + devDependencies: z.record(z.string()).optional() + }); + + type InferredType = z.infer; + + assert>(); + + return id>(zParsedPackageJson); + })(); + + const parsedPackageJson = JSON.parse( + (await fsPr.readFile(buildContext.packageJsonFilePath)).toString("utf8") + ); + + zParsedPackageJson.parse(parsedPackageJson); + + assert(is(parsedPackageJson)); + + return parsedPackageJson; + })(); + + const parsedPackageJson_before = JSON.parse(JSON.stringify(parsedPackageJson)); + + for (const [moduleName, versionRange] of Object.entries(uiModulesPerDependencies)) { + if (moduleName.startsWith("@types/")) { + (parsedPackageJson.devDependencies ??= {})[moduleName] = versionRange; + continue; + } + + if (parsedPackageJson.devDependencies !== undefined) { + delete parsedPackageJson.devDependencies[moduleName]; + } + + (parsedPackageJson.dependencies ??= {})[moduleName] = versionRange; + } + + if (same(parsedPackageJson, parsedPackageJson_before)) { + return; + } + + let packageJsonContentStr = JSON.stringify(parsedPackageJson, null, 2); + + format: { + if (!(await getIsPrettierAvailable())) { + break format; + } + + packageJsonContentStr = await runPrettier({ + sourceCode: packageJsonContentStr, + filePath: buildContext.packageJsonFilePath + }); + } + + await fsPr.writeFile(buildContext.packageJsonFilePath, packageJsonContentStr); + + npmInstall({ + packageJsonDirPath: buildContext.packageJsonFilePath + }); + + process.exit(0); +} diff --git a/src/bin/sync-ui-modules/managedGitignoreFile.ts b/src/bin/sync-ui-modules/managedGitignoreFile.ts index d7cf10bb..2c2f9405 100644 --- a/src/bin/sync-ui-modules/managedGitignoreFile.ts +++ b/src/bin/sync-ui-modules/managedGitignoreFile.ts @@ -1,9 +1,15 @@ import * as fsPr from "fs/promises"; -import { join as pathJoin, sep as pathSep, dirname as pathDirname } from "path"; +import { + join as pathJoin, + sep as pathSep, + dirname as pathDirname, + relative as pathRelative +} from "path"; import { assert } from "tsafe/assert"; import type { BuildContext } from "../shared/buildContext"; import type { UiModuleMeta } from "./uiModuleMeta"; import { existsAsync } from "../tools/fs.existsAsync"; +import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath"; export type BuildContextLike = { themeSrcDirPath: string; @@ -82,3 +88,48 @@ export async function writeManagedGitignoreFile(params: { await fsPr.writeFile(filePath, content_new); } + +export async function readManagedGitignoreFile(params: { + buildContext: BuildContextLike; +}): Promise<{ + ejectedFilesRelativePaths: string[]; +}> { + const { buildContext } = params; + + const filePath = pathJoin(buildContext.themeSrcDirPath, ".gitignore"); + + if (!(await existsAsync(filePath))) { + return { ejectedFilesRelativePaths: [] }; + } + + const contentStr = (await fsPr.readFile(filePath)).toString("utf8"); + + const payload = (() => { + const index_start = contentStr.indexOf(DELIMITER_START); + const index_end = contentStr.indexOf(DELIMITER_END); + + if (index_start === -1 || index_end === -1) { + return undefined; + } + + return contentStr.slice(index_start + DELIMITER_START.length, index_end).trim(); + })(); + + if (payload === undefined) { + return { ejectedFilesRelativePaths: [] }; + } + + const ejectedFilesRelativePaths = payload + .split("\n") + .map(line => line.trim()) + .filter(line => line !== "") + .map(line => + getAbsoluteAndInOsFormatPath({ + cwd: buildContext.themeSrcDirPath, + pathIsh: line + }) + ) + .map(filePath => pathRelative(buildContext.themeSrcDirPath, filePath)); + + return { ejectedFilesRelativePaths }; +} diff --git a/src/bin/tools/npmInstall.ts b/src/bin/tools/npmInstall.ts index a95a99df..2d62e129 100644 --- a/src/bin/tools/npmInstall.ts +++ b/src/bin/tools/npmInstall.ts @@ -23,6 +23,10 @@ export function npmInstall(params: { packageJsonDirPath: string }) { { binName: "bun", lockFileBasename: "bun.lockdb" + }, + { + binName: "deno", + lockFileBasename: "deno.lock" } ] as const; @@ -37,27 +41,23 @@ export function npmInstall(params: { packageJsonDirPath: string }) { } } - return undefined; + throw new Error( + "No lock file found, cannot tell which package manager to use for installing dependencies." + ); })(); - install_dependencies: { - if (packageManagerBinName === undefined) { - break install_dependencies; - } + console.log(`Installing the new dependencies...`); - console.log(`Installing the new dependencies...`); - - try { - child_process.execSync(`${packageManagerBinName} install`, { - cwd: packageJsonDirPath, - stdio: "inherit" - }); - } catch { - console.log( - chalk.yellow( - `\`${packageManagerBinName} install\` failed, continuing anyway...` - ) - ); - } + try { + child_process.execSync(`${packageManagerBinName} install`, { + cwd: packageJsonDirPath, + stdio: "inherit" + }); + } catch { + console.log( + chalk.yellow( + `\`${packageManagerBinName} install\` failed, continuing anyway...` + ) + ); } }