@cytario/web 2.1.1 → 2.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/cytario-web.mjs +4 -2
- package/package.json +6 -2
- package/{server.ts → server.js} +18 -46
- package/server.js.map +1 -0
package/bin/cytario-web.mjs
CHANGED
|
@@ -76,9 +76,11 @@ async function runReactRouterPipeline(subcommand, forwardedArgs) {
|
|
|
76
76
|
}
|
|
77
77
|
|
|
78
78
|
function runStart(forwardedArgs) {
|
|
79
|
-
|
|
79
|
+
// Node 24 won't type-strip under `node_modules/`, so we spawn the
|
|
80
|
+
// precompiled `server.js` (emitted by `npm run build:server`).
|
|
81
|
+
const serverEntry = resolve(PACKAGE_ROOT, "server.js");
|
|
80
82
|
if (!existsSync(serverEntry)) {
|
|
81
|
-
fail(`missing server entry at ${serverEntry}
|
|
83
|
+
fail(`missing server entry at ${serverEntry} — run \`npm run build:server\``);
|
|
82
84
|
}
|
|
83
85
|
const child = spawn(process.execPath, [serverEntry, ...forwardedArgs], {
|
|
84
86
|
cwd: PACKAGE_ROOT,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cytario/web",
|
|
3
|
-
"version": "2.1.
|
|
3
|
+
"version": "2.1.2",
|
|
4
4
|
"description": "Cytario Web — scientific imaging data browser and viewer for OME-TIFF, OME-Zarr, Parquet and GeoTIFF on S3-compatible storage.",
|
|
5
5
|
"license": "AGPL-3.0",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -29,7 +29,8 @@
|
|
|
29
29
|
"vite-plugins",
|
|
30
30
|
"prisma.config.ts",
|
|
31
31
|
"react-router.config.ts",
|
|
32
|
-
"server.
|
|
32
|
+
"server.js",
|
|
33
|
+
"server.js.map",
|
|
33
34
|
"tsconfig.json",
|
|
34
35
|
"vite.config.ts",
|
|
35
36
|
"!**/__tests__",
|
|
@@ -47,14 +48,17 @@
|
|
|
47
48
|
},
|
|
48
49
|
"scripts": {
|
|
49
50
|
"prepare": "husky || true",
|
|
51
|
+
"prepublishOnly": "npm run build:server",
|
|
50
52
|
"prebuild": "node scripts/prebuild.mjs",
|
|
51
53
|
"build": "node bin/cytario-web.mjs build",
|
|
54
|
+
"build:server": "tsup --config tsup.server.config.ts",
|
|
52
55
|
"codegen:check": "tsx scripts/codegen-check.ts",
|
|
53
56
|
"dev": "node bin/cytario-web.mjs dev",
|
|
54
57
|
"predev": "if [ -L node_modules/@cytario/design ]; then npm unlink @cytario/design --no-save 2>/dev/null && npm install @cytario/design --quiet; fi",
|
|
55
58
|
"predev:design": "cd ../cytario-design && npm link --quiet && cd - > /dev/null && npm link @cytario/design --quiet && for p in react react-dom react-aria-components; do rm -rf ../cytario-design/node_modules/$p && ln -s \"$(pwd)/node_modules/$p\" ../cytario-design/node_modules/$p; done",
|
|
56
59
|
"dev:design": "concurrently -n tsup,storybook,web -c blue,magenta,green \"cd ../cytario-design && npx tsup --watch\" \"cd ../cytario-design && npm run dev\" \"node bin/cytario-web.mjs dev\"",
|
|
57
60
|
"lint": "eslint --cache --cache-location ./node_modules/.cache/eslint .",
|
|
61
|
+
"prestart": "npm run build:server",
|
|
58
62
|
"start": "node bin/cytario-web.mjs start",
|
|
59
63
|
"typecheck": "react-router typegen && tsc",
|
|
60
64
|
"test": "vitest",
|
package/{server.ts → server.js}
RENAMED
|
@@ -1,28 +1,17 @@
|
|
|
1
|
+
// server.ts
|
|
1
2
|
import "dotenv/config";
|
|
2
3
|
import { createRequestHandler } from "@react-router/express";
|
|
3
4
|
import compression from "compression";
|
|
4
5
|
import express from "express";
|
|
5
6
|
import morgan from "morgan";
|
|
6
|
-
import path from "
|
|
7
|
-
import url from "
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
const buildModule = await import(url.pathToFileURL(BUILD_PATH).href);
|
|
12
|
-
|
|
13
|
-
const app = express();
|
|
14
|
-
|
|
15
|
-
// Trust the first proxy (Traefik) so req.protocol reflects X-Forwarded-Proto
|
|
16
|
-
// and req.ip reflects X-Forwarded-For. This is critical for secure cookie
|
|
17
|
-
// handling when TLS is terminated at the reverse proxy.
|
|
7
|
+
import path from "path";
|
|
8
|
+
import url from "url";
|
|
9
|
+
var BUILD_PATH = path.resolve("build/server/index.js");
|
|
10
|
+
var buildModule = await import(url.pathToFileURL(BUILD_PATH).href);
|
|
11
|
+
var app = express();
|
|
18
12
|
app.set("trust proxy", 1);
|
|
19
|
-
|
|
20
13
|
app.disable("x-powered-by");
|
|
21
|
-
|
|
22
|
-
let isShuttingDown = false;
|
|
23
|
-
|
|
24
|
-
// Kubernetes readiness/liveness probe — before all middleware so it's fast and
|
|
25
|
-
// never blocked by compression, static-file serving, or request logging.
|
|
14
|
+
var isShuttingDown = false;
|
|
26
15
|
app.get("/healthz", (_req, res) => {
|
|
27
16
|
if (isShuttingDown) {
|
|
28
17
|
res.status(503).send("shutting down");
|
|
@@ -30,54 +19,38 @@ app.get("/healthz", (_req, res) => {
|
|
|
30
19
|
res.status(200).send("ok");
|
|
31
20
|
}
|
|
32
21
|
});
|
|
33
|
-
|
|
34
22
|
app.use(compression());
|
|
35
|
-
|
|
36
|
-
// Vite-fingerprinted assets — immutable, cache forever
|
|
37
23
|
app.use(
|
|
38
24
|
path.posix.join(buildModule.publicPath, "assets"),
|
|
39
25
|
express.static(path.join(buildModule.assetsBuildDirectory, "assets"), {
|
|
40
26
|
immutable: true,
|
|
41
|
-
maxAge: "1y"
|
|
42
|
-
})
|
|
27
|
+
maxAge: "1y"
|
|
28
|
+
})
|
|
43
29
|
);
|
|
44
|
-
|
|
45
|
-
// Other build assets — short cache
|
|
46
30
|
app.use(
|
|
47
31
|
buildModule.publicPath,
|
|
48
|
-
express.static(buildModule.assetsBuildDirectory, { maxAge: "1h" })
|
|
32
|
+
express.static(buildModule.assetsBuildDirectory, { maxAge: "1h" })
|
|
49
33
|
);
|
|
50
|
-
|
|
51
|
-
// Public static files (fonts, logos, etc.)
|
|
52
34
|
app.use(express.static("public", { maxAge: "1h" }));
|
|
53
|
-
|
|
54
35
|
app.use(morgan("tiny"));
|
|
55
|
-
|
|
56
36
|
app.all(
|
|
57
37
|
"*",
|
|
58
38
|
createRequestHandler({
|
|
59
39
|
build: buildModule,
|
|
60
|
-
mode: process.env.NODE_ENV
|
|
61
|
-
})
|
|
40
|
+
mode: process.env.NODE_ENV
|
|
41
|
+
})
|
|
62
42
|
);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
const server = app.listen(port, host, () => {
|
|
43
|
+
var port = Number.parseInt(process.env.PORT || "3000", 10);
|
|
44
|
+
var host = process.env.HOST || "0.0.0.0";
|
|
45
|
+
var server = app.listen(port, host, () => {
|
|
68
46
|
console.log(`[cytario-web] http://${host}:${port}`);
|
|
69
47
|
});
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
const SHUTDOWN_DELAY_MS = 5_000;
|
|
73
|
-
const DRAIN_TIMEOUT_MS = 15_000;
|
|
74
|
-
|
|
48
|
+
var SHUTDOWN_DELAY_MS = 5e3;
|
|
49
|
+
var DRAIN_TIMEOUT_MS = 15e3;
|
|
75
50
|
for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
76
51
|
process.once(signal, () => {
|
|
77
52
|
console.log(`[cytario-web] ${signal} received, shutting down gracefully`);
|
|
78
53
|
isShuttingDown = true;
|
|
79
|
-
|
|
80
|
-
// Wait for load balancer to deregister the pod before closing connections
|
|
81
54
|
setTimeout(() => {
|
|
82
55
|
console.log("[cytario-web] closing server to new connections");
|
|
83
56
|
server.close((err) => {
|
|
@@ -88,8 +61,6 @@ for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
|
88
61
|
}
|
|
89
62
|
process.exit(err ? 1 : 0);
|
|
90
63
|
});
|
|
91
|
-
|
|
92
|
-
// Force exit if connections don't drain in time
|
|
93
64
|
setTimeout(() => {
|
|
94
65
|
console.error("[cytario-web] drain timeout, forcing exit");
|
|
95
66
|
process.exit(1);
|
|
@@ -97,3 +68,4 @@ for (const signal of ["SIGTERM", "SIGINT"]) {
|
|
|
97
68
|
}, SHUTDOWN_DELAY_MS);
|
|
98
69
|
});
|
|
99
70
|
}
|
|
71
|
+
//# sourceMappingURL=server.js.map
|
package/server.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["server.ts"],"sourcesContent":["import \"dotenv/config\";\nimport { createRequestHandler } from \"@react-router/express\";\nimport compression from \"compression\";\nimport express from \"express\";\nimport morgan from \"morgan\";\nimport path from \"node:path\";\nimport url from \"node:url\";\n\nconst BUILD_PATH = path.resolve(\"build/server/index.js\");\n\nconst buildModule = await import(url.pathToFileURL(BUILD_PATH).href);\n\nconst app = express();\n\n// Trust the first proxy (Traefik) so req.protocol reflects X-Forwarded-Proto\n// and req.ip reflects X-Forwarded-For. This is critical for secure cookie\n// handling when TLS is terminated at the reverse proxy.\napp.set(\"trust proxy\", 1);\n\napp.disable(\"x-powered-by\");\n\nlet isShuttingDown = false;\n\n// Kubernetes readiness/liveness probe — before all middleware so it's fast and\n// never blocked by compression, static-file serving, or request logging.\napp.get(\"/healthz\", (_req, res) => {\n if (isShuttingDown) {\n res.status(503).send(\"shutting down\");\n } else {\n res.status(200).send(\"ok\");\n }\n});\n\napp.use(compression());\n\n// Vite-fingerprinted assets — immutable, cache forever\napp.use(\n path.posix.join(buildModule.publicPath, \"assets\"),\n express.static(path.join(buildModule.assetsBuildDirectory, \"assets\"), {\n immutable: true,\n maxAge: \"1y\",\n }),\n);\n\n// Other build assets — short cache\napp.use(\n buildModule.publicPath,\n express.static(buildModule.assetsBuildDirectory, { maxAge: \"1h\" }),\n);\n\n// Public static files (fonts, logos, etc.)\napp.use(express.static(\"public\", { maxAge: \"1h\" }));\n\napp.use(morgan(\"tiny\"));\n\napp.all(\n \"*\",\n createRequestHandler({\n build: buildModule,\n mode: process.env.NODE_ENV,\n }),\n);\n\nconst port = Number.parseInt(process.env.PORT || \"3000\", 10);\nconst host = process.env.HOST || \"0.0.0.0\";\n\nconst server = app.listen(port, host, () => {\n console.log(`[cytario-web] http://${host}:${port}`);\n});\n\n// Graceful shutdown on SIGTERM/SIGINT\nconst SHUTDOWN_DELAY_MS = 5_000;\nconst DRAIN_TIMEOUT_MS = 15_000;\n\nfor (const signal of [\"SIGTERM\", \"SIGINT\"]) {\n process.once(signal, () => {\n console.log(`[cytario-web] ${signal} received, shutting down gracefully`);\n isShuttingDown = true;\n\n // Wait for load balancer to deregister the pod before closing connections\n setTimeout(() => {\n console.log(\"[cytario-web] closing server to new connections\");\n server.close((err) => {\n if (err) {\n console.error(\"[cytario-web] error during close:\", err);\n } else {\n console.log(\"[cytario-web] all connections drained, exiting\");\n }\n process.exit(err ? 1 : 0);\n });\n\n // Force exit if connections don't drain in time\n setTimeout(() => {\n console.error(\"[cytario-web] drain timeout, forcing exit\");\n process.exit(1);\n }, DRAIN_TIMEOUT_MS).unref();\n }, SHUTDOWN_DELAY_MS);\n });\n}\n"],"mappings":";AAAA,OAAO;AACP,SAAS,4BAA4B;AACrC,OAAO,iBAAiB;AACxB,OAAO,aAAa;AACpB,OAAO,YAAY;AACnB,OAAO,UAAU;AACjB,OAAO,SAAS;AAEhB,IAAM,aAAa,KAAK,QAAQ,uBAAuB;AAEvD,IAAM,cAAc,MAAM,OAAO,IAAI,cAAc,UAAU,EAAE;AAE/D,IAAM,MAAM,QAAQ;AAKpB,IAAI,IAAI,eAAe,CAAC;AAExB,IAAI,QAAQ,cAAc;AAE1B,IAAI,iBAAiB;AAIrB,IAAI,IAAI,YAAY,CAAC,MAAM,QAAQ;AACjC,MAAI,gBAAgB;AAClB,QAAI,OAAO,GAAG,EAAE,KAAK,eAAe;AAAA,EACtC,OAAO;AACL,QAAI,OAAO,GAAG,EAAE,KAAK,IAAI;AAAA,EAC3B;AACF,CAAC;AAED,IAAI,IAAI,YAAY,CAAC;AAGrB,IAAI;AAAA,EACF,KAAK,MAAM,KAAK,YAAY,YAAY,QAAQ;AAAA,EAChD,QAAQ,OAAO,KAAK,KAAK,YAAY,sBAAsB,QAAQ,GAAG;AAAA,IACpE,WAAW;AAAA,IACX,QAAQ;AAAA,EACV,CAAC;AACH;AAGA,IAAI;AAAA,EACF,YAAY;AAAA,EACZ,QAAQ,OAAO,YAAY,sBAAsB,EAAE,QAAQ,KAAK,CAAC;AACnE;AAGA,IAAI,IAAI,QAAQ,OAAO,UAAU,EAAE,QAAQ,KAAK,CAAC,CAAC;AAElD,IAAI,IAAI,OAAO,MAAM,CAAC;AAEtB,IAAI;AAAA,EACF;AAAA,EACA,qBAAqB;AAAA,IACnB,OAAO;AAAA,IACP,MAAM,QAAQ,IAAI;AAAA,EACpB,CAAC;AACH;AAEA,IAAM,OAAO,OAAO,SAAS,QAAQ,IAAI,QAAQ,QAAQ,EAAE;AAC3D,IAAM,OAAO,QAAQ,IAAI,QAAQ;AAEjC,IAAM,SAAS,IAAI,OAAO,MAAM,MAAM,MAAM;AAC1C,UAAQ,IAAI,wBAAwB,IAAI,IAAI,IAAI,EAAE;AACpD,CAAC;AAGD,IAAM,oBAAoB;AAC1B,IAAM,mBAAmB;AAEzB,WAAW,UAAU,CAAC,WAAW,QAAQ,GAAG;AAC1C,UAAQ,KAAK,QAAQ,MAAM;AACzB,YAAQ,IAAI,iBAAiB,MAAM,qCAAqC;AACxE,qBAAiB;AAGjB,eAAW,MAAM;AACf,cAAQ,IAAI,iDAAiD;AAC7D,aAAO,MAAM,CAAC,QAAQ;AACpB,YAAI,KAAK;AACP,kBAAQ,MAAM,qCAAqC,GAAG;AAAA,QACxD,OAAO;AACL,kBAAQ,IAAI,gDAAgD;AAAA,QAC9D;AACA,gBAAQ,KAAK,MAAM,IAAI,CAAC;AAAA,MAC1B,CAAC;AAGD,iBAAW,MAAM;AACf,gBAAQ,MAAM,2CAA2C;AACzD,gBAAQ,KAAK,CAAC;AAAA,MAChB,GAAG,gBAAgB,EAAE,MAAM;AAAA,IAC7B,GAAG,iBAAiB;AAAA,EACtB,CAAC;AACH;","names":[]}
|