@yoyoboot/yoyo-admin-cli 10.5.5

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 (85) hide show
  1. package/dist/bin.d.ts +9 -0
  2. package/dist/bin.d.ts.map +1 -0
  3. package/dist/bin.js +10 -0
  4. package/dist/catalogs/capability.d.ts +54 -0
  5. package/dist/catalogs/capability.d.ts.map +1 -0
  6. package/dist/catalogs/capability.js +129 -0
  7. package/dist/catalogs/crud.d.ts +64 -0
  8. package/dist/catalogs/crud.d.ts.map +1 -0
  9. package/dist/catalogs/crud.js +83 -0
  10. package/dist/catalogs/source.d.ts +16 -0
  11. package/dist/catalogs/source.d.ts.map +1 -0
  12. package/dist/catalogs/source.js +8 -0
  13. package/dist/catalogs/standard-crud.d.ts +51 -0
  14. package/dist/catalogs/standard-crud.d.ts.map +1 -0
  15. package/dist/catalogs/standard-crud.js +100 -0
  16. package/dist/cli.d.ts +20 -0
  17. package/dist/cli.d.ts.map +1 -0
  18. package/dist/cli.js +86 -0
  19. package/dist/commands/capabilities.d.ts +78 -0
  20. package/dist/commands/capabilities.d.ts.map +1 -0
  21. package/dist/commands/capabilities.js +72 -0
  22. package/dist/commands/contracts.d.ts +16 -0
  23. package/dist/commands/contracts.d.ts.map +1 -0
  24. package/dist/commands/contracts.js +83 -0
  25. package/dist/commands/create-frontend-module.d.ts +8 -0
  26. package/dist/commands/create-frontend-module.d.ts.map +1 -0
  27. package/dist/commands/create-frontend-module.js +90 -0
  28. package/dist/commands/create-package.d.ts +17 -0
  29. package/dist/commands/create-package.d.ts.map +1 -0
  30. package/dist/commands/create-package.js +75 -0
  31. package/dist/commands/create-registry.d.ts +32 -0
  32. package/dist/commands/create-registry.d.ts.map +1 -0
  33. package/dist/commands/create-registry.js +20 -0
  34. package/dist/commands/create.d.ts +23 -0
  35. package/dist/commands/create.d.ts.map +1 -0
  36. package/dist/commands/create.js +111 -0
  37. package/dist/commands/doctor.d.ts +17 -0
  38. package/dist/commands/doctor.d.ts.map +1 -0
  39. package/dist/commands/doctor.js +150 -0
  40. package/dist/commands/inspect.d.ts +139 -0
  41. package/dist/commands/inspect.d.ts.map +1 -0
  42. package/dist/commands/inspect.js +379 -0
  43. package/dist/generators/frontend-module.d.ts +81 -0
  44. package/dist/generators/frontend-module.d.ts.map +1 -0
  45. package/dist/generators/frontend-module.js +358 -0
  46. package/dist/generators/package.d.ts +46 -0
  47. package/dist/generators/package.d.ts.map +1 -0
  48. package/dist/generators/package.js +230 -0
  49. package/dist/index.d.ts +26 -0
  50. package/dist/index.d.ts.map +1 -0
  51. package/dist/index.js +24 -0
  52. package/dist/kernel/arguments.d.ts +44 -0
  53. package/dist/kernel/arguments.d.ts.map +1 -0
  54. package/dist/kernel/arguments.js +127 -0
  55. package/dist/kernel/command.d.ts +71 -0
  56. package/dist/kernel/command.d.ts.map +1 -0
  57. package/dist/kernel/command.js +120 -0
  58. package/dist/kernel/fs-plan.d.ts +50 -0
  59. package/dist/kernel/fs-plan.d.ts.map +1 -0
  60. package/dist/kernel/fs-plan.js +82 -0
  61. package/dist/kernel/help.d.ts +54 -0
  62. package/dist/kernel/help.d.ts.map +1 -0
  63. package/dist/kernel/help.js +109 -0
  64. package/dist/kernel/lockfile.d.ts +73 -0
  65. package/dist/kernel/lockfile.d.ts.map +1 -0
  66. package/dist/kernel/lockfile.js +117 -0
  67. package/dist/kernel/manifests-wiring.d.ts +44 -0
  68. package/dist/kernel/manifests-wiring.d.ts.map +1 -0
  69. package/dist/kernel/manifests-wiring.js +165 -0
  70. package/dist/kernel/plan-runner.d.ts +81 -0
  71. package/dist/kernel/plan-runner.d.ts.map +1 -0
  72. package/dist/kernel/plan-runner.js +176 -0
  73. package/dist/kernel/process-runner.d.ts +25 -0
  74. package/dist/kernel/process-runner.d.ts.map +1 -0
  75. package/dist/kernel/process-runner.js +29 -0
  76. package/dist/kernel/render.d.ts +20 -0
  77. package/dist/kernel/render.d.ts.map +1 -0
  78. package/dist/kernel/render.js +63 -0
  79. package/dist/kernel/result.d.ts +66 -0
  80. package/dist/kernel/result.d.ts.map +1 -0
  81. package/dist/kernel/result.js +36 -0
  82. package/dist/kernel/workspace.d.ts +31 -0
  83. package/dist/kernel/workspace.d.ts.map +1 -0
  84. package/dist/kernel/workspace.js +66 -0
  85. package/package.json +31 -0
@@ -0,0 +1,176 @@
1
+ /**
2
+ * FsPlan 落盘执行器。
3
+ *
4
+ * 三段式:
5
+ * 1. **preflight** —— 只读校验全部 change,任何一条不满足就抛错,**零写入**。
6
+ * 2. **apply** —— 事务化写盘:先快照、逐文件落盘;任一文件失败,
7
+ * 回滚本次已产生的全部文件系统影响(恢复 update 的原内容、删除
8
+ * 已 create 的文件与本 CLI 新建的目录),**工作树恢复原状**。
9
+ * 3. 报告实际写入路径。
10
+ *
11
+ * 「零部分写入」由 preflight(fail-closed)与 apply(transactional)共同保证:
12
+ * 即使在 preflight 与 apply 之间文件系统发生变化(竞态),apply 也不会留下半成品。
13
+ *
14
+ * 路径收束(confinement)是本层的第一道动作:**每条 change 的路径都要先通过
15
+ * `confineRepoPath`,全部通过后才允许任何写入**。preflight 与 apply 各自独立
16
+ * 执行这一层,因此写入边界不依赖 generator 自律。
17
+ */
18
+ import { existsSync, mkdirSync, readFileSync, rmSync, rmdirSync, writeFileSync } from 'node:fs';
19
+ import { dirname, isAbsolute, relative, resolve } from 'node:path';
20
+ import { assertConfinedRelativePath } from './fs-plan.js';
21
+ import { YoyoAdminCliUsageError } from './result.js';
22
+ /**
23
+ * 把 plan 相对路径收束成仓库内的绝对路径。
24
+ *
25
+ * 两层防御:静态字符串判定(`assertConfinedRelativePath`)+ 解析后的
26
+ * 真实包含关系校验(`resolve` 结果必须仍在 repoRoot 内)。
27
+ */
28
+ export function confineRepoPath(repoRoot, relativePath) {
29
+ assertConfinedRelativePath(relativePath);
30
+ const root = resolve(repoRoot);
31
+ const absolute = resolve(root, relativePath);
32
+ const inside = relative(root, absolute);
33
+ if (inside.length === 0 || inside.startsWith('..') || isAbsolute(inside)) {
34
+ throw new YoyoAdminCliUsageError('unsafe-plan-path', `FsPlan 目标路径越出仓库根:${relativePath}。`);
35
+ }
36
+ return absolute;
37
+ }
38
+ /**
39
+ * 只读校验。
40
+ *
41
+ * 规则:
42
+ * - 全部路径先收束(非法路径 → `unsafe-plan-path`,零写入);
43
+ * - `create` 目标必须**不存在**(存在即视为重复执行,禁止静默覆盖);
44
+ * 父目录允许不存在 —— 新模块目录按定义就是新的,由 apply 阶段创建。
45
+ * - `update` 目标必须**存在且可读**(防止路径拼写错误被静默新建掩盖)。
46
+ */
47
+ export function preflightFsPlan(repoRoot, plan) {
48
+ const confined = plan.changes.map(change => ({
49
+ change,
50
+ absolute: confineRepoPath(repoRoot, change.path),
51
+ }));
52
+ const targets = [];
53
+ for (const { change, absolute } of confined) {
54
+ if (change.kind === 'create' && existsSync(absolute)) {
55
+ throw new YoyoAdminCliUsageError('target-already-exists', `目标文件已存在,拒绝静默覆盖:${change.path}`);
56
+ }
57
+ if (change.kind === 'update' && !existsSync(absolute)) {
58
+ throw new YoyoAdminCliUsageError('update-target-missing', `待更新文件不存在:${change.path}`);
59
+ }
60
+ targets.push(change.kind === 'update'
61
+ ? { path: change.path, kind: change.kind, origin: change.origin, summary: change.summary }
62
+ : { path: change.path, kind: change.kind, origin: change.origin });
63
+ }
64
+ return { targets };
65
+ }
66
+ /** 创建父目录链,返回「本次新建」的目录(此前不存在),从浅到深排列。 */
67
+ function ensureParentDirectories(absoluteFile) {
68
+ const missing = [];
69
+ let cursor = dirname(absoluteFile);
70
+ while (!existsSync(cursor)) {
71
+ missing.unshift(cursor);
72
+ const parent = dirname(cursor);
73
+ if (parent === cursor)
74
+ break;
75
+ cursor = parent;
76
+ }
77
+ for (const directory of missing) {
78
+ mkdirSync(directory, { recursive: false });
79
+ }
80
+ return missing;
81
+ }
82
+ /**
83
+ * 事务化落盘。
84
+ *
85
+ * 调用前建议先通过 `preflightFsPlan`;即使未调用,本函数自身也保持原子性——
86
+ * 全部路径先收束(非法路径在**任何写入之前**整体失败),任何一条 change 失败
87
+ * (含 update 目标缺失、路径非法等 I/O 错误)都会触发回滚:已 create 的文件被
88
+ * 删除、本 CLI 新建的目录被移除(仅当仍为空)、已 update 的文件恢复写入前的原内容。
89
+ */
90
+ export function applyFsPlan(repoRoot, plan) {
91
+ return applyFsPlanTransaction(repoRoot, plan).written;
92
+ }
93
+ /**
94
+ * 事务化落盘并返回可撤销句柄。
95
+ *
96
+ * 用于 `create package` 这类「写文件 + 额外外部步骤(lockfile 刷新)」的闭环:
97
+ * 额外步骤失败时调用 `rollback()`,保证全有或全无。
98
+ */
99
+ export function applyFsPlanTransaction(repoRoot, plan) {
100
+ // 先收束全部路径:非法目标在任何写入之前整体失败,天然零写入。
101
+ const confined = plan.changes.map(change => ({
102
+ change,
103
+ absolute: confineRepoPath(repoRoot, change.path),
104
+ }));
105
+ const applied = [];
106
+ try {
107
+ for (const { change, absolute } of confined) {
108
+ if (change.kind === 'create') {
109
+ if (existsSync(absolute)) {
110
+ throw new YoyoAdminCliUsageError('target-already-exists', `目标文件已存在,拒绝静默覆盖:${change.path}`);
111
+ }
112
+ const createdDirectories = ensureParentDirectories(absolute);
113
+ writeFileSync(absolute, change.content, 'utf8');
114
+ applied.push({ change, absolute, priorContent: null, createdDirectories });
115
+ continue;
116
+ }
117
+ // update:先读原内容作为回滚凭据(目标缺失时此处抛错,天然安全)。
118
+ const priorContent = readFileSync(absolute, 'utf8');
119
+ writeFileSync(absolute, change.content, 'utf8');
120
+ applied.push({ change, absolute, priorContent, createdDirectories: [] });
121
+ }
122
+ }
123
+ catch (error) {
124
+ rollbackApplied(applied);
125
+ throw error;
126
+ }
127
+ return {
128
+ written: applied.map(entry => entry.change.path),
129
+ rollback: () => rollbackApplied(applied),
130
+ };
131
+ }
132
+ /** 逆序回滚全部已落盘影响;回滚自身的失败不掩盖原始错误。 */
133
+ function rollbackApplied(applied) {
134
+ for (const entry of [...applied].reverse()) {
135
+ try {
136
+ if (entry.change.kind === 'create') {
137
+ if (existsSync(entry.absolute)) {
138
+ rmSync(entry.absolute, { force: true });
139
+ }
140
+ for (const directory of [...entry.createdDirectories].reverse()) {
141
+ try {
142
+ rmdirSync(directory);
143
+ }
144
+ catch {
145
+ // 目录非空(被并发写入)时保守保留,不影响工作树正确性。
146
+ }
147
+ }
148
+ continue;
149
+ }
150
+ if (entry.priorContent !== null) {
151
+ writeFileSync(entry.absolute, entry.priorContent, 'utf8');
152
+ }
153
+ }
154
+ catch {
155
+ // 回滚失败不抛出:调用方拿到的仍是最初的真实错误。
156
+ }
157
+ }
158
+ }
159
+ export function readRepoFile(repoRoot, path) {
160
+ const absolute = confineRepoPath(repoRoot, path);
161
+ if (!existsSync(absolute)) {
162
+ throw new YoyoAdminCliUsageError('file-not-found', `文件不存在:${path}`);
163
+ }
164
+ return readFileSync(absolute, 'utf8');
165
+ }
166
+ /**
167
+ * 执行门面。
168
+ *
169
+ * 把「preflight → apply」封装成对象,确保调用方无法跳过校验直接写盘。
170
+ */
171
+ export const planRunner = {
172
+ preflight: preflightFsPlan,
173
+ apply: applyFsPlan,
174
+ /** 返回可撤销句柄的 apply;额外外部步骤失败时用 `rollback()` 保证全有或全无。 */
175
+ applyTransaction: applyFsPlanTransaction,
176
+ };
@@ -0,0 +1,25 @@
1
+ export interface ProcessRequest {
2
+ readonly command: string;
3
+ readonly args: readonly string[];
4
+ readonly cwd: string;
5
+ }
6
+ export interface ProcessResult {
7
+ readonly status: number | null;
8
+ readonly stdout: string;
9
+ readonly stderr: string;
10
+ readonly error?: NodeJS.ErrnoException;
11
+ }
12
+ export type ProcessRunner = (request: ProcessRequest) => ProcessResult;
13
+ export interface ProcessInvocation {
14
+ readonly command: string;
15
+ readonly args: readonly string[];
16
+ }
17
+ /**
18
+ * Node 24 on Windows does not launch `.cmd` shims through `spawnSync()` directly.
19
+ * Route pnpm through the platform command processor while preserving the original
20
+ * argument vector; other executables keep the normal shell-free path.
21
+ */
22
+ export declare function createProcessInvocation(command: string, args: readonly string[], platform?: NodeJS.Platform, commandShell?: string | undefined): ProcessInvocation;
23
+ /** Owner checks are captured, never streamed into the CLI result envelope. */
24
+ export declare const runProcess: ProcessRunner;
25
+ //# sourceMappingURL=process-runner.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"process-runner.d.ts","sourceRoot":"","sources":["../../src/kernel/process-runner.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;IAChC,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAA;CACrB;AAED,MAAM,WAAW,aAAa;IAC5B,QAAQ,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAAA;IAC9B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC,cAAc,CAAA;CACvC;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,cAAc,KAAK,aAAa,CAAA;AAEtE,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,CAAA;CACjC;AAED;;;;GAIG;AACH,wBAAgB,uBAAuB,CACrC,OAAO,EAAE,MAAM,EACf,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,QAAQ,GAAE,MAAM,CAAC,QAA2B,EAC5C,YAAY,GAAE,MAAM,GAAG,SAA+B,GACrD,iBAAiB,CASnB;AAED,8EAA8E;AAC9E,eAAO,MAAM,UAAU,EAAE,aAYxB,CAAA"}
@@ -0,0 +1,29 @@
1
+ import { spawnSync } from 'node:child_process';
2
+ /**
3
+ * Node 24 on Windows does not launch `.cmd` shims through `spawnSync()` directly.
4
+ * Route pnpm through the platform command processor while preserving the original
5
+ * argument vector; other executables keep the normal shell-free path.
6
+ */
7
+ export function createProcessInvocation(command, args, platform = process.platform, commandShell = process.env.ComSpec) {
8
+ if (platform !== 'win32' || !/^pnpm(?:\.cmd)?$/i.test(command)) {
9
+ return { command, args };
10
+ }
11
+ return {
12
+ command: commandShell?.trim() || 'cmd.exe',
13
+ args: ['/d', '/s', '/c', 'pnpm.cmd', ...args],
14
+ };
15
+ }
16
+ /** Owner checks are captured, never streamed into the CLI result envelope. */
17
+ export const runProcess = ({ command, args, cwd }) => {
18
+ const invocation = createProcessInvocation(command, args);
19
+ const result = spawnSync(invocation.command, [...invocation.args], {
20
+ cwd, encoding: 'utf8', windowsHide: true,
21
+ timeout: 180_000, maxBuffer: 16 * 1024 * 1024,
22
+ });
23
+ return {
24
+ status: result.status,
25
+ stdout: result.stdout ?? '',
26
+ stderr: result.stderr ?? '',
27
+ ...(result.error === undefined ? {} : { error: result.error }),
28
+ };
29
+ };
@@ -0,0 +1,20 @@
1
+ /**
2
+ * 结果渲染。
3
+ *
4
+ * 规则(需求第 8 条):
5
+ * - **不记录「是否运行过 CLI」**,只输出本次真实观察到的结果。
6
+ * - `--json` 输出完整结果信封,Agent 直接解析。
7
+ * - 人类模式输出稳定、可 grep 的简洁行。
8
+ * - 有专用渲染器的载荷(FsPlan / inspect)不再重复 dump JSON。
9
+ */
10
+ import { type FsPlan } from './fs-plan.js';
11
+ import type { YoyoAdminCliCommandDescriptor, YoyoAdminCliResult } from './result.js';
12
+ export declare function renderJson(value: unknown): string;
13
+ export declare function renderResult(result: YoyoAdminCliResult, options?: {
14
+ readonly omitData?: boolean;
15
+ }): string;
16
+ export declare function renderFsPlan(plan: FsPlan, options: {
17
+ dryRun: boolean;
18
+ }): string;
19
+ export declare function renderCapabilities(commands: readonly YoyoAdminCliCommandDescriptor[]): string;
20
+ //# sourceMappingURL=render.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../src/kernel/render.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,OAAO,EAAY,KAAK,MAAM,EAAE,MAAM,cAAc,CAAA;AACpD,OAAO,KAAK,EAAE,6BAA6B,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAQpF,wBAAgB,UAAU,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAEjD;AAED,wBAAgB,YAAY,CAC1B,MAAM,EAAE,kBAAkB,EAC1B,OAAO,GAAE;IAAE,QAAQ,CAAC,QAAQ,CAAC,EAAE,OAAO,CAAA;CAAO,GAC5C,MAAM,CAaR;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE;IAAE,MAAM,EAAE,OAAO,CAAA;CAAE,GAAG,MAAM,CAkB/E;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,SAAS,6BAA6B,EAAE,GAAG,MAAM,CAiB7F"}
@@ -0,0 +1,63 @@
1
+ /**
2
+ * 结果渲染。
3
+ *
4
+ * 规则(需求第 8 条):
5
+ * - **不记录「是否运行过 CLI」**,只输出本次真实观察到的结果。
6
+ * - `--json` 输出完整结果信封,Agent 直接解析。
7
+ * - 人类模式输出稳定、可 grep 的简洁行。
8
+ * - 有专用渲染器的载荷(FsPlan / inspect)不再重复 dump JSON。
9
+ */
10
+ import { isFsPlan } from './fs-plan.js';
11
+ const STATUS_MARK = {
12
+ ok: '[ok]',
13
+ warn: '[warn]',
14
+ error: '[error]',
15
+ };
16
+ export function renderJson(value) {
17
+ return `${JSON.stringify(value, null, 2)}\n`;
18
+ }
19
+ export function renderResult(result, options = {}) {
20
+ const lines = [`${STATUS_MARK[result.status] ?? '[?]'} ${result.command}`];
21
+ for (const item of result.findings) {
22
+ const detail = item.detail === undefined ? '' : `\n ${item.detail}`;
23
+ lines.push(` ${STATUS_MARK[item.status] ?? '[?]'} ${item.code}: ${item.message}${detail}`);
24
+ }
25
+ if (result.data !== undefined && options.omitData !== true && !isFsPlan(result.data)) {
26
+ lines.push('', JSON.stringify(result.data, null, 2));
27
+ }
28
+ return `${lines.join('\n')}\n`;
29
+ }
30
+ export function renderFsPlan(plan, options) {
31
+ const action = options.dryRun ? 'will create' : 'created';
32
+ const updateAction = options.dryRun ? 'will update' : 'updated';
33
+ const lines = [
34
+ options.dryRun ? 'dry-run: no files were written' : 'applied',
35
+ ` ${plan.summary.creates} create(s), ${plan.summary.updates} update(s)`,
36
+ '',
37
+ ];
38
+ for (const change of plan.changes) {
39
+ if (change.kind === 'create') {
40
+ lines.push(` ${action} ${change.path} (${change.origin})`);
41
+ }
42
+ else {
43
+ lines.push(` ${updateAction} ${change.path} (${change.origin}) — ${change.summary}`);
44
+ }
45
+ }
46
+ return `${lines.join('\n')}\n`;
47
+ }
48
+ export function renderCapabilities(commands) {
49
+ const lines = ['yoyo-admin capabilities', ''];
50
+ for (const command of commands) {
51
+ lines.push(` ${command.name}`);
52
+ lines.push(` ${command.summary}`);
53
+ lines.push(` usage: ${command.usage}`);
54
+ if (command.authoring) {
55
+ lines.push(' authoring: writes files; supports --dry-run');
56
+ }
57
+ if (command.capability !== undefined) {
58
+ lines.push(` capability: ${command.capability}`);
59
+ }
60
+ lines.push('');
61
+ }
62
+ return `${lines.join('\n')}\n`;
63
+ }
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 统一结果信封。
3
+ *
4
+ * 所有命令都返回本结构,`--json` 只是把它序列化输出。
5
+ * 这样 CLI 的任何命令都天然机器可读,Agent 不需要为每个命令单独解析文本。
6
+ */
7
+ export type YoyoAdminCliExitCode = 0 | 1 | 2;
8
+ /** 命令执行结果的严重级别。`error` 一定伴随非零退出码。 */
9
+ export type YoyoAdminCliStatus = 'ok' | 'warn' | 'error';
10
+ /** 单条诊断/能力项。`code` 是稳定机器标识,`message` 面向人类。 */
11
+ export interface YoyoAdminCliFinding {
12
+ readonly code: string;
13
+ readonly status: YoyoAdminCliStatus;
14
+ readonly message: string;
15
+ readonly detail?: string;
16
+ }
17
+ /** 命令声明的元数据,供 `capabilities` 与 help 复用。 */
18
+ export interface YoyoAdminCliCommandDescriptor {
19
+ readonly name: string;
20
+ readonly summary: string;
21
+ readonly usage: string;
22
+ readonly arguments: readonly {
23
+ readonly name: string;
24
+ readonly required: boolean;
25
+ readonly description: string;
26
+ }[];
27
+ readonly options: readonly {
28
+ readonly flag: string;
29
+ readonly description: string;
30
+ readonly values?: readonly string[];
31
+ readonly default?: string;
32
+ }[];
33
+ /**
34
+ * 该命令额外声明的布尔 flag(除全局 `--json` / `--dry-run` / `--help` 外)。
35
+ * `options` 中的 flag 一律视为**取值选项**;两者不得重叠。
36
+ */
37
+ readonly booleanFlags?: readonly string[];
38
+ /** 该命令是否为作者操作(会写文件),`--dry-run` 只对这类命令有意义。 */
39
+ readonly authoring: boolean;
40
+ /** 该命令的成功执行能让哪些治理能力从其下移出。 */
41
+ readonly capability?: string;
42
+ }
43
+ export interface YoyoAdminCliResult {
44
+ readonly command: string;
45
+ readonly status: YoyoAdminCliStatus;
46
+ readonly findings: readonly YoyoAdminCliFinding[];
47
+ /** 命令特有的结构化产物,例如 capabilities 清单或 FsPlan。 */
48
+ readonly data?: unknown;
49
+ }
50
+ export declare function ok(command: string, findings?: readonly YoyoAdminCliFinding[], data?: unknown): YoyoAdminCliResult;
51
+ export declare function warn(command: string, findings: readonly YoyoAdminCliFinding[], data?: unknown): YoyoAdminCliResult;
52
+ export declare function failure(command: string, findings: readonly YoyoAdminCliFinding[], data?: unknown): YoyoAdminCliResult;
53
+ export declare function finding(code: string, status: YoyoAdminCliStatus, message: string, detail?: string): YoyoAdminCliFinding;
54
+ /** 结果中是否含有 error 级 finding —— 决定退出码。 */
55
+ export declare function hasError(result: YoyoAdminCliResult): boolean;
56
+ /**
57
+ * CLI 的预期失败。
58
+ *
59
+ * 抛出它表示「用户输入或仓库状态不允许执行」,不是程序缺陷。
60
+ * 入口会把它转成 error 级 finding + 退出码 2,不打印堆栈。
61
+ */
62
+ export declare class YoyoAdminCliUsageError extends Error {
63
+ readonly code: string;
64
+ constructor(code: string, message: string);
65
+ }
66
+ //# sourceMappingURL=result.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"result.d.ts","sourceRoot":"","sources":["../../src/kernel/result.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,MAAM,MAAM,oBAAoB,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAA;AAE5C,qCAAqC;AACrC,MAAM,MAAM,kBAAkB,GAAG,IAAI,GAAG,MAAM,GAAG,OAAO,CAAA;AAExD,8CAA8C;AAC9C,MAAM,WAAW,mBAAmB;IAClC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAA;IACnC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CACzB;AAED,2CAA2C;AAC3C,MAAM,WAAW,6BAA6B;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,SAAS,EAAE,SAAS;QAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;QAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;KAC7B,EAAE,CAAA;IACH,QAAQ,CAAC,OAAO,EAAE,SAAS;QACzB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;QACrB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;QAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;QACnC,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;KAC1B,EAAE,CAAA;IACH;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IACzC,8CAA8C;IAC9C,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAA;IAC3B,6BAA6B;IAC7B,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,CAAA;CAC7B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAA;IACnC,QAAQ,CAAC,QAAQ,EAAE,SAAS,mBAAmB,EAAE,CAAA;IACjD,6CAA6C;IAC7C,QAAQ,CAAC,IAAI,CAAC,EAAE,OAAO,CAAA;CACxB;AAED,wBAAgB,EAAE,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,GAAE,SAAS,mBAAmB,EAAO,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAErH;AAED,wBAAgB,IAAI,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,mBAAmB,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAElH;AAED,wBAAgB,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,SAAS,mBAAmB,EAAE,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,kBAAkB,CAErH;AAED,wBAAgB,OAAO,CACrB,IAAI,EAAE,MAAM,EACZ,MAAM,EAAE,kBAAkB,EAC1B,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,MAAM,GACd,mBAAmB,CAErB;AAED,wCAAwC;AACxC,wBAAgB,QAAQ,CAAC,MAAM,EAAE,kBAAkB,GAAG,OAAO,CAE5D;AAED;;;;;GAKG;AACH,qBAAa,sBAAuB,SAAQ,KAAK;IAC/C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;gBAET,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM;CAK1C"}
@@ -0,0 +1,36 @@
1
+ /**
2
+ * 统一结果信封。
3
+ *
4
+ * 所有命令都返回本结构,`--json` 只是把它序列化输出。
5
+ * 这样 CLI 的任何命令都天然机器可读,Agent 不需要为每个命令单独解析文本。
6
+ */
7
+ export function ok(command, findings = [], data) {
8
+ return data === undefined ? { command, status: 'ok', findings } : { command, status: 'ok', findings, data };
9
+ }
10
+ export function warn(command, findings, data) {
11
+ return data === undefined ? { command, status: 'warn', findings } : { command, status: 'warn', findings, data };
12
+ }
13
+ export function failure(command, findings, data) {
14
+ return data === undefined ? { command, status: 'error', findings } : { command, status: 'error', findings, data };
15
+ }
16
+ export function finding(code, status, message, detail) {
17
+ return detail === undefined ? { code, status, message } : { code, status, message, detail };
18
+ }
19
+ /** 结果中是否含有 error 级 finding —— 决定退出码。 */
20
+ export function hasError(result) {
21
+ return result.status === 'error' || result.findings.some(item => item.status === 'error');
22
+ }
23
+ /**
24
+ * CLI 的预期失败。
25
+ *
26
+ * 抛出它表示「用户输入或仓库状态不允许执行」,不是程序缺陷。
27
+ * 入口会把它转成 error 级 finding + 退出码 2,不打印堆栈。
28
+ */
29
+ export class YoyoAdminCliUsageError extends Error {
30
+ code;
31
+ constructor(code, message) {
32
+ super(message);
33
+ this.name = 'YoyoAdminCliUsageError';
34
+ this.code = code;
35
+ }
36
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * 仓库与工作区定位。
3
+ *
4
+ * CLI 依赖的稳定锚点(三者必须同时满足,缺一不可):
5
+ * - 仓库根:含 `.git` 的祖先目录;
6
+ * - admin 工作区:`packages/yoyo-admin`;
7
+ * - 宿主:`apps/yoyo-admin/admin-vue`。
8
+ *
9
+ * 查找起点由调用方给出(`--repo <path>` → `runCli` 的 `startDirectory` →
10
+ * `process.cwd()`)。命中的祖先目录若只有 `.git` 而没有 Yoyo Admin 结构,
11
+ * 继续向上查找;一路到盘根都没有完整仓库则 fail-closed,不做兜底猜测。
12
+ */
13
+ export interface YoyoAdminWorkspace {
14
+ readonly repoRoot: string;
15
+ readonly adminWorkspace: string;
16
+ readonly adminVueHost: string;
17
+ readonly packagesRoot: string;
18
+ }
19
+ export declare const ADMIN_WORKSPACE_RELATIVE = "packages/yoyo-admin";
20
+ export declare const ADMIN_VUE_HOST_RELATIVE = "apps/yoyo-admin/admin-vue";
21
+ export declare const APP_MANIFESTS_RELATIVE = "apps/yoyo-admin/admin-vue/src/app/manifests.ts";
22
+ export declare const ADMIN_VUE_APP_ROOT_RELATIVE = "apps/yoyo-admin/admin-vue/src/app";
23
+ /** 仓库根必须同时具备的标记(错误消息与测试共用同一事实源)。 */
24
+ export declare const REQUIRED_REPO_MARKERS: readonly [".git", "packages/yoyo-admin", "apps/yoyo-admin/admin-vue"];
25
+ /**
26
+ * 从起点向上查找同时满足 `.git` + `packages/yoyo-admin` +
27
+ * `apps/yoyo-admin/admin-vue` 的仓库根。
28
+ */
29
+ export declare function findRepoRoot(startDirectory: string): string;
30
+ export declare function resolveWorkspace(startDirectory?: string): YoyoAdminWorkspace;
31
+ //# sourceMappingURL=workspace.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspace.d.ts","sourceRoot":"","sources":["../../src/kernel/workspace.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAOH,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;IAC7B,QAAQ,CAAC,YAAY,EAAE,MAAM,CAAA;CAC9B;AAED,eAAO,MAAM,wBAAwB,wBAAwB,CAAA;AAC7D,eAAO,MAAM,uBAAuB,8BAA8B,CAAA;AAClE,eAAO,MAAM,sBAAsB,mDAAmD,CAAA;AACtF,eAAO,MAAM,2BAA2B,sCAAsC,CAAA;AAE9E,oCAAoC;AACpC,eAAO,MAAM,qBAAqB,uEAIxB,CAAA;AAMV;;;GAGG;AACH,wBAAgB,YAAY,CAAC,cAAc,EAAE,MAAM,GAAG,MAAM,CAsC3D;AAED,wBAAgB,gBAAgB,CAAC,cAAc,GAAE,MAAsB,GAAG,kBAAkB,CAS3F"}
@@ -0,0 +1,66 @@
1
+ /**
2
+ * 仓库与工作区定位。
3
+ *
4
+ * CLI 依赖的稳定锚点(三者必须同时满足,缺一不可):
5
+ * - 仓库根:含 `.git` 的祖先目录;
6
+ * - admin 工作区:`packages/yoyo-admin`;
7
+ * - 宿主:`apps/yoyo-admin/admin-vue`。
8
+ *
9
+ * 查找起点由调用方给出(`--repo <path>` → `runCli` 的 `startDirectory` →
10
+ * `process.cwd()`)。命中的祖先目录若只有 `.git` 而没有 Yoyo Admin 结构,
11
+ * 继续向上查找;一路到盘根都没有完整仓库则 fail-closed,不做兜底猜测。
12
+ */
13
+ import { existsSync } from 'node:fs';
14
+ import { dirname, join, resolve } from 'node:path';
15
+ import { YoyoAdminCliUsageError } from './result.js';
16
+ export const ADMIN_WORKSPACE_RELATIVE = 'packages/yoyo-admin';
17
+ export const ADMIN_VUE_HOST_RELATIVE = 'apps/yoyo-admin/admin-vue';
18
+ export const APP_MANIFESTS_RELATIVE = 'apps/yoyo-admin/admin-vue/src/app/manifests.ts';
19
+ export const ADMIN_VUE_APP_ROOT_RELATIVE = 'apps/yoyo-admin/admin-vue/src/app';
20
+ /** 仓库根必须同时具备的标记(错误消息与测试共用同一事实源)。 */
21
+ export const REQUIRED_REPO_MARKERS = [
22
+ '.git',
23
+ ADMIN_WORKSPACE_RELATIVE,
24
+ ADMIN_VUE_HOST_RELATIVE,
25
+ ];
26
+ function hasCompleteLayout(candidate) {
27
+ return REQUIRED_REPO_MARKERS.every(marker => existsSync(join(candidate, marker)));
28
+ }
29
+ /**
30
+ * 从起点向上查找同时满足 `.git` + `packages/yoyo-admin` +
31
+ * `apps/yoyo-admin/admin-vue` 的仓库根。
32
+ */
33
+ export function findRepoRoot(startDirectory) {
34
+ const start = resolve(startDirectory);
35
+ if (!existsSync(start)) {
36
+ throw new YoyoAdminCliUsageError('start-directory-missing', `查找起点不存在:${startDirectory}。`);
37
+ }
38
+ let current = start;
39
+ let gitRootWithoutLayout;
40
+ for (;;) {
41
+ if (existsSync(join(current, '.git'))) {
42
+ if (hasCompleteLayout(current))
43
+ return current;
44
+ gitRootWithoutLayout ??= current;
45
+ }
46
+ const parent = dirname(current);
47
+ if (parent === current)
48
+ break;
49
+ current = parent;
50
+ }
51
+ if (gitRootWithoutLayout !== undefined) {
52
+ const missing = REQUIRED_REPO_MARKERS.filter(marker => marker !== '.git' && !existsSync(join(gitRootWithoutLayout, marker)));
53
+ throw new YoyoAdminCliUsageError('repo-layout-incomplete', `仓库根 ${gitRootWithoutLayout} 缺少 Yoyo Admin 结构:${missing.join('、')}。` +
54
+ `CLI 需要在 YoyoBoot 仓库内运行(${ADMIN_WORKSPACE_RELATIVE} 与 ${ADMIN_VUE_HOST_RELATIVE} 必须同时存在)。`);
55
+ }
56
+ throw new YoyoAdminCliUsageError('repo-root-not-found', `未能从 ${start} 向上找到含 .git 的仓库根目录;可用 --repo <path> 指定起点。`);
57
+ }
58
+ export function resolveWorkspace(startDirectory = process.cwd()) {
59
+ const repoRoot = findRepoRoot(startDirectory);
60
+ return {
61
+ repoRoot,
62
+ adminWorkspace: join(repoRoot, ADMIN_WORKSPACE_RELATIVE),
63
+ adminVueHost: join(repoRoot, ADMIN_VUE_HOST_RELATIVE),
64
+ packagesRoot: join(repoRoot, 'packages'),
65
+ };
66
+ }
package/package.json ADDED
@@ -0,0 +1,31 @@
1
+ {
2
+ "name": "@yoyoboot/yoyo-admin-cli",
3
+ "version": "10.5.5",
4
+ "description": "Yoyo Admin 统一作者入口 CLI:环境诊断、能力发现与标准结构生成。",
5
+ "type": "module",
6
+ "types": "./dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "exports": {
11
+ ".": {
12
+ "types": "./dist/index.d.ts",
13
+ "import": "./dist/index.js"
14
+ }
15
+ },
16
+ "bin": {
17
+ "yoyo-admin": "./dist/bin.js"
18
+ },
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/yoyoboot/YoyoBoot.git",
22
+ "directory": "packages/yoyo-admin/admin-cli"
23
+ },
24
+ "homepage": "https://github.com/yoyoboot/YoyoBoot#readme",
25
+ "bugs": {
26
+ "url": "https://github.com/yoyoboot/YoyoBoot/issues"
27
+ },
28
+ "publishConfig": {
29
+ "access": "public"
30
+ }
31
+ }