250 lines
8.3 KiB
JavaScript
250 lines
8.3 KiB
JavaScript
const fs = require("fs")
|
|
const { execSync } = require("child_process")
|
|
const postcssPlugin = require("esbuild-postcss")
|
|
|
|
// Resolve version at build time via git describe (tag + hash), fallback to env var or "dev"
|
|
let gitHash = "dev"
|
|
try {
|
|
gitHash = process.env.GIT_HASH || execSync("git describe --tags --always --dirty").toString().trim()
|
|
} catch (_) {
|
|
// .git not available (e.g. Docker build without .git context)
|
|
}
|
|
|
|
// Generate buildInfo module that can be imported by frontend code
|
|
function writeBuildInfo() {
|
|
const info = {
|
|
gitHash,
|
|
buildTime: new Date().toISOString(),
|
|
}
|
|
fs.writeFileSync(
|
|
__dirname + "/frontend/src/lib/buildInfo.ts",
|
|
`// AUTO-GENERATED by esbuild.config.js \u2013 do not edit\nexport const gitHash = ${JSON.stringify(info.gitHash)}\nexport const buildTime = ${JSON.stringify(info.buildTime)}\n`
|
|
)
|
|
// Write same buildInfo for backend hooks (X-Build-Time / X-Release headers)
|
|
fs.writeFileSync(
|
|
__dirname + "/api/hooks/lib/buildInfo.js",
|
|
`// AUTO-GENERATED by esbuild.config.js \u2013 do not edit\nmodule.exports = { gitHash: ${JSON.stringify(info.gitHash)}, buildTime: ${JSON.stringify(info.buildTime)} }\n`
|
|
)
|
|
}
|
|
// NOTE: writeBuildInfo() is NOT called here at top-level.
|
|
// It is called by esbuild-wrapper.js before each build.
|
|
// This prevents the server build from overwriting the frontend's timestamp
|
|
// (which would cause a version mismatch and spurious auto-reload).
|
|
|
|
const resolvePlugin = {
|
|
name: "resolvePlugin",
|
|
setup(build) {
|
|
let path = require("path")
|
|
// url in css does not resolve via esbuild-svelte correctly
|
|
build.onResolve({ filter: /.*/, namespace: "fakecss" }, (args) => {
|
|
// console.log(args)
|
|
if (args.path.match(/^\./)) return { path: path.dirname(args.importer) + "/" + args.path }
|
|
// return { path: path.join(args.resolveDir, "public", args.path) }
|
|
})
|
|
},
|
|
}
|
|
|
|
// When MOCK is disabled, replace the mock module with a no-op stub so
|
|
// esbuild can tree-shake all mock data out of the production bundle.
|
|
const mockPlugin = {
|
|
name: "mockPlugin",
|
|
setup(build) {
|
|
if (process.env.MOCK !== "1") {
|
|
build.onResolve({ filter: /\/mock$/ }, (args) => {
|
|
if (args.importer.includes("api.ts") || args.importer.includes("api.js")) {
|
|
return { path: "mock-noop", namespace: "mock-noop" }
|
|
}
|
|
})
|
|
build.onLoad({ filter: /.*/, namespace: "mock-noop" }, () => ({
|
|
contents: "export function mockApiRequest() { return null }",
|
|
loader: "ts",
|
|
}))
|
|
}
|
|
},
|
|
}
|
|
|
|
////////////////////////// esbuild-svelte
|
|
|
|
const sveltePlugin = require("esbuild-svelte")
|
|
|
|
const frontendDir = "./frontend"
|
|
const distDir = frontendDir + "/dist"
|
|
|
|
// console.log("copy public dir...")
|
|
// const copydir = require("copy-dir")
|
|
// copydir.sync(__dirname + "/public", __dirname + "/" + distDir)
|
|
/*copydir.sync(
|
|
__dirname + "/public/index.html",
|
|
__dirname + "/" + distDir + "/template.html"
|
|
)*/
|
|
|
|
const svelteConfig = require("./svelte.config")
|
|
const esbuildSvelte = sveltePlugin({
|
|
compilerOptions: {
|
|
css: "external",
|
|
dev: (process.argv?.length > 2 ? process.argv[2] : "build") !== "build",
|
|
},
|
|
preprocess: svelteConfig.preprocess,
|
|
cache: true,
|
|
filterWarnings: (warning) => {
|
|
// filter out a11y
|
|
if (warning.code.match(/^a11y/)) return false
|
|
return true
|
|
},
|
|
})
|
|
|
|
const options = {
|
|
logLevel: "info",
|
|
color: true,
|
|
entryPoints: ["./frontend/src/index.ts"],
|
|
outfile: distDir + "/index.mjs",
|
|
metafile: true,
|
|
format: "esm",
|
|
minify: process.argv[2] == "build",
|
|
bundle: true,
|
|
splitting: false,
|
|
define: {
|
|
__MOCK__: process.env.MOCK === "1" ? "true" : "false",
|
|
},
|
|
plugins: [esbuildSvelte, postcssPlugin(), resolvePlugin, mockPlugin],
|
|
loader: {
|
|
".woff2": "file",
|
|
".woff": "file",
|
|
".eot": "file",
|
|
".svg": "file",
|
|
".ttf": "file",
|
|
},
|
|
sourcemap: true,
|
|
target: ["es2022", "chrome100", "firefox100", "safari15", "edge100"],
|
|
}
|
|
|
|
const bsMiddleware = []
|
|
|
|
if (process.argv[2] == "start") {
|
|
const { createProxyMiddleware } = require("http-proxy-middleware")
|
|
const dotEnv = fs.readFileSync(__dirname + "/.env", "utf8")
|
|
const TIBI_NAMESPACE = dotEnv.match(/TIBI_NAMESPACE=(.*)/)[1]
|
|
const apiBase = process.env.API_BASE || "http://localhost:8080/api/v1/_/" + TIBI_NAMESPACE
|
|
bsMiddleware.push(
|
|
createProxyMiddleware({
|
|
pathFilter: "/api",
|
|
target: apiBase,
|
|
pathRewrite: { "^/api": "" },
|
|
changeOrigin: true,
|
|
logLevel: "debug",
|
|
})
|
|
)
|
|
|
|
// Sentry tunnel proxy: forwards /_s requests to Sentry ingest to avoid ad-blockers
|
|
const https = require("https")
|
|
const http = require("http")
|
|
bsMiddleware.push(function (req, res, next) {
|
|
if (req.url !== "/_s" || req.method !== "POST") return next()
|
|
let body = []
|
|
req.on("data", (chunk) => body.push(chunk))
|
|
req.on("end", () => {
|
|
const payload = Buffer.concat(body).toString()
|
|
const firstLine = payload.split("\n")[0]
|
|
let envelope
|
|
try {
|
|
envelope = JSON.parse(firstLine)
|
|
} catch {
|
|
res.writeHead(400)
|
|
res.end()
|
|
return
|
|
}
|
|
const dsn = envelope.dsn || ""
|
|
let sentryUrl
|
|
try {
|
|
sentryUrl = new URL(dsn)
|
|
} catch {
|
|
res.writeHead(400)
|
|
res.end()
|
|
return
|
|
}
|
|
const projectId = sentryUrl.pathname.replace(/\//g, "")
|
|
const ingestUrl = `${sentryUrl.protocol}//${sentryUrl.host}/api/${projectId}/envelope/`
|
|
const transport = ingestUrl.startsWith("https") ? https : http
|
|
const proxyReq = transport.request(
|
|
ingestUrl,
|
|
{
|
|
method: "POST",
|
|
headers: { "Content-Type": "application/x-sentry-envelope" },
|
|
},
|
|
(proxyRes) => {
|
|
res.writeHead(proxyRes.statusCode || 200)
|
|
proxyRes.pipe(res)
|
|
}
|
|
)
|
|
proxyReq.on("error", () => {
|
|
res.writeHead(502)
|
|
res.end()
|
|
})
|
|
proxyReq.write(payload)
|
|
proxyReq.end()
|
|
})
|
|
})
|
|
|
|
if (process.env.SSR) {
|
|
bsMiddleware.push(
|
|
createProxyMiddleware({
|
|
pathFilter: function (path, req) {
|
|
return !path.match(/\./)
|
|
},
|
|
target: apiBase,
|
|
changeOrigin: true,
|
|
logLevel: "debug",
|
|
pathRewrite: function (path, req) {
|
|
return "/ssr?url=" + encodeURIComponent(path)
|
|
},
|
|
})
|
|
)
|
|
}
|
|
}
|
|
|
|
module.exports = {
|
|
sveltePlugin: sveltePlugin,
|
|
resolvePlugin: resolvePlugin,
|
|
writeBuildInfo: writeBuildInfo,
|
|
options: options,
|
|
distDir,
|
|
watch: {
|
|
path: [__dirname + "/" + frontendDir + "/src"],
|
|
},
|
|
serve: {
|
|
onRequest(args) {
|
|
console.log(args)
|
|
},
|
|
},
|
|
browserSync: {
|
|
server: {
|
|
baseDir: frontendDir,
|
|
middleware: [
|
|
require("morgan")("dev"),
|
|
...bsMiddleware,
|
|
require("connect-history-api-fallback")({
|
|
index: "/spa.html",
|
|
// Allow dots in URL paths (e.g. /version-3.7.1) to fall through to SPA,
|
|
// but only if they don't look like actual file requests.
|
|
rewrites: [
|
|
{
|
|
from: /\.(js|css|mjs|map|woff2?|ttf|eot|svg|png|jpe?g|gif|webp|ico|json|pdf)(\?.*)?$/i,
|
|
to: function (context) {
|
|
return context.parsedUrl.pathname
|
|
},
|
|
},
|
|
{
|
|
from: /./,
|
|
to: "/spa.html",
|
|
},
|
|
],
|
|
// verbose: true,
|
|
}),
|
|
],
|
|
},
|
|
open: false,
|
|
// logLevel: "debug",
|
|
ghostMode: false,
|
|
},
|
|
}
|