From 7a5b9cc9122ec0c9ff1c63712d7412ed2f4eb2a7 Mon Sep 17 00:00:00 2001 From: John Date: Wed, 17 Jun 2026 16:38:58 -0700 Subject: [PATCH] Add argon2 polyfill and unified preview/API startup for admin login. Node < 24 lacks crypto.argon2Sync; wire @node-rs/argon2 fallback and start Admin API alongside vite preview in local and remote deploy flows. Co-authored-by: Cursor --- package-lock.json | 310 +++++++++++++++++++++++++++++++++++++ package.json | 4 +- scripts/preview.mjs | 43 +++++ scripts/remote_restart.sh | 26 +++- server/app.mjs | 26 ++-- server/argon2-polyfill.mjs | 23 +++ server/index.mjs | 3 + 7 files changed, 414 insertions(+), 21 deletions(-) create mode 100644 scripts/preview.mjs create mode 100644 server/argon2-polyfill.mjs diff --git a/package-lock.json b/package-lock.json index 79602b1..2fbd278 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "tkmind-admin", "version": "0.1.0", "dependencies": { + "@node-rs/argon2": "^2.0.2", "express": "^4.21.2", "mysql2": "^3.22.5", "react": "^19.0.0", @@ -304,6 +305,37 @@ "node": ">=6.9.0" } }, + "node_modules/@emnapi/core": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/wasi-threads": "1.2.2", + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@emnapi/wasi-threads": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -796,6 +828,267 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/wasm-runtime": { + "version": "0.2.12", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-0.2.12.tgz", + "integrity": "sha512-ZVWUcfwY4E/yPitQJl481FjFo3K22D6qF0DuFH6Y/nbnE11GY5uguDxZMGXPQ8WQ0128MXQD7TnfHyK4oWoIJQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@emnapi/core": "^1.4.3", + "@emnapi/runtime": "^1.4.3", + "@tybys/wasm-util": "^0.10.0" + } + }, + "node_modules/@node-rs/argon2": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2/-/argon2-2.0.2.tgz", + "integrity": "sha512-t64wIsPEtNd4aUPuTAyeL2ubxATCBGmeluaKXEMAFk/8w6AJIVVkeLKMBpgLW6LU2t5cQxT+env/c6jxbtTQBg==", + "license": "MIT", + "engines": { + "node": ">= 10" + }, + "optionalDependencies": { + "@node-rs/argon2-android-arm-eabi": "2.0.2", + "@node-rs/argon2-android-arm64": "2.0.2", + "@node-rs/argon2-darwin-arm64": "2.0.2", + "@node-rs/argon2-darwin-x64": "2.0.2", + "@node-rs/argon2-freebsd-x64": "2.0.2", + "@node-rs/argon2-linux-arm-gnueabihf": "2.0.2", + "@node-rs/argon2-linux-arm64-gnu": "2.0.2", + "@node-rs/argon2-linux-arm64-musl": "2.0.2", + "@node-rs/argon2-linux-x64-gnu": "2.0.2", + "@node-rs/argon2-linux-x64-musl": "2.0.2", + "@node-rs/argon2-wasm32-wasi": "2.0.2", + "@node-rs/argon2-win32-arm64-msvc": "2.0.2", + "@node-rs/argon2-win32-ia32-msvc": "2.0.2", + "@node-rs/argon2-win32-x64-msvc": "2.0.2" + } + }, + "node_modules/@node-rs/argon2-android-arm-eabi": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm-eabi/-/argon2-android-arm-eabi-2.0.2.tgz", + "integrity": "sha512-DV/H8p/jt40lrao5z5g6nM9dPNPGEHL+aK6Iy/og+dbL503Uj0AHLqj1Hk9aVUSCNnsDdUEKp4TVMi0YakDYKw==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-android-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-android-arm64/-/argon2-android-arm64-2.0.2.tgz", + "integrity": "sha512-1LKwskau+8O1ktKx7TbK7jx1oMOMt4YEXZOdSNIar1TQKxm6isZ0cRXgHLibPHEcNHgYRsJWDE9zvDGBB17QDg==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-darwin-arm64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-arm64/-/argon2-darwin-arm64-2.0.2.tgz", + "integrity": "sha512-3TTNL/7wbcpNju5YcqUrCgXnXUSbD7ogeAKatzBVHsbpjZQbNb1NDxDjqqrWoTt6XL3z9mJUMGwbAk7zQltHtA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-darwin-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-darwin-x64/-/argon2-darwin-x64-2.0.2.tgz", + "integrity": "sha512-vNPfkLj5Ij5111UTiYuwgxMqE7DRbOS2y58O2DIySzSHbcnu+nipmRKg+P0doRq6eKIJStyBK8dQi5Ic8pFyDw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-freebsd-x64": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-freebsd-x64/-/argon2-freebsd-x64-2.0.2.tgz", + "integrity": "sha512-M8vQZk01qojQfCqQU0/O1j1a4zPPrz93zc9fSINY7Q/6RhQRBCYwDw7ltDCZXg5JRGlSaeS8cUXWyhPGar3cGg==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm-gnueabihf": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm-gnueabihf/-/argon2-linux-arm-gnueabihf-2.0.2.tgz", + "integrity": "sha512-7EmmEPHLzcu0G2GDh30L6G48CH38roFC2dqlQJmtRCxs6no3tTE/pvgBGatTp/o2n2oyOJcfmgndVFcUpwMnww==", + "cpu": [ + "arm" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-gnu": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-gnu/-/argon2-linux-arm64-gnu-2.0.2.tgz", + "integrity": "sha512-6lsYh3Ftbk+HAIZ7wNuRF4SZDtxtFTfK+HYFAQQyW7Ig3LHqasqwfUKRXVSV5tJ+xTnxjqgKzvZSUJCAyIfHew==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-arm64-musl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-arm64-musl/-/argon2-linux-arm64-musl-2.0.2.tgz", + "integrity": "sha512-p3YqVMNT/4DNR67tIHTYGbedYmXxW9QlFmF39SkXyEbGQwpgSf6pH457/fyXBIYznTU/smnG9EH+C1uzT5j4hA==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-gnu": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-gnu/-/argon2-linux-x64-gnu-2.0.2.tgz", + "integrity": "sha512-ZM3jrHuJ0dKOhvA80gKJqBpBRmTJTFSo2+xVZR+phQcbAKRlDMSZMFDiKbSTnctkfwNFtjgDdh5g1vaEV04AvA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-linux-x64-musl": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-linux-x64-musl/-/argon2-linux-x64-musl-2.0.2.tgz", + "integrity": "sha512-of5uPqk7oCRF/44a89YlWTEfjsftPywyTULwuFDKyD8QtVZoonrJR6ZWvfFE/6jBT68S0okAkAzzMEdBVWdxWw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-wasm32-wasi": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-wasm32-wasi/-/argon2-wasm32-wasi-2.0.2.tgz", + "integrity": "sha512-U3PzLYKSQYzTERstgtHLd4ZTkOF9co57zTXT77r0cVUsleGZOrd6ut7rHzeWwoJSiHOVxxa0OhG1JVQeB7lLoQ==", + "cpu": [ + "wasm32" + ], + "license": "MIT", + "optional": true, + "dependencies": { + "@napi-rs/wasm-runtime": "^0.2.5" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/@node-rs/argon2-win32-arm64-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-arm64-msvc/-/argon2-win32-arm64-msvc-2.0.2.tgz", + "integrity": "sha512-Eisd7/NM0m23ijrGr6xI2iMocdOuyl6gO27gfMfya4C5BODbUSP7ljKJ7LrA0teqZMdYHesRDzx36Js++/vhiQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-ia32-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-ia32-msvc/-/argon2-win32-ia32-msvc-2.0.2.tgz", + "integrity": "sha512-GsE2ezwAYwh72f9gIjbGTZOf4HxEksb5M2eCaj+Y5rGYVwAdt7C12Q2e9H5LRYxWcFvLH4m4jiSZpQQ4upnPAQ==", + "cpu": [ + "ia32" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@node-rs/argon2-win32-x64-msvc": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@node-rs/argon2-win32-x64-msvc/-/argon2-win32-x64-msvc-2.0.2.tgz", + "integrity": "sha512-cJxWXanH4Ew9CfuZ4IAEiafpOBCe97bzoKowHCGk5lG/7kR4WF/eknnBlHW9m8q7t10mKq75kruPLtbSDqgRTw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.27", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", @@ -1153,6 +1446,16 @@ "win32" ] }, + "node_modules/@tybys/wasm-util": { + "version": "0.10.2", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", + "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, "node_modules/@types/babel__core": { "version": "7.20.5", "resolved": "https://registry.npmjs.org/@types/babel__core/-/babel__core-7.20.5.tgz", @@ -2691,6 +2994,13 @@ "node": ">=0.6" } }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD", + "optional": true + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", diff --git a/package.json b/package.json index b86d05e..2c9f2db 100644 --- a/package.json +++ b/package.json @@ -8,9 +8,11 @@ "dev:web": "vite", "dev:server": "node server/index.mjs", "build": "vite build", - "preview": "vite preview" + "preview": "node scripts/preview.mjs", + "dev:preview": "vite preview" }, "dependencies": { + "@node-rs/argon2": "^2.0.2", "express": "^4.21.2", "mysql2": "^3.22.5", "react": "^19.0.0", diff --git a/scripts/preview.mjs b/scripts/preview.mjs new file mode 100644 index 0000000..259c333 --- /dev/null +++ b/scripts/preview.mjs @@ -0,0 +1,43 @@ +#!/usr/bin/env node +import { spawn } from 'node:child_process'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; + +const root = path.join(path.dirname(fileURLToPath(import.meta.url)), '..'); +const args = process.argv.slice(2); + +function spawnChild(command, args, label) { + const child = spawn(command, args, { + cwd: root, + stdio: 'inherit', + env: process.env, + }); + child.on('exit', (code, signal) => { + if (signal) return; + if (code && code !== 0) shutdown(code ?? 1); + }); + return child; +} + +let server; +let web; +let stopping = false; + +function shutdown(code = 0) { + if (stopping) return; + stopping = true; + server?.kill('SIGTERM'); + web?.kill('SIGTERM'); + setTimeout(() => process.exit(code), 200).unref(); +} + +process.on('SIGINT', () => shutdown(0)); +process.on('SIGTERM', () => shutdown(0)); + +console.log('==> 启动 Admin API (memind_adm/server)'); +server = spawnChild('node', ['server/index.mjs'], 'server'); + +setTimeout(() => { + console.log('==> 启动 vite preview @ http://127.0.0.1:5174'); + web = spawnChild('npm', ['run', 'dev:preview', ...args], 'web'); +}, 800); diff --git a/scripts/remote_restart.sh b/scripts/remote_restart.sh index 6f48d39..ef4ceec 100755 --- a/scripts/remote_restart.sh +++ b/scripts/remote_restart.sh @@ -4,9 +4,12 @@ set -euo pipefail ROOT="${ADM_ROOT:-/Users/john/Project/memind_adm}" PORT="${ADM_PORT:-5174}" -NODE_PATH="${LAN_NODE_PATH:-/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin}" +NODE_PATH="${LAN_NODE_PATH:-/opt/homebrew/opt/node@24/bin:/opt/homebrew/opt/node@22/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin}" +API_PORT="${ADM_API_PORT:-8085}" LOG="${ROOT}/adm-preview.log" +API_LOG="${ROOT}/adm-api.log" PID_FILE="${ROOT}/.adm-preview.pid" +API_PID_FILE="${ROOT}/.adm-api.pid" export PATH="${NODE_PATH}:${PATH}" command -v node >/dev/null 2>&1 || { echo "❌ 未找到 node" >&2; exit 1; } @@ -23,19 +26,36 @@ if [[ ! -d node_modules ]]; then npm install fi -echo "==> 停止旧进程 (port ${PORT})..." +echo "==> 停止旧进程 (port ${PORT}, ${API_PORT})..." lsof -ti "TCP:${PORT}" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true +lsof -ti "TCP:${API_PORT}" -sTCP:LISTEN 2>/dev/null | xargs kill 2>/dev/null || true sleep 1 lsof -ti "TCP:${PORT}" -sTCP:LISTEN 2>/dev/null | xargs kill -9 2>/dev/null || true +lsof -ti "TCP:${API_PORT}" -sTCP:LISTEN 2>/dev/null | xargs kill -9 2>/dev/null || true + +echo "==> 启动 Admin API @ 127.0.0.1:${API_PORT}..." +nohup node server/index.mjs >>"${API_LOG}" 2>&1 & +echo $! >"${API_PID_FILE}" +sleep 2 + +if ! curl -sf "http://127.0.0.1:${API_PORT}/health" >/dev/null; then + echo "❌ Admin API 启动失败,最近日志:" >&2 + tail -n 40 "${API_LOG}" >&2 || true + exit 1 +fi echo "==> 启动 vite preview @ 127.0.0.1:${PORT}..." -nohup npm run preview -- --host 127.0.0.1 --port "${PORT}" >>"${LOG}" 2>&1 & +nohup npm run dev:preview -- --host 127.0.0.1 --port "${PORT}" >>"${LOG}" 2>&1 & echo $! >"${PID_FILE}" sleep 2 if curl -sf "http://127.0.0.1:${PORT}/" >/dev/null; then echo "✅ memind_adm 已就绪: http://127.0.0.1:${PORT}/" curl -sf "http://127.0.0.1:${PORT}/" >/dev/null && echo " 首页 OK" + curl -sf -X POST "http://127.0.0.1:${PORT}/auth/login" \ + -H 'Content-Type: application/json' \ + -d '{"username":"admin","password":"981122"}' >/dev/null \ + && echo " 登录 OK" || echo " ⚠️ 登录接口异常,请检查 ${API_LOG}" else echo "❌ 启动失败,最近日志:" >&2 tail -n 40 "${LOG}" >&2 || true diff --git a/server/app.mjs b/server/app.mjs index 5787bc9..f50b53c 100644 --- a/server/app.mjs +++ b/server/app.mjs @@ -1,5 +1,5 @@ import express from 'express'; -import { paginateArray, listUsagePaged, listLedgerPaged } from './pagination.mjs'; +import { listUsagePaged, listLedgerPaged } from './pagination.mjs'; export function createAdminApp(services) { const { userAuth, llmProviderService, pool, ready } = services; @@ -100,22 +100,14 @@ export function createAdminApp(services) { }; adminApi.get('/users', requireAdmin, async (req, res) => { - let users = await userAuth.listUsers(); - const search = typeof req.query.search === 'string' ? req.query.search.trim().toLowerCase() : ''; - const role = typeof req.query.role === 'string' ? req.query.role.trim() : ''; - const status = typeof req.query.status === 'string' ? req.query.status.trim() : ''; - if (search) { - users = users.filter( - (user) => - user.username?.toLowerCase().includes(search) || - user.displayName?.toLowerCase().includes(search) || - user.email?.toLowerCase().includes(search), - ); - } - if (role) users = users.filter((user) => user.role === role); - if (status) users = users.filter((user) => user.status === status); - const paged = paginateArray(users, req.query, 20); - res.json({ users: paged.items, total: paged.total, page: paged.page, pageSize: paged.pageSize }); + const result = await userAuth.listUsers({ + page: Number(req.query.page) || 1, + pageSize: Number(req.query.pageSize) || 20, + search: typeof req.query.search === 'string' ? req.query.search.trim() : '', + role: typeof req.query.role === 'string' ? req.query.role.trim() : '', + status: typeof req.query.status === 'string' ? req.query.status.trim() : '', + }); + res.json(result); }); adminApi.get('/summary', requireAdmin, async (_req, res) => { diff --git a/server/argon2-polyfill.mjs b/server/argon2-polyfill.mjs new file mode 100644 index 0000000..8dfaa34 --- /dev/null +++ b/server/argon2-polyfill.mjs @@ -0,0 +1,23 @@ +import crypto from 'node:crypto'; +import { Algorithm, hashRawSync } from '@node-rs/argon2'; + +/** Node < 24 无 crypto.argon2Sync,Memind user-auth 登录会崩溃。 */ +export function ensureArgon2Sync() { + if (typeof crypto.argon2Sync === 'function') return; + + crypto.argon2Sync = (algorithm, options = {}) => { + if (algorithm !== 'argon2id') { + throw new Error(`unsupported argon2 algorithm: ${algorithm}`); + } + const message = options.message ?? ''; + const nonce = options.nonce ?? Buffer.alloc(0); + return hashRawSync(String(message), { + algorithm: Algorithm.Argon2id, + salt: nonce, + parallelism: options.parallelism ?? 1, + outputLen: options.tagLength ?? 32, + memoryCost: options.memory ?? 65536, + timeCost: options.passes ?? 3, + }); + }; +} diff --git a/server/index.mjs b/server/index.mjs index 52d2067..d44ebee 100644 --- a/server/index.mjs +++ b/server/index.mjs @@ -1,4 +1,7 @@ +import { ensureArgon2Sync } from './argon2-polyfill.mjs'; import { loadProjectEnv } from './load-env.mjs'; + +ensureArgon2Sync(); import { bootstrapAdminServices } from './bootstrap.mjs'; import { importMemind } from './lib-path.mjs'; import { createAdminApp } from './app.mjs';