diff --git a/scripts/build/main.ts b/scripts/build/main.ts index 027e80d8..097c886a 100644 --- a/scripts/build/main.ts +++ b/scripts/build/main.ts @@ -40,7 +40,9 @@ import { vendorFrontendDependencies } from "./vendorFrontendDependencies"; ); } - run(`npx ncc build ${join("dist", "bin", "main.js")} -o ${join("dist", "ncc_out")}`); + run( + `npx ncc build ${join("dist", "bin", "main.js")} --external prettier -o ${join("dist", "ncc_out")}` + ); transformCodebase({ srcDirPath: join("dist", "ncc_out"), diff --git a/src/bin/shared/constants.ts b/src/bin/shared/constants.ts index cfddb472..ea54c411 100644 --- a/src/bin/shared/constants.ts +++ b/src/bin/shared/constants.ts @@ -76,3 +76,5 @@ export const CUSTOM_HANDLER_ENV_NAMES = { COMMAND_NAME: "KEYCLOAKIFY_COMMAND_NAME", BUILD_CONTEXT: "KEYCLOAKIFY_BUILD_CONTEXT" }; + +export const KC_GEN_FILE_PATH_RELATIVE_TO_THEME_SRC_DIR = "kc-gen.tsx"; diff --git a/src/bin/shared/customHandler_delegate.ts b/src/bin/shared/customHandler_delegate.ts index 4a719f34..ed38152b 100644 --- a/src/bin/shared/customHandler_delegate.ts +++ b/src/bin/shared/customHandler_delegate.ts @@ -8,7 +8,7 @@ import { ApiVersion } from "./customHandler"; import * as child_process from "child_process"; -import { sep as pathSep } from "path"; +import { getNodeModulesBinDirPath } from "../tools/nodeModulesBinDirPath"; import * as fs from "fs"; assert>(); @@ -19,32 +19,7 @@ export function maybeDelegateCommandToCustomHandler(params: { }): { hasBeenHandled: boolean } { const { commandName, buildContext } = params; - const nodeModulesBinDirPath = (() => { - const binPath = process.argv[1]; - - const segments: string[] = [".bin"]; - - let foundNodeModules = false; - - for (const segment of binPath.split(pathSep).reverse()) { - skip_segment: { - if (foundNodeModules) { - break skip_segment; - } - - if (segment === "node_modules") { - foundNodeModules = true; - break skip_segment; - } - - continue; - } - - segments.unshift(segment); - } - - return segments.join(pathSep); - })(); + const nodeModulesBinDirPath = getNodeModulesBinDirPath(); if (!fs.readdirSync(nodeModulesBinDirPath).includes(BIN_NAME)) { return { hasBeenHandled: false }; diff --git a/src/bin/sync-ui-modules/getSourceCodeToCopyInUserCodebase.ts b/src/bin/sync-ui-modules/getSourceCodeToCopyInUserCodebase.ts new file mode 100644 index 00000000..8306e67d --- /dev/null +++ b/src/bin/sync-ui-modules/getSourceCodeToCopyInUserCodebase.ts @@ -0,0 +1,62 @@ +import { getIsPrettierAvailable, runPrettier } from "../tools/runPrettier"; +import * as fsPr from "fs/promises"; +import { join as pathJoin, sep as pathSep } from "path"; +import { assert } from "tsafe/assert"; +import type { BuildContext } from "../shared/buildContext"; + +export type BuildContextLike = { + themeSrcDirPath: string; +}; + +assert(); + +export async function getSourceCodeToCopyInUserCodebase(params: { + buildContext: BuildContextLike; + relativeFromDirPath: string; + fileRelativePath: string; + commentData: { + isForEjection: boolean; + uiModuleName: string; + uiModuleVersion: string; + }; +}): Promise { + const { buildContext, relativeFromDirPath, fileRelativePath, commentData } = params; + + let sourceCode = ( + await fsPr.readFile(pathJoin(relativeFromDirPath, fileRelativePath)) + ).toString("utf8"); + + const comment = (() => { + if (commentData.isForEjection) { + return [ + `/*`, + ` This file was ejected from ${commentData.uiModuleName} version ${commentData.uiModuleVersion}.`, + `*/` + ].join("\n"); + } else { + return [ + `/*`, + ` WARNING: Before modifying this file run the following command:`, + ` \`npx keycloakify eject-file ${fileRelativePath.split(pathSep).join("/")}\``, + ` `, + ` This file comes from ${commentData.uiModuleName} version ${commentData.uiModuleVersion}.`, + `*/` + ]; + } + })(); + + sourceCode = [comment, ``, sourceCode].join("\n"); + + format: { + if (!(await getIsPrettierAvailable())) { + break format; + } + + sourceCode = await runPrettier({ + filePath: pathJoin(buildContext.themeSrcDirPath, fileRelativePath), + sourceCode + }); + } + + return sourceCode; +} diff --git a/src/bin/sync-ui-modules/index.ts b/src/bin/sync-ui-modules/index.ts new file mode 100644 index 00000000..45d420bd --- /dev/null +++ b/src/bin/sync-ui-modules/index.ts @@ -0,0 +1 @@ +export * from "./sync-ui-modules"; diff --git a/src/bin/sync-ui-modules/listOfEjectedFiles.ts b/src/bin/sync-ui-modules/listOfEjectedFiles.ts new file mode 100644 index 00000000..ccfe8046 --- /dev/null +++ b/src/bin/sync-ui-modules/listOfEjectedFiles.ts @@ -0,0 +1,3 @@ +export async function getListOfEjectedFiles(params: {}): Promise {} + +export async function writeListOfEjectedFiles(params: {}) {} diff --git a/src/bin/sync-ui-modules/sync-ui-modules.ts b/src/bin/sync-ui-modules/sync-ui-modules.ts new file mode 100644 index 00000000..abd1c4c8 --- /dev/null +++ b/src/bin/sync-ui-modules/sync-ui-modules.ts @@ -0,0 +1,13 @@ +import type { BuildContext } from "./shared/buildContext"; +import { assert, type Equals } from "tsafe/assert"; +import { id } from "tsafe/id"; +import { is } from "tsafe/is"; +import { z } from "zod"; +import { join as pathJoin } from "path"; +import { existsAsync } from "./tools/fs.existsAsync"; + +import * as fsPr from "fs/promises"; + +export async function command(params: { buildContext: BuildContext }) { + const { buildContext } = params; +} diff --git a/src/bin/sync-ui-modules/uiModulesMeta.ts b/src/bin/sync-ui-modules/uiModulesMeta.ts new file mode 100644 index 00000000..b87d09b5 --- /dev/null +++ b/src/bin/sync-ui-modules/uiModulesMeta.ts @@ -0,0 +1,225 @@ +import { assert, type Equals } from "tsafe/assert"; +import { id } from "tsafe/id"; +import { z } from "zod"; +import { join as pathJoin } from "path"; +import * as fsPr from "fs/promises"; +import type { BuildContext } from "../shared/buildContext"; +import { is } from "tsafe/is"; +import { existsAsync } from "../tools/fs.existsAsync"; +import { listInstalledModules } from "../tools/listInstalledModules"; +import { crawlAsync } from "../tools/crawlAsync"; +import { getIsPrettierAvailable, getPrettierAndConfig } from "../tools/runPrettier"; +import { readThisNpmPackageVersion } from "../tools/readThisNpmPackageVersion"; +import { + getSourceCodeToCopyInUserCodebase, + type BuildContextLike as BuildContextLike_getSourceCodeToCopyInUserCodebase +} from "./getSourceCodeToCopyInUserCodebase"; +import * as crypto from "crypto"; + +export type UiModulesMeta = { + keycloakifyVersion: string; + prettierConfigHash: string | null; + entries: UiModulesMeta.Entry[]; +}; + +export namespace UiModulesMeta { + export type Entry = { + moduleName: string; + version: string; + files: { + fileRelativePath: string; + hash: string; + }[]; + }; +} + +const zUiModuleMetasEntry = (() => { + type ExpectedType = UiModulesMeta.Entry; + + const zTargetType = z.object({ + moduleName: z.string(), + version: z.string(), + files: z.array( + z.object({ + fileRelativePath: z.string(), + hash: z.string() + }) + ) + }); + + type InferredType = z.infer; + + assert>(); + + return id>(zTargetType); +})(); + +const zUiModulesMeta = (() => { + type ExpectedType = UiModulesMeta; + + const zTargetType = z.object({ + keycloakifyVersion: z.string(), + prettierConfigHash: z.union([z.string(), z.null()]), + entries: z.array(zUiModuleMetasEntry) + }); + + type InferredType = z.infer; + + assert>(); + + return id>(zTargetType); +})(); + +const RELATIVE_FILE_PATH = pathJoin("uiModulesMeta.json"); + +export type BuildContextLike = BuildContextLike_getSourceCodeToCopyInUserCodebase & { + cacheDirPath: string; + packageJsonFilePath: string; + projectDirPath: string; +}; + +assert(); + +export async function readOrCreateUiModulesMeta(params: { + buildContext: BuildContextLike; +}): Promise { + const { buildContext } = params; + + const filePath = pathJoin(buildContext.cacheDirPath, RELATIVE_FILE_PATH); + + const keycloakifyVersion = readThisNpmPackageVersion(); + + const prettierConfigHash = await (async () => { + if (!(await getIsPrettierAvailable())) { + return null; + } + + const { config } = await getPrettierAndConfig(); + + return crypto.createHash("sha256").update(JSON.stringify(config)).digest("hex"); + })(); + + const installedUiModules = await listInstalledModules({ + packageJsonFilePath: buildContext.packageJsonFilePath, + projectDirPath: buildContext.packageJsonFilePath, + filter: ({ moduleName }) => + moduleName.includes("keycloakify") && moduleName.endsWith("-ui") + }); + + const upToDateEntries: UiModulesMeta.Entry[] = await (async () => { + const uiModulesMeta_cache: UiModulesMeta | undefined = await (async () => { + if (!(await existsAsync(filePath))) { + return undefined; + } + + const contentStr = (await fsPr.readFile(filePath)).toString("utf8"); + + let uiModuleMeta: unknown; + + try { + uiModuleMeta = JSON.parse(contentStr); + } catch { + return undefined; + } + + try { + zUiModulesMeta.parse(uiModuleMeta); + } catch { + return undefined; + } + + assert(is(uiModuleMeta)); + + return uiModuleMeta; + })(); + + if (uiModulesMeta_cache === undefined) { + return []; + } + + if (uiModulesMeta_cache.keycloakifyVersion !== keycloakifyVersion) { + return []; + } + + if (uiModulesMeta_cache.prettierConfigHash !== prettierConfigHash) { + return []; + } + + const upToDateEntries = uiModulesMeta_cache.entries.filter(entry => { + const correspondingInstalledUiModule = installedUiModules.find( + installedUiModule => installedUiModule.moduleName === entry.moduleName + ); + + if (correspondingInstalledUiModule === undefined) { + return false; + } + + return correspondingInstalledUiModule.version === entry.version; + }); + + return upToDateEntries; + })(); + + const entries = await Promise.all( + installedUiModules.map( + async ({ moduleName, version, dirPath }): Promise => { + use_cache: { + const cachedEntry = upToDateEntries.find( + entry => entry.moduleName === moduleName + ); + + if (cachedEntry === undefined) { + break use_cache; + } + + return cachedEntry; + } + + const files: UiModulesMeta.Entry["files"] = []; + + { + const srcDirPath = pathJoin(dirPath, "src"); + + await crawlAsync({ + dirPath: srcDirPath, + returnedPathsType: "relative to dirPath", + onFileFound: async fileRelativePath => { + const sourceCode = await getSourceCodeToCopyInUserCodebase({ + buildContext, + relativeFromDirPath: srcDirPath, + fileRelativePath, + commentData: { + isForEjection: false, + uiModuleName: moduleName, + uiModuleVersion: version + } + }); + + const hash = crypto + .createHash("sha256") + .update(sourceCode) + .digest("hex"); + + files.push({ + fileRelativePath, + hash + }); + } + }); + } + + return id({ + files, + moduleName, + version + }); + } + ) + ); + + return id({ + keycloakifyVersion, + prettierConfigHash, + entries + }); +} diff --git a/src/bin/tools/crawlAsync.ts b/src/bin/tools/crawlAsync.ts new file mode 100644 index 00000000..e1fda91b --- /dev/null +++ b/src/bin/tools/crawlAsync.ts @@ -0,0 +1,51 @@ +import * as fsPr from "fs/promises"; +import { join as pathJoin, relative as pathRelative } from "path"; +import { assert, type Equals } from "tsafe/assert"; + +/** List all files in a given directory return paths relative to the dir_path */ +export async function crawlAsync(params: { + dirPath: string; + returnedPathsType: "absolute" | "relative to dirPath"; + onFileFound: (filePath: string) => void; +}) { + const { dirPath, returnedPathsType, onFileFound } = params; + + await crawlAsyncRec({ + dirPath, + onFileFound: ({ filePath }) => { + switch (returnedPathsType) { + case "absolute": + onFileFound(filePath); + return; + case "relative to dirPath": + onFileFound(pathRelative(dirPath, filePath)); + return; + } + assert>(); + } + }); +} + +async function crawlAsyncRec(params: { + dirPath: string; + onFileFound: (params: { filePath: string }) => void; +}) { + const { dirPath, onFileFound } = params; + + await Promise.all( + (await fsPr.readdir(dirPath)).map(async basename => { + const fileOrDirPath = pathJoin(dirPath, basename); + + const isDirectory = await fsPr + .lstat(fileOrDirPath) + .then(stat => stat.isDirectory()); + + if (isDirectory) { + await crawlAsyncRec({ dirPath: fileOrDirPath, onFileFound }); + return; + } + + onFileFound({ filePath: fileOrDirPath }); + }) + ); +} diff --git a/src/bin/tools/getInstalledModuleDirPath.ts b/src/bin/tools/getInstalledModuleDirPath.ts new file mode 100644 index 00000000..260c3c5e --- /dev/null +++ b/src/bin/tools/getInstalledModuleDirPath.ts @@ -0,0 +1,53 @@ +import { dirname as pathDirname, join as pathJoin } from "path"; +import { existsAsync } from "./fs.existsAsync"; +import * as child_process from "child_process"; +import { assert } from "tsafe/assert"; + +export async function getInstalledModuleDirPath(params: { + moduleName: string; + packageJsonFilePath: string; + projectDirPath: string; +}) { + const { moduleName, packageJsonFilePath, projectDirPath } = params; + + const packageJsonDirPath = pathDirname(packageJsonFilePath); + + common_case: { + const dirPath = pathJoin( + ...[packageJsonDirPath, "node_modules", ...moduleName.split("/")] + ); + + if (!(await existsAsync(dirPath))) { + break common_case; + } + + return dirPath; + } + + node_modules_at_root_case: { + if (projectDirPath === packageJsonDirPath) { + break node_modules_at_root_case; + } + + const dirPath = pathJoin( + ...[projectDirPath, "node_modules", ...moduleName.split("/")] + ); + + if (!(await existsAsync(dirPath))) { + break node_modules_at_root_case; + } + + return dirPath; + } + + const dirPath = child_process + .execSync(`npm list ${moduleName}`, { + cwd: packageJsonDirPath + }) + .toString("utf8") + .trim(); + + assert(dirPath !== ""); + + return dirPath; +} diff --git a/src/bin/tools/listInstalledModules.ts b/src/bin/tools/listInstalledModules.ts new file mode 100644 index 00000000..5cecad83 --- /dev/null +++ b/src/bin/tools/listInstalledModules.ts @@ -0,0 +1,114 @@ +import { assert, type Equals } from "tsafe/assert"; +import { id } from "tsafe/id"; +import { z } from "zod"; +import { join as pathJoin } from "path"; +import * as fsPr from "fs/promises"; +import { is } from "tsafe/is"; +import { getInstalledModuleDirPath } from "../tools/getInstalledModuleDirPath"; + +export async function listInstalledModules(params: { + packageJsonFilePath: string; + projectDirPath: string; + filter: (params: { moduleName: string }) => boolean; +}): Promise<{ moduleName: string; version: string; dirPath: string }[]> { + const { packageJsonFilePath, projectDirPath, filter } = params; + + const parsedPackageJson = await readPackageJsonDependencies({ + packageJsonFilePath + }); + + const uiModuleNames = ( + [parsedPackageJson.dependencies, parsedPackageJson.devDependencies] as const + ) + .filter(obj => obj !== undefined) + .map(obj => Object.keys(obj)) + .flat() + .filter(moduleName => filter({ moduleName })); + const result = await Promise.all( + uiModuleNames.map(async moduleName => { + const dirPath = await getInstalledModuleDirPath({ + moduleName, + packageJsonFilePath, + projectDirPath + }); + + const { version } = await readPackageJsonVersion({ + packageJsonFilePath: pathJoin(dirPath, "package.json") + }); + + return { moduleName, version, dirPath } as const; + }) + ); + + return result; +} + +const { readPackageJsonDependencies } = (() => { + 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); + })(); + + async function readPackageJsonDependencies(params: { packageJsonFilePath: string }) { + const { packageJsonFilePath } = params; + + const parsedPackageJson = JSON.parse( + (await fsPr.readFile(packageJsonFilePath)).toString("utf8") + ); + + zParsedPackageJson.parse(parsedPackageJson); + + assert(is(parsedPackageJson)); + + return parsedPackageJson; + } + + return { readPackageJsonDependencies }; +})(); + +const { readPackageJsonVersion } = (() => { + type ParsedPackageJson = { + version: string; + }; + + const zParsedPackageJson = (() => { + type TargetType = ParsedPackageJson; + + const zTargetType = z.object({ + version: z.string() + }); + + assert, TargetType>>(); + + return id>(zTargetType); + })(); + + async function readPackageJsonVersion(params: { packageJsonFilePath: string }) { + const { packageJsonFilePath } = params; + + const parsedPackageJson = JSON.parse( + (await fsPr.readFile(packageJsonFilePath)).toString("utf8") + ); + + zParsedPackageJson.parse(parsedPackageJson); + + assert(is(parsedPackageJson)); + + return parsedPackageJson; + } + + return { readPackageJsonVersion }; +})(); diff --git a/src/bin/tools/nodeModulesBinDirPath.ts b/src/bin/tools/nodeModulesBinDirPath.ts new file mode 100644 index 00000000..d9f7b130 --- /dev/null +++ b/src/bin/tools/nodeModulesBinDirPath.ts @@ -0,0 +1,38 @@ +import { sep as pathSep } from "path"; + +let cache: string | undefined = undefined; + +export function getNodeModulesBinDirPath() { + if (cache !== undefined) { + return cache; + } + + const binPath = process.argv[1]; + + const segments: string[] = [".bin"]; + + let foundNodeModules = false; + + for (const segment of binPath.split(pathSep).reverse()) { + skip_segment: { + if (foundNodeModules) { + break skip_segment; + } + + if (segment === "node_modules") { + foundNodeModules = true; + break skip_segment; + } + + continue; + } + + segments.unshift(segment); + } + + const nodeModulesBinDirPath = segments.join(pathSep); + + cache = nodeModulesBinDirPath; + + return nodeModulesBinDirPath; +} diff --git a/src/bin/tools/readThisNpmPackageVersion.ts b/src/bin/tools/readThisNpmPackageVersion.ts index b493c991..b8e2bf95 100644 --- a/src/bin/tools/readThisNpmPackageVersion.ts +++ b/src/bin/tools/readThisNpmPackageVersion.ts @@ -3,7 +3,13 @@ import { assert } from "tsafe/assert"; import * as fs from "fs"; import { join as pathJoin } from "path"; +let cache: string | undefined = undefined; + export function readThisNpmPackageVersion(): string { + if (cache !== undefined) { + return cache; + } + const version = JSON.parse( fs .readFileSync(pathJoin(getThisCodebaseRootDirPath(), "package.json")) @@ -12,5 +18,7 @@ export function readThisNpmPackageVersion(): string { assert(typeof version === "string"); + cache = version; + return version; } diff --git a/src/bin/tools/runFormat.ts b/src/bin/tools/runFormat.ts deleted file mode 100644 index b7a4d787..00000000 --- a/src/bin/tools/runFormat.ts +++ /dev/null @@ -1,71 +0,0 @@ -import * as fs from "fs"; -import { dirname as pathDirname } from "path"; -import { assert, Equals } from "tsafe/assert"; -import chalk from "chalk"; -import { id } from "tsafe/id"; -import { z } from "zod"; -import { is } from "tsafe/is"; -import * as child_process from "child_process"; - -export function runFormat(params: { packageJsonFilePath: string }) { - const { packageJsonFilePath } = params; - - const parsedPackageJson = (() => { - type ParsedPackageJson = { - scripts?: Record; - }; - - const zParsedPackageJson = (() => { - type TargetType = ParsedPackageJson; - - const zTargetType = z.object({ - scripts: z.record(z.string()).optional() - }); - - assert, TargetType>>(); - - return id>(zTargetType); - })(); - - const parsedPackageJson = JSON.parse( - fs.readFileSync(packageJsonFilePath).toString("utf8") - ); - - zParsedPackageJson.parse(parsedPackageJson); - - assert(is(parsedPackageJson)); - - return parsedPackageJson; - })(); - - const { scripts } = parsedPackageJson; - - if (scripts === undefined) { - return; - } - - for (const scriptName of ["format", "lint"]) { - if (!(scriptName in scripts)) { - continue; - } - - const command = `npm run ${scriptName}`; - - console.log(chalk.grey(`$ ${command}`)); - - try { - child_process.execSync(`npm run ${scriptName}`, { - stdio: "inherit", - cwd: pathDirname(packageJsonFilePath) - }); - } catch { - console.log( - chalk.yellow( - `\`${command}\` failed, it does not matter, please format your code manually, continuing...` - ) - ); - } - - return; - } -} diff --git a/src/bin/tools/runPrettier.ts b/src/bin/tools/runPrettier.ts new file mode 100644 index 00000000..98901ef0 --- /dev/null +++ b/src/bin/tools/runPrettier.ts @@ -0,0 +1,77 @@ +import { getNodeModulesBinDirPath } from "./nodeModulesBinDirPath"; +import { join as pathJoin } from "path"; +import * as fsPr from "fs/promises"; +import { id } from "tsafe/id"; +import { assert } from "tsafe/assert"; +import chalk from "chalk"; + +getIsPrettierAvailable.cache = id(undefined); + +export async function getIsPrettierAvailable(): Promise { + if (getIsPrettierAvailable.cache !== undefined) { + return getIsPrettierAvailable.cache; + } + + const nodeModulesBinDirPath = getNodeModulesBinDirPath(); + + const prettierBinPath = pathJoin(nodeModulesBinDirPath, "prettier"); + + const stats = await fsPr.stat(prettierBinPath).catch(() => undefined); + + const isPrettierAvailable = stats?.isFile() ?? false; + + getIsPrettierAvailable.cache = isPrettierAvailable; + + return isPrettierAvailable; +} + +type PrettierAndConfig = { + prettier: typeof import("prettier"); + config: import("prettier").Options | null; +}; + +getPrettierAndConfig.cache = id(undefined); + +export async function getPrettierAndConfig(): Promise { + assert(getIsPrettierAvailable()); + + if (getPrettierAndConfig.cache !== undefined) { + return getPrettierAndConfig.cache; + } + + const prettier = await import("prettier"); + + const prettierAndConfig: PrettierAndConfig = { + prettier, + config: await prettier.resolveConfig(pathJoin(getNodeModulesBinDirPath(), "..")) + }; + + getPrettierAndConfig.cache = prettierAndConfig; + + return prettierAndConfig; +} + +export async function runPrettier(params: { + sourceCode: string; + filePath: string; +}): Promise { + const { sourceCode, filePath } = params; + + let formattedSourceCode: string; + + try { + const { prettier, config } = await getPrettierAndConfig(); + + formattedSourceCode = await prettier.format(sourceCode, { ...config, filePath }); + } catch (error) { + console.log( + chalk.red( + `You probably need to upgrade the version of prettier in your project` + ) + ); + + throw error; + } + + return formattedSourceCode; +} diff --git a/src/bin/update-kc-gen.ts b/src/bin/update-kc-gen.ts index 24e8d95b..9eb9b5c2 100644 --- a/src/bin/update-kc-gen.ts +++ b/src/bin/update-kc-gen.ts @@ -1,10 +1,11 @@ import type { BuildContext } from "./shared/buildContext"; +import { KC_GEN_FILE_PATH_RELATIVE_TO_THEME_SRC_DIR } from "./shared/constants"; import * as fs from "fs/promises"; import { join as pathJoin } from "path"; import { existsAsync } from "./tools/fs.existsAsync"; import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_delegate"; -import { runFormat } from "./tools/runFormat"; import * as crypto from "crypto"; +import { getIsPrettierAvailable, runPrettier } from "./tools/runPrettier"; export async function command(params: { buildContext: BuildContext }) { const { buildContext } = params; @@ -18,13 +19,16 @@ export async function command(params: { buildContext: BuildContext }) { return; } - const filePath = pathJoin(buildContext.themeSrcDirPath, `kc.gen.tsx`); + const filePath = pathJoin( + buildContext.themeSrcDirPath, + KC_GEN_FILE_PATH_RELATIVE_TO_THEME_SRC_DIR + ); const hasLoginTheme = buildContext.implementedThemeTypes.login.isImplemented; const hasAccountTheme = buildContext.implementedThemeTypes.account.isImplemented; const hasAdminTheme = buildContext.implementedThemeTypes.admin.isImplemented; - const newContent = [ + let newContent = [ ``, `/* eslint-disable */`, ``, @@ -114,20 +118,25 @@ export async function command(params: { buildContext: BuildContext }) { return; } - await fs.writeFile( - filePath, - Buffer.from( - [ - `// This file is auto-generated by the \`update-kc-gen\` command. Do not edit it manually.`, - `// Hash: ${hash}`, - ``, - newContent - ].join("\n"), - "utf8" - ) - ); + newContent = [ + `// This file is auto-generated by the \`update-kc-gen\` command. Do not edit it manually.`, + `// Hash: ${hash}`, + ``, + newContent + ].join("\n"); - runFormat({ packageJsonFilePath: buildContext.packageJsonFilePath }); + format: { + if (!(await getIsPrettierAvailable())) { + break format; + } + + newContent = await runPrettier({ + filePath, + sourceCode: newContent + }); + } + + await fs.writeFile(filePath, Buffer.from(newContent, "utf8")); delete_legacy_file: { const legacyFilePath = filePath.replace(/tsx$/, "ts");