Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
25e040a5dd | ||
|
|
f2df990746 | ||
|
|
77bc174e43 |
@@ -61,7 +61,16 @@ jobs:
|
||||
STAGING=".ci/docker/backend"
|
||||
rm -rf "$STAGING"
|
||||
mkdir -p "$STAGING/packages/contracts" "$STAGING/backend"
|
||||
cp package.json package-lock.json "$STAGING/"
|
||||
cp package-lock.json "$STAGING/"
|
||||
node <<'NODE'
|
||||
const fs = require("node:fs")
|
||||
const pkg = JSON.parse(fs.readFileSync("package.json", "utf8"))
|
||||
pkg.workspaces = ["packages/*", "backend"]
|
||||
pkg.dependencies = {}
|
||||
pkg.devDependencies = {}
|
||||
delete pkg.scripts
|
||||
fs.writeFileSync(".ci/docker/backend/package.json", `${JSON.stringify(pkg, null, 2)}\n`)
|
||||
NODE
|
||||
cp packages/contracts/package.json packages/contracts/tsconfig.json "$STAGING/packages/contracts/"
|
||||
cp -R packages/contracts/src "$STAGING/packages/contracts/"
|
||||
cp backend/package.json backend/tsconfig.json "$STAGING/backend/"
|
||||
@@ -70,6 +79,7 @@ jobs:
|
||||
cp -R backend/drizzle "$STAGING/backend/"
|
||||
fi
|
||||
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@v3
|
||||
|
||||
|
||||
@@ -135,6 +135,7 @@ sequenceDiagram
|
||||
- **Node.js 22** (как в `Dockerfile.frontend` и `backend/Dockerfile`).
|
||||
- **npm** с workspaces; установка из корня: `npm ci` или `npm install`.
|
||||
- Для нативной сборки `better-sqlite3` на Linux может понадобиться toolchain (`python3`, `make`, `g++`); в Docker-образе backend они уже ставятся.
|
||||
- Backend Docker-образ ставит только workspaces `backend` + `contracts` (без корневых Next/React deps); в production логи — JSON без `pino-pretty`.
|
||||
|
||||
### Запуск
|
||||
|
||||
|
||||
@@ -875,8 +875,11 @@ export default function SettingsPage() {
|
||||
await evo.saveSettings(patch)
|
||||
setEvoKeyDraft("")
|
||||
markSaved()
|
||||
toast.success("Настройки EvoBGP сохранены")
|
||||
} catch (e) {
|
||||
setEvoSaveErr(e instanceof Error ? e.message : "Ошибка сохранения")
|
||||
const msg = e instanceof Error ? e.message : "Ошибка сохранения"
|
||||
setEvoSaveErr(msg)
|
||||
toast.error(msg)
|
||||
} finally {
|
||||
setEvoSaveBusy(false)
|
||||
}
|
||||
|
||||
+19
-2
@@ -5,10 +5,23 @@ RUN apt-get update \
|
||||
&& apt-get install -y --no-install-recommends python3 make g++ \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
WORKDIR /app
|
||||
# Do not set NODE_ENV=production here — npm would omit typescript needed for the build stage.
|
||||
COPY package.json package-lock.json ./
|
||||
COPY packages/contracts/package.json packages/contracts/
|
||||
COPY backend/package.json backend/
|
||||
RUN npm ci --workspace=@mmapp/contracts --workspace=mikrotik-manager-backend --include-workspace-root --ignore-scripts \
|
||||
# Drop root frontend deps (Next/React/UI) so backend image stays lean.
|
||||
RUN node -e "\
|
||||
const fs=require('fs');\
|
||||
const p=JSON.parse(fs.readFileSync('package.json','utf8'));\
|
||||
p.dependencies={};\
|
||||
p.devDependencies={};\
|
||||
delete p.scripts;\
|
||||
p.workspaces=['packages/*','backend'];\
|
||||
fs.writeFileSync('package.json', JSON.stringify(p,null,2)+'\\n');\
|
||||
"
|
||||
# Prefer npm ci; if lockfile rejects stripped root package.json, fall back to install.
|
||||
RUN (npm ci --workspace=@mmapp/contracts --workspace=mikrotik-manager-backend --ignore-scripts \
|
||||
|| npm install --workspace=@mmapp/contracts --workspace=mikrotik-manager-backend --ignore-scripts) \
|
||||
&& npm rebuild better-sqlite3
|
||||
|
||||
FROM deps AS build
|
||||
@@ -18,7 +31,9 @@ COPY packages/contracts packages/contracts
|
||||
COPY backend backend
|
||||
RUN npm run build -w @mmapp/contracts \
|
||||
&& npm run build -w mikrotik-manager-backend \
|
||||
&& npm prune --omit=dev
|
||||
&& npm prune --omit=dev \
|
||||
# npm may nest workspace deps (e.g. dotenv) under backend/node_modules — keep dir for COPY
|
||||
&& mkdir -p backend/node_modules
|
||||
|
||||
FROM node:22-bookworm-slim AS runner
|
||||
WORKDIR /app
|
||||
@@ -32,6 +47,8 @@ COPY --from=build /app/node_modules ./node_modules
|
||||
COPY --from=build /app/packages/contracts ./packages/contracts
|
||||
COPY --from=build /app/backend/dist ./backend/dist
|
||||
COPY --from=build /app/backend/package.json ./backend/package.json
|
||||
# Nested install from lockfile (dotenv etc.) — ESM resolves from /app/backend/dist → ../node_modules
|
||||
COPY --from=build /app/backend/node_modules ./backend/node_modules
|
||||
RUN mkdir -p /app/data
|
||||
EXPOSE 8000
|
||||
CMD ["node", "backend/dist/index.js"]
|
||||
|
||||
@@ -24,7 +24,6 @@
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"fastify": "^5.8.5",
|
||||
"fastify-plugin": "^5.1.0",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"undici": "^8.1.0",
|
||||
"zod": "^4.4.1"
|
||||
},
|
||||
@@ -33,6 +32,7 @@
|
||||
"@types/node": "^22.15.3",
|
||||
"drizzle-kit": "^0.31.10",
|
||||
"jose": "^6.2.11",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
|
||||
+13
-9
@@ -29,22 +29,26 @@ export async function buildApp(opts?: {
|
||||
logger?: boolean
|
||||
startScheduler?: boolean
|
||||
}): Promise<FastifyInstance> {
|
||||
const usePrettyLogger =
|
||||
opts?.logger !== false && process.env.NODE_ENV !== "production"
|
||||
const app = Fastify({
|
||||
bodyLimit: 512 * 1024 * 1024,
|
||||
requestTimeout: 10 * 60 * 1000,
|
||||
logger:
|
||||
opts?.logger === false
|
||||
? false
|
||||
: {
|
||||
transport: {
|
||||
target: "pino-pretty",
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: "HH:MM:ss",
|
||||
ignore: "pid,hostname",
|
||||
: usePrettyLogger
|
||||
? {
|
||||
transport: {
|
||||
target: "pino-pretty",
|
||||
options: {
|
||||
colorize: true,
|
||||
translateTime: "HH:MM:ss",
|
||||
ignore: "pid,hostname",
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
: true,
|
||||
})
|
||||
|
||||
app.setValidatorCompiler(validatorCompiler)
|
||||
|
||||
@@ -37,6 +37,12 @@ function normalizeBaseUrl(raw: string): string {
|
||||
}
|
||||
}
|
||||
|
||||
/** Сырой API-ключ без префикса Bearer (иначе EvoBGP получит `Bearer Bearer …`). */
|
||||
function normalizeApiKey(raw: string): string {
|
||||
const trimmed = raw.trim()
|
||||
return trimmed.replace(/^Bearer\s+/i, "").trim()
|
||||
}
|
||||
|
||||
interface EvoCatalogRaw {
|
||||
modules: { items: Array<{ id: string; name: string; type: string }> }
|
||||
domains: {
|
||||
@@ -158,7 +164,7 @@ async function fetchEvoJson<T>(root: string, path: string, token: string): Promi
|
||||
function credentialsFromDb(): { root: string; apiKey: string } | null {
|
||||
const row = ensureEvobgpRow()
|
||||
const root = normalizeBaseUrl(row.baseUrl)
|
||||
const apiKey = row.apiKey.trim()
|
||||
const apiKey = normalizeApiKey(row.apiKey)
|
||||
if (!root || !apiKey) return null
|
||||
return { root, apiKey }
|
||||
}
|
||||
@@ -168,8 +174,8 @@ const evobgpRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
const row = ensureEvobgpRow()
|
||||
return reply.send({
|
||||
baseUrl: row.baseUrl ?? "",
|
||||
enabled: row.enabled ?? false,
|
||||
secretConfigured: Boolean(row.apiKey?.trim()),
|
||||
enabled: Boolean(row.enabled),
|
||||
secretConfigured: Boolean(normalizeApiKey(row.apiKey ?? "")),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -183,10 +189,15 @@ const evobgpRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
let nextEnabled = cur.enabled
|
||||
let nextKey = cur.apiKey
|
||||
|
||||
if (parsed.data.baseUrl !== undefined) nextBase = parsed.data.baseUrl.trim()
|
||||
if (parsed.data.baseUrl !== undefined) {
|
||||
nextBase = normalizeBaseUrl(parsed.data.baseUrl)
|
||||
}
|
||||
if (parsed.data.enabled !== undefined) nextEnabled = parsed.data.enabled
|
||||
if (parsed.data.apiKey !== undefined) {
|
||||
nextKey = parsed.data.apiKey === null || parsed.data.apiKey === "" ? "" : parsed.data.apiKey.trim()
|
||||
nextKey =
|
||||
parsed.data.apiKey === null || parsed.data.apiKey === ""
|
||||
? ""
|
||||
: normalizeApiKey(parsed.data.apiKey)
|
||||
}
|
||||
|
||||
db.update(evobgpSettings)
|
||||
@@ -202,8 +213,8 @@ const evobgpRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
const row = ensureEvobgpRow()
|
||||
return reply.send({
|
||||
baseUrl: row.baseUrl ?? "",
|
||||
enabled: row.enabled ?? false,
|
||||
secretConfigured: Boolean(row.apiKey?.trim()),
|
||||
enabled: Boolean(row.enabled),
|
||||
secretConfigured: Boolean(normalizeApiKey(row.apiKey ?? "")),
|
||||
})
|
||||
})
|
||||
|
||||
@@ -223,7 +234,7 @@ const evobgpRoutes: FastifyPluginAsyncZod = async (app) => {
|
||||
const keyRaw =
|
||||
d.apiKey !== undefined && d.apiKey.trim() !== "" ? d.apiKey : row.apiKey
|
||||
const root = normalizeBaseUrl(urlRaw.trim())
|
||||
const token = keyRaw.trim()
|
||||
const token = normalizeApiKey(keyRaw)
|
||||
if (!root || !token) {
|
||||
return reply.status(400).send({
|
||||
error: "Нужны базовый URL и API-ключ (в форме или уже сохранённые в БД)",
|
||||
|
||||
+56
-57
@@ -10,6 +10,7 @@ import {
|
||||
} from "react"
|
||||
import { useDataSource } from "@/lib/data-source"
|
||||
import type { Domain, IpRange, Asn } from "@/lib/data"
|
||||
import { ApiClientError, requestJson } from "@/shared/api/http-client"
|
||||
|
||||
export interface EvoBgpCommunityRow {
|
||||
id: string
|
||||
@@ -66,6 +67,12 @@ interface EvoBgpContextValue {
|
||||
|
||||
const EvoBgpContext = createContext<EvoBgpContextValue | null>(null)
|
||||
|
||||
function errorMessage(e: unknown, fallback: string): string {
|
||||
if (e instanceof ApiClientError) return e.message || fallback
|
||||
if (e instanceof Error) return e.message || fallback
|
||||
return fallback
|
||||
}
|
||||
|
||||
export function EvoBGPProvider({ children }: { children: React.ReactNode }) {
|
||||
const { mode, backendUrl, backendStatus } = useDataSource()
|
||||
const [baseUrl, setBaseUrlState] = useState("")
|
||||
@@ -86,24 +93,15 @@ export function EvoBGPProvider({ children }: { children: React.ReactNode }) {
|
||||
setLoading(true)
|
||||
setError(null)
|
||||
try {
|
||||
const res = await fetch(`${backendUrl.replace(/\/$/, "")}/api/evobgp/catalog`, {
|
||||
method: "POST",
|
||||
})
|
||||
const text = await res.text()
|
||||
if (!res.ok) {
|
||||
let msg = res.statusText
|
||||
try {
|
||||
const j = JSON.parse(text) as { error?: string; detail?: string }
|
||||
msg = j.error ?? j.detail ?? msg
|
||||
} catch {
|
||||
if (text) msg = text
|
||||
}
|
||||
throw new Error(msg || "Ошибка EvoBGP")
|
||||
}
|
||||
setSnapshot(JSON.parse(text) as EvoBgpCatalogSnapshot)
|
||||
const data = await requestJson<EvoBgpCatalogSnapshot>(
|
||||
backendUrl,
|
||||
"/api/evobgp/catalog",
|
||||
{ method: "POST" },
|
||||
)
|
||||
setSnapshot(data)
|
||||
} catch (e) {
|
||||
setSnapshot(null)
|
||||
setError(e instanceof Error ? e.message : "Ошибка загрузки")
|
||||
setError(errorMessage(e, "Ошибка загрузки"))
|
||||
} finally {
|
||||
setLoading(false)
|
||||
}
|
||||
@@ -119,16 +117,19 @@ export function EvoBGPProvider({ children }: { children: React.ReactNode }) {
|
||||
return
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`${backendUrl.replace(/\/$/, "")}/api/evobgp/settings`)
|
||||
if (!res.ok) throw new Error(await res.text())
|
||||
const data = (await res.json()) as EvoBgpSettingsDto
|
||||
const data = await requestJson<EvoBgpSettingsDto>(
|
||||
backendUrl,
|
||||
"/api/evobgp/settings",
|
||||
)
|
||||
setBaseUrlState(data.baseUrl ?? "")
|
||||
setEnabledState(data.enabled ?? false)
|
||||
setSecretConfigured(data.secretConfigured ?? false)
|
||||
setEnabledState(Boolean(data.enabled))
|
||||
setSecretConfigured(Boolean(data.secretConfigured))
|
||||
setSettingsLoaded(true)
|
||||
await pullCatalog(data.enabled ?? false)
|
||||
} catch {
|
||||
setError(null)
|
||||
await pullCatalog(Boolean(data.enabled))
|
||||
} catch (e) {
|
||||
setSettingsLoaded(true)
|
||||
setError(errorMessage(e, "Не удалось загрузить настройки EvoBGP"))
|
||||
}
|
||||
}, [mode, backendStatus, backendUrl, pullCatalog])
|
||||
|
||||
@@ -140,27 +141,25 @@ export function EvoBGPProvider({ children }: { children: React.ReactNode }) {
|
||||
|
||||
const saveSettings = useCallback(
|
||||
async (patch: EvoBgpSavePayload) => {
|
||||
const res = await fetch(`${backendUrl.replace(/\/$/, "")}/api/evobgp/settings`, {
|
||||
method: "PUT",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(patch),
|
||||
})
|
||||
const text = await res.text()
|
||||
if (!res.ok) {
|
||||
let msg = res.statusText
|
||||
try {
|
||||
const j = JSON.parse(text) as { error?: string }
|
||||
msg = j.error ?? msg
|
||||
} catch {
|
||||
if (text) msg = text
|
||||
}
|
||||
throw new Error(msg || "Не удалось сохранить")
|
||||
}
|
||||
const data = JSON.parse(text) as EvoBgpSettingsDto
|
||||
const data = await requestJson<EvoBgpSettingsDto>(
|
||||
backendUrl,
|
||||
"/api/evobgp/settings",
|
||||
{
|
||||
method: "PUT",
|
||||
body: JSON.stringify(patch),
|
||||
},
|
||||
)
|
||||
const nextEnabled = Boolean(data.enabled)
|
||||
setBaseUrlState(data.baseUrl ?? "")
|
||||
setEnabledState(data.enabled ?? false)
|
||||
setSecretConfigured(data.secretConfigured ?? false)
|
||||
await pullCatalog(data.enabled ?? false)
|
||||
setEnabledState(nextEnabled)
|
||||
setSecretConfigured(Boolean(data.secretConfigured))
|
||||
setError(null)
|
||||
// Каталог не должен ронять успех сохранения (401/502 на catalog ≠ «настройки не сохранились»)
|
||||
try {
|
||||
await pullCatalog(nextEnabled)
|
||||
} catch {
|
||||
/* pullCatalog already sets error state */
|
||||
}
|
||||
},
|
||||
[backendUrl, pullCatalog],
|
||||
)
|
||||
@@ -169,20 +168,20 @@ export function EvoBGPProvider({ children }: { children: React.ReactNode }) {
|
||||
await pullCatalog(enabled)
|
||||
}, [enabled, pullCatalog])
|
||||
|
||||
const testConnection = useCallback(async (draft?: EvoBgpTestDraft) => {
|
||||
try {
|
||||
const res = await fetch(`${backendUrl.replace(/\/$/, "")}/api/evobgp/test`, {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(draft ?? {}),
|
||||
})
|
||||
const data = (await res.json().catch(() => ({}))) as { ok?: boolean; error?: string }
|
||||
if (!res.ok) throw new Error(data.error ?? res.statusText)
|
||||
return { ok: true, message: "Соединение с EvoBGP установлено" }
|
||||
} catch (e) {
|
||||
return { ok: false, message: e instanceof Error ? e.message : "Ошибка" }
|
||||
}
|
||||
}, [backendUrl])
|
||||
const testConnection = useCallback(
|
||||
async (draft?: EvoBgpTestDraft) => {
|
||||
try {
|
||||
await requestJson<{ ok?: boolean }>(backendUrl, "/api/evobgp/test", {
|
||||
method: "POST",
|
||||
body: JSON.stringify(draft ?? {}),
|
||||
})
|
||||
return { ok: true, message: "Соединение с EvoBGP установлено" }
|
||||
} catch (e) {
|
||||
return { ok: false, message: errorMessage(e, "Ошибка") }
|
||||
}
|
||||
},
|
||||
[backendUrl],
|
||||
)
|
||||
|
||||
const value = useMemo(
|
||||
() => ({
|
||||
|
||||
Generated
+73
-1
@@ -58,7 +58,6 @@
|
||||
"drizzle-orm": "^0.45.2",
|
||||
"fastify": "^5.8.5",
|
||||
"fastify-plugin": "^5.1.0",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"undici": "^8.1.0",
|
||||
"zod": "^4.4.1"
|
||||
},
|
||||
@@ -67,6 +66,7 @@
|
||||
"@types/node": "^22.15.3",
|
||||
"drizzle-kit": "^0.31.10",
|
||||
"jose": "^6.2.11",
|
||||
"pino-pretty": "^13.1.3",
|
||||
"tsx": "^4.19.3",
|
||||
"typescript": "^5.8.3"
|
||||
}
|
||||
@@ -3951,6 +3951,70 @@
|
||||
"node": ">=14.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/core": {
|
||||
"version": "1.8.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/wasi-threads": "1.1.0",
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/runtime": {
|
||||
"version": "1.8.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@emnapi/wasi-threads": {
|
||||
"version": "1.1.0",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@napi-rs/wasm-runtime": {
|
||||
"version": "1.1.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"@emnapi/core": "^1.7.1",
|
||||
"@emnapi/runtime": "^1.7.1",
|
||||
"@tybys/wasm-util": "^0.10.1"
|
||||
},
|
||||
"funding": {
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/Brooooooklyn"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/@tybys/wasm-util": {
|
||||
"version": "0.10.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "MIT",
|
||||
"optional": true,
|
||||
"dependencies": {
|
||||
"tslib": "^2.4.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-wasm32-wasi/node_modules/tslib": {
|
||||
"version": "2.8.1",
|
||||
"dev": true,
|
||||
"inBundle": true,
|
||||
"license": "0BSD",
|
||||
"optional": true
|
||||
},
|
||||
"node_modules/@tailwindcss/oxide-win32-arm64-msvc": {
|
||||
"version": "4.2.4",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/oxide-win32-arm64-msvc/-/oxide-win32-arm64-msvc-4.2.4.tgz",
|
||||
@@ -5741,6 +5805,7 @@
|
||||
"version": "2.0.20",
|
||||
"resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz",
|
||||
"integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/combined-stream": {
|
||||
@@ -5967,6 +6032,7 @@
|
||||
"version": "4.6.3",
|
||||
"resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz",
|
||||
"integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "*"
|
||||
@@ -7237,6 +7303,7 @@
|
||||
"version": "4.0.3",
|
||||
"resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-4.0.3.tgz",
|
||||
"integrity": "sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-decode-uri-component": {
|
||||
@@ -7370,6 +7437,7 @@
|
||||
"version": "2.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz",
|
||||
"integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/fast-string-truncated-width": {
|
||||
@@ -8166,6 +8234,7 @@
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz",
|
||||
"integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/hermes-estree": {
|
||||
@@ -8977,6 +9046,7 @@
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz",
|
||||
"integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=10"
|
||||
@@ -10573,6 +10643,7 @@
|
||||
"version": "13.1.3",
|
||||
"resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.3.tgz",
|
||||
"integrity": "sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"colorette": "^2.0.7",
|
||||
@@ -10597,6 +10668,7 @@
|
||||
"version": "5.0.3",
|
||||
"resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz",
|
||||
"integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14.16"
|
||||
|
||||
Reference in New Issue
Block a user