mirror of
https://github.com/fosrl/pangolin.git
synced 2025-10-30 14:17:30 +00:00
280 lines
10 KiB
JavaScript
280 lines
10 KiB
JavaScript
import esbuild from "esbuild";
|
|
import yargs from "yargs";
|
|
import { hideBin } from "yargs/helpers";
|
|
import { nodeExternalsPlugin } from "esbuild-node-externals";
|
|
import path from "path";
|
|
import fs from "fs";
|
|
// import { glob } from "glob";
|
|
|
|
const banner = `
|
|
// patch __dirname
|
|
// import { fileURLToPath } from "url";
|
|
// import path from "path";
|
|
// const __filename = fileURLToPath(import.meta.url);
|
|
// const __dirname = path.dirname(__filename);
|
|
|
|
// allow top level await
|
|
import { createRequire as topLevelCreateRequire } from "module";
|
|
const require = topLevelCreateRequire(import.meta.url);
|
|
`;
|
|
|
|
const argv = yargs(hideBin(process.argv))
|
|
.usage("Usage: $0 -entry [string] -out [string] -build [string]")
|
|
.option("entry", {
|
|
alias: "e",
|
|
describe: "Entry point file",
|
|
type: "string",
|
|
demandOption: true,
|
|
})
|
|
.option("out", {
|
|
alias: "o",
|
|
describe: "Output file path",
|
|
type: "string",
|
|
demandOption: true,
|
|
})
|
|
.option("build", {
|
|
alias: "b",
|
|
describe: "Build type (oss, saas, enterprise)",
|
|
type: "string",
|
|
choices: ["oss", "saas", "enterprise"],
|
|
default: "oss",
|
|
})
|
|
.help()
|
|
.alias("help", "h").argv;
|
|
|
|
// generate a list of all package.json files in the monorepo
|
|
function getPackagePaths() {
|
|
// const packagePaths = [];
|
|
// const packageGlob = "package.json";
|
|
// const packageJsonFiles = glob.sync(packageGlob);
|
|
// for (const packageJsonFile of packageJsonFiles) {
|
|
// packagePaths.push(path.dirname(packageJsonFile) + "/package.json");
|
|
// }
|
|
// return packagePaths;
|
|
return ["package.json"];
|
|
}
|
|
|
|
// Plugin to guard against bad imports from #private
|
|
function privateImportGuardPlugin() {
|
|
return {
|
|
name: "private-import-guard",
|
|
setup(build) {
|
|
const violations = [];
|
|
|
|
build.onResolve({ filter: /^#private\// }, (args) => {
|
|
const importingFile = args.importer;
|
|
|
|
// Check if the importing file is NOT in server/private
|
|
const normalizedImporter = path.normalize(importingFile);
|
|
const isInServerPrivate = normalizedImporter.includes(path.normalize("server/private"));
|
|
|
|
if (!isInServerPrivate) {
|
|
const violation = {
|
|
file: importingFile,
|
|
importPath: args.path,
|
|
resolveDir: args.resolveDir
|
|
};
|
|
violations.push(violation);
|
|
|
|
console.log(`PRIVATE IMPORT VIOLATION:`);
|
|
console.log(` File: ${importingFile}`);
|
|
console.log(` Import: ${args.path}`);
|
|
console.log(` Resolve dir: ${args.resolveDir || 'N/A'}`);
|
|
console.log('');
|
|
}
|
|
|
|
// Return null to let the default resolver handle it
|
|
return null;
|
|
});
|
|
|
|
build.onEnd((result) => {
|
|
if (violations.length > 0) {
|
|
console.log(`\nSUMMARY: Found ${violations.length} private import violation(s):`);
|
|
violations.forEach((v, i) => {
|
|
console.log(` ${i + 1}. ${path.relative(process.cwd(), v.file)} imports ${v.importPath}`);
|
|
});
|
|
console.log('');
|
|
|
|
result.errors.push({
|
|
text: `Private import violations detected: ${violations.length} violation(s) found`,
|
|
location: null,
|
|
notes: violations.map(v => ({
|
|
text: `${path.relative(process.cwd(), v.file)} imports ${v.importPath}`,
|
|
location: null
|
|
}))
|
|
});
|
|
}
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
// Plugin to guard against bad imports from #private
|
|
function dynamicImportGuardPlugin() {
|
|
return {
|
|
name: "dynamic-import-guard",
|
|
setup(build) {
|
|
const violations = [];
|
|
|
|
build.onResolve({ filter: /^#dynamic\// }, (args) => {
|
|
const importingFile = args.importer;
|
|
|
|
// Check if the importing file is NOT in server/private
|
|
const normalizedImporter = path.normalize(importingFile);
|
|
const isInServerPrivate = normalizedImporter.includes(path.normalize("server/private"));
|
|
|
|
if (isInServerPrivate) {
|
|
const violation = {
|
|
file: importingFile,
|
|
importPath: args.path,
|
|
resolveDir: args.resolveDir
|
|
};
|
|
violations.push(violation);
|
|
|
|
console.log(`DYNAMIC IMPORT VIOLATION:`);
|
|
console.log(` File: ${importingFile}`);
|
|
console.log(` Import: ${args.path}`);
|
|
console.log(` Resolve dir: ${args.resolveDir || 'N/A'}`);
|
|
console.log('');
|
|
}
|
|
|
|
// Return null to let the default resolver handle it
|
|
return null;
|
|
});
|
|
|
|
build.onEnd((result) => {
|
|
if (violations.length > 0) {
|
|
console.log(`\nSUMMARY: Found ${violations.length} dynamic import violation(s):`);
|
|
violations.forEach((v, i) => {
|
|
console.log(` ${i + 1}. ${path.relative(process.cwd(), v.file)} imports ${v.importPath}`);
|
|
});
|
|
console.log('');
|
|
|
|
result.errors.push({
|
|
text: `Dynamic import violations detected: ${violations.length} violation(s) found`,
|
|
location: null,
|
|
notes: violations.map(v => ({
|
|
text: `${path.relative(process.cwd(), v.file)} imports ${v.importPath}`,
|
|
location: null
|
|
}))
|
|
});
|
|
}
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
// Plugin to dynamically switch imports based on build type
|
|
function dynamicImportSwitcherPlugin(buildValue) {
|
|
return {
|
|
name: "dynamic-import-switcher",
|
|
setup(build) {
|
|
const switches = [];
|
|
|
|
build.onStart(() => {
|
|
console.log(`Dynamic import switcher using build type: ${buildValue}`);
|
|
});
|
|
|
|
build.onResolve({ filter: /^#dynamic\// }, (args) => {
|
|
// Extract the path after #dynamic/
|
|
const dynamicPath = args.path.replace(/^#dynamic\//, '');
|
|
|
|
// Determine the replacement based on build type
|
|
let replacement;
|
|
if (buildValue === "oss") {
|
|
replacement = `#open/${dynamicPath}`;
|
|
} else if (buildValue === "saas" || buildValue === "enterprise") {
|
|
replacement = `#closed/${dynamicPath}`; // We use #closed here so that the route guards dont complain after its been changed but this is the same as #private
|
|
} else {
|
|
console.warn(`Unknown build type '${buildValue}', defaulting to #open/`);
|
|
replacement = `#open/${dynamicPath}`;
|
|
}
|
|
|
|
const switchInfo = {
|
|
file: args.importer,
|
|
originalPath: args.path,
|
|
replacementPath: replacement,
|
|
buildType: buildValue
|
|
};
|
|
switches.push(switchInfo);
|
|
|
|
console.log(`DYNAMIC IMPORT SWITCH:`);
|
|
console.log(` File: ${args.importer}`);
|
|
console.log(` Original: ${args.path}`);
|
|
console.log(` Switched to: ${replacement} (build: ${buildValue})`);
|
|
console.log('');
|
|
|
|
// Rewrite the import path and let the normal resolution continue
|
|
return build.resolve(replacement, {
|
|
importer: args.importer,
|
|
namespace: args.namespace,
|
|
resolveDir: args.resolveDir,
|
|
kind: args.kind
|
|
});
|
|
});
|
|
|
|
build.onEnd((result) => {
|
|
if (switches.length > 0) {
|
|
console.log(`\nDYNAMIC IMPORT SUMMARY: Switched ${switches.length} import(s) for build type '${buildValue}':`);
|
|
switches.forEach((s, i) => {
|
|
console.log(` ${i + 1}. ${path.relative(process.cwd(), s.file)}`);
|
|
console.log(` ${s.originalPath} → ${s.replacementPath}`);
|
|
});
|
|
console.log('');
|
|
}
|
|
});
|
|
}
|
|
};
|
|
}
|
|
|
|
esbuild
|
|
.build({
|
|
entryPoints: [argv.entry],
|
|
bundle: true,
|
|
outfile: argv.out,
|
|
format: "esm",
|
|
minify: false,
|
|
banner: {
|
|
js: banner,
|
|
},
|
|
platform: "node",
|
|
external: ["body-parser"],
|
|
plugins: [
|
|
privateImportGuardPlugin(),
|
|
dynamicImportGuardPlugin(),
|
|
dynamicImportSwitcherPlugin(argv.build),
|
|
nodeExternalsPlugin({
|
|
packagePath: getPackagePaths(),
|
|
}),
|
|
],
|
|
sourcemap: "inline",
|
|
target: "node22",
|
|
})
|
|
.then((result) => {
|
|
// Check if there were any errors in the build result
|
|
if (result.errors && result.errors.length > 0) {
|
|
console.error(`Build failed with ${result.errors.length} error(s):`);
|
|
result.errors.forEach((error, i) => {
|
|
console.error(`${i + 1}. ${error.text}`);
|
|
if (error.notes) {
|
|
error.notes.forEach(note => {
|
|
console.error(` - ${note.text}`);
|
|
});
|
|
}
|
|
});
|
|
|
|
// remove the output file if it was created
|
|
if (fs.existsSync(argv.out)) {
|
|
fs.unlinkSync(argv.out);
|
|
}
|
|
|
|
process.exit(1);
|
|
}
|
|
|
|
console.log("Build completed successfully");
|
|
})
|
|
.catch((error) => {
|
|
console.error("Build failed:", error);
|
|
process.exit(1);
|
|
});
|