diff --git a/src/bin/add-story.ts b/src/bin/add-story.ts index 81cc80dd..81952d4a 100644 --- a/src/bin/add-story.ts +++ b/src/bin/add-story.ts @@ -20,7 +20,7 @@ import { maybeDelegateCommandToCustomHandler } from "./shared/customHandler_dele export async function command(params: { buildContext: BuildContext }) { const { buildContext } = params; - const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ + const { hasBeenHandled } = await maybeDelegateCommandToCustomHandler({ commandName: "add-story", buildContext }); diff --git a/src/bin/copy-keycloak-resources-to-public.ts b/src/bin/copy-keycloak-resources-to-public.ts index 1925860d..34fda1b9 100644 --- a/src/bin/copy-keycloak-resources-to-public.ts +++ b/src/bin/copy-keycloak-resources-to-public.ts @@ -11,7 +11,7 @@ import { getThisCodebaseRootDirPath } from "./tools/getThisCodebaseRootDirPath"; export async function command(params: { buildContext: BuildContext }) { const { buildContext } = params; - const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ + const { hasBeenHandled } = await maybeDelegateCommandToCustomHandler({ commandName: "copy-keycloak-resources-to-public", buildContext }); diff --git a/src/bin/eject-page.ts b/src/bin/eject-page.ts index 77d054da..9f7976e3 100644 --- a/src/bin/eject-page.ts +++ b/src/bin/eject-page.ts @@ -22,7 +22,7 @@ import { runPrettier, getIsPrettierAvailable } from "./tools/runPrettier"; export async function command(params: { buildContext: BuildContext }) { const { buildContext } = params; - const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ + const { hasBeenHandled } = await maybeDelegateCommandToCustomHandler({ commandName: "eject-page", buildContext }); diff --git a/src/bin/initialize-account-theme/initialize-account-theme.ts b/src/bin/initialize-account-theme/initialize-account-theme.ts index eac4313f..ce3354e1 100644 --- a/src/bin/initialize-account-theme/initialize-account-theme.ts +++ b/src/bin/initialize-account-theme/initialize-account-theme.ts @@ -12,7 +12,7 @@ import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath" export async function command(params: { buildContext: BuildContext }) { const { buildContext } = params; - const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ + const { hasBeenHandled } = await maybeDelegateCommandToCustomHandler({ commandName: "initialize-account-theme", buildContext }); diff --git a/src/bin/initialize-admin-theme.ts b/src/bin/initialize-admin-theme.ts index 4aff0d25..e4a00206 100644 --- a/src/bin/initialize-admin-theme.ts +++ b/src/bin/initialize-admin-theme.ts @@ -7,7 +7,7 @@ import { command as updateKcGenCommand } from "./update-kc-gen"; export async function command(params: { buildContext: BuildContext }) { const { buildContext } = params; - const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ + const { hasBeenHandled } = await maybeDelegateCommandToCustomHandler({ commandName: "initialize-admin-theme", buildContext }); diff --git a/src/bin/initialize-email-theme.ts b/src/bin/initialize-email-theme.ts index 4e681762..9acf3e24 100644 --- a/src/bin/initialize-email-theme.ts +++ b/src/bin/initialize-email-theme.ts @@ -17,7 +17,7 @@ import chalk from "chalk"; export async function command(params: { buildContext: BuildContext }) { const { buildContext } = params; - const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ + const { hasBeenHandled } = await maybeDelegateCommandToCustomHandler({ commandName: "initialize-account-theme", buildContext }); diff --git a/src/bin/shared/customHandler_delegate.ts b/src/bin/shared/customHandler_delegate.ts index ed38152b..851932d8 100644 --- a/src/bin/shared/customHandler_delegate.ts +++ b/src/bin/shared/customHandler_delegate.ts @@ -13,13 +13,15 @@ import * as fs from "fs"; assert>(); -export function maybeDelegateCommandToCustomHandler(params: { +export async function maybeDelegateCommandToCustomHandler(params: { commandName: CommandName; buildContext: BuildContext; -}): { hasBeenHandled: boolean } { +}): Promise<{ hasBeenHandled: boolean }> { const { commandName, buildContext } = params; - const nodeModulesBinDirPath = getNodeModulesBinDirPath(); + const nodeModulesBinDirPath = await getNodeModulesBinDirPath({ + packageJsonFilePath: buildContext.packageJsonFilePath + }); if (!fs.readdirSync(nodeModulesBinDirPath).includes(BIN_NAME)) { return { hasBeenHandled: false }; diff --git a/src/bin/tools/nodeModulesBinDirPath.ts b/src/bin/tools/nodeModulesBinDirPath.ts index d9f7b130..446c251c 100644 --- a/src/bin/tools/nodeModulesBinDirPath.ts +++ b/src/bin/tools/nodeModulesBinDirPath.ts @@ -1,10 +1,29 @@ -import { sep as pathSep } from "path"; +import { sep as pathSep, dirname as pathDirname, join as pathJoin } from "path"; +import { getThisCodebaseRootDirPath } from "./getThisCodebaseRootDirPath"; +import { getInstalledModuleDirPath } from "./getInstalledModuleDirPath"; +import { existsAsync } from "./fs.existsAsync"; +import { z } from "zod"; +import * as fs from "fs/promises"; +import { assert, is, type Equals } from "tsafe/assert"; +import { id } from "tsafe/id"; -let cache: string | undefined = undefined; +let cache_bestEffort: string | undefined = undefined; -export function getNodeModulesBinDirPath() { - if (cache !== undefined) { - return cache; +/** NOTE: Careful, this function can fail when the binary + * Used is not in the node_modules directory of the project + * (for example when running tests with vscode extension we'll get + * '/Users/dylan/.vscode/extensions/vitest.explorer-1.16.0/dist/worker.js' + * + * instead of + * '/Users/joseph/.nvm/versions/node/v22.12.0/bin/node' + * or + * '/Users/joseph/github/keycloakify-starter/node_modules/.bin/vite' + * + * as the value of process.argv[1] + */ +function getNodeModulesBinDirPath_bestEffort() { + if (cache_bestEffort !== undefined) { + return cache_bestEffort; } const binPath = process.argv[1]; @@ -30,9 +49,122 @@ export function getNodeModulesBinDirPath() { segments.unshift(segment); } + if (!foundNodeModules) { + throw new Error(`Could not find node_modules in path ${binPath}`); + } + const nodeModulesBinDirPath = segments.join(pathSep); - cache = nodeModulesBinDirPath; + cache_bestEffort = nodeModulesBinDirPath; return nodeModulesBinDirPath; } + +let cache_withPackageJsonFileDirPath: + | { packageJsonFilePath: string; nodeModulesBinDirPath: string } + | undefined = undefined; + +async function getNodeModulesBinDirPath_withPackageJsonFileDirPath(params: { + packageJsonFilePath: string; +}): Promise { + const { packageJsonFilePath } = params; + + use_cache: { + if (cache_withPackageJsonFileDirPath === undefined) { + break use_cache; + } + + if ( + cache_withPackageJsonFileDirPath.packageJsonFilePath !== packageJsonFilePath + ) { + cache_withPackageJsonFileDirPath = undefined; + break use_cache; + } + + return cache_withPackageJsonFileDirPath.nodeModulesBinDirPath; + } + + // [...]node_modules/keycloakify + const installedModuleDirPath = await getInstalledModuleDirPath({ + // Here it will always be "keycloakify" but since we are in tools/ we make something generic + moduleName: await (async () => { + type ParsedPackageJson = { + name: string; + }; + + const zParsedPackageJson = (() => { + type TargetType = ParsedPackageJson; + + const zTargetType = z.object({ + name: z.string() + }); + + assert, TargetType>>; + + return id>(zTargetType); + })(); + + const parsedPackageJson = JSON.parse( + ( + await fs.readFile( + pathJoin(getThisCodebaseRootDirPath(), "package.json") + ) + ).toString("utf8") + ); + + zParsedPackageJson.parse(parsedPackageJson); + + assert(is(parsedPackageJson)); + + return parsedPackageJson.name; + })(), + packageJsonDirPath: pathDirname(packageJsonFilePath) + }); + + const segments = installedModuleDirPath.split(pathSep); + + while (true) { + const segment = segments.pop(); + + if (segment === undefined) { + throw new Error( + `Could not find .bin directory relative to ${packageJsonFilePath}` + ); + } + + if (segment !== "node_modules") { + continue; + } + + const candidate = pathJoin(segments.join(pathSep), segment, ".bin"); + + if (!(await existsAsync(candidate))) { + continue; + } + + cache_withPackageJsonFileDirPath = { + packageJsonFilePath, + nodeModulesBinDirPath: candidate + }; + + break; + } + + return cache_withPackageJsonFileDirPath.nodeModulesBinDirPath; +} + +export function getNodeModulesBinDirPath(params: { + packageJsonFilePath: string; +}): Promise; +export function getNodeModulesBinDirPath(params: { + packageJsonFilePath: undefined; +}): string; +export function getNodeModulesBinDirPath(params: { + packageJsonFilePath: string | undefined; +}): string | Promise { + const { packageJsonFilePath } = params ?? {}; + + return packageJsonFilePath === undefined + ? getNodeModulesBinDirPath_bestEffort() + : getNodeModulesBinDirPath_withPackageJsonFileDirPath({ packageJsonFilePath }); +} diff --git a/src/bin/tools/runPrettier.ts b/src/bin/tools/runPrettier.ts index 31c03f22..024d72c9 100644 --- a/src/bin/tools/runPrettier.ts +++ b/src/bin/tools/runPrettier.ts @@ -15,7 +15,9 @@ export async function getIsPrettierAvailable(): Promise { return getIsPrettierAvailable.cache; } - const nodeModulesBinDirPath = getNodeModulesBinDirPath(); + const nodeModulesBinDirPath = getNodeModulesBinDirPath({ + packageJsonFilePath: undefined + }); const prettierBinPath = pathJoin(nodeModulesBinDirPath, "prettier"); @@ -51,7 +53,7 @@ export async function getPrettier(): Promise { // We make sure to only do that when linking, otherwise we import properly. if (readThisNpmPackageVersion().startsWith("0.0.0")) { eval( - `${symToStr({ prettier })} = require("${pathResolve(pathJoin(getNodeModulesBinDirPath(), "..", "prettier"))}")` + `${symToStr({ prettier })} = require("${pathResolve(pathJoin(getNodeModulesBinDirPath({ packageJsonFilePath: undefined }), "..", "prettier"))}")` ); assert(!is(prettier)); @@ -64,7 +66,7 @@ export async function getPrettier(): Promise { const configHash = await (async () => { const configFilePath = await prettier.resolveConfigFile( - pathJoin(getNodeModulesBinDirPath(), "..") + pathJoin(getNodeModulesBinDirPath({ packageJsonFilePath: undefined }), "..") ); if (configFilePath === null) { diff --git a/src/bin/update-kc-gen.ts b/src/bin/update-kc-gen.ts index dff24fd2..5be6356d 100644 --- a/src/bin/update-kc-gen.ts +++ b/src/bin/update-kc-gen.ts @@ -19,7 +19,7 @@ export async function command(params: { buildContext: BuildContext }) { await command({ buildContext }); } - const { hasBeenHandled } = maybeDelegateCommandToCustomHandler({ + const { hasBeenHandled } = await maybeDelegateCommandToCustomHandler({ commandName: "update-kc-gen", buildContext });