import { join as pathJoin, relative as pathRelative, sep as pathSep } from "path"; import type { Plugin } from "vite"; import { WELL_KNOWN_DIRECTORY_BASE_NAME, VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES } from "../bin/shared/constants"; import { id } from "tsafe/id"; import { rm } from "../bin/tools/fs.rm"; import { command as copyKeycloakResourcesToPublicCommand } from "../bin/copy-keycloak-resources-to-public"; import { assert } from "tsafe/assert"; import { getBuildContext, type BuildContext, type BuildOptions, type ResolvedViteConfig } from "../bin/shared/buildContext"; import MagicString from "magic-string"; import { command as updateKcGenCommand } from "../bin/update-kc-gen"; import { replaceAll } from "../bin/tools/String.prototype.replaceAll"; export namespace keycloakify { export type Params = BuildOptions & { postBuild?: (buildContext: Omit) => Promise; }; } export function keycloakify(params: keycloakify.Params) { const { postBuild, ...buildOptions } = params; let projectDirPath: string | undefined = undefined; let urlPathname: string | undefined = undefined; let buildDirPath: string | undefined = undefined; let command: "build" | "serve" | undefined = undefined; let shouldGenerateSourcemap: boolean | undefined = undefined; const plugin = { name: "keycloakify", configResolved: async resolvedConfig => { shouldGenerateSourcemap = resolvedConfig.build.sourcemap !== false; run_post_build_script_case: { const envValue = process.env[VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RUN_POST_BUILD_SCRIPT]; if (envValue === undefined) { break run_post_build_script_case; } const { buildContext, resourcesDirPath } = JSON.parse(envValue) as { buildContext: BuildContext; resourcesDirPath: string; }; process.chdir(resourcesDirPath); await postBuild?.(buildContext); process.exit(0); } command = resolvedConfig.command; projectDirPath = resolvedConfig.root; urlPathname = (() => { let out = resolvedConfig.env.BASE_URL; if ( out.startsWith(".") && command === "build" && resolvedConfig.envPrefix?.includes("STORYBOOK_") !== true ) { throw new Error( [ `BASE_URL=${out} is not supported By Keycloakify. Use an absolute path instead.`, `If this is a problem, please open an issue at https://github.com/keycloakify/keycloakify/issues/new` ].join("\n") ); } if (out === undefined) { return undefined; } if (!out.startsWith("/")) { out = "/" + out; } if (!out.endsWith("/")) { out += "/"; } return out; })(); buildDirPath = pathJoin(projectDirPath, resolvedConfig.build.outDir); resolve_vite_config_case: { const envValue = process.env[VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RESOLVE_VITE_CONFIG]; if (envValue === undefined) { break resolve_vite_config_case; } console.log(VITE_PLUGIN_SUB_SCRIPTS_ENV_NAMES.RESOLVE_VITE_CONFIG); console.log( JSON.stringify( id({ publicDir: pathRelative( projectDirPath, resolvedConfig.publicDir ), assetsDir: resolvedConfig.build.assetsDir, buildDir: resolvedConfig.build.outDir, urlPathname, buildOptions }) ) ); process.exit(0); } const buildContext = getBuildContext({ projectDirPath }); await copyKeycloakResourcesToPublicCommand({ buildContext }); await updateKcGenCommand({ buildContext }); }, transform: (code, id) => { id = replaceAll(id, "/", pathSep); assert(command !== undefined); assert(shouldGenerateSourcemap !== undefined); if (command !== "build") { return; } assert(projectDirPath !== undefined); { const isWithinSourceDirectory = id.startsWith( pathJoin(projectDirPath, "src") + pathSep ); if (!isWithinSourceDirectory) { return; } } { const isJavascriptFile = id.endsWith(".js") || id.endsWith(".jsx"); const isTypeScriptFile = id.endsWith(".ts") || id.endsWith(".tsx"); if (!isTypeScriptFile && !isJavascriptFile) { return; } } const transformedCode = new MagicString(code); transformedCode.replaceAll( /import\.meta\.env(?:(?:\.BASE_URL)|(?:\["BASE_URL"\]))/g, [ `(`, `(window.kcContext === undefined || import.meta.env.MODE === "development")?`, `import.meta.env.BASE_URL:`, `(window.kcContext["x-keycloakify"].resourcesPath + "/${WELL_KNOWN_DIRECTORY_BASE_NAME.DIST}/")`, `)` ].join("") ); if (!transformedCode.hasChanged()) { return; } if (!shouldGenerateSourcemap) { return transformedCode.toString(); } const map = transformedCode.generateMap({ source: id, includeContent: true, hires: true }); return { code: transformedCode.toString(), map: map.toString() }; }, closeBundle: async () => { assert(command !== undefined); if (command !== "build") { return; } assert(buildDirPath !== undefined); await rm( pathJoin( buildDirPath, WELL_KNOWN_DIRECTORY_BASE_NAME.KEYCLOAKIFY_DEV_RESOURCES ), { recursive: true, force: true } ); } } satisfies Plugin; return plugin as any; }