251 lines
6.1 KiB
JavaScript
Executable File

#!/usr/bin/env node
require("dotenv").config()
require("sucrase/register")
const path = require("node:path")
const Module = require("node:module")
const { Buffer } = require("node:buffer")
const { webcrypto: crypto } = require("node:crypto")
const { InfisicalClient } = require("@infisical/sdk")
const moduleAlias = require("module-alias")
const { onExit } = require("signal-exit")
const opentelemetry = require("@opentelemetry/sdk-node")
const {
getNodeAutoInstrumentations,
} = require("@opentelemetry/auto-instrumentations-node")
const { OTLPTraceExporter } = require("@opentelemetry/exporter-trace-otlp-http")
const { OTLPLogExporter } = require("@opentelemetry/exporter-logs-otlp-http")
const { Resource } = require("@opentelemetry/resources")
const {
SemanticResourceAttributes,
} = require("@opentelemetry/semantic-conventions")
// Override file execution arg
process.argv.splice(1, 1)
process.argv[1] = path.resolve(process.argv[1])
// Expose boot function to global
global.Boot = Boot
global.isProduction = process.env.NODE_ENV === "production"
global["__root"] = path.resolve(process.cwd())
global["__src"] = path.resolve(
globalThis["__root"],
path.dirname(process.argv[1]),
)
global["aliases"] = {
root: global["__root"],
src: global["__src"],
// expose shared resources
"@db_models": path.resolve(__dirname, "db_models"),
"@shared-utils": path.resolve(__dirname, "utils"),
"@shared-classes": path.resolve(__dirname, "classes"),
"@shared-lib": path.resolve(__dirname, "lib"),
"@shared-middlewares": path.resolve(__dirname, "middlewares"),
// expose internal resources
"@lib": path.resolve(global["__src"], "lib"),
"@middlewares": path.resolve(global["__src"], "middlewares"),
"@controllers": path.resolve(global["__src"], "controllers"),
"@config": path.resolve(global["__src"], "config"),
"@shared": path.resolve(global["__src"], "shared"),
"@classes": path.resolve(global["__src"], "classes"),
"@models": path.resolve(global["__src"], "models"),
"@services": path.resolve(global["__src"], "services"),
"@utils": path.resolve(global["__src"], "utils"),
}
function registerBaseAliases(fromPath, customAliases = {}) {
if (typeof fromPath === "undefined") {
if (module.parent.filename.includes("dist")) {
fromPath = path.resolve(process.cwd(), "dist")
} else {
fromPath = path.resolve(process.cwd(), "src")
}
}
moduleAlias.addAliases({
...customAliases,
"@controllers": path.resolve(fromPath, "controllers"),
"@middlewares": path.resolve(fromPath, "middlewares"),
"@models": path.resolve(fromPath, "models"),
"@classes": path.resolve(fromPath, "classes"),
"@lib": path.resolve(fromPath, "lib"),
"@utils": path.resolve(fromPath, "utils"),
})
}
function registerPatches() {
global.b64Decode = (data) => {
return Buffer.from(data, "base64").toString("utf-8")
}
global.b64Encode = (data) => {
return Buffer.from(data, "utf-8").toString("base64")
}
global.nanoid = (t = 21) =>
crypto
.getRandomValues(new Uint8Array(t))
.reduce(
(t, e) =>
(t +=
(e &= 63) < 36
? e.toString(36)
: e < 62
? (e - 26).toString(36).toUpperCase()
: e > 62
? "-"
: "_"),
"",
)
Array.prototype.updateFromObjectKeys = function (obj) {
this.forEach((value, index) => {
if (obj[value] !== undefined) {
this[index] = obj[value]
}
})
return this
}
global.ToBoolean = (value) => {
if (typeof value === "boolean") {
return value
}
if (typeof value === "string") {
return value.toLowerCase() === "true"
}
return false
}
}
function registerAliases() {
registerBaseAliases(global["__src"], global["aliases"])
}
async function injectEnvFromInfisical() {
const envMode = (global.FORCE_ENV ?? global.isProduction) ? "prod" : "dev"
console.log(
`[BOOT] 🔑 Injecting env variables from INFISICAL in [${envMode}] mode...`,
)
const client = new InfisicalClient({
auth: {
universalAuth: {
clientId: process.env.INFISICAL_CLIENT_ID,
clientSecret: process.env.INFISICAL_CLIENT_SECRET,
},
},
})
const secrets = await client.listSecrets({
environment: envMode,
path: process.env.INFISICAL_PATH ?? "/",
projectId: process.env.INFISICAL_PROJECT_ID ?? null,
includeImports: false,
})
//inject to process.env
secrets.forEach((secret) => {
if (!process.env[secret.secretKey]) {
process.env[secret.secretKey] = secret.secretValue
}
})
}
async function Boot(main) {
if (!main) {
throw new Error("main class is not defined")
}
const service_id = process.env.lb_service.id
console.log(
`[BOOT] Booting (${service_id}) in [${global.isProduction ? "production" : "development"}] mode...`,
)
const traceExporter = new OTLPTraceExporter({
url:
process.env.OTEL_EXPORTER_OTLP_TRACES_ENDPOINT ??
"http://fr02.ragestudio.net:4318/v1/traces",
})
const logExporter = new OTLPLogExporter({
url:
process.env.OTEL_EXPORTER_OTLP_LOGS_ENDPOINT ??
"http://fr02.ragestudio.net:4318/v1/logs",
})
const sdk = new opentelemetry.NodeSDK({
traceExporter,
logExporter,
instrumentations: [getNodeAutoInstrumentations()],
resource: new Resource({
[SemanticResourceAttributes.SERVICE_NAME]: service_id ?? "node_app",
}),
})
sdk.start()
if (
process.env.INFISICAL_CLIENT_ID &&
process.env.INFISICAL_CLIENT_SECRET
) {
console.log(
`[BOOT] INFISICAL Credentials found, injecting env variables from INFISICAL...`,
)
await injectEnvFromInfisical()
}
const instance = new main()
onExit(
(code, signal) => {
console.log(`[BOOT] Cleaning up...`)
sdk.shutdown()
.then(() => console.log("Tracing terminated"))
.catch((error) =>
console.log("Error terminating tracing", error),
)
if (typeof instance.onClose === "function") {
instance.onClose()
}
instance.engine.close()
},
{
alwaysLast: true,
},
)
await instance.initialize()
if (process.env.lb_service && process.send) {
process.send({
status: "ready",
})
}
return instance
}
try {
// Apply patches
registerPatches()
// Apply aliases
registerAliases()
// execute main
Module.runMain()
} catch (error) {
console.error("[BOOT] ❌ Boot error: ", error)
}