@dreamor/atlas-cli 0.7.22 → 0.7.24

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. package/README.github.md +230 -0
  2. package/README.md +1 -55
  3. package/atlas.cjs +226 -0
  4. package/package.json +7 -9
  5. package/atlas.js +0 -5
  6. package/dist/adapters/atlas/auth/browser.js +0 -37
  7. package/dist/adapters/atlas/auth/index.js +0 -3
  8. package/dist/adapters/atlas/auth/login.js +0 -147
  9. package/dist/adapters/atlas/auth/refresh.js +0 -138
  10. package/dist/adapters/atlas/auth/session.js +0 -115
  11. package/dist/adapters/atlas/cli.js +0 -512
  12. package/dist/adapters/atlas/commands/_output_schema.js +0 -379
  13. package/dist/adapters/atlas/commands/actual/_logic.js +0 -116
  14. package/dist/adapters/atlas/commands/actual/index.js +0 -138
  15. package/dist/adapters/atlas/commands/auth.js +0 -1
  16. package/dist/adapters/atlas/commands/baseline/index.js +0 -137
  17. package/dist/adapters/atlas/commands/compare/_logic.js +0 -39
  18. package/dist/adapters/atlas/commands/compare/index.js +0 -89
  19. package/dist/adapters/atlas/commands/exec.js +0 -81
  20. package/dist/adapters/atlas/commands/project/index.js +0 -218
  21. package/dist/adapters/atlas/commands/schema.js +0 -25
  22. package/dist/adapters/atlas/commands/suggest.js +0 -83
  23. package/dist/adapters/atlas/commands/update.js +0 -104
  24. package/dist/adapters/atlas/daemon/index.js +0 -64
  25. package/dist/adapters/atlas/dict/index.js +0 -41
  26. package/dist/adapters/atlas/http/client.js +0 -200
  27. package/dist/adapters/atlas/http/index.js +0 -1
  28. package/dist/adapters/atlas/schema/actual.js +0 -16
  29. package/dist/adapters/atlas/schema/baseline.js +0 -34
  30. package/dist/adapters/atlas/schema/department.js +0 -11
  31. package/dist/adapters/atlas/schema/index.js +0 -4
  32. package/dist/adapters/atlas/schema/project.js +0 -13
  33. package/dist/adapters/atlas/util/constants.js +0 -4
  34. package/dist/adapters/atlas/util/env.js +0 -56
  35. package/dist/adapters/atlas/util/errors.js +0 -49
  36. package/dist/adapters/atlas/util/helpers.js +0 -17
  37. package/dist/adapters/atlas/util/months.js +0 -65
  38. package/dist/adapters/atlas/util/output-limit.js +0 -21
  39. package/dist/adapters/atlas/util/output.js +0 -70
  40. package/dist/adapters/atlas/util/paths.js +0 -40
  41. package/dist/adapters/atlas/util/secure-fs.js +0 -41
  42. package/dist/adapters/atlas/util/time.js +0 -17
  43. package/dist/adapters/atlas/util/version.js +0 -1
@@ -1,137 +0,0 @@
1
- import { createClient } from '../../http/client.js';
2
- import { isJsonMode, jsonOk, log } from '../../util/output.js';
3
- import { enforceOutputLimit } from '../../util/output-limit.js';
4
- import { expandMonths } from '../../util/months.js';
5
- import { monthTsToKey } from '../../util/time.js';
6
- import { resolveSecureExportPath, secureWriteFile } from '../../util/secure-fs.js';
7
- import { resolveProjectId, resolveProjectInfo } from '../../util/env.js';
8
- function getProjectDisplay(opts) {
9
- const info = resolveProjectInfo(opts.projectId);
10
- return info.name ?? info.id;
11
- }
12
- function getProjectId(opts) {
13
- return resolveProjectId(opts.projectId);
14
- }
15
- /**
16
- * 月基线 API 返回 data 为数组,每项含 linePlanMonthDetailList
17
- */
18
- async function fetchBaselineMonth(pid, month) {
19
- const client = createClient();
20
- const body = { projectId: pid, month };
21
- return client.post('/yuntu-service/line/plan/month/select.json', body);
22
- }
23
- /** 强化的类型守卫(C6):检查 month 为有限 number,manpower 可选 */
24
- function isMonthKey(v) {
25
- return typeof v === 'object' && v !== null
26
- && typeof v.month === 'number'
27
- && Number.isFinite(v.month);
28
- }
29
- /** mpType 值映射:0=斑马 1=智软 */
30
- const MP_TYPE_MAP = { '0': '斑马', '1': '智软' };
31
- function filterBaseline(details, opts) {
32
- return details.filter(d => (!opts.department || (d.departmentName && d.departmentName.includes(opts.department)))
33
- && (!opts.role || (d.role && d.role.includes(opts.role)))
34
- && (!opts.area || (d.areaCode && d.areaCode.includes(opts.area)))
35
- && (!opts.manpowerType || (d.mpType && MP_TYPE_MAP[d.mpType]?.includes(opts.manpowerType))));
36
- }
37
- export async function monthCmd(opts) {
38
- const pid = getProjectId(opts);
39
- // 基线 API 一次返回项目全部月份数据,不在月份循环内调用(B1)
40
- const months = opts.month ? [opts.month] : expandMonths(opts.from, opts.to);
41
- const data = await fetchBaselineMonth(pid, months[0]);
42
- const allDetails = [];
43
- for (const item of (data ?? [])) {
44
- const details = (item.linePlanMonthDetailList ?? []);
45
- const mpType = String(item.mpType ?? '');
46
- const areaCode = item.areaCode;
47
- for (const d of details) {
48
- if (isMonthKey(d)) {
49
- allDetails.push({
50
- month: monthTsToKey(d.month),
51
- manpower: d.manpower ?? 0,
52
- role: item.role,
53
- departmentName: item.departmentName,
54
- mpType,
55
- areaCode,
56
- });
57
- }
58
- }
59
- }
60
- // Apply filters
61
- const filtered = filterBaseline(allDetails, opts);
62
- // 聚合
63
- const byMonth = new Map();
64
- for (const d of filtered) {
65
- byMonth.set(d.month, (byMonth.get(d.month) ?? 0) + d.manpower);
66
- }
67
- const monthKeys = [...byMonth.keys()].sort();
68
- const total = monthKeys.reduce((s, m) => s + (byMonth.get(m) ?? 0), 0);
69
- if (opts.json || isJsonMode()) {
70
- jsonOk({ projectId: pid, months: monthKeys, entries: allDetails, totalManpower: Math.round(total * 100) / 100 });
71
- return;
72
- }
73
- log(`项目 ${getProjectDisplay(opts)} 基线人力:`);
74
- for (const m of monthKeys) {
75
- log(` ${m}: ${(byMonth.get(m) ?? 0).toFixed(2)} 人月`);
76
- }
77
- log(` 合计: ${total.toFixed(2)} 人月`);
78
- }
79
- export async function summaryCmd(opts) {
80
- const pid = getProjectId(opts);
81
- // 基线 API 一次返回项目全部月份数据,不在月份循环内调用(B1)
82
- const months = opts.month ? [opts.month] : expandMonths(opts.from, opts.to);
83
- const data = await fetchBaselineMonth(pid, months[0]);
84
- const allDetails = [];
85
- for (const item of (data ?? [])) {
86
- const mpType = String(item.mpType ?? '');
87
- const areaCode = item.areaCode;
88
- for (const d of (item.linePlanMonthDetailList ?? [])) {
89
- if (isMonthKey(d)) {
90
- allDetails.push({ month: monthTsToKey(d.month), manpower: d.manpower ?? 0, role: item.role, departmentName: item.departmentName, mpType, areaCode });
91
- }
92
- }
93
- }
94
- // Apply filters
95
- const filtered = filterBaseline(allDetails, opts);
96
- const axis = opts.by ?? 'month';
97
- const groups = new Map();
98
- for (const d of filtered) {
99
- const k = axis === 'month' ? d.month : axis === 'department' ? (d.departmentName ?? '其他') : (d.role ?? '其他');
100
- groups.set(k, (groups.get(k) ?? 0) + d.manpower);
101
- }
102
- const rows = [...groups.entries()].map(([k, v]) => ({ [axis]: k, manpower: Math.round(v * 100) / 100 }));
103
- if (opts.json || isJsonMode()) {
104
- jsonOk({ rows });
105
- return;
106
- }
107
- log(`汇总:`);
108
- for (const r of rows) {
109
- log(` ${String(r[axis])}: ${r.manpower.toFixed(2)} 人月`);
110
- }
111
- }
112
- export async function exportCmd(opts) {
113
- const pid = getProjectId(opts);
114
- // 基线 API 一次返回项目全部月份数据,不在月份循环内调用(B1)
115
- const months = expandMonths(opts.from, opts.to);
116
- const data = await fetchBaselineMonth(pid, months[0]);
117
- const rows = [];
118
- for (const item of (data ?? [])) {
119
- for (const d of (item.linePlanMonthDetailList ?? [])) {
120
- if (isMonthKey(d)) {
121
- rows.push({ projectId: pid, month: monthTsToKey(d.month), role: item.role ?? '', manpower: d.manpower ?? 0 });
122
- }
123
- }
124
- }
125
- const content = opts.format === 'csv'
126
- ? (await import('papaparse')).default.unparse(rows)
127
- : JSON.stringify(rows, null, 2);
128
- enforceOutputLimit(content);
129
- const safePath = resolveSecureExportPath(opts.out);
130
- await secureWriteFile(safePath, content);
131
- log(`已导出 ${rows.length} 条记录`);
132
- if (opts.json || isJsonMode()) {
133
- jsonOk({ exported: rows.length, format: opts.format, out: safePath });
134
- }
135
- }
136
- // 注:baseline fill / import(服务端写操作)已移除,CLI 聚焦查询与导出。
137
- // 未来按需恢复时,同步接通 ATLAS_SANDBOX dry-run 护卫与导出/导入字节护栏。
@@ -1,39 +0,0 @@
1
- /**
2
- * compare 命令的纯逻辑层。
3
- *
4
- * 与命令解析层(index.ts)分离:这里不做 IO、不调 API、不输出,
5
- * 只做数据变换(聚合、阈值过滤)。
6
- */
7
- /**
8
- * 按轴分组聚合 manpower。
9
- * - month → 直接使用 month 字段
10
- * - department → departmentName ?? '其他'
11
- * - role → role ?? '其他'
12
- */
13
- export function groupByAxis(entries, axis) {
14
- const map = new Map();
15
- for (const e of entries) {
16
- const k = axis === 'department' ? (e.departmentName ?? '其他')
17
- : axis === 'role' ? (e.role ?? '其他')
18
- : e.month;
19
- map.set(k, (map.get(k) ?? 0) + e.manpower);
20
- }
21
- return map;
22
- }
23
- /**
24
- * 合并基线 map + 实际 map → CompareRow 数组。
25
- * 所有 axis key 去重排序,每个 key 生成一条 { [axis], baselineManpower, actualManpower, diff }。
26
- */
27
- export function mergeBaselineActual(blMap, acMap, axis, threshold) {
28
- const keys = [...new Set([...blMap.keys(), ...acMap.keys()])].sort();
29
- let rows = keys.map((k) => ({
30
- [axis]: k,
31
- baselineManpower: Math.round((blMap.get(k) ?? 0) * 100) / 100,
32
- actualManpower: Math.round((acMap.get(k) ?? 0) * 100) / 100,
33
- diff: Math.round(((acMap.get(k) ?? 0) - (blMap.get(k) ?? 0)) * 100) / 100,
34
- }));
35
- if (threshold > 0) {
36
- rows = rows.filter((r) => Math.abs(r.diff) >= threshold);
37
- }
38
- return rows;
39
- }
@@ -1,89 +0,0 @@
1
- import { createClient } from '../../http/client.js';
2
- import { isJsonMode, jsonOk, log } from '../../util/output.js';
3
- import { ConfigError, SessionExpiredError } from '../../util/errors.js';
4
- import { expandMonths, expandMonthsDefault } from '../../util/months.js';
5
- import { monthTsToKey } from '../../util/time.js';
6
- import { groupByAxis, mergeBaselineActual, } from './_logic.js';
7
- import { resolveProjectId, resolveProjectInfo } from '../../util/env.js';
8
- import { readBanmaIdentity } from '../../auth/session.js';
9
- import { flattenTree as flattenActualTree, normalizeStatusFilter } from '../actual/_logic.js';
10
- function getProjectId(opts) {
11
- return resolveProjectId(opts.projectId);
12
- }
13
- export async function compareCmd(opts) {
14
- const pid = getProjectId(opts);
15
- const months = opts.month ? [opts.month] : (opts.from || opts.to ? expandMonths(opts.from, opts.to) : expandMonthsDefault());
16
- const statusFilter = normalizeStatusFilter(opts.status);
17
- const allBl = [];
18
- const allAc = [];
19
- const client = createClient();
20
- const failedMonths = [];
21
- // 基线 API 一次返回项目全部月份数据,在循环外只调用一次(B1)
22
- try {
23
- const blData = await client.post('/yuntu-service/line/plan/month/select.json', { projectId: pid, month: months[0] ?? '' });
24
- for (const item of (blData ?? [])) {
25
- const details = (item.linePlanMonthDetailList ?? []);
26
- for (const d of details) {
27
- if (d && typeof d === 'object') {
28
- const monthVal = d.month;
29
- const monthKey = typeof monthVal === 'number'
30
- ? monthTsToKey(monthVal)
31
- : months[0];
32
- allBl.push({ month: monthKey, manpower: Number(d.manpower ?? 0), role: item.role, departmentName: item.departmentName });
33
- }
34
- }
35
- }
36
- }
37
- catch (e) {
38
- if (e instanceof SessionExpiredError)
39
- throw e;
40
- log(`基线数据拉取失败: ${e instanceof Error ? e.message : String(e)}`);
41
- }
42
- for (const m of months) {
43
- // 指数退避:避免连续触发限流(B2)
44
- try {
45
- const identity = await readBanmaIdentity();
46
- const loginStaffId = identity?.staffId ?? '';
47
- const raw = await client.post('/yuntu-service/manpower/weekly/summaryByProject.json', {
48
- month: m, staffIds: [], projectIds: [pid], isConfirm: false, loginStaffId,
49
- });
50
- const tree = (Array.isArray(raw) ? raw : []);
51
- const results = [];
52
- if (tree.length > 0 && tree[0].children) {
53
- const entries = flattenActualTree(tree[0].children, pid, statusFilter);
54
- for (const p of entries) {
55
- results.push({ month: m, manpower: p.manpower ?? 0, role: p.role, departmentName: p.departmentName });
56
- }
57
- }
58
- for (const ac of results) {
59
- allAc.push(ac);
60
- }
61
- }
62
- catch (e) {
63
- // 实际工时 API 对无数据月份也返回 501(错误信息"当前token已失效"),
64
- // 这不是真正的 token 过期——基线 API 正常就说明 token 有效。
65
- // 所以不 rethrow SessionExpiredError,只记录到 failedMonths。
66
- // 真正的 token 过期会由基线 API 的 catch 块捕获并抛出。
67
- failedMonths.push({ month: m, error: `实际工时拉取失败: ${e instanceof Error ? e.message : String(e)}` });
68
- }
69
- }
70
- const axis = (opts.by ?? 'month');
71
- const blMap = groupByAxis(allBl, axis);
72
- const acMap = groupByAxis(allAc, axis);
73
- // C4: 阈值参数校验
74
- const threshold = Number(opts.threshold ?? '0');
75
- if (!Number.isFinite(threshold) || threshold < 0)
76
- throw new ConfigError(`--threshold 必须为非负数,实际: "${opts.threshold}"`);
77
- const rows = mergeBaselineActual(blMap, acMap, axis, threshold);
78
- if (opts.json || isJsonMode()) {
79
- jsonOk({ rows }, { failedMonths });
80
- return;
81
- }
82
- const pinfo = resolveProjectInfo(opts.projectId);
83
- log(`项目 ${pinfo.name ?? pid} 对比:${axis === 'month' ? '按月份' : axis === 'department' ? '按部门' : '按角色'}`);
84
- for (const r of rows) {
85
- const flag = opts.flagOverrun && r.actualManpower > r.baselineManpower ? ' ⚠️' : '';
86
- log(` ${String(r[axis])}: 基线 ${r.baselineManpower} / 实际 ${r.actualManpower} / 差异 ${r.diff > 0 ? '+' : ''}${r.diff}${flag}`);
87
- }
88
- }
89
- // monthTsToKey 已从 util/time.ts 导入
@@ -1,81 +0,0 @@
1
- import { readFile } from 'fs/promises';
2
- import { spawn } from 'child_process';
3
- import { z } from 'zod';
4
- import { isJsonMode, jsonOk, log } from '../util/output.js';
5
- import { ConfigError } from '../util/errors.js';
6
- const ExecStepSchema = z.object({
7
- command: z.string().regex(/^atlas( [a-z\-]+)+$/, 'command 必须是 atlas 子命令,如 "atlas auth login"'),
8
- args: z.array(z.string()).optional(),
9
- });
10
- const ExecPlanSchema = z.object({
11
- steps: z.array(ExecStepSchema).max(100, '步骤数不能超过 100'),
12
- });
13
- /**
14
- * 用 spawn 执行一条 atlas 子命令,返回 Promise。
15
- * 使用 process.execPath + process.argv[1] 确保无论 npx / global / direct 都能正确找到入口。
16
- */
17
- function runStep(command, extraArgs = []) {
18
- const parts = command.split(/\s+/);
19
- // parts[0] === 'atlas', 去掉后剩下的才是子命令参数
20
- const args = [process.argv[1], ...parts.slice(1), ...extraArgs];
21
- return new Promise((resolve, reject) => {
22
- const child = spawn(process.execPath, args, {
23
- stdio: 'inherit',
24
- env: { ...process.env },
25
- });
26
- child.on('close', (code) => {
27
- if (code === 0) {
28
- resolve();
29
- }
30
- else {
31
- reject(new Error(`进程退出码 ${code}`));
32
- }
33
- });
34
- child.on('error', (err) => {
35
- reject(new Error(`无法启动进程: ${err.message}`));
36
- });
37
- });
38
- }
39
- export async function execCmd(opts) {
40
- let raw;
41
- try {
42
- raw = await readFile(opts.planFile, 'utf-8');
43
- }
44
- catch (e) {
45
- throw new ConfigError(`无法读取计划文件: ${e instanceof Error ? e.message : String(e)}`);
46
- }
47
- let plan;
48
- try {
49
- plan = ExecPlanSchema.parse(JSON.parse(raw));
50
- }
51
- catch (e) {
52
- if (e instanceof z.ZodError) {
53
- throw new ConfigError(`计划文件校验失败: ${e.errors.map(ee => `${ee.path.join('.')}: ${ee.message}`).join('; ')}`);
54
- }
55
- throw new ConfigError(`计划文件格式错误: ${e instanceof Error ? e.message : String(e)}`);
56
- }
57
- log(`执行计划: ${plan.steps.length} 个步骤`);
58
- const results = [];
59
- for (let i = 0; i < plan.steps.length; i++) {
60
- const step = plan.steps[i];
61
- log(`[${i + 1}/${plan.steps.length}] ${step.command}`);
62
- try {
63
- await runStep(step.command, step.args);
64
- results.push({ step: i + 1, command: step.command, status: 'success' });
65
- }
66
- catch (e) {
67
- const msg = e instanceof Error ? e.message : String(e);
68
- results.push({ step: i + 1, command: step.command, status: `failed: ${msg}` });
69
- if (opts.json || isJsonMode()) {
70
- jsonOk({ results }, { completed: i, total: plan.steps.length });
71
- }
72
- process.exit(1);
73
- }
74
- }
75
- if (opts.json || isJsonMode()) {
76
- jsonOk({ results });
77
- }
78
- else {
79
- log('计划执行完毕');
80
- }
81
- }
@@ -1,218 +0,0 @@
1
- import { createClient } from '../../http/client.js';
2
- import { isJsonMode, jsonOk, log, table } from '../../util/output.js';
3
- import { getBanmaProjectId } from '../../util/env.js';
4
- import { AtlasError, ConfigError } from '../../util/errors.js';
5
- import { readFile, unlink } from 'fs/promises';
6
- import { existsSync } from 'fs';
7
- import { getLinkFile, getCacheFile, getCacheDir, getAtlasHome } from '../../util/paths.js';
8
- import { secureMkdir, secureWriteFile } from '../../util/secure-fs.js';
9
- const LINK_FILE = getLinkFile();
10
- const PROJECT_CACHE_FILE = getCacheFile('projects');
11
- async function readLink() {
12
- try {
13
- if (!existsSync(LINK_FILE))
14
- return null;
15
- return JSON.parse(await readFile(LINK_FILE, 'utf-8'));
16
- }
17
- catch {
18
- return null;
19
- }
20
- }
21
- async function writeLink(link) {
22
- await secureMkdir(getAtlasHome(), { recursive: true });
23
- await secureWriteFile(LINK_FILE, JSON.stringify(link, null, 2));
24
- }
25
- async function removeLink() {
26
- try {
27
- await unlink(LINK_FILE);
28
- }
29
- catch { }
30
- }
31
- function getProjectName(p) {
32
- return p.name ?? p.projectName ?? String(p.id);
33
- }
34
- /** 统一把 id 转成 string,API 可能返回数字 */
35
- function getProjectId(p) {
36
- return String(p.id);
37
- }
38
- async function fetchProjectsFromApi(refresh) {
39
- if (!refresh) {
40
- try {
41
- if (existsSync(PROJECT_CACHE_FILE)) {
42
- return JSON.parse(await readFile(PROJECT_CACHE_FILE, 'utf-8'));
43
- }
44
- }
45
- catch { }
46
- }
47
- const client = createClient();
48
- // API 直接返回数组: [{id, name}, ...]
49
- const data = await client.post('/yuntu-service/project/selectHasPermisValidProject.json', {});
50
- const projects = (data ?? []);
51
- await secureMkdir(getCacheDir(), { recursive: true });
52
- await secureWriteFile(PROJECT_CACHE_FILE, JSON.stringify(projects, null, 2));
53
- return projects;
54
- }
55
- function resolveProjectId(projects, query) {
56
- const exact = projects.find((p) => getProjectId(p) === query || getProjectName(p) === query);
57
- if (exact)
58
- return { id: getProjectId(exact), name: getProjectName(exact) };
59
- const sub = projects.filter((p) => getProjectId(p).includes(query) || getProjectName(p).includes(query));
60
- if (sub.length === 1)
61
- return { id: getProjectId(sub[0]), name: getProjectName(sub[0]) };
62
- if (sub.length > 1)
63
- throw new AtlasError(`项目匹配歧义: "${query}" 匹配了 ${sub.length} 个项目`, 'AMBIGUOUS_PROJECT');
64
- return null;
65
- }
66
- export async function findCmd(kind, query, opts) {
67
- const client = createClient();
68
- let results = [];
69
- if (kind === 'project') {
70
- const projects = (await fetchProjectsFromApi(opts.refresh)) ?? [];
71
- results = projects
72
- .filter((p) => getProjectId(p).includes(query) || getProjectName(p).includes(query))
73
- .map((p) => ({ id: getProjectId(p), name: getProjectName(p), kind: 'project' }));
74
- // B4 兜底:权限列表搜不到时尝试按 ID 或名称直接查项目
75
- // 部分项目(如 status=2)不会出现在 selectHasPermisValidProject 中,
76
- // 但 queryProjById / queryProjList 可返回完整信息
77
- if (results.length === 0) {
78
- // 兜底一:按 ID 查项目详情
79
- try {
80
- const detail = await client.get('/yuntu-service/projApi/queryProjById.json', { projId: query });
81
- if (detail?.projInfo?.id && detail?.projInfo?.projName) {
82
- results = [{ id: String(detail.projInfo.id), name: detail.projInfo.projName, kind: 'project' }];
83
- }
84
- }
85
- catch {
86
- // 静默忽略
87
- }
88
- // 兜底二:按名称模糊搜索项目列表
89
- if (results.length === 0) {
90
- try {
91
- const list = await client.get('/yuntu-service/projApi/queryProjList.json', { projName: query, staffId: '' });
92
- if (Array.isArray(list)) {
93
- results = list
94
- .filter((p) => p.id && p.projName)
95
- .slice(0, parseInt(opts.limit ?? '20', 10))
96
- .map((p) => ({ id: String(p.id), name: p.projName, kind: 'project' }));
97
- }
98
- }
99
- catch {
100
- // 静默忽略
101
- }
102
- }
103
- }
104
- }
105
- else if (kind === 'department') {
106
- const data = await client.post('/yuntu-service/department/tree/select.json', {});
107
- const nodes = Array.isArray(data) ? data : [];
108
- const flatten = (items) => {
109
- const r = [];
110
- for (const n of items) {
111
- const name = n.deptName ?? '';
112
- if (name.includes(query))
113
- r.push({ id: String(n.id ?? n.deptCode ?? ''), name, kind: 'department' });
114
- if (n.children)
115
- r.push(...flatten(n.children));
116
- }
117
- return r;
118
- };
119
- results = flatten(nodes);
120
- }
121
- else {
122
- const dictData = await client.post('/yuntu-service/dictionary/select.json', { dictType: kind });
123
- const entries = Array.isArray(dictData) ? dictData : [];
124
- // 字典 API 忽略 dictType 参数,始终返回全部 181 条。
125
- // 按 typeDesc 字段在各 kind 间精确过滤
126
- const kindTypeDesc = {
127
- 'manpower-type': '人力类型',
128
- 'role': '人力基线角色',
129
- 'area': '地域',
130
- };
131
- const desc = kindTypeDesc[kind];
132
- const filtered = desc ? entries.filter(e => e.typeDesc === desc) : entries;
133
- results = filtered
134
- .filter((e) => (e.attrName ?? '').includes(query))
135
- .map((e) => ({ id: e.attrValue ?? '', name: e.attrName ?? '', kind }));
136
- }
137
- const limit = parseInt(opts.limit ?? '20', 10);
138
- if (!Number.isFinite(limit) || limit <= 0)
139
- throw new ConfigError(`--limit 必须为正整数,实际: "${opts.limit}"`);
140
- results = results.slice(0, limit);
141
- if (opts.json || isJsonMode()) {
142
- jsonOk(results, { count: results.length, kind, query });
143
- return;
144
- }
145
- log(`找到 ${results.length} 个${kind}:`);
146
- table(results);
147
- }
148
- export async function projectsCmd(opts) {
149
- const rootData = (await fetchProjectsFromApi(opts.refresh)) ?? [];
150
- const projects = rootData;
151
- const formatted = projects.map((p) => ({ id: getProjectId(p), name: getProjectName(p) }));
152
- if (opts.json || isJsonMode()) {
153
- jsonOk(formatted);
154
- return;
155
- }
156
- log(`你有 ${formatted.length} 个项目的权限:`);
157
- table(formatted);
158
- }
159
- export async function linkCmd(project, opts) {
160
- const projects = await fetchProjectsFromApi(opts.refreshProjects);
161
- const resolved = resolveProjectId(projects, project);
162
- if (!resolved)
163
- throw new AtlasError(`未找到项目: "${project}"`, 'PROJECT_NOT_FOUND');
164
- const link = { projectId: resolved.id, projectName: resolved.name, linkedAt: new Date().toISOString() };
165
- if (opts.dryRun) {
166
- log(`[dry-run] 将会绑定项目: ${resolved.name} (${resolved.id})`);
167
- if (opts.json || isJsonMode()) {
168
- jsonOk({ dryRun: true, link });
169
- return;
170
- }
171
- return;
172
- }
173
- await writeLink(link);
174
- log(`已绑定项目: ${resolved.name} (${resolved.id})`);
175
- if (opts.json || isJsonMode()) {
176
- jsonOk(link);
177
- }
178
- }
179
- export async function linkStatusCmd(opts) {
180
- const link = await readLink();
181
- const envProjectId = getBanmaProjectId();
182
- if (opts.json || isJsonMode()) {
183
- jsonOk({ linked: link ?? null, envProjectId: envProjectId ?? null });
184
- return;
185
- }
186
- if (link) {
187
- log(`当前绑定项目: ${link.projectName} (${link.projectId})\n绑定时间: ${link.linkedAt}`);
188
- }
189
- else {
190
- log('未绑定项目。使用 atlas link <project> 绑定');
191
- }
192
- if (envProjectId)
193
- log(`环境变量 BANMA_PROJECT_ID=${envProjectId}`);
194
- }
195
- export async function unlinkCmd(opts) {
196
- const link = await readLink();
197
- if (!link) {
198
- log('当前未绑定项目');
199
- if (opts.json || isJsonMode()) {
200
- jsonOk({ unlinked: false });
201
- return;
202
- }
203
- return;
204
- }
205
- if (opts.dryRun) {
206
- log(`[dry-run] 将会清除项目绑定: ${link.projectName}`);
207
- if (opts.json || isJsonMode()) {
208
- jsonOk({ dryRun: true, link });
209
- return;
210
- }
211
- return;
212
- }
213
- await removeLink();
214
- log(`已清除项目绑定: ${link.projectName}`);
215
- if (opts.json || isJsonMode()) {
216
- jsonOk({ unlinked: true, projectName: link.projectName });
217
- }
218
- }
@@ -1,25 +0,0 @@
1
- import { isJsonMode, jsonOk, log } from '../util/output.js';
2
- import { emitDescribe } from '../cli.js';
3
- export function schemaCommandsCmd(program, opts) {
4
- const commands = [];
5
- program.commands.forEach((cmd) => {
6
- commands.push(cmd.name());
7
- if (cmd.commands.length > 0) {
8
- cmd.commands.forEach((sub) => {
9
- commands.push(`${cmd.name()} ${sub.name()}`);
10
- });
11
- }
12
- });
13
- if (opts.describe) {
14
- program.commands.forEach((cmd) => {
15
- emitDescribe(cmd);
16
- });
17
- return;
18
- }
19
- if (opts.json || isJsonMode()) {
20
- jsonOk({ commands });
21
- return;
22
- }
23
- log('可用命令:');
24
- commands.sort().forEach((c) => log(` ${c}`));
25
- }