![gcp-cherry-pick-bot[bot]](/assets/img/avatar_default.png)
providers/rac: move to open source (#13015) * move RAC to open source * move web out of enterprise * remove enterprise license requirements from RAC * format --------- Signed-off-by: Jens Langhammer <jens@goauthentik.io> Co-authored-by: Simonyi Gergő <28359278+gergosimonyi@users.noreply.github.com> Co-authored-by: Jens Langhammer <jens@goauthentik.io>
193 lines
6.4 KiB
JavaScript
193 lines
6.4 KiB
JavaScript
import { execFileSync } from "child_process";
|
|
import * as chokidar from "chokidar";
|
|
import esbuild from "esbuild";
|
|
import fs from "fs";
|
|
import { globSync } from "glob";
|
|
import path from "path";
|
|
import { cwd } from "process";
|
|
import process from "process";
|
|
import { fileURLToPath } from "url";
|
|
|
|
const __dirname = fileURLToPath(new URL(".", import.meta.url));
|
|
|
|
let authentikProjectRoot = __dirname + "../";
|
|
try {
|
|
// Use the package.json file in the root folder, as it has the current version information.
|
|
authentikProjectRoot = execFileSync("git", ["rev-parse", "--show-toplevel"], {
|
|
encoding: "utf8",
|
|
}).replace("\n", "");
|
|
} catch (_exc) {
|
|
// We probably don't have a .git folder, which could happen in container builds
|
|
}
|
|
const rootPackage = JSON.parse(fs.readFileSync(path.join(authentikProjectRoot, "./package.json")));
|
|
|
|
const isProdBuild = process.env.NODE_ENV === "production";
|
|
|
|
const apiBasePath = process.env.AK_API_BASE_PATH || "";
|
|
|
|
const envGitHashKey = "GIT_BUILD_HASH";
|
|
|
|
const definitions = {
|
|
"process.env.NODE_ENV": JSON.stringify(isProdBuild ? "production" : "development"),
|
|
"process.env.CWD": JSON.stringify(cwd()),
|
|
"process.env.AK_API_BASE_PATH": JSON.stringify(apiBasePath),
|
|
};
|
|
|
|
// All is magic is just to make sure the assets are copied into the right places. This is a very
|
|
// stripped down version of what the rollup-copy-plugin does, without any of the features we don't
|
|
// use, and using globSync instead of globby since we already had globSync lying around thanks to
|
|
// Typescript. If there's a third argument in an array entry, it's used to replace the internal path
|
|
// before concatenating it all together as the destination target.
|
|
|
|
const otherFiles = [
|
|
["node_modules/@patternfly/patternfly/patternfly.min.css", "."],
|
|
["node_modules/@patternfly/patternfly/assets/**", ".", "node_modules/@patternfly/patternfly/"],
|
|
["src/custom.css", "."],
|
|
["src/common/styles/**", "."],
|
|
["src/assets/images/**", "./assets/images"],
|
|
["./icons/*", "./assets/icons"],
|
|
];
|
|
|
|
const isFile = (filePath) => fs.statSync(filePath).isFile();
|
|
function nameCopyTarget(src, dest, strip) {
|
|
const target = path.join(dest, strip ? src.replace(strip, "") : path.parse(src).base);
|
|
return [src, target];
|
|
}
|
|
|
|
for (const [source, rawdest, strip] of otherFiles) {
|
|
const matchedPaths = globSync(source);
|
|
const dest = path.join("dist", rawdest);
|
|
const copyTargets = matchedPaths.map((path) => nameCopyTarget(path, dest, strip));
|
|
for (const [src, dest] of copyTargets) {
|
|
if (isFile(src)) {
|
|
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
fs.copyFileSync(src, dest);
|
|
}
|
|
}
|
|
}
|
|
|
|
// This starts the definitions used for esbuild: Our targets, our arguments, the function for
|
|
// running a build, and three options for building: watching, building, and building the proxy.
|
|
// Ordered by largest to smallest interface to build even faster
|
|
const interfaces = [
|
|
["admin/AdminInterface/AdminInterface.ts", "admin"],
|
|
["user/UserInterface.ts", "user"],
|
|
["flow/FlowInterface.ts", "flow"],
|
|
["standalone/api-browser/index.ts", "standalone/api-browser"],
|
|
["rac/index.ts", "rac"],
|
|
["standalone/loading/index.ts", "standalone/loading"],
|
|
["polyfill/poly.ts", "."],
|
|
];
|
|
|
|
const baseArgs = {
|
|
bundle: true,
|
|
write: true,
|
|
sourcemap: true,
|
|
minify: isProdBuild,
|
|
splitting: true,
|
|
treeShaking: true,
|
|
external: ["*.woff", "*.woff2"],
|
|
tsconfig: "./tsconfig.json",
|
|
loader: { ".css": "text", ".md": "text" },
|
|
define: definitions,
|
|
format: "esm",
|
|
logOverride: {
|
|
/**
|
|
* HACK: Silences issue originating in ESBuild.
|
|
*
|
|
* @see {@link https://github.com/evanw/esbuild/blob/b914dd30294346aa15fcc04278f4b4b51b8b43b5/internal/logger/msg_ids.go#L211 ESBuild source}
|
|
* @expires 2025-08-11
|
|
*/
|
|
"invalid-source-url": "silent",
|
|
},
|
|
};
|
|
|
|
function getVersion() {
|
|
let version = rootPackage.version;
|
|
if (process.env[envGitHashKey]) {
|
|
version = `${version}+${process.env[envGitHashKey]}`;
|
|
}
|
|
return version;
|
|
}
|
|
|
|
async function buildOneSource(source, dest) {
|
|
const DIST = path.join(__dirname, "./dist", dest);
|
|
console.log(`[${new Date(Date.now()).toISOString()}] Starting build for target ${source}`);
|
|
|
|
try {
|
|
const start = Date.now();
|
|
await esbuild.build({
|
|
...baseArgs,
|
|
entryPoints: [`./src/${source}`],
|
|
entryNames: `[dir]/[name]-${getVersion()}`,
|
|
outdir: DIST,
|
|
});
|
|
const end = Date.now();
|
|
console.log(
|
|
`[${new Date(end).toISOString()}] Finished build for target ${source} in ${
|
|
Date.now() - start
|
|
}ms`,
|
|
);
|
|
return 0;
|
|
} catch (exc) {
|
|
console.error(`[${new Date(Date.now()).toISOString()}] Failed to build ${source}: ${exc}`);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
async function buildAuthentik(interfaces) {
|
|
const code = await Promise.allSettled(
|
|
interfaces.map(([source, dest]) => buildOneSource(source, dest)),
|
|
);
|
|
const finalCode = code.reduce((a, res) => a + res.value, 0);
|
|
if (finalCode > 0) {
|
|
return 1;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
let timeoutId = null;
|
|
function debouncedBuild() {
|
|
if (timeoutId !== null) {
|
|
clearTimeout(timeoutId);
|
|
}
|
|
timeoutId = setTimeout(() => {
|
|
console.clear();
|
|
buildAuthentik(interfaces);
|
|
}, 250);
|
|
}
|
|
|
|
if (process.argv.length > 2 && (process.argv[2] === "-h" || process.argv[2] === "--help")) {
|
|
console.log(`Build the authentikUI
|
|
|
|
options:
|
|
-w, --watch: Build all ${interfaces.length} interfaces
|
|
-p, --proxy: Build only the polyfills and the loading application
|
|
-h, --help: This help message
|
|
`);
|
|
process.exit(0);
|
|
}
|
|
|
|
if (process.argv.length > 2 && (process.argv[2] === "-w" || process.argv[2] === "--watch")) {
|
|
console.log("Watching ./src for changes");
|
|
chokidar.watch("./src").on("all", (event, path) => {
|
|
if (!["add", "change", "unlink"].includes(event)) {
|
|
return;
|
|
}
|
|
if (!/(\.css|\.ts|\.js)$/.test(path)) {
|
|
return;
|
|
}
|
|
debouncedBuild();
|
|
});
|
|
} else if (process.argv.length > 2 && (process.argv[2] === "-p" || process.argv[2] === "--proxy")) {
|
|
// There's no watch-for-proxy, sorry.
|
|
process.exit(
|
|
await buildAuthentik(
|
|
interfaces.filter(([_, dest]) => ["standalone/loading", "."].includes(dest)),
|
|
),
|
|
);
|
|
} else {
|
|
// And the fallback: just build it.
|
|
process.exit(await buildAuthentik(interfaces));
|
|
}
|