Improve monorepo project support, work if there only a package.json at the root (like NX)
This commit is contained in:
parent
cf6bc8666b
commit
3878e28b56
@ -10,7 +10,7 @@ fs.rmSync(".yarn_home", { recursive: true, force: true });
|
|||||||
run("yarn install");
|
run("yarn install");
|
||||||
run("yarn build");
|
run("yarn build");
|
||||||
|
|
||||||
const starterName = "keycloakify-starter";
|
const starterName = "keycloakify-starter-webpack";
|
||||||
|
|
||||||
fs.rmSync(join("..", starterName, "node_modules"), {
|
fs.rmSync(join("..", starterName, "node_modules"), {
|
||||||
recursive: true,
|
recursive: true,
|
||||||
|
@ -1,6 +1,12 @@
|
|||||||
import cheerio from "cheerio";
|
import cheerio from "cheerio";
|
||||||
import { replaceImportsInJsCode } from "../replacers/replaceImportsInJsCode";
|
import {
|
||||||
import { replaceImportsInCssCode } from "../replacers/replaceImportsInCssCode";
|
replaceImportsInJsCode,
|
||||||
|
BuildContextLike as BuildContextLike_replaceImportsInJsCode
|
||||||
|
} from "../replacers/replaceImportsInJsCode";
|
||||||
|
import {
|
||||||
|
replaceImportsInCssCode,
|
||||||
|
BuildContextLike as BuildContextLike_replaceImportsInCssCode
|
||||||
|
} from "../replacers/replaceImportsInCssCode";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
import { join as pathJoin } from "path";
|
import { join as pathJoin } from "path";
|
||||||
import type { BuildContext } from "../../shared/buildContext";
|
import type { BuildContext } from "../../shared/buildContext";
|
||||||
@ -12,12 +18,10 @@ import {
|
|||||||
} from "../../shared/constants";
|
} from "../../shared/constants";
|
||||||
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
import { getThisCodebaseRootDirPath } from "../../tools/getThisCodebaseRootDirPath";
|
||||||
|
|
||||||
export type BuildContextLike = {
|
export type BuildContextLike = BuildContextLike_replaceImportsInJsCode &
|
||||||
bundler: "vite" | "webpack";
|
BuildContextLike_replaceImportsInCssCode & {
|
||||||
themeVersion: string;
|
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
projectBuildDirPath: string;
|
themeVersion: string;
|
||||||
assetsDirPath: string;
|
|
||||||
kcContextExclusionsFtlCode: string | undefined;
|
kcContextExclusionsFtlCode: string | undefined;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -53,6 +53,7 @@ export type BuildContextLike = BuildContextLike_kcContextExclusionsFtlCode &
|
|||||||
environmentVariables: { name: string; default: string }[];
|
environmentVariables: { name: string; default: string }[];
|
||||||
recordIsImplementedByThemeType: BuildContext["recordIsImplementedByThemeType"];
|
recordIsImplementedByThemeType: BuildContext["recordIsImplementedByThemeType"];
|
||||||
themeSrcDirPath: string;
|
themeSrcDirPath: string;
|
||||||
|
bundler: { type: "vite" } | { type: "webpack" };
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
@ -113,7 +114,7 @@ export async function generateResourcesForMainTheme(params: {
|
|||||||
);
|
);
|
||||||
|
|
||||||
if (fs.existsSync(dirPath)) {
|
if (fs.existsSync(dirPath)) {
|
||||||
assert(buildContext.bundler === "webpack");
|
assert(buildContext.bundler.type === "webpack");
|
||||||
|
|
||||||
throw new Error(
|
throw new Error(
|
||||||
[
|
[
|
||||||
|
@ -85,7 +85,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
});
|
});
|
||||||
|
|
||||||
run_post_build_script: {
|
run_post_build_script: {
|
||||||
if (buildContext.bundler !== "vite") {
|
if (buildContext.bundler.type !== "vite") {
|
||||||
break run_post_build_script;
|
break run_post_build_script;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -8,7 +8,7 @@ export type BuildContextLike = {
|
|||||||
projectBuildDirPath: string;
|
projectBuildDirPath: string;
|
||||||
assetsDirPath: string;
|
assetsDirPath: string;
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
bundler: "vite" | "webpack";
|
bundler: { type: "vite" } | { type: "webpack" };
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
@ -20,7 +20,7 @@ export function replaceImportsInJsCode(params: {
|
|||||||
const { jsCode, buildContext } = params;
|
const { jsCode, buildContext } = params;
|
||||||
|
|
||||||
const { fixedJsCode } = (() => {
|
const { fixedJsCode } = (() => {
|
||||||
switch (buildContext.bundler) {
|
switch (buildContext.bundler.type) {
|
||||||
case "vite":
|
case "vite":
|
||||||
return replaceImportsInJsCode_vite({
|
return replaceImportsInJsCode_vite({
|
||||||
jsCode,
|
jsCode,
|
||||||
|
@ -1,7 +1,12 @@
|
|||||||
import { parse as urlParse } from "url";
|
import { parse as urlParse } from "url";
|
||||||
import { join as pathJoin, sep as pathSep, relative as pathRelative } from "path";
|
import {
|
||||||
|
join as pathJoin,
|
||||||
|
sep as pathSep,
|
||||||
|
relative as pathRelative,
|
||||||
|
resolve as pathResolve,
|
||||||
|
dirname as pathDirname
|
||||||
|
} from "path";
|
||||||
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
|
import { getAbsoluteAndInOsFormatPath } from "../tools/getAbsoluteAndInOsFormatPath";
|
||||||
import { getNpmWorkspaceRootDirPath } from "../tools/getNpmWorkspaceRootDirPath";
|
|
||||||
import type { CliCommandOptions } from "../main";
|
import type { CliCommandOptions } from "../main";
|
||||||
import { z } from "zod";
|
import { z } from "zod";
|
||||||
import * as fs from "fs";
|
import * as fs from "fs";
|
||||||
@ -21,9 +26,9 @@ import { type ThemeType } from "./constants";
|
|||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import { symToStr } from "tsafe/symToStr";
|
import { symToStr } from "tsafe/symToStr";
|
||||||
import chalk from "chalk";
|
import chalk from "chalk";
|
||||||
|
import { getProxyFetchOptions, type ProxyFetchOptions } from "../tools/fetchProxyOptions";
|
||||||
|
|
||||||
export type BuildContext = {
|
export type BuildContext = {
|
||||||
bundler: "vite" | "webpack";
|
|
||||||
themeVersion: string;
|
themeVersion: string;
|
||||||
themeNames: [string, ...string[]];
|
themeNames: [string, ...string[]];
|
||||||
extraThemeProperties: string[] | undefined;
|
extraThemeProperties: string[] | undefined;
|
||||||
@ -40,7 +45,7 @@ export type BuildContext = {
|
|||||||
* In this case the urlPathname will be "/my-app/" */
|
* In this case the urlPathname will be "/my-app/" */
|
||||||
urlPathname: string | undefined;
|
urlPathname: string | undefined;
|
||||||
assetsDirPath: string;
|
assetsDirPath: string;
|
||||||
npmWorkspaceRootDirPath: string;
|
fetchOptions: ProxyFetchOptions;
|
||||||
kcContextExclusionsFtlCode: string | undefined;
|
kcContextExclusionsFtlCode: string | undefined;
|
||||||
environmentVariables: { name: string; default: string }[];
|
environmentVariables: { name: string; default: string }[];
|
||||||
themeSrcDirPath: string;
|
themeSrcDirPath: string;
|
||||||
@ -49,6 +54,15 @@ export type BuildContext = {
|
|||||||
keycloakVersionRange: KeycloakVersionRange;
|
keycloakVersionRange: KeycloakVersionRange;
|
||||||
jarFileBasename: string;
|
jarFileBasename: string;
|
||||||
}[];
|
}[];
|
||||||
|
bundler:
|
||||||
|
| {
|
||||||
|
type: "vite";
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
type: "webpack";
|
||||||
|
packageJsonDirPath: string;
|
||||||
|
packageJsonScripts: Record<string, string>;
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
export type BuildOptions = {
|
export type BuildOptions = {
|
||||||
@ -174,6 +188,40 @@ export function getBuildContext(params: {
|
|||||||
return { resolvedViteConfig };
|
return { resolvedViteConfig };
|
||||||
})();
|
})();
|
||||||
|
|
||||||
|
const packageJsonFilePath = (function getPackageJSonDirPath(upCount: number): string {
|
||||||
|
const dirPath = pathResolve(
|
||||||
|
pathJoin(...[projectDirPath, ...Array(upCount).fill("..")])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(dirPath !== pathSep, "Root package.json not found");
|
||||||
|
|
||||||
|
success: {
|
||||||
|
const packageJsonFilePath = pathJoin(dirPath, "package.json");
|
||||||
|
|
||||||
|
if (!fs.existsSync(packageJsonFilePath)) {
|
||||||
|
break success;
|
||||||
|
}
|
||||||
|
|
||||||
|
const parsedPackageJson = z
|
||||||
|
.object({
|
||||||
|
dependencies: z.record(z.string()).optional(),
|
||||||
|
devDependencies: z.record(z.string()).optional()
|
||||||
|
})
|
||||||
|
.parse(JSON.parse(fs.readFileSync(packageJsonFilePath).toString("utf8")));
|
||||||
|
|
||||||
|
if (
|
||||||
|
parsedPackageJson.dependencies?.keycloakify === undefined &&
|
||||||
|
parsedPackageJson.devDependencies?.keycloakify === undefined
|
||||||
|
) {
|
||||||
|
break success;
|
||||||
|
}
|
||||||
|
|
||||||
|
return packageJsonFilePath;
|
||||||
|
}
|
||||||
|
|
||||||
|
return getPackageJSonDirPath(upCount + 1);
|
||||||
|
})(0);
|
||||||
|
|
||||||
const parsedPackageJson = (() => {
|
const parsedPackageJson = (() => {
|
||||||
type BuildOptions_packageJson = BuildOptions & {
|
type BuildOptions_packageJson = BuildOptions & {
|
||||||
projectBuildDirPath?: string;
|
projectBuildDirPath?: string;
|
||||||
@ -182,14 +230,14 @@ export function getBuildContext(params: {
|
|||||||
};
|
};
|
||||||
|
|
||||||
type ParsedPackageJson = {
|
type ParsedPackageJson = {
|
||||||
name: string;
|
name?: string;
|
||||||
version?: string;
|
version?: string;
|
||||||
homepage?: string;
|
homepage?: string;
|
||||||
keycloakify?: BuildOptions_packageJson;
|
keycloakify?: BuildOptions_packageJson;
|
||||||
};
|
};
|
||||||
|
|
||||||
const zParsedPackageJson = z.object({
|
const zParsedPackageJson = z.object({
|
||||||
name: z.string(),
|
name: z.string().optional(),
|
||||||
version: z.string().optional(),
|
version: z.string().optional(),
|
||||||
homepage: z.string().optional(),
|
homepage: z.string().optional(),
|
||||||
keycloakify: id<z.ZodType<BuildOptions_packageJson>>(
|
keycloakify: id<z.ZodType<BuildOptions_packageJson>>(
|
||||||
@ -267,10 +315,16 @@ export function getBuildContext(params: {
|
|||||||
assert<Equals<Got, Expected>>();
|
assert<Equals<Got, Expected>>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const configurationPackageJsonFilePath = (() => {
|
||||||
|
const rootPackageJsonFilePath = pathJoin(projectDirPath, "package.json");
|
||||||
|
|
||||||
|
return fs.existsSync(rootPackageJsonFilePath)
|
||||||
|
? rootPackageJsonFilePath
|
||||||
|
: packageJsonFilePath;
|
||||||
|
})();
|
||||||
|
|
||||||
return zParsedPackageJson.parse(
|
return zParsedPackageJson.parse(
|
||||||
JSON.parse(
|
JSON.parse(fs.readFileSync(configurationPackageJsonFilePath).toString("utf8"))
|
||||||
fs.readFileSync(pathJoin(projectDirPath, "package.json")).toString("utf8")
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
@ -288,7 +342,9 @@ export function getBuildContext(params: {
|
|||||||
|
|
||||||
const themeNames = ((): [string, ...string[]] => {
|
const themeNames = ((): [string, ...string[]] => {
|
||||||
if (buildOptions.themeName === undefined) {
|
if (buildOptions.themeName === undefined) {
|
||||||
return [
|
return parsedPackageJson.name === undefined
|
||||||
|
? ["keycloakify"]
|
||||||
|
: [
|
||||||
parsedPackageJson.name
|
parsedPackageJson.name
|
||||||
.replace(/^@(.*)/, "$1")
|
.replace(/^@(.*)/, "$1")
|
||||||
.split("/")
|
.split("/")
|
||||||
@ -326,15 +382,29 @@ export function getBuildContext(params: {
|
|||||||
return pathJoin(projectDirPath, resolvedViteConfig.buildDir);
|
return pathJoin(projectDirPath, resolvedViteConfig.buildDir);
|
||||||
})();
|
})();
|
||||||
|
|
||||||
const { npmWorkspaceRootDirPath } = getNpmWorkspaceRootDirPath({
|
|
||||||
projectDirPath,
|
|
||||||
dependencyExpected: "keycloakify"
|
|
||||||
});
|
|
||||||
|
|
||||||
const bundler = resolvedViteConfig !== undefined ? "vite" : "webpack";
|
const bundler = resolvedViteConfig !== undefined ? "vite" : "webpack";
|
||||||
|
|
||||||
return {
|
return {
|
||||||
bundler,
|
bundler:
|
||||||
|
resolvedViteConfig !== undefined
|
||||||
|
? { type: "vite" }
|
||||||
|
: (() => {
|
||||||
|
const { scripts } = z
|
||||||
|
.object({
|
||||||
|
scripts: z.record(z.string()).optional()
|
||||||
|
})
|
||||||
|
.parse(
|
||||||
|
JSON.parse(
|
||||||
|
fs.readFileSync(packageJsonFilePath).toString("utf8")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: "webpack",
|
||||||
|
packageJsonDirPath: pathDirname(packageJsonFilePath),
|
||||||
|
packageJsonScripts: scripts ?? {}
|
||||||
|
};
|
||||||
|
})(),
|
||||||
themeVersion: buildOptions.themeVersion ?? parsedPackageJson.version ?? "0.0.0",
|
themeVersion: buildOptions.themeVersion ?? parsedPackageJson.version ?? "0.0.0",
|
||||||
themeNames,
|
themeNames,
|
||||||
extraThemeProperties: buildOptions.extraThemeProperties,
|
extraThemeProperties: buildOptions.extraThemeProperties,
|
||||||
@ -411,7 +481,11 @@ export function getBuildContext(params: {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
return pathJoin(npmWorkspaceRootDirPath, "node_modules", ".cache");
|
return pathJoin(
|
||||||
|
pathDirname(packageJsonFilePath),
|
||||||
|
"node_modules",
|
||||||
|
".cache"
|
||||||
|
);
|
||||||
})(),
|
})(),
|
||||||
"keycloakify"
|
"keycloakify"
|
||||||
);
|
);
|
||||||
@ -460,7 +534,6 @@ export function getBuildContext(params: {
|
|||||||
|
|
||||||
return pathJoin(projectBuildDirPath, resolvedViteConfig.assetsDir);
|
return pathJoin(projectBuildDirPath, resolvedViteConfig.assetsDir);
|
||||||
})(),
|
})(),
|
||||||
npmWorkspaceRootDirPath,
|
|
||||||
kcContextExclusionsFtlCode: (() => {
|
kcContextExclusionsFtlCode: (() => {
|
||||||
if (buildOptions.kcContextExclusionsFtl === undefined) {
|
if (buildOptions.kcContextExclusionsFtl === undefined) {
|
||||||
return undefined;
|
return undefined;
|
||||||
@ -480,6 +553,33 @@ export function getBuildContext(params: {
|
|||||||
environmentVariables: buildOptions.environmentVariables ?? [],
|
environmentVariables: buildOptions.environmentVariables ?? [],
|
||||||
recordIsImplementedByThemeType,
|
recordIsImplementedByThemeType,
|
||||||
themeSrcDirPath,
|
themeSrcDirPath,
|
||||||
|
fetchOptions: getProxyFetchOptions({
|
||||||
|
npmConfigGetCwd: (function callee(upCount: number): string {
|
||||||
|
const dirPath = pathResolve(
|
||||||
|
pathJoin(...[projectDirPath, ...Array(upCount).fill("..")])
|
||||||
|
);
|
||||||
|
|
||||||
|
assert(
|
||||||
|
dirPath !== pathSep,
|
||||||
|
"Couldn't find a place to run 'npm config get'"
|
||||||
|
);
|
||||||
|
|
||||||
|
try {
|
||||||
|
child_process.execSync("npm config get", {
|
||||||
|
cwd: dirPath,
|
||||||
|
stdio: "ignore"
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (String(error).includes("ENOWORKSPACES")) {
|
||||||
|
return callee(upCount + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
|
||||||
|
return dirPath;
|
||||||
|
})(0)
|
||||||
|
}),
|
||||||
jarTargets: (() => {
|
jarTargets: (() => {
|
||||||
const getDefaultJarFileBasename = (range: string) =>
|
const getDefaultJarFileBasename = (range: string) =>
|
||||||
`keycloak-theme-for-kc-${range}.jar`;
|
`keycloak-theme-for-kc-${range}.jar`;
|
||||||
|
@ -37,10 +37,7 @@ export async function copyKeycloakResourcesToPublic(params: {
|
|||||||
buildContext: {
|
buildContext: {
|
||||||
loginThemeResourcesFromKeycloakVersion: readThisNpmPackageVersion(),
|
loginThemeResourcesFromKeycloakVersion: readThisNpmPackageVersion(),
|
||||||
cacheDirPath: pathRelative(destDirPath, buildContext.cacheDirPath),
|
cacheDirPath: pathRelative(destDirPath, buildContext.cacheDirPath),
|
||||||
npmWorkspaceRootDirPath: pathRelative(
|
fetchOptions: buildContext.fetchOptions
|
||||||
destDirPath,
|
|
||||||
buildContext.npmWorkspaceRootDirPath
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
null,
|
null,
|
||||||
|
@ -6,7 +6,7 @@ import { downloadAndExtractArchive } from "../tools/downloadAndExtractArchive";
|
|||||||
|
|
||||||
export type BuildContextLike = {
|
export type BuildContextLike = {
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
npmWorkspaceRootDirPath: string;
|
fetchOptions: BuildContext["fetchOptions"];
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
@ -23,7 +23,7 @@ export async function downloadKeycloakDefaultTheme(params: {
|
|||||||
const { extractedDirPath } = await downloadAndExtractArchive({
|
const { extractedDirPath } = await downloadAndExtractArchive({
|
||||||
url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`,
|
url: `https://repo1.maven.org/maven2/org/keycloak/keycloak-themes/${keycloakVersion}/keycloak-themes-${keycloakVersion}.jar`,
|
||||||
cacheDirPath: buildContext.cacheDirPath,
|
cacheDirPath: buildContext.cacheDirPath,
|
||||||
npmWorkspaceRootDirPath: buildContext.npmWorkspaceRootDirPath,
|
fetchOptions: buildContext.fetchOptions,
|
||||||
uniqueIdOfOnOnArchiveFile: "downloadKeycloakDefaultTheme",
|
uniqueIdOfOnOnArchiveFile: "downloadKeycloakDefaultTheme",
|
||||||
onArchiveFile: async params => {
|
onArchiveFile: async params => {
|
||||||
const fileRelativePath = pathRelative("theme", params.fileRelativePath);
|
const fileRelativePath = pathRelative("theme", params.fileRelativePath);
|
||||||
|
@ -1,16 +1,14 @@
|
|||||||
import * as child_process from "child_process";
|
import * as child_process from "child_process";
|
||||||
import { Deferred } from "evt/tools/Deferred";
|
import { Deferred } from "evt/tools/Deferred";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { is } from "tsafe/is";
|
|
||||||
import type { BuildContext } from "../shared/buildContext";
|
import type { BuildContext } from "../shared/buildContext";
|
||||||
import * as fs from "fs";
|
import chalk from "chalk";
|
||||||
import { join as pathJoin } from "path";
|
import { sep as pathSep, join as pathJoin } from "path";
|
||||||
|
|
||||||
export type BuildContextLike = {
|
export type BuildContextLike = {
|
||||||
projectDirPath: string;
|
projectDirPath: string;
|
||||||
keycloakifyBuildDirPath: string;
|
keycloakifyBuildDirPath: string;
|
||||||
bundler: "vite" | "webpack";
|
bundler: BuildContext["bundler"];
|
||||||
npmWorkspaceRootDirPath: string;
|
|
||||||
projectBuildDirPath: string;
|
projectBuildDirPath: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -21,95 +19,27 @@ export async function appBuild(params: {
|
|||||||
}): Promise<{ isAppBuildSuccess: boolean }> {
|
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||||
const { buildContext } = params;
|
const { buildContext } = params;
|
||||||
|
|
||||||
const { bundler } = buildContext;
|
switch (buildContext.bundler.type) {
|
||||||
|
|
||||||
const { command, args, cwd } = (() => {
|
|
||||||
switch (bundler) {
|
|
||||||
case "vite":
|
case "vite":
|
||||||
return {
|
return appBuild_vite({ buildContext });
|
||||||
command: "npx",
|
case "webpack":
|
||||||
args: ["vite", "build"],
|
return appBuild_webpack({ buildContext });
|
||||||
cwd: buildContext.projectDirPath
|
|
||||||
};
|
|
||||||
case "webpack": {
|
|
||||||
for (const dirPath of [
|
|
||||||
buildContext.projectDirPath,
|
|
||||||
buildContext.npmWorkspaceRootDirPath
|
|
||||||
]) {
|
|
||||||
try {
|
|
||||||
const parsedPackageJson = JSON.parse(
|
|
||||||
fs
|
|
||||||
.readFileSync(pathJoin(dirPath, "package.json"))
|
|
||||||
.toString("utf8")
|
|
||||||
);
|
|
||||||
|
|
||||||
const [scriptName] =
|
|
||||||
Object.entries(parsedPackageJson.scripts).find(
|
|
||||||
([, scriptValue]) => {
|
|
||||||
assert(is<string>(scriptValue));
|
|
||||||
if (
|
|
||||||
scriptValue.includes("webpack") &&
|
|
||||||
scriptValue.includes("--mode production")
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
scriptValue.includes("react-scripts") &&
|
|
||||||
scriptValue.includes("build")
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
scriptValue.includes("react-app-rewired") &&
|
|
||||||
scriptValue.includes("build")
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
scriptValue.includes("craco") &&
|
|
||||||
scriptValue.includes("build")
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (
|
|
||||||
scriptValue.includes("ng") &&
|
|
||||||
scriptValue.includes("build")
|
|
||||||
) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
) ?? [];
|
|
||||||
|
|
||||||
if (scriptName === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
command: "npm",
|
|
||||||
args: ["run", scriptName],
|
|
||||||
cwd: dirPath
|
|
||||||
};
|
|
||||||
} catch {
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
throw new Error(
|
async function appBuild_vite(params: {
|
||||||
"Keycloakify was unable to determine which script is responsible for building the app."
|
buildContext: BuildContextLike;
|
||||||
);
|
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||||
}
|
const { buildContext } = params;
|
||||||
}
|
|
||||||
})();
|
assert(buildContext.bundler.type === "vite");
|
||||||
|
|
||||||
const dResult = new Deferred<{ isSuccess: boolean }>();
|
const dResult = new Deferred<{ isSuccess: boolean }>();
|
||||||
|
|
||||||
const child = child_process.spawn(command, args, { cwd, shell: true });
|
const child = child_process.spawn("npx", ["vite", "build"], {
|
||||||
|
cwd: buildContext.projectDirPath,
|
||||||
|
shell: true
|
||||||
|
});
|
||||||
|
|
||||||
child.stdout.on("data", data => {
|
child.stdout.on("data", data => {
|
||||||
if (data.toString("utf8").includes("gzip:")) {
|
if (data.toString("utf8").includes("gzip:")) {
|
||||||
@ -127,3 +57,113 @@ export async function appBuild(params: {
|
|||||||
|
|
||||||
return { isAppBuildSuccess: isSuccess };
|
return { isAppBuildSuccess: isSuccess };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async function appBuild_webpack(params: {
|
||||||
|
buildContext: BuildContextLike;
|
||||||
|
}): Promise<{ isAppBuildSuccess: boolean }> {
|
||||||
|
const { buildContext } = params;
|
||||||
|
|
||||||
|
assert(buildContext.bundler.type === "webpack");
|
||||||
|
|
||||||
|
const entries = Object.entries(buildContext.bundler.packageJsonScripts).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}`,
|
||||||
|
`that includes the 'keycloakify build' command`
|
||||||
|
].join(" ")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const entry =
|
||||||
|
entries.length === 1
|
||||||
|
? entries[0]
|
||||||
|
: entries.find(([scriptName]) => scriptName === "build-keycloak-theme");
|
||||||
|
|
||||||
|
if (entry === undefined) {
|
||||||
|
console.log(
|
||||||
|
chalk.red(
|
||||||
|
"There's multiple candidate script for building your app, name one 'build-keycloak-theme'"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
const [scriptName, scriptCommand] = entry;
|
||||||
|
|
||||||
|
const { appBuildSubCommands } = (() => {
|
||||||
|
const appBuildSubCommands: string[] = [];
|
||||||
|
|
||||||
|
for (const subCmd of scriptCommand.split("&&").map(s => s.trim())) {
|
||||||
|
if (subCmd.includes("keycloakify build")) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
appBuildSubCommands.push(subCmd);
|
||||||
|
}
|
||||||
|
|
||||||
|
return { appBuildSubCommands };
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (appBuildSubCommands.length === 0) {
|
||||||
|
console.log(
|
||||||
|
chalk.red(
|
||||||
|
`Your ${scriptName} script should look like "... && keycloakify build ..."`
|
||||||
|
)
|
||||||
|
);
|
||||||
|
process.exit(-1);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (const subCommand of appBuildSubCommands) {
|
||||||
|
const dIsSuccess = new Deferred<boolean>();
|
||||||
|
|
||||||
|
child_process.exec(
|
||||||
|
subCommand,
|
||||||
|
{
|
||||||
|
cwd: buildContext.bundler.packageJsonDirPath,
|
||||||
|
env: {
|
||||||
|
...process.env,
|
||||||
|
PATH: (() => {
|
||||||
|
const separator = pathSep === "/" ? ":" : ";";
|
||||||
|
|
||||||
|
return [
|
||||||
|
pathJoin(
|
||||||
|
buildContext.bundler.packageJsonDirPath,
|
||||||
|
"node_modules",
|
||||||
|
".bin"
|
||||||
|
),
|
||||||
|
...(process.env.PATH ?? "").split(separator)
|
||||||
|
].join(separator);
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
},
|
||||||
|
(error, stdout, stderr) => {
|
||||||
|
if (error) {
|
||||||
|
dIsSuccess.resolve(false);
|
||||||
|
|
||||||
|
console.log(chalk.red(`Error running: '${subCommand}'`));
|
||||||
|
console.log(stdout);
|
||||||
|
console.log(stderr);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
dIsSuccess.resolve(true);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
const isSuccess = await dIsSuccess.pr;
|
||||||
|
|
||||||
|
if (!isSuccess) {
|
||||||
|
return { isAppBuildSuccess: false };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return { isAppBuildSuccess: true };
|
||||||
|
}
|
||||||
|
@ -7,7 +7,6 @@ import type { BuildContext } from "../shared/buildContext";
|
|||||||
export type BuildContextLike = {
|
export type BuildContextLike = {
|
||||||
projectDirPath: string;
|
projectDirPath: string;
|
||||||
keycloakifyBuildDirPath: string;
|
keycloakifyBuildDirPath: string;
|
||||||
bundler: "vite" | "webpack";
|
|
||||||
};
|
};
|
||||||
|
|
||||||
assert<BuildContext extends BuildContextLike ? true : false>();
|
assert<BuildContext extends BuildContextLike ? true : false>();
|
||||||
|
@ -121,7 +121,7 @@ export async function command(params: { cliCommandOptions: CliCommandOptions })
|
|||||||
if (!isAppBuildSuccess) {
|
if (!isAppBuildSuccess) {
|
||||||
console.log(
|
console.log(
|
||||||
chalk.red(
|
chalk.red(
|
||||||
`App build failed, exiting. Try running 'npm run build' and see what's wrong.`
|
`App build failed, exiting. Try building your app (e.g 'npm run build') and see what's wrong.`
|
||||||
)
|
)
|
||||||
);
|
);
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
|
@ -1,12 +1,12 @@
|
|||||||
import fetch from "make-fetch-happen";
|
import fetch, { type FetchOptions } from "make-fetch-happen";
|
||||||
import { mkdir, unlink, writeFile, readdir, readFile } from "fs/promises";
|
import { mkdir, unlink, writeFile, readdir, readFile } from "fs/promises";
|
||||||
import { dirname as pathDirname, join as pathJoin } from "path";
|
import { dirname as pathDirname, join as pathJoin } from "path";
|
||||||
import { assert } from "tsafe/assert";
|
import { assert } from "tsafe/assert";
|
||||||
import { extractArchive } from "../extractArchive";
|
import { extractArchive } from "./extractArchive";
|
||||||
import { existsAsync } from "../fs.existsAsync";
|
import { existsAsync } from "./fs.existsAsync";
|
||||||
import { getProxyFetchOptions } from "./fetchProxyOptions";
|
|
||||||
import * as crypto from "crypto";
|
import * as crypto from "crypto";
|
||||||
import { rm } from "../fs.rm";
|
import { rm } from "./fs.rm";
|
||||||
|
|
||||||
export async function downloadAndExtractArchive(params: {
|
export async function downloadAndExtractArchive(params: {
|
||||||
url: string;
|
url: string;
|
||||||
@ -20,15 +20,10 @@ export async function downloadAndExtractArchive(params: {
|
|||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
}) => Promise<void>;
|
}) => Promise<void>;
|
||||||
cacheDirPath: string;
|
cacheDirPath: string;
|
||||||
npmWorkspaceRootDirPath: string;
|
fetchOptions: FetchOptions | undefined;
|
||||||
}): Promise<{ extractedDirPath: string }> {
|
}): Promise<{ extractedDirPath: string }> {
|
||||||
const {
|
const { url, uniqueIdOfOnOnArchiveFile, onArchiveFile, cacheDirPath, fetchOptions } =
|
||||||
url,
|
params;
|
||||||
uniqueIdOfOnOnArchiveFile,
|
|
||||||
onArchiveFile,
|
|
||||||
cacheDirPath,
|
|
||||||
npmWorkspaceRootDirPath
|
|
||||||
} = params;
|
|
||||||
|
|
||||||
const archiveFileBasename = url.split("?")[0].split("/").reverse()[0];
|
const archiveFileBasename = url.split("?")[0].split("/").reverse()[0];
|
||||||
|
|
||||||
@ -55,10 +50,7 @@ export async function downloadAndExtractArchive(params: {
|
|||||||
|
|
||||||
await mkdir(pathDirname(archiveFilePath), { recursive: true });
|
await mkdir(pathDirname(archiveFilePath), { recursive: true });
|
||||||
|
|
||||||
const response = await fetch(
|
const response = await fetch(url, fetchOptions);
|
||||||
url,
|
|
||||||
await getProxyFetchOptions({ npmWorkspaceRootDirPath })
|
|
||||||
);
|
|
||||||
|
|
||||||
response.body?.setMaxListeners(Number.MAX_VALUE);
|
response.body?.setMaxListeners(Number.MAX_VALUE);
|
||||||
assert(typeof response.body !== "undefined" && response.body != null);
|
assert(typeof response.body !== "undefined" && response.body != null);
|
@ -1 +0,0 @@
|
|||||||
export * from "./downloadAndExtractArchive";
|
|
@ -1,61 +1,40 @@
|
|||||||
import { exec as execCallback } from "child_process";
|
|
||||||
import { readFile } from "fs/promises";
|
|
||||||
import { type FetchOptions } from "make-fetch-happen";
|
import { type FetchOptions } from "make-fetch-happen";
|
||||||
import { promisify } from "util";
|
import * as child_process from "child_process";
|
||||||
|
import * as fs from "fs";
|
||||||
function ensureArray<T>(arg0: T | T[]) {
|
|
||||||
return Array.isArray(arg0) ? arg0 : typeof arg0 === "undefined" ? [] : [arg0];
|
|
||||||
}
|
|
||||||
|
|
||||||
function ensureSingleOrNone<T>(arg0: T | T[]) {
|
|
||||||
if (!Array.isArray(arg0)) return arg0;
|
|
||||||
if (arg0.length === 0) return undefined;
|
|
||||||
if (arg0.length === 1) return arg0[0];
|
|
||||||
throw new Error(
|
|
||||||
"Illegal configuration, expected a single value but found multiple: " +
|
|
||||||
arg0.map(String).join(", ")
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
type NPMConfig = Record<string, string | string[]>;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Get npm configuration as map
|
|
||||||
*/
|
|
||||||
async function getNmpConfig(params: { npmWorkspaceRootDirPath: string }) {
|
|
||||||
const { npmWorkspaceRootDirPath } = params;
|
|
||||||
|
|
||||||
const exec = promisify(execCallback);
|
|
||||||
|
|
||||||
const stdout = await exec("npm config get", {
|
|
||||||
encoding: "utf8",
|
|
||||||
cwd: npmWorkspaceRootDirPath
|
|
||||||
}).then(({ stdout }) => stdout);
|
|
||||||
|
|
||||||
const npmConfigReducer = (cfg: NPMConfig, [key, value]: [string, string]) =>
|
|
||||||
key in cfg
|
|
||||||
? { ...cfg, [key]: [...ensureArray(cfg[key]), value] }
|
|
||||||
: { ...cfg, [key]: value };
|
|
||||||
|
|
||||||
return stdout
|
|
||||||
.split("\n")
|
|
||||||
.filter(line => !line.startsWith(";"))
|
|
||||||
.map(line => line.trim())
|
|
||||||
.map(line => line.split("=", 2) as [string, string])
|
|
||||||
.reduce(npmConfigReducer, {} as NPMConfig);
|
|
||||||
}
|
|
||||||
|
|
||||||
export type ProxyFetchOptions = Pick<
|
export type ProxyFetchOptions = Pick<
|
||||||
FetchOptions,
|
FetchOptions,
|
||||||
"proxy" | "noProxy" | "strictSSL" | "cert" | "ca"
|
"proxy" | "noProxy" | "strictSSL" | "cert" | "ca"
|
||||||
>;
|
>;
|
||||||
|
|
||||||
export async function getProxyFetchOptions(params: {
|
export function getProxyFetchOptions(params: {
|
||||||
npmWorkspaceRootDirPath: string;
|
npmConfigGetCwd: string;
|
||||||
}): Promise<ProxyFetchOptions> {
|
}): ProxyFetchOptions {
|
||||||
const { npmWorkspaceRootDirPath } = params;
|
const { npmConfigGetCwd } = params;
|
||||||
|
|
||||||
const cfg = await getNmpConfig({ npmWorkspaceRootDirPath });
|
const cfg = (() => {
|
||||||
|
const output = child_process
|
||||||
|
.execSync("npm config get", {
|
||||||
|
cwd: npmConfigGetCwd
|
||||||
|
})
|
||||||
|
.toString("utf8");
|
||||||
|
|
||||||
|
return output
|
||||||
|
.split("\n")
|
||||||
|
.filter(line => !line.startsWith(";"))
|
||||||
|
.map(line => line.trim())
|
||||||
|
.map(line => line.split("=", 2) as [string, string])
|
||||||
|
.reduce(
|
||||||
|
(
|
||||||
|
cfg: Record<string, string | string[]>,
|
||||||
|
[key, value]: [string, string]
|
||||||
|
) =>
|
||||||
|
key in cfg
|
||||||
|
? { ...cfg, [key]: [...ensureArray(cfg[key]), value] }
|
||||||
|
: { ...cfg, [key]: value },
|
||||||
|
{}
|
||||||
|
);
|
||||||
|
})();
|
||||||
|
|
||||||
const proxy = ensureSingleOrNone(cfg["https-proxy"] ?? cfg["proxy"]);
|
const proxy = ensureSingleOrNone(cfg["https-proxy"] ?? cfg["proxy"]);
|
||||||
const noProxy = cfg["noproxy"] ?? cfg["no-proxy"];
|
const noProxy = cfg["noproxy"] ?? cfg["no-proxy"];
|
||||||
@ -71,17 +50,16 @@ export async function getProxyFetchOptions(params: {
|
|||||||
|
|
||||||
if (typeof cafile !== "undefined" && cafile !== "null") {
|
if (typeof cafile !== "undefined" && cafile !== "null") {
|
||||||
ca.push(
|
ca.push(
|
||||||
...(await (async () => {
|
...(() => {
|
||||||
function chunks<T>(arr: T[], size: number = 2) {
|
const cafileContent = fs.readFileSync(cafile).toString("utf8");
|
||||||
return arr
|
|
||||||
.map((_, i) => i % size == 0 && arr.slice(i, i + size))
|
|
||||||
.filter(Boolean) as T[][];
|
|
||||||
}
|
|
||||||
|
|
||||||
const cafileContent = await readFile(cafile, "utf-8");
|
|
||||||
|
|
||||||
const newLinePlaceholder = "NEW_LINE_PLACEHOLDER_xIsPsK23svt";
|
const newLinePlaceholder = "NEW_LINE_PLACEHOLDER_xIsPsK23svt";
|
||||||
|
|
||||||
|
const chunks = <T>(arr: T[], size: number = 2) =>
|
||||||
|
arr
|
||||||
|
.map((_, i) => i % size == 0 && arr.slice(i, i + size))
|
||||||
|
.filter(Boolean) as T[][];
|
||||||
|
|
||||||
return chunks(cafileContent.split(/(-----END CERTIFICATE-----)/), 2).map(
|
return chunks(cafileContent.split(/(-----END CERTIFICATE-----)/), 2).map(
|
||||||
ca =>
|
ca =>
|
||||||
ca
|
ca
|
||||||
@ -90,7 +68,7 @@ export async function getProxyFetchOptions(params: {
|
|||||||
.replace(new RegExp(`^${newLinePlaceholder}`), "")
|
.replace(new RegExp(`^${newLinePlaceholder}`), "")
|
||||||
.replace(new RegExp(newLinePlaceholder, "g"), "\\n")
|
.replace(new RegExp(newLinePlaceholder, "g"), "\\n")
|
||||||
);
|
);
|
||||||
})())
|
})()
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -102,3 +80,17 @@ export async function getProxyFetchOptions(params: {
|
|||||||
ca: ca.length === 0 ? undefined : ca
|
ca: ca.length === 0 ? undefined : ca
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ensureArray<T>(arg0: T | T[]) {
|
||||||
|
return Array.isArray(arg0) ? arg0 : typeof arg0 === "undefined" ? [] : [arg0];
|
||||||
|
}
|
||||||
|
|
||||||
|
function ensureSingleOrNone<T>(arg0: T | T[]) {
|
||||||
|
if (!Array.isArray(arg0)) return arg0;
|
||||||
|
if (arg0.length === 0) return undefined;
|
||||||
|
if (arg0.length === 1) return arg0[0];
|
||||||
|
throw new Error(
|
||||||
|
"Illegal configuration, expected a single value but found multiple: " +
|
||||||
|
arg0.map(String).join(", ")
|
||||||
|
);
|
||||||
|
}
|
@ -1,73 +0,0 @@
|
|||||||
import * as child_process from "child_process";
|
|
||||||
import { join as pathJoin, resolve as pathResolve, sep as pathSep } from "path";
|
|
||||||
import { assert } from "tsafe/assert";
|
|
||||||
import * as fs from "fs";
|
|
||||||
|
|
||||||
export function getNpmWorkspaceRootDirPath(params: {
|
|
||||||
projectDirPath: string;
|
|
||||||
dependencyExpected: string;
|
|
||||||
}) {
|
|
||||||
const { projectDirPath, dependencyExpected } = params;
|
|
||||||
|
|
||||||
const npmWorkspaceRootDirPath = (function callee(depth: number): string {
|
|
||||||
const cwd = pathResolve(
|
|
||||||
pathJoin(...[projectDirPath, ...Array(depth).fill("..")])
|
|
||||||
);
|
|
||||||
|
|
||||||
assert(cwd !== pathSep, "NPM workspace not found");
|
|
||||||
|
|
||||||
try {
|
|
||||||
child_process.execSync("npm config get", {
|
|
||||||
cwd,
|
|
||||||
stdio: "ignore"
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
if (String(error).includes("ENOWORKSPACES")) {
|
|
||||||
return callee(depth + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
throw error;
|
|
||||||
}
|
|
||||||
|
|
||||||
const packageJsonFilePath = pathJoin(cwd, "package.json");
|
|
||||||
|
|
||||||
if (!fs.existsSync(packageJsonFilePath)) {
|
|
||||||
return callee(depth + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(fs.existsSync(packageJsonFilePath));
|
|
||||||
|
|
||||||
const parsedPackageJson = JSON.parse(
|
|
||||||
fs.readFileSync(packageJsonFilePath).toString("utf8")
|
|
||||||
);
|
|
||||||
|
|
||||||
let isExpectedDependencyFound = false;
|
|
||||||
|
|
||||||
for (const dependenciesOrDevDependencies of [
|
|
||||||
"dependencies",
|
|
||||||
"devDependencies"
|
|
||||||
] as const) {
|
|
||||||
const dependencies = parsedPackageJson[dependenciesOrDevDependencies];
|
|
||||||
|
|
||||||
if (dependencies === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
assert(dependencies instanceof Object);
|
|
||||||
|
|
||||||
if (dependencies[dependencyExpected] === undefined) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
isExpectedDependencyFound = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!isExpectedDependencyFound && parsedPackageJson.name !== dependencyExpected) {
|
|
||||||
return callee(depth + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return cwd;
|
|
||||||
})(0);
|
|
||||||
|
|
||||||
return { npmWorkspaceRootDirPath };
|
|
||||||
}
|
|
Loading…
x
Reference in New Issue
Block a user