feat(ci,ai): P2-1 权限注册表 + P2-2 AI utoipa 注解全覆盖
P2-1 权限注册表单一真相源: - 新增 permissions.yaml: 131 个权限码 × 8 模块,含冻结标记 - 新增 scripts/gen-permissions.js: 生成器脚本 --sql 输出 seed SQL, --frontend 输出 routeConfig 片段, --validate 验证一致性(131/131 = 0 mismatches) P2-2 AI 模块 utoipa 注解: - 为 30 个 handler 函数添加 #[utoipa::path] 注解 (mod.rs 18 + insight 3 + risk 1 + rule 4 + suggestion 4) - 为 6 个 DTO struct 添加 ToSchema/IntoParams derive (AnalyzeBody, CreatePromptBody, CreateRuleBody, UpdateRuleBody, ApproveBody, ExecuteBody, DialysisLabInput, ListAnalysisQuery, ListPromptsQuery) - AI handler utoipa 覆盖率: 0/5 → 5/5 (100%)
This commit is contained in:
@@ -27,7 +27,7 @@ pub enum RiskLevel {
|
||||
Critical,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct CreateRuleBody {
|
||||
pub name: String,
|
||||
pub category: String,
|
||||
@@ -39,7 +39,7 @@ pub struct CreateRuleBody {
|
||||
pub sort_order: Option<i32>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct UpdateRuleBody {
|
||||
pub name: Option<String>,
|
||||
pub category: Option<String>,
|
||||
|
||||
@@ -8,6 +8,13 @@ use serde::Deserialize;
|
||||
use crate::dto::copilot::ListInsightsQuery;
|
||||
use crate::state::AiState;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/copilot/insights",
|
||||
responses((status = 200, description = "Copilot 洞察列表")),
|
||||
tag = "Copilot 洞察",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn list_insights<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -41,6 +48,13 @@ where
|
||||
}))))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/copilot/insights/{id}",
|
||||
responses((status = 200, description = "洞察详情")),
|
||||
tag = "Copilot 洞察",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn get_insight<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -68,6 +82,13 @@ pub struct DismissBody {
|
||||
pub reason: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/copilot/insights/{id}/dismiss",
|
||||
responses((status = 200, description = "忽略洞察")),
|
||||
tag = "Copilot 洞察",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn dismiss_insight<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
|
||||
@@ -18,7 +18,7 @@ pub mod suggestion_handler;
|
||||
|
||||
// === 分析请求 Body ===
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct AnalyzeBody {
|
||||
pub report_id: Option<uuid::Uuid>,
|
||||
pub patient_id: Option<uuid::Uuid>,
|
||||
@@ -27,6 +27,14 @@ pub struct AnalyzeBody {
|
||||
|
||||
// === SSE 分析端点 ===
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/analyze/lab-report",
|
||||
request_body = AnalyzeBody,
|
||||
responses((status = 200, description = "SSE 化验报告分析流")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn stream_lab_report<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -101,6 +109,14 @@ where
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/analyze/trends",
|
||||
request_body = AnalyzeBody,
|
||||
responses((status = 200, description = "SSE 趋势分析流")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn stream_trends<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -190,6 +206,14 @@ where
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/analyze/checkup-plan",
|
||||
request_body = AnalyzeBody,
|
||||
responses((status = 200, description = "SSE 体检计划分析流")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn stream_checkup_plan<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -258,6 +282,14 @@ where
|
||||
Ok(Sse::new(sse_stream).keep_alive(KeepAlive::default()))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/analyze/report-summary",
|
||||
request_body = AnalyzeBody,
|
||||
responses((status = 200, description = "SSE 报告摘要分析流")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn stream_report_summary<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -335,7 +367,7 @@ where
|
||||
|
||||
// === 分析历史 ===
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::IntoParams)]
|
||||
pub struct ListAnalysisQuery {
|
||||
pub patient_id: Option<uuid::Uuid>,
|
||||
pub analysis_type: Option<String>,
|
||||
@@ -343,6 +375,14 @@ pub struct ListAnalysisQuery {
|
||||
pub page_size: Option<u64>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/analysis/history",
|
||||
params(ListAnalysisQuery),
|
||||
responses((status = 200, description = "分析历史列表")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn list_analysis<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -419,6 +459,13 @@ where
|
||||
}))))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/analysis/{id}",
|
||||
responses((status = 200, description = "分析详情")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn get_analysis<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -435,13 +482,21 @@ where
|
||||
|
||||
// === Prompt 管理 ===
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::IntoParams)]
|
||||
pub struct ListPromptsQuery {
|
||||
pub category: Option<String>,
|
||||
pub page: Option<u64>,
|
||||
pub page_size: Option<u64>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/prompts",
|
||||
params(ListPromptsQuery),
|
||||
responses((status = 200, description = "Prompt 模板列表")),
|
||||
tag = "AI Prompt",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn list_prompts<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -468,7 +523,7 @@ where
|
||||
}))))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct CreatePromptBody {
|
||||
pub name: String,
|
||||
pub description: Option<String>,
|
||||
@@ -478,6 +533,14 @@ pub struct CreatePromptBody {
|
||||
pub category: String,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/prompts",
|
||||
request_body = CreatePromptBody,
|
||||
responses((status = 200, description = "创建 Prompt 模板")),
|
||||
tag = "AI Prompt",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn create_prompt<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -505,6 +568,13 @@ where
|
||||
Ok(Json(ApiResponse::ok(prompt)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/prompts/{id}/activate",
|
||||
responses((status = 200, description = "激活 Prompt 模板")),
|
||||
tag = "AI Prompt",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn activate_prompt<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -519,6 +589,13 @@ where
|
||||
Ok(Json(ApiResponse::ok(prompt)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/prompts/{id}/rollback",
|
||||
responses((status = 200, description = "回滚 Prompt 模板")),
|
||||
tag = "AI Prompt",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn rollback_prompt<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -535,6 +612,13 @@ where
|
||||
|
||||
// === 用量统计 ===
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/usage/overview",
|
||||
responses((status = 200, description = "AI 用量概览")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn usage_overview<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -550,6 +634,13 @@ where
|
||||
}))))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/usage/by-type",
|
||||
responses((status = 200, description = "按类型用量统计")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn usage_by_type<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -574,6 +665,13 @@ where
|
||||
|
||||
// === Provider 管理 ===
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/providers/health",
|
||||
responses((status = 200, description = "AI Provider 健康检查")),
|
||||
tag = "AI Provider",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn provider_health<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -602,6 +700,13 @@ where
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/providers",
|
||||
responses((status = 200, description = "AI Provider 列表")),
|
||||
tag = "AI Provider",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn provider_names<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -616,6 +721,13 @@ where
|
||||
)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/quota/summary",
|
||||
responses((status = 200, description = "AI 配额汇总")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn quota_summary<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -631,6 +743,13 @@ where
|
||||
|
||||
// === 透析风险评估(KDIGO 规则) ===
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/dialysis/risk-assessment",
|
||||
responses((status = 200, description = "透析风险评估")),
|
||||
tag = "AI 分析",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn assess_dialysis_risk<S>(
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Json(body): Json<crate::service::dialysis_risk_scorer::DialysisLabInput>,
|
||||
@@ -650,6 +769,13 @@ where
|
||||
|
||||
// === 成本与预算 ===
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/budget/status",
|
||||
responses((status = 200, description = "AI 预算状态")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn budget_status<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -670,6 +796,13 @@ pub struct CostEstimateQuery {
|
||||
pub model: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/cost/estimate",
|
||||
responses((status = 200, description = "AI 成本预估")),
|
||||
tag = "AI 用量",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn cost_estimate<S>(
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
Query(params): Query<CostEstimateQuery>,
|
||||
|
||||
@@ -5,6 +5,13 @@ use erp_core::types::{ApiResponse, TenantContext};
|
||||
|
||||
use crate::state::AiState;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/copilot/patients/{id}/risk",
|
||||
responses((status = 200, description = "患者风险评分")),
|
||||
tag = "Copilot 风险",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn get_patient_risk<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
|
||||
@@ -8,6 +8,13 @@ use crate::dto::copilot::{CreateRuleBody, UpdateRuleBody};
|
||||
use crate::entity::copilot_rules;
|
||||
use crate::state::AiState;
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/copilot/rules",
|
||||
responses((status = 200, description = "Copilot 规则列表")),
|
||||
tag = "Copilot 规则",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn list_rules<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -40,6 +47,13 @@ where
|
||||
}))))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/copilot/rules",
|
||||
responses((status = 200, description = "创建 Copilot 规则")),
|
||||
tag = "Copilot 规则",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn create_rule<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -75,6 +89,13 @@ where
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
put,
|
||||
path = "/copilot/rules/{id}",
|
||||
responses((status = 200, description = "更新 Copilot 规则")),
|
||||
tag = "Copilot 规则",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn update_rule<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -128,6 +149,13 @@ where
|
||||
Ok(Json(ApiResponse::ok(result)))
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
delete,
|
||||
path = "/copilot/rules/{id}",
|
||||
responses((status = 200, description = "删除 Copilot 规则")),
|
||||
tag = "Copilot 规则",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn delete_rule<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -151,6 +179,7 @@ where
|
||||
active.deleted_at = Set(Some(chrono::Utc::now()));
|
||||
active.updated_at = Set(chrono::Utc::now());
|
||||
active.updated_by = Set(Some(ctx.user_id));
|
||||
active.version_lock = Set(active.version_lock.unwrap() + 1);
|
||||
active.update(&state.db).await?;
|
||||
|
||||
Ok(Json(ApiResponse::ok(serde_json::json!({"deleted": true}))))
|
||||
|
||||
@@ -14,6 +14,13 @@ pub struct ListSuggestionsQuery {
|
||||
pub status: Option<String>,
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/suggestions",
|
||||
responses((status = 200, description = "AI 建议列表")),
|
||||
tag = "AI 建议",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn list_suggestions<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -41,11 +48,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct ApproveBody {
|
||||
pub action: String, // "approve" or "reject"
|
||||
}
|
||||
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/suggestions/{id}/approve",
|
||||
responses((status = 200, description = "审批 AI 建议")),
|
||||
tag = "AI 建议",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn approve_suggestion<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -86,12 +100,19 @@ where
|
||||
}))))
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[derive(Debug, Deserialize, utoipa::ToSchema)]
|
||||
pub struct ExecuteBody {
|
||||
pub action_result: Option<serde_json::Value>,
|
||||
}
|
||||
|
||||
/// 执行建议:护士标记建议为已执行,可选记录执行结果
|
||||
#[utoipa::path(
|
||||
post,
|
||||
path = "/ai/suggestions/{id}/execute",
|
||||
responses((status = 200, description = "执行 AI 建议")),
|
||||
tag = "AI 建议",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn execute_suggestion<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
@@ -123,6 +144,13 @@ where
|
||||
}
|
||||
|
||||
/// 获取 AI 建议的前后对比报告。
|
||||
#[utoipa::path(
|
||||
get,
|
||||
path = "/ai/suggestions/{id}/comparison",
|
||||
responses((status = 200, description = "AI 建议对比")),
|
||||
tag = "AI 建议",
|
||||
security(("bearer_auth" = [])),
|
||||
)]
|
||||
pub async fn get_comparison<S>(
|
||||
State(state): State<AiState>,
|
||||
Extension(ctx): Extension<TenantContext>,
|
||||
|
||||
@@ -2,7 +2,7 @@ use crate::dto::suggestion::{RiskLevel, StructuredSuggestion, SuggestionType};
|
||||
use crate::service::local_rules::{CompareOp, LocalRule, LocalRulesEngine};
|
||||
|
||||
/// 透析患者实验室指标输入
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]
|
||||
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize, utoipa::ToSchema)]
|
||||
pub struct DialysisLabInput {
|
||||
/// Kt/V(透析充分性指标)
|
||||
pub kt_v: Option<f64>,
|
||||
|
||||
800
package-lock.json
generated
Normal file
800
package-lock.json
generated
Normal file
@@ -0,0 +1,800 @@
|
||||
{
|
||||
"name": "erp",
|
||||
"lockfileVersion": 3,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "erp",
|
||||
"devDependencies": {
|
||||
"js-yaml": "^4.1.1",
|
||||
"lint-staged": "^15.0.0",
|
||||
"simple-git-hooks": "^2.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-escapes": {
|
||||
"version": "7.3.0",
|
||||
"resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-7.3.0.tgz",
|
||||
"integrity": "sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"environment": "^1.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-regex": {
|
||||
"version": "6.2.2",
|
||||
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz",
|
||||
"integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-regex?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/ansi-styles": {
|
||||
"version": "6.2.3",
|
||||
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz",
|
||||
"integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/ansi-styles?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/argparse": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
|
||||
"integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==",
|
||||
"dev": true,
|
||||
"license": "Python-2.0"
|
||||
},
|
||||
"node_modules/braces": {
|
||||
"version": "3.0.3",
|
||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"fill-range": "^7.1.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/chalk": {
|
||||
"version": "5.6.2",
|
||||
"resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz",
|
||||
"integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.17.0 || ^14.13 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/chalk?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-cursor": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-5.0.0.tgz",
|
||||
"integrity": "sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"restore-cursor": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/cli-truncate": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-4.0.0.tgz",
|
||||
"integrity": "sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"slice-ansi": "^5.0.0",
|
||||
"string-width": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/colorette": {
|
||||
"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/commander": {
|
||||
"version": "13.1.0",
|
||||
"resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz",
|
||||
"integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
}
|
||||
},
|
||||
"node_modules/cross-spawn": {
|
||||
"version": "7.0.6",
|
||||
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
|
||||
"integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^3.1.0",
|
||||
"shebang-command": "^2.0.0",
|
||||
"which": "^2.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/debug": {
|
||||
"version": "4.4.3",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
|
||||
"integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "^2.1.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.0"
|
||||
},
|
||||
"peerDependenciesMeta": {
|
||||
"supports-color": {
|
||||
"optional": true
|
||||
}
|
||||
}
|
||||
},
|
||||
"node_modules/emoji-regex": {
|
||||
"version": "10.6.0",
|
||||
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-10.6.0.tgz",
|
||||
"integrity": "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/environment": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/environment/-/environment-1.1.0.tgz",
|
||||
"integrity": "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "5.0.4",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz",
|
||||
"integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/execa": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz",
|
||||
"integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cross-spawn": "^7.0.3",
|
||||
"get-stream": "^8.0.1",
|
||||
"human-signals": "^5.0.0",
|
||||
"is-stream": "^3.0.0",
|
||||
"merge-stream": "^2.0.0",
|
||||
"npm-run-path": "^5.1.0",
|
||||
"onetime": "^6.0.0",
|
||||
"signal-exit": "^4.1.0",
|
||||
"strip-final-newline": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16.17"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sindresorhus/execa?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.1.1",
|
||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"to-regex-range": "^5.0.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/get-east-asian-width": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/get-east-asian-width/-/get-east-asian-width-1.5.0.tgz",
|
||||
"integrity": "sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/get-stream": {
|
||||
"version": "8.0.1",
|
||||
"resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz",
|
||||
"integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/human-signals": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz",
|
||||
"integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==",
|
||||
"dev": true,
|
||||
"license": "Apache-2.0",
|
||||
"engines": {
|
||||
"node": ">=16.17.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-fullwidth-code-point": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-4.0.0.tgz",
|
||||
"integrity": "sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/is-number": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.12.0"
|
||||
}
|
||||
},
|
||||
"node_modules/is-stream": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz",
|
||||
"integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/isexe": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
|
||||
"integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
|
||||
"dev": true,
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/js-yaml": {
|
||||
"version": "4.1.1",
|
||||
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
|
||||
"integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"argparse": "^2.0.1"
|
||||
},
|
||||
"bin": {
|
||||
"js-yaml": "bin/js-yaml.js"
|
||||
}
|
||||
},
|
||||
"node_modules/lilconfig": {
|
||||
"version": "3.1.3",
|
||||
"resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-3.1.3.tgz",
|
||||
"integrity": "sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/antonk52"
|
||||
}
|
||||
},
|
||||
"node_modules/lint-staged": {
|
||||
"version": "15.5.2",
|
||||
"resolved": "https://registry.npmjs.org/lint-staged/-/lint-staged-15.5.2.tgz",
|
||||
"integrity": "sha512-YUSOLq9VeRNAo/CTaVmhGDKG+LBtA8KF1X4K5+ykMSwWST1vDxJRB2kv2COgLb1fvpCo+A/y9A0G0znNVmdx4w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"chalk": "^5.4.1",
|
||||
"commander": "^13.1.0",
|
||||
"debug": "^4.4.0",
|
||||
"execa": "^8.0.1",
|
||||
"lilconfig": "^3.1.3",
|
||||
"listr2": "^8.2.5",
|
||||
"micromatch": "^4.0.8",
|
||||
"pidtree": "^0.6.0",
|
||||
"string-argv": "^0.3.2",
|
||||
"yaml": "^2.7.0"
|
||||
},
|
||||
"bin": {
|
||||
"lint-staged": "bin/lint-staged.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.12.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://opencollective.com/lint-staged"
|
||||
}
|
||||
},
|
||||
"node_modules/listr2": {
|
||||
"version": "8.3.3",
|
||||
"resolved": "https://registry.npmjs.org/listr2/-/listr2-8.3.3.tgz",
|
||||
"integrity": "sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"cli-truncate": "^4.0.0",
|
||||
"colorette": "^2.0.20",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"log-update": "^6.1.0",
|
||||
"rfdc": "^1.4.1",
|
||||
"wrap-ansi": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update": {
|
||||
"version": "6.1.0",
|
||||
"resolved": "https://registry.npmjs.org/log-update/-/log-update-6.1.0.tgz",
|
||||
"integrity": "sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-escapes": "^7.0.0",
|
||||
"cli-cursor": "^5.0.0",
|
||||
"slice-ansi": "^7.1.0",
|
||||
"strip-ansi": "^7.1.0",
|
||||
"wrap-ansi": "^9.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/is-fullwidth-code-point": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-5.1.0.tgz",
|
||||
"integrity": "sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"get-east-asian-width": "^1.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/log-update/node_modules/slice-ansi": {
|
||||
"version": "7.1.2",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-7.1.2.tgz",
|
||||
"integrity": "sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"is-fullwidth-code-point": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/merge-stream": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz",
|
||||
"integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/micromatch": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"braces": "^3.0.3",
|
||||
"picomatch": "^2.3.1"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-fn": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz",
|
||||
"integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/mimic-function": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/mimic-function/-/mimic-function-5.0.1.tgz",
|
||||
"integrity": "sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/ms": {
|
||||
"version": "2.1.3",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/npm-run-path": {
|
||||
"version": "5.3.0",
|
||||
"resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz",
|
||||
"integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"path-key": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^12.20.0 || ^14.13.1 || >=16.0.0"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/npm-run-path/node_modules/path-key": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz",
|
||||
"integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/onetime": {
|
||||
"version": "6.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz",
|
||||
"integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-fn": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/path-key": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
|
||||
"integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/picomatch": {
|
||||
"version": "2.3.2",
|
||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz",
|
||||
"integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/jonschlinkert"
|
||||
}
|
||||
},
|
||||
"node_modules/pidtree": {
|
||||
"version": "0.6.0",
|
||||
"resolved": "https://registry.npmjs.org/pidtree/-/pidtree-0.6.0.tgz",
|
||||
"integrity": "sha512-eG2dWTVw5bzqGRztnHExczNxt5VGsE6OwTeCG3fdUf9KBsZzO3R5OIIIzWR+iZA0NtZ+RDVdaoE2dK1cn6jH4g==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"pidtree": "bin/pidtree.js"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor": {
|
||||
"version": "5.1.0",
|
||||
"resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-5.1.0.tgz",
|
||||
"integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"onetime": "^7.0.0",
|
||||
"signal-exit": "^4.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/restore-cursor/node_modules/onetime": {
|
||||
"version": "7.0.0",
|
||||
"resolved": "https://registry.npmjs.org/onetime/-/onetime-7.0.0.tgz",
|
||||
"integrity": "sha512-VXJjc87FScF88uafS3JllDgvAm+c/Slfz06lorj2uAY34rlUu0Nt+v8wreiImcrgAjjIHp1rXpTDlLOGw29WwQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"mimic-function": "^5.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/rfdc": {
|
||||
"version": "1.4.1",
|
||||
"resolved": "https://registry.npmjs.org/rfdc/-/rfdc-1.4.1.tgz",
|
||||
"integrity": "sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==",
|
||||
"dev": true,
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/shebang-command": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
|
||||
"integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"shebang-regex": "^3.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/shebang-regex": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
|
||||
"integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/signal-exit": {
|
||||
"version": "4.1.0",
|
||||
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz",
|
||||
"integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"engines": {
|
||||
"node": ">=14"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/isaacs"
|
||||
}
|
||||
},
|
||||
"node_modules/simple-git-hooks": {
|
||||
"version": "2.13.1",
|
||||
"resolved": "https://registry.npmjs.org/simple-git-hooks/-/simple-git-hooks-2.13.1.tgz",
|
||||
"integrity": "sha512-WszCLXwT4h2k1ufIXAgsbiTOazqqevFCIncOuUBZJ91DdvWcC5+OFkluWRQPrcuSYd8fjq+o2y1QfWqYMoAToQ==",
|
||||
"dev": true,
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"bin": {
|
||||
"simple-git-hooks": "cli.js"
|
||||
}
|
||||
},
|
||||
"node_modules/slice-ansi": {
|
||||
"version": "5.0.0",
|
||||
"resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-5.0.0.tgz",
|
||||
"integrity": "sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.0.0",
|
||||
"is-fullwidth-code-point": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/slice-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/string-argv": {
|
||||
"version": "0.3.2",
|
||||
"resolved": "https://registry.npmjs.org/string-argv/-/string-argv-0.3.2.tgz",
|
||||
"integrity": "sha512-aqD2Q0144Z+/RqG52NeHEkZauTAUWJO8c6yTftGJKO3Tja5tUgIfmIl6kExvhtxSDP7fXB6DvzkfMpCd/F3G+Q==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.6.19"
|
||||
}
|
||||
},
|
||||
"node_modules/string-width": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz",
|
||||
"integrity": "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"emoji-regex": "^10.3.0",
|
||||
"get-east-asian-width": "^1.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-ansi": {
|
||||
"version": "7.2.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.2.0.tgz",
|
||||
"integrity": "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-regex": "^6.2.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/strip-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/strip-final-newline": {
|
||||
"version": "3.0.0",
|
||||
"resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz",
|
||||
"integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=12"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/sindresorhus"
|
||||
}
|
||||
},
|
||||
"node_modules/to-regex-range": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-number": "^7.0.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/which": {
|
||||
"version": "2.0.2",
|
||||
"resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
|
||||
"integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"isexe": "^2.0.0"
|
||||
},
|
||||
"bin": {
|
||||
"node-which": "bin/node-which"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/wrap-ansi": {
|
||||
"version": "9.0.2",
|
||||
"resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-9.0.2.tgz",
|
||||
"integrity": "sha512-42AtmgqjV+X1VpdOfyTGOYRi0/zsoLqtXQckTmqTeybT+BDIbM/Guxo7x3pE2vtpr1ok6xRqM9OpBe+Jyoqyww==",
|
||||
"dev": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ansi-styles": "^6.2.1",
|
||||
"string-width": "^7.0.0",
|
||||
"strip-ansi": "^7.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=18"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/chalk/wrap-ansi?sponsor=1"
|
||||
}
|
||||
},
|
||||
"node_modules/yaml": {
|
||||
"version": "2.8.4",
|
||||
"resolved": "https://registry.npmjs.org/yaml/-/yaml-2.8.4.tgz",
|
||||
"integrity": "sha512-ml/JPOj9fOQK8RNnWojA67GbZ0ApXAUlN2UQclwv2eVgTgn7O9gg9o7paZWKMp4g0H3nTLtS9LVzhkpOFIKzog==",
|
||||
"dev": true,
|
||||
"license": "ISC",
|
||||
"bin": {
|
||||
"yaml": "bin.mjs"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">= 14.6"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://github.com/sponsors/eemeli"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,6 +12,7 @@
|
||||
"pre-commit": "npx lint-staged"
|
||||
},
|
||||
"devDependencies": {
|
||||
"js-yaml": "^4.1.1",
|
||||
"lint-staged": "^15.0.0",
|
||||
"simple-git-hooks": "^2.12.0"
|
||||
}
|
||||
|
||||
317
permissions.yaml
Normal file
317
permissions.yaml
Normal file
@@ -0,0 +1,317 @@
|
||||
# HMS 权限注册表 — 单一真相源
|
||||
#
|
||||
# 此文件是权限码的权威来源。所有模块的权限必须在此声明。
|
||||
# CI 脚本 check-permissions.sh 从此文件验证一致性。
|
||||
#
|
||||
# 用法:
|
||||
# - 新增权限: 在对应模块下添加条目
|
||||
# - 生成 seed: node scripts/gen-permissions.js --seed
|
||||
# - 验证一致: bash scripts/check-permissions.sh
|
||||
|
||||
auth:
|
||||
module: erp-auth
|
||||
description: 用户/角色/权限/组织/部门/岗位
|
||||
permissions:
|
||||
- code: user.list
|
||||
name: 查看用户列表
|
||||
- code: user.create
|
||||
name: 创建用户
|
||||
- code: user.read
|
||||
name: 查看用户详情
|
||||
- code: user.update
|
||||
name: 编辑用户
|
||||
- code: user.delete
|
||||
name: 删除用户
|
||||
- code: role.list
|
||||
name: 查看角色列表
|
||||
- code: role.create
|
||||
name: 创建角色
|
||||
- code: role.read
|
||||
name: 查看角色详情
|
||||
- code: role.update
|
||||
name: 编辑角色
|
||||
- code: role.delete
|
||||
name: 删除角色
|
||||
- code: permission.list
|
||||
name: 查看权限
|
||||
- code: organization.list
|
||||
name: 查看组织列表
|
||||
- code: organization.create
|
||||
name: 创建组织
|
||||
- code: organization.update
|
||||
name: 编辑组织
|
||||
- code: organization.delete
|
||||
name: 删除组织
|
||||
- code: department.list
|
||||
name: 查看部门列表
|
||||
- code: department.create
|
||||
name: 创建部门
|
||||
- code: department.update
|
||||
name: 编辑部门
|
||||
- code: department.delete
|
||||
name: 删除部门
|
||||
- code: position.list
|
||||
name: 查看岗位列表
|
||||
- code: position.create
|
||||
name: 创建岗位
|
||||
- code: position.update
|
||||
name: 编辑岗位
|
||||
- code: position.delete
|
||||
name: 删除岗位
|
||||
|
||||
config:
|
||||
module: erp-config
|
||||
description: 字典/菜单/配置/编号/主题/语言
|
||||
permissions:
|
||||
- code: dictionary.list
|
||||
name: 查看字典
|
||||
- code: dictionary.create
|
||||
name: 创建字典
|
||||
- code: dictionary.update
|
||||
name: 编辑字典
|
||||
- code: dictionary.delete
|
||||
name: 删除字典
|
||||
- code: menu.list
|
||||
name: 查看菜单
|
||||
- code: menu.update
|
||||
name: 编辑菜单
|
||||
- code: setting.read
|
||||
name: 查看配置
|
||||
- code: setting.update
|
||||
name: 编辑配置
|
||||
- code: setting.delete
|
||||
name: 删除配置
|
||||
- code: numbering.list
|
||||
name: 查看编号规则
|
||||
- code: numbering.create
|
||||
name: 创建编号规则
|
||||
- code: numbering.update
|
||||
name: 编辑编号规则
|
||||
- code: numbering.delete
|
||||
name: 删除编号规则
|
||||
- code: numbering.generate
|
||||
name: 生成编号
|
||||
- code: theme.read
|
||||
name: 查看主题
|
||||
- code: theme.update
|
||||
name: 编辑主题
|
||||
- code: language.list
|
||||
name: 查看语言
|
||||
- code: language.update
|
||||
name: 编辑语言
|
||||
|
||||
workflow:
|
||||
module: erp-workflow
|
||||
description: 流程定义/审批/委派
|
||||
permissions:
|
||||
- code: workflow.create
|
||||
name: 创建流程
|
||||
- code: workflow.list
|
||||
name: 查看流程
|
||||
- code: workflow.read
|
||||
name: 查看流程详情
|
||||
- code: workflow.update
|
||||
name: 编辑流程
|
||||
- code: workflow.publish
|
||||
name: 发布流程
|
||||
- code: workflow.start
|
||||
name: 发起流程
|
||||
- code: workflow.approve
|
||||
name: 审批任务
|
||||
- code: workflow.delegate
|
||||
name: 委派任务
|
||||
|
||||
message:
|
||||
module: erp-message
|
||||
description: 消息/模板
|
||||
permissions:
|
||||
- code: message.list
|
||||
name: 查看消息
|
||||
- code: message.send
|
||||
name: 发送消息
|
||||
- code: message.template.list
|
||||
name: 查看消息模板
|
||||
- code: message.template.create
|
||||
name: 创建消息模板
|
||||
- code: message.template.manage
|
||||
name: 管理消息模板
|
||||
|
||||
plugin:
|
||||
module: erp-plugin
|
||||
description: 插件管理
|
||||
permissions:
|
||||
- code: plugin.admin
|
||||
name: 插件管理
|
||||
- code: plugin.list
|
||||
name: 查看插件
|
||||
|
||||
health:
|
||||
module: erp-health
|
||||
description: 患者管理/健康数据/预约排班/随访/咨询/告警/设备/积分/内容/媒体
|
||||
permissions:
|
||||
- code: health.patient.list
|
||||
name: 查看患者列表
|
||||
- code: health.patient.manage
|
||||
name: 管理患者
|
||||
- code: health.health-data.list
|
||||
name: 查看健康数据
|
||||
- code: health.health-data.manage
|
||||
name: 管理健康数据
|
||||
- code: health.appointment.list
|
||||
name: 查看预约
|
||||
- code: health.appointment.manage
|
||||
name: 管理预约
|
||||
- code: health.follow-up.list
|
||||
name: 查看随访
|
||||
- code: health.follow-up.manage
|
||||
name: 管理随访
|
||||
- code: health.consultation.list
|
||||
name: 查看咨询
|
||||
- code: health.consultation.manage
|
||||
name: 管理咨询
|
||||
- code: health.doctor.list
|
||||
name: 查看医护
|
||||
- code: health.doctor.manage
|
||||
name: 管理医护
|
||||
- code: health.articles.list
|
||||
name: 查看资讯
|
||||
- code: health.articles.manage
|
||||
name: 管理资讯
|
||||
- code: health.articles.review
|
||||
name: 审核资讯
|
||||
- code: health.points.list
|
||||
name: 查看积分
|
||||
- code: health.points.manage
|
||||
name: 管理积分
|
||||
- code: health.device-readings.list
|
||||
name: 查看设备数据
|
||||
- code: health.device-readings.manage
|
||||
name: 管理设备数据
|
||||
- code: health.devices.list
|
||||
name: 查看设备绑定
|
||||
- code: health.devices.manage
|
||||
name: 管理设备绑定
|
||||
- code: health.alerts.list
|
||||
name: 查看告警
|
||||
- code: health.alerts.manage
|
||||
name: 管理告警
|
||||
- code: health.alert-rules.list
|
||||
name: 查看告警规则
|
||||
- code: health.alert-rules.manage
|
||||
name: 管理告警规则
|
||||
- code: health.critical-alerts.list
|
||||
name: 查看危急值告警
|
||||
- code: health.critical-alerts.manage
|
||||
name: 处理危急值告警
|
||||
- code: health.critical-value-thresholds.list
|
||||
name: 查看危急值阈值
|
||||
- code: health.critical-value-thresholds.manage
|
||||
name: 管理危急值阈值
|
||||
- code: health.follow-up-templates.list
|
||||
name: 查看随访模板
|
||||
- code: health.follow-up-templates.manage
|
||||
name: 管理随访模板
|
||||
- code: health.daily-monitoring.list
|
||||
name: 查看日常监测
|
||||
- code: health.daily-monitoring.manage
|
||||
name: 管理日常监测
|
||||
- code: health.consent.list
|
||||
name: 查看知情同意
|
||||
- code: health.consent.manage
|
||||
name: 管理知情同意
|
||||
- code: health.medication-records.list
|
||||
name: 查看用药记录
|
||||
- code: health.medication-records.manage
|
||||
name: 管理用药记录
|
||||
- code: health.medication-reminders.list
|
||||
name: 查看药物提醒
|
||||
- code: health.medication-reminders.manage
|
||||
name: 管理药物提醒
|
||||
- code: health.action-inbox.list
|
||||
name: 查看行动收件箱
|
||||
- code: health.action-inbox.manage
|
||||
name: 管理行动项
|
||||
- code: health.action-inbox.team
|
||||
name: 查看团队概览
|
||||
- code: health.dashboard.manage
|
||||
name: 工作台管理
|
||||
- code: health.oauth.list
|
||||
name: 查看合作方
|
||||
- code: health.oauth.manage
|
||||
name: 管理合作方
|
||||
- code: health.care-plan.list
|
||||
name: 查看护理计划
|
||||
frozen: true
|
||||
- code: health.care-plan.manage
|
||||
name: 管理护理计划
|
||||
frozen: true
|
||||
- code: health.shifts.list
|
||||
name: 查看班次
|
||||
frozen: true
|
||||
- code: health.shifts.manage
|
||||
name: 管理班次
|
||||
frozen: true
|
||||
- code: health.ble-gateways.list
|
||||
name: 查看 BLE 网关
|
||||
- code: health.ble-gateways.manage
|
||||
name: 管理 BLE 网关
|
||||
- code: health.family-proxy.list
|
||||
name: 查看家庭健康代理
|
||||
frozen: true
|
||||
- code: health.family-proxy.manage
|
||||
name: 管理家庭健康代理
|
||||
frozen: true
|
||||
- code: health.media.list
|
||||
name: 查看媒体库
|
||||
- code: health.media.manage
|
||||
name: 管理媒体库
|
||||
- code: health.banners.list
|
||||
name: 查看轮播图
|
||||
- code: health.banners.manage
|
||||
name: 管理轮播图
|
||||
|
||||
ai:
|
||||
module: erp-ai
|
||||
description: AI 分析/Prompt/Copilot
|
||||
permissions:
|
||||
- code: ai.analysis.list
|
||||
name: 查看分析历史
|
||||
- code: ai.analysis.manage
|
||||
name: 请求分析
|
||||
- code: ai.prompt.list
|
||||
name: 查看 Prompt
|
||||
- code: ai.prompt.manage
|
||||
name: 管理 Prompt
|
||||
- code: ai.usage.list
|
||||
name: 查看用量
|
||||
- code: ai.provider.manage
|
||||
name: 管理提供商
|
||||
- code: ai.suggestion.list
|
||||
name: 查看 AI 建议
|
||||
- code: ai.suggestion.manage
|
||||
name: 审批 AI 建议
|
||||
- code: copilot.insights.list
|
||||
name: 查看 Copilot 洞察
|
||||
- code: copilot.insights.manage
|
||||
name: 管理 Copilot 洞察
|
||||
- code: copilot.risk.view
|
||||
name: 查看风险评分
|
||||
- code: copilot.rules.list
|
||||
name: 查看 Copilot 规则
|
||||
- code: copilot.rules.manage
|
||||
name: 管理 Copilot 规则
|
||||
|
||||
dialysis:
|
||||
module: erp-dialysis
|
||||
description: 透析管理
|
||||
permissions:
|
||||
- code: health.dialysis.list
|
||||
name: 查看透析记录
|
||||
- code: health.dialysis.manage
|
||||
name: 管理透析记录
|
||||
- code: health.dialysis-prescription.list
|
||||
name: 查看透析处方
|
||||
- code: health.dialysis-prescription.manage
|
||||
name: 管理透析处方
|
||||
- code: health.dialysis.stats
|
||||
name: 查看透析统计
|
||||
155
scripts/gen-permissions.js
Normal file
155
scripts/gen-permissions.js
Normal file
@@ -0,0 +1,155 @@
|
||||
#!/usr/bin/env node
|
||||
// gen-permissions.js — 从 permissions.yaml 生成 seed SQL 和前端 routeConfig 片段
|
||||
//
|
||||
// 用法:
|
||||
// node scripts/gen-permissions.js --sql 输出 seed INSERT SQL
|
||||
// node scripts/gen-permissions.js --frontend 输出 routeConfig.ts 条目
|
||||
// node scripts/gen-permissions.js --validate 验证与代码一致性
|
||||
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const yaml = require('js-yaml');
|
||||
|
||||
const YAML_PATH = path.resolve(__dirname, '..', 'permissions.yaml');
|
||||
|
||||
function loadPermissions() {
|
||||
const raw = fs.readFileSync(YAML_PATH, 'utf-8');
|
||||
return yaml.load(raw);
|
||||
}
|
||||
|
||||
function getAllCodes(registry) {
|
||||
const codes = [];
|
||||
for (const [, group] of Object.entries(registry)) {
|
||||
for (const perm of group.permissions) {
|
||||
codes.push(perm.code);
|
||||
}
|
||||
}
|
||||
return codes;
|
||||
}
|
||||
|
||||
function generateSeedSQL(registry) {
|
||||
const sys = '00000000-0000-0000-0000-000000000000';
|
||||
const lines = [
|
||||
'-- Auto-generated from permissions.yaml by gen-permissions.js',
|
||||
`-- Generated: ${new Date().toISOString().slice(0, 10)}`,
|
||||
'',
|
||||
];
|
||||
|
||||
for (const [groupKey, group] of Object.entries(registry)) {
|
||||
lines.push(`-- ${groupKey}: ${group.description}`);
|
||||
for (const perm of group.permissions) {
|
||||
const parts = perm.code.split('.');
|
||||
const resource = parts.length >= 2 ? parts[0] : groupKey;
|
||||
const action = parts.slice(1).join('.');
|
||||
lines.push(
|
||||
`INSERT INTO permissions (id, tenant_id, code, name, resource, action, description,` +
|
||||
` created_at, updated_at, created_by, updated_by, deleted_at, version)` +
|
||||
` SELECT gen_random_uuid(), t.id, '${perm.code}', '${perm.name}', '${resource}', '${action}', '${perm.name}',` +
|
||||
` NOW(), NOW(), '${sys}', '${sys}', NULL, 1` +
|
||||
` FROM tenant t` +
|
||||
` WHERE NOT EXISTS (SELECT 1 FROM permissions p WHERE p.code = '${perm.code}' AND p.tenant_id = t.id AND p.deleted_at IS NULL)` +
|
||||
` ON CONFLICT DO NOTHING;`
|
||||
);
|
||||
}
|
||||
lines.push('');
|
||||
}
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function generateFrontendSnippet(registry) {
|
||||
const lines = ['// Auto-generated from permissions.yaml by gen-permissions.js', ''];
|
||||
|
||||
for (const [, group] of Object.entries(registry)) {
|
||||
const frozenPerms = group.permissions.filter(p => p.frozen);
|
||||
const activePerms = group.permissions.filter(p => !p.frozen);
|
||||
|
||||
// Group by entity prefix (e.g., health.patient → {list, manage})
|
||||
const entities = {};
|
||||
for (const perm of [...activePerms, ...frozenPerms]) {
|
||||
const parts = perm.code.split('.');
|
||||
if (parts.length < 2) continue;
|
||||
const entity = parts.slice(0, -1).join('.');
|
||||
const action = parts[parts.length - 1];
|
||||
if (!entities[entity]) entities[entity] = { list: [], frozen: perm.frozen || false };
|
||||
entities[entity].list.push(perm.code);
|
||||
}
|
||||
|
||||
for (const [entity, info] of Object.entries(entities)) {
|
||||
const frozenAttr = info.frozen ? ',\n frozen: true' : '';
|
||||
lines.push(` {`);
|
||||
lines.push(` path: "/${entity.replace(/\./g, '/')}",`);
|
||||
lines.push(` permissions: [${info.list.map(c => `"${c}"`).join(', ')}]${frozenAttr}`);
|
||||
lines.push(` },`);
|
||||
}
|
||||
}
|
||||
|
||||
return lines.join('\n');
|
||||
}
|
||||
|
||||
function validate(registry) {
|
||||
const yamlCodes = getAllCodes(registry);
|
||||
const rootDir = path.resolve(__dirname, '..');
|
||||
|
||||
// Recursively find .rs files and extract permission codes
|
||||
const backendCodes = new Set();
|
||||
function walkDir(dir) {
|
||||
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
const full = path.join(dir, entry.name);
|
||||
if (entry.isDirectory() && !entry.name.startsWith('.') && entry.name !== 'target') {
|
||||
walkDir(full);
|
||||
} else if (entry.isFile() && entry.name.endsWith('.rs')) {
|
||||
const content = fs.readFileSync(full, 'utf-8');
|
||||
// Match code: "xxx.yyy.zzz" pattern in PermissionDescriptor
|
||||
// Must be lowercase letters/digits/hyphens with dots (permission code pattern)
|
||||
const matches = content.matchAll(/code:\s*"([a-z][a-z0-9-]*\.[a-z][a-z0-9-]*(?:\.[a-z][a-z0-9-]*)*)"/g);
|
||||
for (const m of matches) {
|
||||
backendCodes.add(m[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
walkDir(path.join(rootDir, 'crates'));
|
||||
|
||||
let errors = 0;
|
||||
|
||||
// Check YAML covers all backend codes
|
||||
for (const code of backendCodes) {
|
||||
if (!yamlCodes.includes(code)) {
|
||||
console.log(`MISSING: Backend '${code}' not in permissions.yaml`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
// Check backend covers all YAML codes
|
||||
for (const code of yamlCodes) {
|
||||
if (!backendCodes.has(code)) {
|
||||
console.log(`MISSING: YAML '${code}' not in backend module.rs`);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
|
||||
if (errors === 0) {
|
||||
console.log(`OK: ${yamlCodes.length} YAML / ${backendCodes.size} backend — 0 mismatches`);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
// Main
|
||||
const args = process.argv.slice(2);
|
||||
const registry = loadPermissions();
|
||||
|
||||
if (args.includes('--sql')) {
|
||||
console.log(generateSeedSQL(registry));
|
||||
} else if (args.includes('--frontend')) {
|
||||
console.log(generateFrontendSnippet(registry));
|
||||
} else if (args.includes('--validate')) {
|
||||
const errors = validate(registry);
|
||||
process.exit(errors > 0 ? 1 : 0);
|
||||
} else {
|
||||
const codes = getAllCodes(registry);
|
||||
console.log(`permissions.yaml loaded: ${codes.length} permission codes across ${Object.keys(registry).length} modules`);
|
||||
console.log('Usage:');
|
||||
console.log(' node scripts/gen-permissions.js --sql Generate seed SQL');
|
||||
console.log(' node scripts/gen-permissions.js --frontend Generate routeConfig snippet');
|
||||
console.log(' node scripts/gen-permissions.js --validate Validate consistency');
|
||||
}
|
||||
Reference in New Issue
Block a user