Feat: Initialize account theme (before debug)

This commit is contained in:
Joseph Garrone 2024-07-25 18:16:43 +02:00
parent b9ee0afe7f
commit 77249d8a58
23 changed files with 769 additions and 119 deletions

View File

@ -0,0 +1,32 @@
import * as fs from "fs";
import { join as pathJoin } from "path";
import { getThisCodebaseRootDirPath } from "../tools/getThisCodebaseRootDirPath";
import { assert, type Equals } from "tsafe/assert";
export function copyBoilerplate(params: {
accountThemeType: "Single-Page" | "Multi-Page";
accountThemeSrcDirPath: string;
}) {
const { accountThemeType, accountThemeSrcDirPath } = params;
fs.cpSync(
pathJoin(
getThisCodebaseRootDirPath(),
"src",
"bin",
"initialize-account-theme",
"src",
(() => {
switch (accountThemeType) {
case "Single-Page":
return "single-page";
case "Multi-Page":
return "multi-page";
}
assert<Equals<typeof accountThemeType, never>>(false);
})()
),
accountThemeSrcDirPath,
{ recursive: true }
);
}

View File

@ -0,0 +1 @@
export * from "./initialize-account-theme";

View File

@ -0,0 +1,95 @@
import { getBuildContext } from "../shared/buildContext";
import type { CliCommandOptions } from "../main";
import cliSelect from "cli-select";
import child_process from "child_process";
import chalk from "chalk";
import { join as pathJoin, relative as pathRelative } from "path";
import * as fs from "fs";
import { updateAccountThemeImplementationInConfig } from "./updateAccountThemeImplementationInConfig";
export async function command(params: { cliCommandOptions: CliCommandOptions }) {
const { cliCommandOptions } = params;
const buildContext = getBuildContext({ cliCommandOptions });
const accountThemeSrcDirPath = pathJoin(buildContext.themeSrcDirPath, "account");
if (fs.existsSync(accountThemeSrcDirPath)) {
console.warn(
chalk.red(
`There is already a ${pathRelative(
process.cwd(),
accountThemeSrcDirPath
)} directory in your project. Aborting.`
)
);
process.exit(-1);
}
exit_if_uncommitted_changes: {
let hasUncommittedChanges: boolean | undefined = undefined;
try {
hasUncommittedChanges =
child_process
.execSync(`git status --porcelain`, {
cwd: buildContext.projectDirPath
})
.toString()
.trim() !== "";
} catch {
// Probably not a git repository
break exit_if_uncommitted_changes;
}
if (!hasUncommittedChanges) {
break exit_if_uncommitted_changes;
}
console.warn(
[
chalk.red(
"Please commit or stash your changes before running this command.\n"
),
"This command will modify your project's files so it's better to have a clean working directory",
"so that you can easily see what has been changed and revert if needed."
].join(" ")
);
process.exit(-1);
}
const { value: accountThemeType } = await cliSelect({
values: ["Single-Page" as const, "Multi-Page" as const]
}).catch(() => {
process.exit(-1);
});
switch (accountThemeType) {
case "Multi-Page":
{
const { initializeAccountTheme_multiPage } = await import(
"./initializeAccountTheme_multiPage"
);
await initializeAccountTheme_multiPage({
accountThemeSrcDirPath
});
}
break;
case "Single-Page":
{
const { initializeAccountTheme_singlePage } = await import(
"./initializeAccountTheme_singlePage"
);
await initializeAccountTheme_singlePage({
accountThemeSrcDirPath,
buildContext
});
}
break;
}
updateAccountThemeImplementationInConfig({ buildContext, accountThemeType });
}

View File

@ -0,0 +1,21 @@
import { relative as pathRelative } from "path";
import chalk from "chalk";
import { copyBoilerplate } from "./copyBoilerplate";
export async function initializeAccountTheme_multiPage(params: {
accountThemeSrcDirPath: string;
}) {
const { accountThemeSrcDirPath } = params;
copyBoilerplate({
accountThemeType: "Multi-Page",
accountThemeSrcDirPath
});
console.log(
[
chalk.green("The Multi-Page account theme has been initialized."),
`Directory created: ${chalk.bold(pathRelative(process.cwd(), accountThemeSrcDirPath))}`
].join("\n")
);
}

View File

@ -0,0 +1,136 @@
import { 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 } from "../shared/getLatestsSemVersionedTag";
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";
type BuildContextLike = {
cacheDirPath: string;
fetchOptions: BuildContext["fetchOptions"];
packageJsonFilePath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>();
export async function initializeAccountTheme_singlePage(params: {
accountThemeSrcDirPath: string;
buildContext: BuildContextLike;
}) {
const { accountThemeSrcDirPath, buildContext } = params;
const OWNER = "keycloakify";
const REPO = "keycloak-account-ui";
const [semVersionedTag] = await getLatestsSemVersionedTag({
cacheDirPath: buildContext.cacheDirPath,
owner: OWNER,
repo: REPO,
count: 1,
doIgnoreReleaseCandidates: false
});
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<string, string>;
devDependencies?: Record<string, string>;
};
const zDependencies = (() => {
type TargetType = Dependencies;
const zTargetType = z.object({
dependencies: z.record(z.string()),
devDependencies: z.record(z.string()).optional()
});
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
return id<z.ZodType<TargetType>>(zTargetType);
})();
return o => zDependencies.parse(o);
})()
);
dependencies.dependencies["@keycloakify/keycloak-account-ui"] = semVersionedTag.tag;
const parsedPackageJson = (() => {
type ParsedPackageJson = {
dependencies?: Record<string, string>;
devDependencies?: Record<string, string>;
};
const zParsedPackageJson = (() => {
type TargetType = ParsedPackageJson;
const zTargetType = z.object({
dependencies: z.record(z.string()).optional(),
devDependencies: z.record(z.string()).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;
})();
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)
);
npmInstall({ packageJsonDirPath: pathDirname(buildContext.packageJsonFilePath) });
copyBoilerplate({
accountThemeType: "Single-Page",
accountThemeSrcDirPath
});
console.log(
[
chalk.green(
"The Single-Page account theme has been successfully initialized."
),
`Using Account UI of Keycloak version: ${chalk.bold(semVersionedTag.tag.split("-")[0])}`,
`Directory created: ${chalk.bold(pathRelative(process.cwd(), accountThemeSrcDirPath))}`,
`Dependencies added to your project's package.json: `,
chalk.bold(JSON.stringify(dependencies, null, 2))
].join("\n")
);
}

View File

@ -0,0 +1,12 @@
/* eslint-disable @typescript-eslint/ban-types */
import type { ExtendKcContext } from "keycloakify/account";
import type { KcEnvName, ThemeName } from "../kc.gen";
export type KcContextExtension = {
themeName: ThemeName;
properties: Record<KcEnvName, string> & {};
};
export type KcContextExtensionPerPage = {};
export type KcContext = ExtendKcContext<KcContextExtension, KcContextExtensionPerPage>;

View File

@ -0,0 +1,25 @@
import { Suspense } from "react";
import type { ClassKey } from "keycloakify/account";
import type { KcContext } from "./KcContext";
import { useI18n } from "./i18n";
import DefaultPage from "keycloakify/account/DefaultPage";
import Template from "keycloakify/account/Template";
export default function KcPage(props: { kcContext: KcContext }) {
const { kcContext } = props;
const { i18n } = useI18n({ kcContext });
return (
<Suspense>
{(() => {
switch (kcContext.pageId) {
default:
return <DefaultPage kcContext={kcContext} i18n={i18n} classes={classes} Template={Template} doUseDefaultCss={true} />;
}
})()}
</Suspense>
);
}
const classes = {} satisfies { [key in ClassKey]?: string };

View File

@ -0,0 +1,38 @@
import type { DeepPartial } from "keycloakify/tools/DeepPartial";
import type { KcContext } from "./KcContext";
import { createGetKcContextMock } from "keycloakify/account/KcContext";
import type { KcContextExtension, KcContextExtensionPerPage } from "./KcContext";
import KcPage from "./KcPage";
import { themeNames, kcEnvDefaults } from "../kc.gen";
const kcContextExtension: KcContextExtension = {
themeName: themeNames[0],
properties: {
...kcEnvDefaults
}
};
const kcContextExtensionPerPage: KcContextExtensionPerPage = {};
export const { getKcContextMock } = createGetKcContextMock({
kcContextExtension,
kcContextExtensionPerPage,
overrides: {},
overridesPerPage: {}
});
export function createKcPageStory<PageId extends KcContext["pageId"]>(params: { pageId: PageId }) {
const { pageId } = params;
function KcPageStory(props: { kcContext?: DeepPartial<Extract<KcContext, { pageId: PageId }>> }) {
const { kcContext: overrides } = props;
const kcContextMock = getKcContextMock({
pageId,
overrides
});
return <KcPage kcContext={kcContextMock} />;
}
return { KcPageStory };
}

View File

@ -0,0 +1,5 @@
import { createUseI18n } from "keycloakify/account";
export const { useI18n, ofTypeI18n } = createUseI18n({});
export type I18n = typeof ofTypeI18n;

View File

@ -0,0 +1,7 @@
import type { KcContextLike } from "@keycloakify/keycloak-account-ui";
import type { KcEnvName } from "../kc.gen";
export type KcContext = KcContextLike & {
themeType: "account";
properties: Record<KcEnvName, string>;
};

View File

@ -0,0 +1,11 @@
import { lazy } from "react";
import { KcAccountUiLoader } from "@keycloakify/keycloak-account-ui";
import type { KcContext } from "./KcContext";
const KcAccountUi = lazy(() => import("@keycloakify/keycloak-account-ui/KcAccountUi"));
export default function KcPage(props: { kcContext: KcContext }) {
const { kcContext } = props;
return <KcAccountUiLoader kcContext={kcContext} KcAccountUi={KcAccountUi} />;
}

View File

@ -0,0 +1,92 @@
import { join as pathJoin } from "path";
import { assert, type Equals } from "tsafe/assert";
import type { BuildContext } from "../shared/buildContext";
import * as fs from "fs";
import chalk from "chalk";
import { z } from "zod";
import { id } from "tsafe/id";
export type BuildContextLike = {
bundler: BuildContext["bundler"];
};
assert<BuildContext extends BuildContextLike ? true : false>();
export function updateAccountThemeImplementationInConfig(params: {
buildContext: BuildContext;
accountThemeType: "Single-Page" | "Multi-Page";
}) {
const { buildContext, accountThemeType } = params;
switch (buildContext.bundler) {
case "vite":
{
const viteConfigPath = pathJoin(
buildContext.projectDirPath,
"vite.config.ts"
);
if (!fs.existsSync(viteConfigPath)) {
console.log(
chalk.bold(
`You must manually set the accountThemeImplementation to "${accountThemeType}" in your vite config`
)
);
break;
}
const viteConfigContent = fs
.readFileSync(viteConfigPath)
.toString("utf8");
const modifiedViteConfigContent = viteConfigContent.replace(
/accountThemeImplementation\s*:\s*"none"/,
`accountThemeImplementation: "${accountThemeType}"`
);
if (modifiedViteConfigContent === viteConfigContent) {
console.log(
chalk.bold(
`You must manually set the accountThemeImplementation to "${accountThemeType}" in your vite.config.ts`
)
);
break;
}
fs.writeFileSync(viteConfigPath, modifiedViteConfigContent);
}
break;
case "webpack":
{
const parsedPackageJson = (() => {
type ParsedPackageJson = {
keycloakify: Record<string, string>;
};
const zParsedPackageJson = (() => {
type TargetType = ParsedPackageJson;
const zTargetType = z.object({
keycloakify: z.record(z.string())
});
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
return id<z.ZodType<TargetType>>(zTargetType);
})();
return zParsedPackageJson.parse(
JSON.parse(
fs
.readFileSync(buildContext.packageJsonFilePath)
.toString("utf8")
)
);
})();
parsedPackageJson.keycloakify.accountThemeImplementation =
accountThemeType;
}
break;
}
}

View File

@ -55,7 +55,7 @@ export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
environmentVariables: { name: string; default: string }[];
implementedThemeTypes: BuildContext["implementedThemeTypes"];
themeSrcDirPath: string;
bundler: { type: "vite" } | { type: "webpack" };
bundler: "vite" | "webpack";
};
assert<BuildContext extends BuildContextLike ? true : false>();
@ -121,7 +121,7 @@ export async function generateResourcesForMainTheme(params: {
);
if (fs.existsSync(dirPath)) {
assert(buildContext.bundler.type === "webpack");
assert(buildContext.bundler === "webpack");
throw new Error(
[

View File

@ -85,7 +85,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
});
run_post_build_script: {
if (buildContext.bundler.type !== "vite") {
if (buildContext.bundler !== "vite") {
break run_post_build_script;
}

View File

@ -8,7 +8,7 @@ export type BuildContextLike = {
projectBuildDirPath: string;
assetsDirPath: string;
urlPathname: string | undefined;
bundler: { type: "vite" } | { type: "webpack" };
bundler: "vite" | "webpack";
};
assert<BuildContext extends BuildContextLike ? true : false>();
@ -20,7 +20,7 @@ export function replaceImportsInJsCode(params: {
const { jsCode, buildContext } = params;
const { fixedJsCode } = (() => {
switch (buildContext.bundler.type) {
switch (buildContext.bundler) {
case "vite":
return replaceImportsInJsCode_vite({
jsCode,

View File

@ -179,6 +179,20 @@ program
}
});
program
.command({
name: "initialize-account-theme",
description: "Initialize the account theme."
})
.task({
skip,
handler: async cliCommandOptions => {
const { command } = await import("./initialize-account-heme");
await command({ cliCommandOptions });
}
});
program
.command({
name: "copy-keycloak-resources-to-public",

View File

@ -54,15 +54,8 @@ export type BuildContext = {
| { isImplemented: false }
| { isImplemented: true; type: "Single-Page" | "Multi-Page" };
};
bundler:
| {
type: "vite";
}
| {
type: "webpack";
packageJsonDirPath: string;
packageJsonScripts: Record<string, string>;
};
packageJsonFilePath: string;
bundler: "vite" | "webpack";
jarTargets: {
keycloakVersionRange: KeycloakVersionRange;
jarFileBasename: string;
@ -245,7 +238,6 @@ export function getBuildContext(params: {
projectBuildDirPath?: string;
staticDirPathInProjectBuildDirPath?: string;
publicDirPath?: string;
scripts?: Record<string, string>;
};
type ParsedPackageJson = {
@ -346,8 +338,7 @@ export function getBuildContext(params: {
z.object({
projectBuildDirPath: z.string().optional(),
staticDirPathInProjectBuildDirPath: z.string().optional(),
publicDirPath: z.string().optional(),
scripts: z.record(z.string()).optional()
publicDirPath: z.string().optional()
})
);
@ -490,19 +481,8 @@ export function getBuildContext(params: {
})();
return {
bundler: (() => {
switch (bundler) {
case "vite":
return { type: "vite" };
case "webpack":
return {
type: "webpack",
packageJsonDirPath: pathDirname(packageJsonFilePath),
packageJsonScripts: parsedPackageJson.keycloakify?.scripts ?? {}
};
}
assert<Equals<typeof bundler, never>>(false);
})(),
bundler,
packageJsonFilePath,
themeVersion: buildOptions.themeVersion ?? parsedPackageJson.version ?? "0.0.0",
themeNames,
extraThemeProperties: buildOptions.extraThemeProperties,

View File

@ -0,0 +1,180 @@
import { getLatestsSemVersionedTagFactory } from "../tools/octokit-addons/getLatestsSemVersionedTag";
import { Octokit } from "@octokit/rest";
import type { ReturnType } from "tsafe";
import type { Param0 } from "tsafe";
import { join as pathJoin, dirname as pathDirname } from "path";
import * as fs from "fs";
import { z } from "zod";
import { assert, type Equals } from "tsafe/assert";
import { id } from "tsafe/id";
import type { SemVer } from "../tools/SemVer";
import { same } from "evt/tools/inDepth/same";
type GetLatestsSemVersionedTag = ReturnType<
typeof getLatestsSemVersionedTagFactory
>["getLatestsSemVersionedTag"];
type Params = Param0<GetLatestsSemVersionedTag>;
type R = ReturnType<GetLatestsSemVersionedTag>;
let getLatestsSemVersionedTag_stateless: GetLatestsSemVersionedTag | undefined =
undefined;
const CACHE_VERSION = 1;
type Cache = {
version: typeof CACHE_VERSION;
entries: {
time: number;
params: Params;
result: R;
}[];
};
export async function getLatestsSemVersionedTag({
cacheDirPath,
...params
}: Params & { cacheDirPath: string }): Promise<R> {
const cacheFilePath = pathJoin(cacheDirPath, "latest-sem-versioned-tags.json");
const cacheLookupResult = (() => {
const getResult_currentCache = (currentCacheEntries: Cache["entries"]) => ({
hasCachedResult: false as const,
currentCache: {
version: CACHE_VERSION,
entries: currentCacheEntries
}
});
if (!fs.existsSync(cacheFilePath)) {
return getResult_currentCache([]);
}
let cache_json;
try {
cache_json = fs.readFileSync(cacheFilePath).toString("utf8");
} catch {
return getResult_currentCache([]);
}
let cache_json_parsed: unknown;
try {
cache_json_parsed = JSON.parse(cache_json);
} catch {
return getResult_currentCache([]);
}
const zSemVer = (() => {
type TargetType = SemVer;
const zTargetType = z.object({
major: z.number(),
minor: z.number(),
patch: z.number(),
rc: z.number().optional(),
parsedFrom: z.string()
});
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
return id<z.ZodType<TargetType>>(zTargetType);
})();
const zCache = (() => {
type TargetType = Cache;
const zTargetType = z.object({
version: z.literal(CACHE_VERSION),
entries: z.array(
z.object({
time: z.number(),
params: z.object({
owner: z.string(),
repo: z.string(),
count: z.number(),
doIgnoreReleaseCandidates: z.boolean()
}),
result: z.array(
z.object({
tag: z.string(),
version: zSemVer
})
)
})
)
});
assert<Equals<z.infer<typeof zTargetType>, TargetType>>();
return id<z.ZodType<TargetType>>(zTargetType);
})();
let cache: Cache;
try {
cache = zCache.parse(cache_json_parsed);
} catch {
return getResult_currentCache([]);
}
const cacheEntry = cache.entries.find(e => same(e.params, params));
if (cacheEntry === undefined) {
return getResult_currentCache(cache.entries);
}
if (Date.now() - cacheEntry.time > 3_600_000) {
return getResult_currentCache(cache.entries.filter(e => e !== cacheEntry));
}
return {
hasCachedResult: true as const,
cachedResult: cacheEntry.result
};
})();
if (cacheLookupResult.hasCachedResult) {
return cacheLookupResult.cachedResult;
}
const { currentCache } = cacheLookupResult;
getLatestsSemVersionedTag_stateless ??= (() => {
const octokit = (() => {
const githubToken = process.env.GITHUB_TOKEN;
const octokit = new Octokit(
githubToken === undefined ? undefined : { auth: githubToken }
);
return octokit;
})();
const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({
octokit
});
return getLatestsSemVersionedTag;
})();
const result = await getLatestsSemVersionedTag_stateless(params);
currentCache.entries.push({
time: Date.now(),
params,
result
});
{
const dirPath = pathDirname(cacheFilePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
fs.writeFileSync(cacheFilePath, JSON.stringify(currentCache, null, 2));
return result;
}

View File

@ -1,11 +1,6 @@
import { getLatestsSemVersionedTagFactory } from "../tools/octokit-addons/getLatestsSemVersionedTag";
import { Octokit } from "@octokit/rest";
import { getLatestsSemVersionedTag } from "./getLatestsSemVersionedTag";
import cliSelect from "cli-select";
import { SemVer } from "../tools/SemVer";
import { join as pathJoin, dirname as pathDirname } from "path";
import * as fs from "fs";
import type { ReturnType } from "tsafe";
import { id } from "tsafe/id";
export async function promptKeycloakVersion(params: {
startingFromMajor: number | undefined;
@ -14,79 +9,15 @@ export async function promptKeycloakVersion(params: {
}) {
const { startingFromMajor, excludeMajorVersions, cacheDirPath } = params;
const { getLatestsSemVersionedTag } = (() => {
const { octokit } = (() => {
const githubToken = process.env.GITHUB_TOKEN;
const octokit = new Octokit(
githubToken === undefined ? undefined : { auth: githubToken }
);
return { octokit };
})();
const { getLatestsSemVersionedTag } = getLatestsSemVersionedTagFactory({
octokit
});
return { getLatestsSemVersionedTag };
})();
const semVersionedTagByMajor = new Map<number, { tag: string; version: SemVer }>();
const semVersionedTags = await (async () => {
const cacheFilePath = pathJoin(cacheDirPath, "keycloak-versions.json");
type Cache = {
time: number;
semVersionedTags: ReturnType<typeof getLatestsSemVersionedTag>;
};
use_cache: {
if (!fs.existsSync(cacheFilePath)) {
break use_cache;
}
const cache: Cache = JSON.parse(
fs.readFileSync(cacheFilePath).toString("utf8")
);
if (Date.now() - cache.time > 3_600_000) {
fs.unlinkSync(cacheFilePath);
break use_cache;
}
return cache.semVersionedTags;
}
const semVersionedTags = await getLatestsSemVersionedTag({
count: 50,
owner: "keycloak",
repo: "keycloak"
});
{
const dirPath = pathDirname(cacheFilePath);
if (!fs.existsSync(dirPath)) {
fs.mkdirSync(dirPath, { recursive: true });
}
}
fs.writeFileSync(
cacheFilePath,
JSON.stringify(
id<Cache>({
time: Date.now(),
semVersionedTags
}),
null,
2
)
);
return semVersionedTags;
})();
const semVersionedTags = await getLatestsSemVersionedTag({
cacheDirPath,
count: 50,
owner: "keycloak",
repo: "keycloak",
doIgnoreReleaseCandidates: true
});
semVersionedTags.forEach(semVersionedTag => {
if (

View File

@ -5,12 +5,15 @@ import type { BuildContext } from "../shared/buildContext";
import chalk from "chalk";
import { sep as pathSep, join as pathJoin } from "path";
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
import * as fs from "fs";
import { dirname as pathDirname, relative as pathRelative } from "path";
export type BuildContextLike = {
projectDirPath: string;
keycloakifyBuildDirPath: string;
bundler: BuildContext["bundler"];
projectBuildDirPath: string;
packageJsonFilePath: string;
};
assert<BuildContext extends BuildContextLike ? true : false>();
@ -20,7 +23,7 @@ export async function appBuild(params: {
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
switch (buildContext.bundler.type) {
switch (buildContext.bundler) {
case "vite":
return appBuild_vite({ buildContext });
case "webpack":
@ -33,7 +36,7 @@ async function appBuild_vite(params: {
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
assert(buildContext.bundler.type === "vite");
assert(buildContext.bundler === "vite");
const dIsSuccess = new Deferred<boolean>();
@ -66,17 +69,18 @@ async function appBuild_webpack(params: {
}): Promise<{ isAppBuildSuccess: boolean }> {
const { buildContext } = params;
assert(buildContext.bundler.type === "webpack");
assert(buildContext.bundler === "webpack");
const entries = Object.entries(buildContext.bundler.packageJsonScripts).filter(
([, scriptCommand]) => scriptCommand.includes("keycloakify build")
);
const entries = Object.entries(
(JSON.parse(fs.readFileSync(buildContext.packageJsonFilePath).toString("utf8"))
.scripts ?? {}) as Record<string, string>
).filter(([, scriptCommand]) => scriptCommand.includes("keycloakify build"));
if (entries.length === 0) {
console.log(
chalk.red(
[
`You should have a script in your package.json at ${buildContext.bundler.packageJsonDirPath}`,
`You should have a script in your package.json at ${pathRelative(process.cwd(), pathDirname(buildContext.packageJsonFilePath))}`,
`that includes the 'keycloakify build' command`
].join(" ")
)
@ -123,7 +127,7 @@ async function appBuild_webpack(params: {
process.exit(-1);
}
let commandCwd = buildContext.bundler.packageJsonDirPath;
let commandCwd = pathDirname(buildContext.packageJsonFilePath);
for (const subCommand of appBuildSubCommands) {
const dIsSuccess = new Deferred<boolean>();
@ -152,7 +156,7 @@ async function appBuild_webpack(params: {
return [
pathJoin(
buildContext.bundler.packageJsonDirPath,
pathDirname(buildContext.packageJsonFilePath),
"node_modules",
".bin"
),

View File

@ -0,0 +1,63 @@
import * as fs from "fs";
import { join as pathJoin } from "path";
import * as child_process from "child_process";
import chalk from "chalk";
export function npmInstall(params: { packageJsonDirPath: string }) {
const { packageJsonDirPath } = params;
const packageManagerBinName = (() => {
const packageMangers = [
{
binName: "yarn",
lockFileBasename: "yarn.lock"
},
{
binName: "npm",
lockFileBasename: "package-lock.json"
},
{
binName: "pnpm",
lockFileBasename: "pnpm-lock.yaml"
},
{
binName: "bun",
lockFileBasename: "bun.lockdb"
}
] as const;
for (const packageManager of packageMangers) {
if (
fs.existsSync(
pathJoin(packageJsonDirPath, packageManager.lockFileBasename)
) ||
fs.existsSync(pathJoin(process.cwd(), packageManager.lockFileBasename))
) {
return packageManager.binName;
}
}
return undefined;
})();
install_dependencies: {
if (packageManagerBinName === undefined) {
break install_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...`
)
);
}
}
}

View File

@ -9,13 +9,14 @@ export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
owner: string;
repo: string;
count: number;
doIgnoreReleaseCandidates: boolean;
}): Promise<
{
tag: string;
version: SemVer;
}[]
> {
const { owner, repo, count } = params;
const { owner, repo, count, doIgnoreReleaseCandidates } = params;
const semVersionedTags: { tag: string; version: SemVer }[] = [];
@ -30,7 +31,7 @@ export function getLatestsSemVersionedTagFactory(params: { octokit: Octokit }) {
continue;
}
if (version.rc !== undefined) {
if (doIgnoreReleaseCandidates && version.rc !== undefined) {
continue;
}

View File

@ -8,5 +8,7 @@
"moduleResolution": "node",
"outDir": "../../dist/bin",
"rootDir": "."
}
},
"include": ["**/*.ts", "**/*.tsx"],
"exclude": ["initialize-account-heme/src"]
}