feat: 新增web配置中心
This commit is contained in:
parent
cab53e7947
commit
8d20792fc3
7
.gitignore
vendored
Normal file
7
.gitignore
vendored
Normal file
@ -0,0 +1,7 @@
|
||||
node_modules/
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
14
README.md
14
README.md
@ -95,7 +95,21 @@ npm run typecheck
|
||||
| 方法 | 路径 | 说明 |
|
||||
| --- | --- | --- |
|
||||
| `GET` | `http://localhost:<proxyPort>/__config` | 查看当前路由与配置 |
|
||||
| `POST` | `http://localhost:<proxyPort>/__config` | 保存 `config.json`(包含 `routes` 与 `config`) |
|
||||
| `POST` | `http://localhost:<proxyPort>/__reload-config` | 手动重新加载 `config.json` |
|
||||
| `POST` | `http://localhost:<proxyPort>/__routes` | 动态新增单个路由与 mock 文件 |
|
||||
| `GET` | `http://localhost:<proxyPort>/__admin` | 配置管理页面(Element UI) |
|
||||
|
||||
#### `POST /__routes` 请求示例
|
||||
|
||||
```json
|
||||
{
|
||||
"route": "/api/new/mock",
|
||||
"filePath": "mock/new-api.json",
|
||||
"fileContent": "{\"code\":0,\"message\":\"ok\"}",
|
||||
"overwrite": false
|
||||
}
|
||||
```
|
||||
|
||||
### TypeScript 与编译说明
|
||||
|
||||
|
||||
293
admin.html
Normal file
293
admin.html
Normal file
@ -0,0 +1,293 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>API Proxy Mock 配置管理</title>
|
||||
<link
|
||||
rel="stylesheet"
|
||||
href="https://unpkg.com/element-ui/lib/theme-chalk/index.css"
|
||||
/>
|
||||
<style>
|
||||
body {
|
||||
margin: 0;
|
||||
background: #f5f7fa;
|
||||
}
|
||||
.container {
|
||||
max-width: 1200px;
|
||||
margin: 20px auto;
|
||||
padding: 0 16px 24px;
|
||||
}
|
||||
.section-card {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.actions {
|
||||
display: flex;
|
||||
gap: 8px;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.small-text {
|
||||
color: #909399;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="app" class="container">
|
||||
<el-card class="section-card">
|
||||
<div slot="header"><strong>基础配置</strong></div>
|
||||
<el-form :model="form.config" label-width="180px">
|
||||
<el-form-item label="Mock 开关">
|
||||
<el-switch v-model="form.config.mockEnabled"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="自动重载配置文件">
|
||||
<el-switch v-model="form.config.reloadOnChange"></el-switch>
|
||||
</el-form-item>
|
||||
<el-form-item label="默认响应 Content-Type">
|
||||
<el-input v-model="form.config.defaultContentType"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="本地代理端口">
|
||||
<el-input-number
|
||||
v-model="form.config.proxyPort"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标主机">
|
||||
<el-input v-model="form.config.targetHost"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标端口">
|
||||
<el-input-number
|
||||
v-model="form.config.targetPort"
|
||||
:min="1"
|
||||
:max="65535"
|
||||
></el-input-number>
|
||||
</el-form-item>
|
||||
<el-form-item label="目标 HTTPS">
|
||||
<el-switch v-model="form.config.targetHttps"></el-switch>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</el-card>
|
||||
|
||||
<el-card class="section-card">
|
||||
<div slot="header" style="display:flex;justify-content:space-between;align-items:center;">
|
||||
<strong>路由配置(routes)</strong>
|
||||
<el-button size="mini" type="primary" @click="addRouteRow">新增一行</el-button>
|
||||
</div>
|
||||
<el-table :data="form.routes" border>
|
||||
<el-table-column label="请求路径" min-width="260">
|
||||
<template slot-scope="scope">
|
||||
<el-input v-model="scope.row.route" placeholder="/api/example"></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="Mock 文件路径" min-width="300">
|
||||
<template slot-scope="scope">
|
||||
<el-input
|
||||
v-model="scope.row.filePath"
|
||||
placeholder="mock/example.json"
|
||||
></el-input>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="120">
|
||||
<template slot-scope="scope">
|
||||
<el-button size="mini" type="danger" @click="removeRoute(scope.$index)">删除</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<div class="small-text" style="margin-top:8px;">
|
||||
说明:如果路由以 # 开头,会作为注释保留,不参与 mock 命中。
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="section-card">
|
||||
<div slot="header"><strong>动态新增接口配置文件</strong></div>
|
||||
<el-form :model="newRoute" label-width="180px">
|
||||
<el-form-item label="内容模板">
|
||||
<el-select
|
||||
v-model="newRoute.template"
|
||||
placeholder="请选择模板"
|
||||
@change="applyTemplate"
|
||||
style="width: 100%;"
|
||||
>
|
||||
<el-option label="自定义内容" value="custom"></el-option>
|
||||
<el-option label="基础失败模板" value="basicError"></el-option>
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
<el-form-item label="请求路径">
|
||||
<el-input v-model="newRoute.route" placeholder="/api/new/mock"></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="Mock 文件路径">
|
||||
<el-input
|
||||
v-model="newRoute.filePath"
|
||||
placeholder="mock/new-api.json"
|
||||
:disabled="newRoute.template === 'basicError'"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="文件内容">
|
||||
<el-input
|
||||
type="textarea"
|
||||
:rows="8"
|
||||
v-model="newRoute.fileContent"
|
||||
placeholder='{"code":0,"message":"ok"}'
|
||||
:disabled="newRoute.template === 'basicError'"
|
||||
></el-input>
|
||||
</el-form-item>
|
||||
<el-form-item label="覆盖已存在文件">
|
||||
<el-switch
|
||||
v-model="newRoute.overwrite"
|
||||
:disabled="newRoute.template === 'basicError'"
|
||||
></el-switch>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<el-button type="success" @click="createRouteAndFile">创建路由+文件</el-button>
|
||||
</el-card>
|
||||
|
||||
<el-card>
|
||||
<div class="actions">
|
||||
<el-button type="primary" @click="saveConfig">保存配置到 config.json</el-button>
|
||||
<el-button @click="reloadConfig">重载服务内存配置</el-button>
|
||||
<el-button @click="loadConfig">刷新页面数据</el-button>
|
||||
</div>
|
||||
</el-card>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/vue@2/dist/vue.js"></script>
|
||||
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
|
||||
<script>
|
||||
new Vue({
|
||||
el: "#app",
|
||||
data: function () {
|
||||
return {
|
||||
form: {
|
||||
config: {
|
||||
mockEnabled: true,
|
||||
cacheConfig: true,
|
||||
reloadOnChange: true,
|
||||
defaultContentType: "application/json",
|
||||
proxyPort: 8877,
|
||||
targetHost: "",
|
||||
targetPort: 443,
|
||||
targetHttps: true,
|
||||
},
|
||||
routes: [],
|
||||
},
|
||||
newRoute: {
|
||||
template: "custom",
|
||||
route: "",
|
||||
filePath: "mock/",
|
||||
fileContent: "",
|
||||
overwrite: false,
|
||||
},
|
||||
};
|
||||
},
|
||||
created: function () {
|
||||
this.loadConfig();
|
||||
},
|
||||
methods: {
|
||||
toRouteArray: function (routesObj) {
|
||||
return Object.keys(routesObj || {}).map(function (route) {
|
||||
return { route: route, filePath: routesObj[route] };
|
||||
});
|
||||
},
|
||||
toRouteObject: function (routeArray) {
|
||||
var obj = {};
|
||||
(routeArray || []).forEach(function (item) {
|
||||
var route = (item.route || "").trim();
|
||||
var filePath = (item.filePath || "").trim();
|
||||
if (route && filePath) {
|
||||
obj[route] = filePath;
|
||||
}
|
||||
});
|
||||
return obj;
|
||||
},
|
||||
addRouteRow: function () {
|
||||
this.form.routes.push({ route: "", filePath: "" });
|
||||
},
|
||||
removeRoute: function (index) {
|
||||
this.form.routes.splice(index, 1);
|
||||
},
|
||||
loadConfig: async function () {
|
||||
try {
|
||||
var resp = await fetch("/__config");
|
||||
var data = await resp.json();
|
||||
this.form.config = Object.assign({}, this.form.config, data.config || {});
|
||||
this.form.routes = this.toRouteArray(data.routes || {});
|
||||
} catch (err) {
|
||||
this.$message.error("加载配置失败: " + err.message);
|
||||
}
|
||||
},
|
||||
saveConfig: async function () {
|
||||
var payload = {
|
||||
config: this.form.config,
|
||||
routes: this.toRouteObject(this.form.routes),
|
||||
};
|
||||
try {
|
||||
var resp = await fetch("/__config", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(payload),
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (!resp.ok || data.success === false) {
|
||||
throw new Error(data.error || "保存失败");
|
||||
}
|
||||
this.$message.success("配置已保存");
|
||||
} catch (err) {
|
||||
this.$message.error("保存失败: " + err.message);
|
||||
}
|
||||
},
|
||||
reloadConfig: async function () {
|
||||
try {
|
||||
var resp = await fetch("/__reload-config", { method: "POST" });
|
||||
var data = await resp.json();
|
||||
if (!resp.ok || data.success === false) {
|
||||
throw new Error(data.error || "重载失败");
|
||||
}
|
||||
this.$message.success("配置已重载");
|
||||
this.loadConfig();
|
||||
} catch (err) {
|
||||
this.$message.error("重载失败: " + err.message);
|
||||
}
|
||||
},
|
||||
createRouteAndFile: async function () {
|
||||
try {
|
||||
var resp = await fetch("/__routes", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify(
|
||||
Object.assign({}, this.newRoute, {
|
||||
useExistingFile: this.newRoute.template === "basicError",
|
||||
}),
|
||||
),
|
||||
});
|
||||
var data = await resp.json();
|
||||
if (!resp.ok || data.success === false) {
|
||||
throw new Error(data.error || "创建失败");
|
||||
}
|
||||
this.$message.success("接口配置文件创建成功");
|
||||
this.newRoute.template = "custom";
|
||||
this.newRoute.route = "";
|
||||
this.newRoute.filePath = "mock/";
|
||||
this.newRoute.fileContent = "";
|
||||
this.newRoute.overwrite = false;
|
||||
this.loadConfig();
|
||||
} catch (err) {
|
||||
this.$message.error("创建失败: " + err.message);
|
||||
}
|
||||
},
|
||||
applyTemplate: function (template) {
|
||||
if (template === "basicError") {
|
||||
this.newRoute.filePath = "mock/basic-error.json";
|
||||
this.newRoute.fileContent =
|
||||
'{"code": -1, "success": false, "msg":"失败"}';
|
||||
this.newRoute.overwrite = false;
|
||||
return;
|
||||
}
|
||||
this.newRoute.filePath = "mock/";
|
||||
this.newRoute.fileContent = "";
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@ -1,6 +1,7 @@
|
||||
{
|
||||
"routes": {
|
||||
"/api2/admin/banner/list": "mock/banner.txt"
|
||||
"/api2/admin/banner/list": "mock/banner.txt",
|
||||
"/api2/home/ad/list": "mock/basic-error.json"
|
||||
},
|
||||
"config": {
|
||||
"mockEnabled": true,
|
||||
|
||||
171
index.api.ts
171
index.api.ts
@ -33,6 +33,7 @@ interface ConfigFile {
|
||||
|
||||
// 存储当前的路由配置
|
||||
let MOCK_ROUTES: RouteConfig = {};
|
||||
let RAW_ROUTES: RouteConfig = {};
|
||||
let CONFIG: AppConfig = {
|
||||
cacheConfig: true,
|
||||
reloadOnChange: true,
|
||||
@ -110,13 +111,15 @@ function loadConfig() {
|
||||
const configData: ConfigFile = JSON.parse(
|
||||
fs.readFileSync(CONFIG_FILE, "utf-8"),
|
||||
);
|
||||
MOCK_ROUTES = filterActiveRoutes(configData.routes || {});
|
||||
RAW_ROUTES = configData.routes || {};
|
||||
MOCK_ROUTES = filterActiveRoutes(RAW_ROUTES);
|
||||
CONFIG = configData.config || CONFIG;
|
||||
} else {
|
||||
const configData: ConfigFile = JSON.parse(
|
||||
fs.readFileSync(CONFIG_FILE, "utf-8"),
|
||||
);
|
||||
MOCK_ROUTES = filterActiveRoutes(configData.routes || {});
|
||||
RAW_ROUTES = configData.routes || {};
|
||||
MOCK_ROUTES = filterActiveRoutes(RAW_ROUTES);
|
||||
CONFIG = configData.config || CONFIG;
|
||||
console.log(
|
||||
`[CONFIG] 配置文件已加载,共 ${Object.keys(MOCK_ROUTES).length} 个路由${CONFIG.mockEnabled !== false ? "" : "(mock 已关闭,全部走代理)"}`,
|
||||
@ -131,6 +134,7 @@ function loadConfig() {
|
||||
MOCK_ROUTES = {
|
||||
"/api2/user/list": "mock/user_list.txt",
|
||||
};
|
||||
RAW_ROUTES = { ...MOCK_ROUTES };
|
||||
CONFIG = {
|
||||
cacheConfig: true,
|
||||
reloadOnChange: true,
|
||||
@ -142,6 +146,31 @@ function loadConfig() {
|
||||
}
|
||||
}
|
||||
|
||||
function buildCurrentConfigFile(): ConfigFile {
|
||||
return {
|
||||
routes: { ...RAW_ROUTES },
|
||||
config: { ...CONFIG },
|
||||
};
|
||||
}
|
||||
|
||||
function saveConfigFile(nextConfig: ConfigFile): void {
|
||||
fs.writeFileSync(CONFIG_FILE, JSON.stringify(nextConfig, null, 2), "utf-8");
|
||||
RAW_ROUTES = nextConfig.routes || {};
|
||||
MOCK_ROUTES = filterActiveRoutes(RAW_ROUTES);
|
||||
CONFIG = nextConfig.config || CONFIG;
|
||||
}
|
||||
|
||||
function readBody(req: http.IncomingMessage): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
const chunks: Buffer[] = [];
|
||||
req.on("data", (chunk: Buffer) => {
|
||||
chunks.push(Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk));
|
||||
});
|
||||
req.on("end", () => resolve(Buffer.concat(chunks).toString("utf-8")));
|
||||
req.on("error", reject);
|
||||
});
|
||||
}
|
||||
|
||||
// 验证mock文件是否存在
|
||||
function validateMockFiles() {
|
||||
console.log(`[CONFIG] 验证mock文件...`);
|
||||
@ -255,27 +284,58 @@ const proxyServer = http.createServer((clientReq, clientRes) => {
|
||||
}
|
||||
|
||||
if (requestPath === "/__config") {
|
||||
if (clientReq.method !== "GET") {
|
||||
if (clientReq.method !== "GET" && clientReq.method !== "POST") {
|
||||
clientRes.writeHead(405, {
|
||||
"Content-Type": "application/json",
|
||||
Allow: "GET",
|
||||
Allow: "GET, POST",
|
||||
});
|
||||
clientRes.end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: "Method Not Allowed",
|
||||
allow: ["GET"],
|
||||
allow: ["GET", "POST"],
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
if (clientReq.method === "POST") {
|
||||
readBody(clientReq)
|
||||
.then((bodyText) => {
|
||||
const body = bodyText ? JSON.parse(bodyText) : {};
|
||||
const nextConfig: ConfigFile = {
|
||||
routes: body.routes || {},
|
||||
config: body.config || CONFIG,
|
||||
};
|
||||
saveConfigFile(nextConfig);
|
||||
validateMockFiles();
|
||||
clientRes.writeHead(200, { "Content-Type": "application/json" });
|
||||
clientRes.end(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: "Configuration saved successfully",
|
||||
totalRoutes: Object.keys(MOCK_ROUTES).length,
|
||||
}),
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
clientRes.writeHead(400, { "Content-Type": "application/json" });
|
||||
clientRes.end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
}),
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
clientRes.writeHead(200, { "Content-Type": "application/json" });
|
||||
clientRes.end(
|
||||
JSON.stringify(
|
||||
{
|
||||
routes: MOCK_ROUTES,
|
||||
routes: RAW_ROUTES,
|
||||
config: CONFIG,
|
||||
timestamp: new Date().toISOString(),
|
||||
totalRoutes: Object.keys(MOCK_ROUTES).length,
|
||||
@ -296,6 +356,105 @@ const proxyServer = http.createServer((clientReq, clientRes) => {
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestPath === "/__routes") {
|
||||
if (clientReq.method !== "POST") {
|
||||
clientRes.writeHead(405, {
|
||||
"Content-Type": "application/json",
|
||||
Allow: "POST",
|
||||
});
|
||||
clientRes.end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: "Method Not Allowed",
|
||||
allow: ["POST"],
|
||||
}),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
readBody(clientReq)
|
||||
.then((bodyText) => {
|
||||
const body = bodyText ? JSON.parse(bodyText) : {};
|
||||
const route = String(body.route || "").trim();
|
||||
const filePath = String(body.filePath || "").trim();
|
||||
const fileContent = String(body.fileContent || "");
|
||||
const overwrite = body.overwrite === true;
|
||||
const template = String(body.template || "").trim();
|
||||
const useExistingFile =
|
||||
body.useExistingFile === true || template === "basicError";
|
||||
|
||||
if (!route.startsWith("/")) {
|
||||
throw new Error("route must start with '/'");
|
||||
}
|
||||
if (!filePath) {
|
||||
throw new Error("filePath is required");
|
||||
}
|
||||
if (path.isAbsolute(filePath)) {
|
||||
throw new Error("filePath must be a relative path");
|
||||
}
|
||||
|
||||
const fullPath = path.join(__dirname, filePath);
|
||||
if (!fullPath.startsWith(__dirname)) {
|
||||
throw new Error("filePath is invalid");
|
||||
}
|
||||
if (useExistingFile) {
|
||||
if (!fs.existsSync(fullPath)) {
|
||||
throw new Error("mock file does not exist");
|
||||
}
|
||||
} else {
|
||||
if (!overwrite && fs.existsSync(fullPath)) {
|
||||
throw new Error(
|
||||
"mock file already exists, set overwrite=true to replace",
|
||||
);
|
||||
}
|
||||
fs.mkdirSync(path.dirname(fullPath), { recursive: true });
|
||||
fs.writeFileSync(fullPath, fileContent, "utf-8");
|
||||
}
|
||||
|
||||
const nextConfig = buildCurrentConfigFile();
|
||||
nextConfig.routes[route] = filePath;
|
||||
saveConfigFile(nextConfig);
|
||||
|
||||
clientRes.writeHead(200, { "Content-Type": "application/json" });
|
||||
clientRes.end(
|
||||
JSON.stringify({
|
||||
success: true,
|
||||
message: "Route and mock file created successfully",
|
||||
route,
|
||||
filePath,
|
||||
}),
|
||||
);
|
||||
})
|
||||
.catch((error) => {
|
||||
clientRes.writeHead(400, { "Content-Type": "application/json" });
|
||||
clientRes.end(
|
||||
JSON.stringify({
|
||||
success: false,
|
||||
error: (error as Error).message,
|
||||
}),
|
||||
);
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
if (requestPath === "/__admin") {
|
||||
if (clientReq.method !== "GET") {
|
||||
clientRes.writeHead(405, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
clientRes.end("Method Not Allowed");
|
||||
return;
|
||||
}
|
||||
const adminHtmlPath = path.join(__dirname, "admin.html");
|
||||
if (!fs.existsSync(adminHtmlPath)) {
|
||||
clientRes.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
|
||||
clientRes.end("admin.html not found");
|
||||
return;
|
||||
}
|
||||
const html = fs.readFileSync(adminHtmlPath, "utf-8");
|
||||
clientRes.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
||||
clientRes.end(html);
|
||||
return;
|
||||
}
|
||||
|
||||
// 检查是否为需要mock的路由
|
||||
if (isMockRoute(requestPath)) {
|
||||
const mockFile = getMockFilePath(requestPath);
|
||||
|
||||
1
mock/basic-error.json
Normal file
1
mock/basic-error.json
Normal file
@ -0,0 +1 @@
|
||||
{"code": -1, "success": false, "msg": "失败"}
|
||||
168
pnpm-lock.yaml
generated
Normal file
168
pnpm-lock.yaml
generated
Normal file
@ -0,0 +1,168 @@
|
||||
lockfileVersion: '9.0'
|
||||
|
||||
settings:
|
||||
autoInstallPeers: true
|
||||
excludeLinksFromLockfile: false
|
||||
|
||||
importers:
|
||||
|
||||
.:
|
||||
devDependencies:
|
||||
'@types/node':
|
||||
specifier: ^25.5.0
|
||||
version: 25.6.0
|
||||
ts-node:
|
||||
specifier: ^10.9.2
|
||||
version: 10.9.2(@types/node@25.6.0)(typescript@5.9.3)
|
||||
typescript:
|
||||
specifier: ^5.7.3
|
||||
version: 5.9.3
|
||||
|
||||
packages:
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
resolution: {integrity: sha512-IchNf6dN4tHoMFIn/7OE8LWZ19Y6q/67Bmf6vnGREv8RSbBVb9LPJxEcnwrcwX6ixSvaiGoomAUvu4YSxXrVgw==}
|
||||
engines: {node: '>=12'}
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2':
|
||||
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||
engines: {node: '>=6.0.0'}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5':
|
||||
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.9':
|
||||
resolution: {integrity: sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ==}
|
||||
|
||||
'@tsconfig/node10@1.0.12':
|
||||
resolution: {integrity: sha512-UCYBaeFvM11aU2y3YPZ//O5Rhj+xKyzy7mvcIoAjASbigy8mHMryP5cK7dgjlz2hWxh1g5pLw084E0a/wlUSFQ==}
|
||||
|
||||
'@tsconfig/node12@1.0.11':
|
||||
resolution: {integrity: sha512-cqefuRsh12pWyGsIoBKJA9luFu3mRxCA+ORZvA4ktLSzIuCUtWVxGIuXigEwO5/ywWFMZ2QEGKWvkZG1zDMTag==}
|
||||
|
||||
'@tsconfig/node14@1.0.3':
|
||||
resolution: {integrity: sha512-ysT8mhdixWK6Hw3i1V2AeRqZ5WfXg1G43mqoYlM2nc6388Fq5jcXyr5mRsqViLx/GJYdoL0bfXD8nmF+Zn/Iow==}
|
||||
|
||||
'@tsconfig/node16@1.0.4':
|
||||
resolution: {integrity: sha512-vxhUy4J8lyeyinH7Azl1pdd43GJhZH/tP2weN8TntQblOY+A0XbT8DJk1/oCPuOOyg/Ja757rG0CgHcWC8OfMA==}
|
||||
|
||||
'@types/node@25.6.0':
|
||||
resolution: {integrity: sha512-+qIYRKdNYJwY3vRCZMdJbPLJAtGjQBudzZzdzwQYkEPQd+PJGixUL5QfvCLDaULoLv+RhT3LDkwEfKaAkgSmNQ==}
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
resolution: {integrity: sha512-HEHNfbars9v4pgpW6SO1KSPkfoS0xVOM/9UzkJltjlsHZmJasxg8aXkuZa7SMf8vKGIBhpUsPluQSqhJFCqebw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
|
||||
acorn@8.16.0:
|
||||
resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==}
|
||||
engines: {node: '>=0.4.0'}
|
||||
hasBin: true
|
||||
|
||||
arg@4.1.3:
|
||||
resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==}
|
||||
|
||||
create-require@1.1.1:
|
||||
resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==}
|
||||
|
||||
diff@4.0.4:
|
||||
resolution: {integrity: sha512-X07nttJQkwkfKfvTPG/KSnE2OMdcUCao6+eXF3wmnIQRn2aPAHH3VxDbDOdegkd6JbPsXqShpvEOHfAT+nCNwQ==}
|
||||
engines: {node: '>=0.3.1'}
|
||||
|
||||
make-error@1.3.6:
|
||||
resolution: {integrity: sha512-s8UhlNe7vPKomQhC1qFelMokr/Sc3AgNbso3n74mVPA5LTZwkB9NlXf4XPamLxJE8h0gh73rM94xvwRT2CVInw==}
|
||||
|
||||
ts-node@10.9.2:
|
||||
resolution: {integrity: sha512-f0FFpIdcHgn8zcPSbf1dRevwt047YMnaiJM3u2w2RewrB+fob/zePZcrOyQoLMMO7aBIddLcQIEK5dYjkLnGrQ==}
|
||||
hasBin: true
|
||||
peerDependencies:
|
||||
'@swc/core': '>=1.2.50'
|
||||
'@swc/wasm': '>=1.2.50'
|
||||
'@types/node': '*'
|
||||
typescript: '>=2.7'
|
||||
peerDependenciesMeta:
|
||||
'@swc/core':
|
||||
optional: true
|
||||
'@swc/wasm':
|
||||
optional: true
|
||||
|
||||
typescript@5.9.3:
|
||||
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||
engines: {node: '>=14.17'}
|
||||
hasBin: true
|
||||
|
||||
undici-types@7.19.2:
|
||||
resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==}
|
||||
|
||||
v8-compile-cache-lib@3.0.1:
|
||||
resolution: {integrity: sha512-wa7YjyUGfNZngI/vtK0UHAN+lgDCxBPCylVXGp0zu59Fz5aiGtNXaq3DhIov063MorB+VfufLh3JlF2KdTK3xg==}
|
||||
|
||||
yn@3.1.1:
|
||||
resolution: {integrity: sha512-Ux4ygGWsu2c7isFWe8Yu1YluJmqVhxqK2cLXNQA5AcC3QfbGNpM7fu0Y8b/z16pXLnFxZYvWhd3fhBY9DLmC6Q==}
|
||||
engines: {node: '>=6'}
|
||||
|
||||
snapshots:
|
||||
|
||||
'@cspotcode/source-map-support@0.8.1':
|
||||
dependencies:
|
||||
'@jridgewell/trace-mapping': 0.3.9
|
||||
|
||||
'@jridgewell/resolve-uri@3.1.2': {}
|
||||
|
||||
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||
|
||||
'@jridgewell/trace-mapping@0.3.9':
|
||||
dependencies:
|
||||
'@jridgewell/resolve-uri': 3.1.2
|
||||
'@jridgewell/sourcemap-codec': 1.5.5
|
||||
|
||||
'@tsconfig/node10@1.0.12': {}
|
||||
|
||||
'@tsconfig/node12@1.0.11': {}
|
||||
|
||||
'@tsconfig/node14@1.0.3': {}
|
||||
|
||||
'@tsconfig/node16@1.0.4': {}
|
||||
|
||||
'@types/node@25.6.0':
|
||||
dependencies:
|
||||
undici-types: 7.19.2
|
||||
|
||||
acorn-walk@8.3.5:
|
||||
dependencies:
|
||||
acorn: 8.16.0
|
||||
|
||||
acorn@8.16.0: {}
|
||||
|
||||
arg@4.1.3: {}
|
||||
|
||||
create-require@1.1.1: {}
|
||||
|
||||
diff@4.0.4: {}
|
||||
|
||||
make-error@1.3.6: {}
|
||||
|
||||
ts-node@10.9.2(@types/node@25.6.0)(typescript@5.9.3):
|
||||
dependencies:
|
||||
'@cspotcode/source-map-support': 0.8.1
|
||||
'@tsconfig/node10': 1.0.12
|
||||
'@tsconfig/node12': 1.0.11
|
||||
'@tsconfig/node14': 1.0.3
|
||||
'@tsconfig/node16': 1.0.4
|
||||
'@types/node': 25.6.0
|
||||
acorn: 8.16.0
|
||||
acorn-walk: 8.3.5
|
||||
arg: 4.1.3
|
||||
create-require: 1.1.1
|
||||
diff: 4.0.4
|
||||
make-error: 1.3.6
|
||||
typescript: 5.9.3
|
||||
v8-compile-cache-lib: 3.0.1
|
||||
yn: 3.1.1
|
||||
|
||||
typescript@5.9.3: {}
|
||||
|
||||
undici-types@7.19.2: {}
|
||||
|
||||
v8-compile-cache-lib@3.0.1: {}
|
||||
|
||||
yn@3.1.1: {}
|
||||
Loading…
x
Reference in New Issue
Block a user