@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.
- package/dist/bin.d.ts +9 -0
- package/dist/bin.d.ts.map +1 -0
- package/dist/bin.js +10 -0
- package/dist/catalogs/capability.d.ts +54 -0
- package/dist/catalogs/capability.d.ts.map +1 -0
- package/dist/catalogs/capability.js +129 -0
- package/dist/catalogs/crud.d.ts +64 -0
- package/dist/catalogs/crud.d.ts.map +1 -0
- package/dist/catalogs/crud.js +83 -0
- package/dist/catalogs/source.d.ts +16 -0
- package/dist/catalogs/source.d.ts.map +1 -0
- package/dist/catalogs/source.js +8 -0
- package/dist/catalogs/standard-crud.d.ts +51 -0
- package/dist/catalogs/standard-crud.d.ts.map +1 -0
- package/dist/catalogs/standard-crud.js +100 -0
- package/dist/cli.d.ts +20 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +86 -0
- package/dist/commands/capabilities.d.ts +78 -0
- package/dist/commands/capabilities.d.ts.map +1 -0
- package/dist/commands/capabilities.js +72 -0
- package/dist/commands/contracts.d.ts +16 -0
- package/dist/commands/contracts.d.ts.map +1 -0
- package/dist/commands/contracts.js +83 -0
- package/dist/commands/create-frontend-module.d.ts +8 -0
- package/dist/commands/create-frontend-module.d.ts.map +1 -0
- package/dist/commands/create-frontend-module.js +90 -0
- package/dist/commands/create-package.d.ts +17 -0
- package/dist/commands/create-package.d.ts.map +1 -0
- package/dist/commands/create-package.js +75 -0
- package/dist/commands/create-registry.d.ts +32 -0
- package/dist/commands/create-registry.d.ts.map +1 -0
- package/dist/commands/create-registry.js +20 -0
- package/dist/commands/create.d.ts +23 -0
- package/dist/commands/create.d.ts.map +1 -0
- package/dist/commands/create.js +111 -0
- package/dist/commands/doctor.d.ts +17 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +150 -0
- package/dist/commands/inspect.d.ts +139 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +379 -0
- package/dist/generators/frontend-module.d.ts +81 -0
- package/dist/generators/frontend-module.d.ts.map +1 -0
- package/dist/generators/frontend-module.js +358 -0
- package/dist/generators/package.d.ts +46 -0
- package/dist/generators/package.d.ts.map +1 -0
- package/dist/generators/package.js +230 -0
- package/dist/index.d.ts +26 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/kernel/arguments.d.ts +44 -0
- package/dist/kernel/arguments.d.ts.map +1 -0
- package/dist/kernel/arguments.js +127 -0
- package/dist/kernel/command.d.ts +71 -0
- package/dist/kernel/command.d.ts.map +1 -0
- package/dist/kernel/command.js +120 -0
- package/dist/kernel/fs-plan.d.ts +50 -0
- package/dist/kernel/fs-plan.d.ts.map +1 -0
- package/dist/kernel/fs-plan.js +82 -0
- package/dist/kernel/help.d.ts +54 -0
- package/dist/kernel/help.d.ts.map +1 -0
- package/dist/kernel/help.js +109 -0
- package/dist/kernel/lockfile.d.ts +73 -0
- package/dist/kernel/lockfile.d.ts.map +1 -0
- package/dist/kernel/lockfile.js +117 -0
- package/dist/kernel/manifests-wiring.d.ts +44 -0
- package/dist/kernel/manifests-wiring.d.ts.map +1 -0
- package/dist/kernel/manifests-wiring.js +165 -0
- package/dist/kernel/plan-runner.d.ts +81 -0
- package/dist/kernel/plan-runner.d.ts.map +1 -0
- package/dist/kernel/plan-runner.js +176 -0
- package/dist/kernel/process-runner.d.ts +25 -0
- package/dist/kernel/process-runner.d.ts.map +1 -0
- package/dist/kernel/process-runner.js +29 -0
- package/dist/kernel/render.d.ts +20 -0
- package/dist/kernel/render.d.ts.map +1 -0
- package/dist/kernel/render.js +63 -0
- package/dist/kernel/result.d.ts +66 -0
- package/dist/kernel/result.d.ts.map +1 -0
- package/dist/kernel/result.js +36 -0
- package/dist/kernel/workspace.d.ts +31 -0
- package/dist/kernel/workspace.d.ts.map +1 -0
- package/dist/kernel/workspace.js +66 -0
- package/package.json +31 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `@yoyoboot/yoyo-admin-cli` 包根入口。
|
|
3
|
+
*
|
|
4
|
+
* 对外暴露 CLI kernel 的稳定面,供测试与工具复用。
|
|
5
|
+
* 可执行入口是 `dist/bin.js`(见 package.json 的 bin 字段)。
|
|
6
|
+
*/
|
|
7
|
+
export { runCli, createRegistry, EXIT_OK, EXIT_UNEXPECTED, EXIT_USAGE } from './cli.js';
|
|
8
|
+
export { defineCommand, dispatch, requireWorkspace, YoyoAdminCliRegistry, createCommandContext, } from './kernel/command.js';
|
|
9
|
+
export { GLOBAL_BOOLEAN_FLAGS, GLOBAL_VALUE_OPTIONS, globalArgumentSpec, hasFlag, mergeArgumentSpecs, optionalOption, parseArguments, requireOption, requirePositional, } from './kernel/arguments.js';
|
|
10
|
+
export { helpPayload, isHelpPayload, renderCommandHelp, renderRecipeHelp, renderRecipeListHelp, renderRootHelp, } from './kernel/help.js';
|
|
11
|
+
export { assertConfinedRelativePath, createFile, createFsPlan, isFsPlan, normalizePath, updateFile, } from './kernel/fs-plan.js';
|
|
12
|
+
export { applyFsPlan, applyFsPlanTransaction, confineRepoPath, planRunner, preflightFsPlan, readRepoFile, } from './kernel/plan-runner.js';
|
|
13
|
+
export { LOCKFILE_REFRESH_ARGS, LOCKFILE_RELATIVE, lockfileHasImporter, refreshWorkspaceLockfile, } from './kernel/lockfile.js';
|
|
14
|
+
export { assertWiringIsSafe, buildManifestsWiring, parseManifestsAnchors, renderWiredManifests, } from './kernel/manifests-wiring.js';
|
|
15
|
+
export { finding, failure, hasError, ok, warn, YoyoAdminCliUsageError, } from './kernel/result.js';
|
|
16
|
+
export { ADMIN_VUE_APP_ROOT_RELATIVE, ADMIN_VUE_HOST_RELATIVE, ADMIN_WORKSPACE_RELATIVE, APP_MANIFESTS_RELATIVE, REQUIRED_REPO_MARKERS, findRepoRoot, resolveWorkspace, } from './kernel/workspace.js';
|
|
17
|
+
export { runDoctor, MINIMUM_NODE_MAJOR } from './commands/doctor.js';
|
|
18
|
+
export { buildCapabilities, CAPABILITIES_SCHEMA_VERSION } from './commands/capabilities.js';
|
|
19
|
+
export { inspectCommand, isInspectPayload, listInspectSubjects, renderInspectPayload, resolveInspectSubject, runInspect, } from './commands/inspect.js';
|
|
20
|
+
export { CRUD_CATALOG_RELATIVE, loadCrudCatalog, loadCrudResourceDescriptor, loadCrudResourceDescriptors, loadKnownResourceKeys, } from './catalogs/crud.js';
|
|
21
|
+
export { CAPABILITY_CATALOG_RELATIVE, loadCapabilityCatalog, loadKnownPermissionNames, loadPermissionDescriptor, matchPermissionPrefix, } from './catalogs/capability.js';
|
|
22
|
+
export { deriveStandardCrudCompatibility, deriveStandardCrudShape, RUNTIME_SUPPLIED_FIELDS, } from './catalogs/standard-crud.js';
|
|
23
|
+
export { planStandardCrudFrontendModule, validateFrontendModuleRequest, STANDARD_CRUD_KIND, } from './generators/frontend-module.js';
|
|
24
|
+
export { assertNoPackageConflicts, planAdminPackage, validatePackageRequest, PACKAGE_KIND_TS, } from './generators/package.js';
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 严格参数解析。
|
|
3
|
+
*
|
|
4
|
+
* 只支持本 CLI 需要的形态:
|
|
5
|
+
* yoyo-admin <command> [positional...] [--flag] [--option value]
|
|
6
|
+
*
|
|
7
|
+
* 不引入参数解析库:命令面很小,且显式解析能保证 `--json` / `--dry-run` /
|
|
8
|
+
* `--help` 在所有命令上语义一致(需求要求它们是统一能力,而非逐命令实现)。
|
|
9
|
+
*
|
|
10
|
+
* 解析是**由调用方提供 spec** 的严格解析:
|
|
11
|
+
* - spec 声明哪些是布尔 flag、哪些是取值选项;
|
|
12
|
+
* - 未在 spec 中出现的 `--xxx` 一律 `unknown-option`(exit 2);
|
|
13
|
+
* - 同一个 flag/option 出现两次一律 `duplicate-option`(exit 2);
|
|
14
|
+
* - 取值选项缺值一律 `missing-option-value`(exit 2)。
|
|
15
|
+
*
|
|
16
|
+
* 布尔 flag **永不吞掉后续 token**:`--dry-run package` 里的 `package` 仍是
|
|
17
|
+
* positional。这条不变量是「全局 flag 位置无关」的实现基础。
|
|
18
|
+
*/
|
|
19
|
+
export interface ParsedArguments {
|
|
20
|
+
readonly command?: string;
|
|
21
|
+
readonly positionals: readonly string[];
|
|
22
|
+
/** 布尔 flag,例如 `--json`。 */
|
|
23
|
+
readonly flags: ReadonlySet<string>;
|
|
24
|
+
/** 带值选项,例如 `--kind ts`。 */
|
|
25
|
+
readonly options: ReadonlyMap<string, string>;
|
|
26
|
+
}
|
|
27
|
+
/** 一次解析允许出现的选项集合。 */
|
|
28
|
+
export interface ArgumentSpec {
|
|
29
|
+
readonly booleanFlags: readonly string[];
|
|
30
|
+
readonly valueOptions: readonly string[];
|
|
31
|
+
}
|
|
32
|
+
/** 全局布尔 flag:所有命令通用,位置无关。 */
|
|
33
|
+
export declare const GLOBAL_BOOLEAN_FLAGS: readonly ["--json", "--dry-run", "--help"];
|
|
34
|
+
/** 全局取值选项:所有命令通用,位置无关。 */
|
|
35
|
+
export declare const GLOBAL_VALUE_OPTIONS: readonly ["--repo"];
|
|
36
|
+
export declare function globalArgumentSpec(): ArgumentSpec;
|
|
37
|
+
/** 合并多份 spec(去重)。命令级 spec 由「全局 + descriptor/recipe 声明」合并而成。 */
|
|
38
|
+
export declare function mergeArgumentSpecs(...specs: readonly ArgumentSpec[]): ArgumentSpec;
|
|
39
|
+
export declare function parseArguments(argv: readonly string[], spec: ArgumentSpec): ParsedArguments;
|
|
40
|
+
export declare function requireOption(parsed: ParsedArguments, name: string): string;
|
|
41
|
+
export declare function optionalOption(parsed: ParsedArguments, name: string): string | undefined;
|
|
42
|
+
export declare function requirePositional(parsed: ParsedArguments, index: number, label: string): string;
|
|
43
|
+
export declare function hasFlag(parsed: ParsedArguments, name: string): boolean;
|
|
44
|
+
//# sourceMappingURL=arguments.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"arguments.d.ts","sourceRoot":"","sources":["../../src/kernel/arguments.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAIH,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,CAAA;IACzB,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAA;IACvC,2BAA2B;IAC3B,QAAQ,CAAC,KAAK,EAAE,WAAW,CAAC,MAAM,CAAC,CAAA;IACnC,2BAA2B;IAC3B,QAAQ,CAAC,OAAO,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAC9C;AAED,qBAAqB;AACrB,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;IACxC,QAAQ,CAAC,YAAY,EAAE,SAAS,MAAM,EAAE,CAAA;CACzC;AAED,6BAA6B;AAC7B,eAAO,MAAM,oBAAoB,4CAA6C,CAAA;AAE9E,0BAA0B;AAC1B,eAAO,MAAM,oBAAoB,qBAAsB,CAAA;AAEvD,wBAAgB,kBAAkB,IAAI,YAAY,CAKjD;AAED,+DAA+D;AAC/D,wBAAgB,kBAAkB,CAAC,GAAG,KAAK,EAAE,SAAS,YAAY,EAAE,GAAG,YAAY,CAKlF;AAED,wBAAgB,cAAc,CAAC,IAAI,EAAE,SAAS,MAAM,EAAE,EAAE,IAAI,EAAE,YAAY,GAAG,eAAe,CAoF3F;AAQD,wBAAgB,aAAa,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,CAM3E;AAED,wBAAgB,cAAc,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAExF;AAED,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,eAAe,EAAE,KAAK,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,MAAM,CAM/F;AAED,wBAAgB,OAAO,CAAC,MAAM,EAAE,eAAe,EAAE,IAAI,EAAE,MAAM,GAAG,OAAO,CAEtE"}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 严格参数解析。
|
|
3
|
+
*
|
|
4
|
+
* 只支持本 CLI 需要的形态:
|
|
5
|
+
* yoyo-admin <command> [positional...] [--flag] [--option value]
|
|
6
|
+
*
|
|
7
|
+
* 不引入参数解析库:命令面很小,且显式解析能保证 `--json` / `--dry-run` /
|
|
8
|
+
* `--help` 在所有命令上语义一致(需求要求它们是统一能力,而非逐命令实现)。
|
|
9
|
+
*
|
|
10
|
+
* 解析是**由调用方提供 spec** 的严格解析:
|
|
11
|
+
* - spec 声明哪些是布尔 flag、哪些是取值选项;
|
|
12
|
+
* - 未在 spec 中出现的 `--xxx` 一律 `unknown-option`(exit 2);
|
|
13
|
+
* - 同一个 flag/option 出现两次一律 `duplicate-option`(exit 2);
|
|
14
|
+
* - 取值选项缺值一律 `missing-option-value`(exit 2)。
|
|
15
|
+
*
|
|
16
|
+
* 布尔 flag **永不吞掉后续 token**:`--dry-run package` 里的 `package` 仍是
|
|
17
|
+
* positional。这条不变量是「全局 flag 位置无关」的实现基础。
|
|
18
|
+
*/
|
|
19
|
+
import { YoyoAdminCliUsageError } from './result.js';
|
|
20
|
+
/** 全局布尔 flag:所有命令通用,位置无关。 */
|
|
21
|
+
export const GLOBAL_BOOLEAN_FLAGS = ['--json', '--dry-run', '--help'];
|
|
22
|
+
/** 全局取值选项:所有命令通用,位置无关。 */
|
|
23
|
+
export const GLOBAL_VALUE_OPTIONS = ['--repo'];
|
|
24
|
+
export function globalArgumentSpec() {
|
|
25
|
+
return {
|
|
26
|
+
booleanFlags: [...GLOBAL_BOOLEAN_FLAGS],
|
|
27
|
+
valueOptions: [...GLOBAL_VALUE_OPTIONS],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/** 合并多份 spec(去重)。命令级 spec 由「全局 + descriptor/recipe 声明」合并而成。 */
|
|
31
|
+
export function mergeArgumentSpecs(...specs) {
|
|
32
|
+
return {
|
|
33
|
+
booleanFlags: [...new Set(specs.flatMap(spec => spec.booleanFlags))],
|
|
34
|
+
valueOptions: [...new Set(specs.flatMap(spec => spec.valueOptions))],
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
export function parseArguments(argv, spec) {
|
|
38
|
+
const booleanFlags = new Set(spec.booleanFlags);
|
|
39
|
+
const valueOptions = new Set(spec.valueOptions);
|
|
40
|
+
const positionals = [];
|
|
41
|
+
const flags = new Set();
|
|
42
|
+
const options = new Map();
|
|
43
|
+
let command;
|
|
44
|
+
// `--` 之后一律按 positional 处理(不再尝试识别选项)。
|
|
45
|
+
let positionalsOnly = false;
|
|
46
|
+
for (let index = 0; index < argv.length; index += 1) {
|
|
47
|
+
const token = argv[index];
|
|
48
|
+
if (token === undefined)
|
|
49
|
+
continue;
|
|
50
|
+
// pnpm run <script> -- <args> 在部分平台会把 `--` 字面传入(且常常落在最前面),
|
|
51
|
+
// 因此命令出现之前的裸 `--` 只是分隔符残留,直接跳过;
|
|
52
|
+
// 命令之后的 `--` 按 POSIX 语义终止选项解析,其后一律当作 positional。
|
|
53
|
+
if (!positionalsOnly && token === '--') {
|
|
54
|
+
if (command !== undefined)
|
|
55
|
+
positionalsOnly = true;
|
|
56
|
+
continue;
|
|
57
|
+
}
|
|
58
|
+
if (!positionalsOnly && token.startsWith('--')) {
|
|
59
|
+
const [name, inlineValue] = splitOption(token);
|
|
60
|
+
if (booleanFlags.has(name)) {
|
|
61
|
+
if (inlineValue !== undefined) {
|
|
62
|
+
throw new YoyoAdminCliUsageError('unexpected-option-value', `${name} 是布尔 flag,不接受取值(收到 ${token})。`);
|
|
63
|
+
}
|
|
64
|
+
if (flags.has(name)) {
|
|
65
|
+
throw new YoyoAdminCliUsageError('duplicate-option', `${name} 重复出现;每次调用只允许一次。`);
|
|
66
|
+
}
|
|
67
|
+
flags.add(name);
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (!valueOptions.has(name)) {
|
|
71
|
+
throw new YoyoAdminCliUsageError('unknown-option', `未知选项 ${name}。用 \`yoyo-admin --help\` 查看全局选项,` +
|
|
72
|
+
'`yoyo-admin capabilities --json` 查看命令与 recipe 的真实参数。');
|
|
73
|
+
}
|
|
74
|
+
if (options.has(name)) {
|
|
75
|
+
throw new YoyoAdminCliUsageError('duplicate-option', `${name} 重复出现;每次调用只允许一次。`);
|
|
76
|
+
}
|
|
77
|
+
if (inlineValue !== undefined) {
|
|
78
|
+
if (inlineValue.length === 0) {
|
|
79
|
+
throw new YoyoAdminCliUsageError('missing-option-value', `${name} 缺少取值;请写成 \`${name} <value>\`。`);
|
|
80
|
+
}
|
|
81
|
+
options.set(name, inlineValue);
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
const next = argv[index + 1];
|
|
85
|
+
if (next === undefined || next.startsWith('--')) {
|
|
86
|
+
throw new YoyoAdminCliUsageError('missing-option-value', `${name} 缺少取值;请写成 \`${name} <value>\`。`);
|
|
87
|
+
}
|
|
88
|
+
options.set(name, next);
|
|
89
|
+
index += 1;
|
|
90
|
+
continue;
|
|
91
|
+
}
|
|
92
|
+
if (command === undefined) {
|
|
93
|
+
command = token;
|
|
94
|
+
continue;
|
|
95
|
+
}
|
|
96
|
+
positionals.push(token);
|
|
97
|
+
}
|
|
98
|
+
return command === undefined
|
|
99
|
+
? { positionals, flags, options }
|
|
100
|
+
: { command, positionals, flags, options };
|
|
101
|
+
}
|
|
102
|
+
function splitOption(token) {
|
|
103
|
+
const separator = token.indexOf('=');
|
|
104
|
+
if (separator < 0)
|
|
105
|
+
return [token, undefined];
|
|
106
|
+
return [token.slice(0, separator), token.slice(separator + 1)];
|
|
107
|
+
}
|
|
108
|
+
export function requireOption(parsed, name) {
|
|
109
|
+
const value = parsed.options.get(name);
|
|
110
|
+
if (value === undefined || value.length === 0) {
|
|
111
|
+
throw new YoyoAdminCliUsageError('missing-option', `缺少必需选项 ${name}`);
|
|
112
|
+
}
|
|
113
|
+
return value;
|
|
114
|
+
}
|
|
115
|
+
export function optionalOption(parsed, name) {
|
|
116
|
+
return parsed.options.get(name);
|
|
117
|
+
}
|
|
118
|
+
export function requirePositional(parsed, index, label) {
|
|
119
|
+
const value = parsed.positionals[index];
|
|
120
|
+
if (value === undefined || value.length === 0) {
|
|
121
|
+
throw new YoyoAdminCliUsageError('missing-argument', `缺少必需参数 ${label}`);
|
|
122
|
+
}
|
|
123
|
+
return value;
|
|
124
|
+
}
|
|
125
|
+
export function hasFlag(parsed, name) {
|
|
126
|
+
return parsed.flags.has(name);
|
|
127
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 命令注册表。
|
|
3
|
+
*
|
|
4
|
+
* `capabilities` 直接从这里投影,因此**新增命令即自动出现在能力清单中**,
|
|
5
|
+
* 不需要单独维护一份文档 —— 这是「Skill 不硬编码未来 capability」的实现基础。
|
|
6
|
+
* `--help` 同样从本表投影(见 `kernel/help.ts`)。
|
|
7
|
+
*
|
|
8
|
+
* 分发是**两段严格解析**:
|
|
9
|
+
* 1. 先用「全局 + 全部命令声明」的并集解析,得到 command/positionals,
|
|
10
|
+
* 并在这里就拒绝真正未知的 option;
|
|
11
|
+
* 2. 再用「全局 + 该命令(及其子 recipe)声明」的 spec 重新解析,
|
|
12
|
+
* 拒绝「存在但不属于本命令」的 option。
|
|
13
|
+
* 两段都通过后,才解析 workspace 并调用命令。
|
|
14
|
+
*/
|
|
15
|
+
import { type ArgumentSpec, type ParsedArguments } from './arguments.js';
|
|
16
|
+
import { type YoyoAdminCliCommandDescriptor, type YoyoAdminCliResult, YoyoAdminCliUsageError } from './result.js';
|
|
17
|
+
import { type YoyoAdminWorkspace } from './workspace.js';
|
|
18
|
+
export interface CommandContext {
|
|
19
|
+
readonly parsed: ParsedArguments;
|
|
20
|
+
readonly dryRun: boolean;
|
|
21
|
+
readonly json: boolean;
|
|
22
|
+
/** 仓库查找起点:`--repo <path>` → `runCli` 的 startDirectory → `process.cwd()`。 */
|
|
23
|
+
readonly startDirectory: string;
|
|
24
|
+
/**
|
|
25
|
+
* 已解析的仓库工作区。
|
|
26
|
+
*
|
|
27
|
+
* 由分发层统一解析一次;命令与 recipe **不得**再自行调用 `resolveWorkspace()`。
|
|
28
|
+
* 仅诊断命令(doctor)允许拿到 `undefined`(定位失败时如实报告),
|
|
29
|
+
* 其余命令一律通过 `requireWorkspace()` 取用。
|
|
30
|
+
*/
|
|
31
|
+
readonly workspace?: YoyoAdminWorkspace;
|
|
32
|
+
/** workspace 缺失时的原始失败原因(保留稳定 code,供诊断命令如实转述)。 */
|
|
33
|
+
readonly workspaceError?: YoyoAdminCliUsageError;
|
|
34
|
+
}
|
|
35
|
+
/** 取用上下文里的 workspace;缺失即按分发层记录的原始原因 fail-closed。 */
|
|
36
|
+
export declare function requireWorkspace(context: CommandContext): YoyoAdminWorkspace;
|
|
37
|
+
export interface RegisteredCommand {
|
|
38
|
+
readonly descriptor: YoyoAdminCliCommandDescriptor;
|
|
39
|
+
/**
|
|
40
|
+
* 该命令接受的选项集合(不含全局选项)。
|
|
41
|
+
* 缺省只用 descriptor 声明的 options;`create` 用它在 recipe 级收窄。
|
|
42
|
+
*/
|
|
43
|
+
readonly argumentSpec?: (positionals: readonly string[]) => ArgumentSpec;
|
|
44
|
+
/** 命令级 help 文本;缺省由 descriptor 渲染。 */
|
|
45
|
+
readonly renderHelp?: (positionals: readonly string[]) => string;
|
|
46
|
+
readonly run: (context: CommandContext) => YoyoAdminCliResult;
|
|
47
|
+
}
|
|
48
|
+
export declare function defineCommand(command: RegisteredCommand): RegisteredCommand;
|
|
49
|
+
export declare class YoyoAdminCliRegistry {
|
|
50
|
+
#private;
|
|
51
|
+
register(...commands: readonly RegisteredCommand[]): this;
|
|
52
|
+
get(name: string): RegisteredCommand | undefined;
|
|
53
|
+
list(): readonly RegisteredCommand[];
|
|
54
|
+
/** 能力清单投影:命令的机器可读契约。 */
|
|
55
|
+
describeCapabilities(): readonly YoyoAdminCliCommandDescriptor[];
|
|
56
|
+
/** 第一段解析用的并集 spec:全局 + 全部命令声明的选项。 */
|
|
57
|
+
unionArgumentSpec(): ArgumentSpec;
|
|
58
|
+
}
|
|
59
|
+
export interface DispatchOptions {
|
|
60
|
+
/** 仓库查找起点;被 `--repo <path>` 覆盖。 */
|
|
61
|
+
readonly startDirectory?: string;
|
|
62
|
+
}
|
|
63
|
+
export interface DispatchOutcome {
|
|
64
|
+
readonly result: YoyoAdminCliResult;
|
|
65
|
+
readonly parsed: ParsedArguments;
|
|
66
|
+
readonly json: boolean;
|
|
67
|
+
readonly dryRun: boolean;
|
|
68
|
+
}
|
|
69
|
+
export declare function dispatch(registry: YoyoAdminCliRegistry, argv: readonly string[], options?: DispatchOptions): DispatchOutcome;
|
|
70
|
+
export declare function createCommandContext(parsed: ParsedArguments, startDirectory: string): CommandContext;
|
|
71
|
+
//# sourceMappingURL=command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"command.d.ts","sourceRoot":"","sources":["../../src/kernel/command.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EAIL,KAAK,YAAY,EACjB,KAAK,eAAe,EACrB,MAAM,gBAAgB,CAAA;AAEvB,OAAO,EAAM,KAAK,6BAA6B,EAAE,KAAK,kBAAkB,EAAE,sBAAsB,EAAE,MAAM,aAAa,CAAA;AACrH,OAAO,EAAuE,KAAK,kBAAkB,EAAE,MAAM,gBAAgB,CAAA;AAE7H,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;IACxB,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,4EAA4E;IAC5E,QAAQ,CAAC,cAAc,EAAE,MAAM,CAAA;IAC/B;;;;;;OAMG;IACH,QAAQ,CAAC,SAAS,CAAC,EAAE,kBAAkB,CAAA;IACvC,iDAAiD;IACjD,QAAQ,CAAC,cAAc,CAAC,EAAE,sBAAsB,CAAA;CACjD;AAED,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,cAAc,GAAG,kBAAkB,CAW5E;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,UAAU,EAAE,6BAA6B,CAAA;IAClD;;;OAGG;IACH,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,KAAK,YAAY,CAAA;IACxE,qCAAqC;IACrC,QAAQ,CAAC,UAAU,CAAC,EAAE,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,KAAK,MAAM,CAAA;IAChE,QAAQ,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,cAAc,KAAK,kBAAkB,CAAA;CAC9D;AAED,wBAAgB,aAAa,CAAC,OAAO,EAAE,iBAAiB,GAAG,iBAAiB,CAE3E;AAED,qBAAa,oBAAoB;;IAG/B,QAAQ,CAAC,GAAG,QAAQ,EAAE,SAAS,iBAAiB,EAAE,GAAG,IAAI;IAUzD,GAAG,CAAC,IAAI,EAAE,MAAM,GAAG,iBAAiB,GAAG,SAAS;IAIhD,IAAI,IAAI,SAAS,iBAAiB,EAAE;IAMpC,wBAAwB;IACxB,oBAAoB,IAAI,SAAS,6BAA6B,EAAE;IAIhE,qCAAqC;IACrC,iBAAiB,IAAI,YAAY;CAMlC;AASD,MAAM,WAAW,eAAe;IAC9B,mCAAmC;IACnC,QAAQ,CAAC,cAAc,CAAC,EAAE,MAAM,CAAA;CACjC;AAED,MAAM,WAAW,eAAe;IAC9B,QAAQ,CAAC,MAAM,EAAE,kBAAkB,CAAA;IACnC,QAAQ,CAAC,MAAM,EAAE,eAAe,CAAA;IAChC,QAAQ,CAAC,IAAI,EAAE,OAAO,CAAA;IACtB,QAAQ,CAAC,MAAM,EAAE,OAAO,CAAA;CACzB;AAED,wBAAgB,QAAQ,CACtB,QAAQ,EAAE,oBAAoB,EAC9B,IAAI,EAAE,SAAS,MAAM,EAAE,EACvB,OAAO,GAAE,eAAoB,GAC5B,eAAe,CAsDjB;AAoBD,wBAAgB,oBAAoB,CAAC,MAAM,EAAE,eAAe,EAAE,cAAc,EAAE,MAAM,GAAG,cAAc,CAOpG"}
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 命令注册表。
|
|
3
|
+
*
|
|
4
|
+
* `capabilities` 直接从这里投影,因此**新增命令即自动出现在能力清单中**,
|
|
5
|
+
* 不需要单独维护一份文档 —— 这是「Skill 不硬编码未来 capability」的实现基础。
|
|
6
|
+
* `--help` 同样从本表投影(见 `kernel/help.ts`)。
|
|
7
|
+
*
|
|
8
|
+
* 分发是**两段严格解析**:
|
|
9
|
+
* 1. 先用「全局 + 全部命令声明」的并集解析,得到 command/positionals,
|
|
10
|
+
* 并在这里就拒绝真正未知的 option;
|
|
11
|
+
* 2. 再用「全局 + 该命令(及其子 recipe)声明」的 spec 重新解析,
|
|
12
|
+
* 拒绝「存在但不属于本命令」的 option。
|
|
13
|
+
* 两段都通过后,才解析 workspace 并调用命令。
|
|
14
|
+
*/
|
|
15
|
+
import { globalArgumentSpec, mergeArgumentSpecs, parseArguments, } from './arguments.js';
|
|
16
|
+
import { renderCommandHelp, renderRootHelp } from './help.js';
|
|
17
|
+
import { ok, YoyoAdminCliUsageError } from './result.js';
|
|
18
|
+
import { ADMIN_VUE_HOST_RELATIVE, ADMIN_WORKSPACE_RELATIVE, resolveWorkspace } from './workspace.js';
|
|
19
|
+
/** 取用上下文里的 workspace;缺失即按分发层记录的原始原因 fail-closed。 */
|
|
20
|
+
export function requireWorkspace(context) {
|
|
21
|
+
if (context.workspace !== undefined)
|
|
22
|
+
return context.workspace;
|
|
23
|
+
throw (context.workspaceError ??
|
|
24
|
+
new YoyoAdminCliUsageError('repo-root-not-found', `未能从 ${context.startDirectory} 定位 YoyoBoot 仓库(需要同时存在 .git、` +
|
|
25
|
+
`${ADMIN_WORKSPACE_RELATIVE} 与 ${ADMIN_VUE_HOST_RELATIVE});可用 --repo <path> 指定起点。`));
|
|
26
|
+
}
|
|
27
|
+
export function defineCommand(command) {
|
|
28
|
+
return command;
|
|
29
|
+
}
|
|
30
|
+
export class YoyoAdminCliRegistry {
|
|
31
|
+
#commands = new Map();
|
|
32
|
+
register(...commands) {
|
|
33
|
+
for (const command of commands) {
|
|
34
|
+
if (this.#commands.has(command.descriptor.name)) {
|
|
35
|
+
throw new Error(`Duplicate CLI command registration: ${command.descriptor.name}`);
|
|
36
|
+
}
|
|
37
|
+
this.#commands.set(command.descriptor.name, command);
|
|
38
|
+
}
|
|
39
|
+
return this;
|
|
40
|
+
}
|
|
41
|
+
get(name) {
|
|
42
|
+
return this.#commands.get(name);
|
|
43
|
+
}
|
|
44
|
+
list() {
|
|
45
|
+
return [...this.#commands.values()].sort((left, right) => left.descriptor.name.localeCompare(right.descriptor.name));
|
|
46
|
+
}
|
|
47
|
+
/** 能力清单投影:命令的机器可读契约。 */
|
|
48
|
+
describeCapabilities() {
|
|
49
|
+
return this.list().map(command => command.descriptor);
|
|
50
|
+
}
|
|
51
|
+
/** 第一段解析用的并集 spec:全局 + 全部命令声明的选项。 */
|
|
52
|
+
unionArgumentSpec() {
|
|
53
|
+
return mergeArgumentSpecs(globalArgumentSpec(), ...this.list().map(command => argumentSpecOf(command)));
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function argumentSpecOf(command) {
|
|
57
|
+
return {
|
|
58
|
+
booleanFlags: command.descriptor.booleanFlags ?? [],
|
|
59
|
+
valueOptions: command.descriptor.options.map(option => option.flag),
|
|
60
|
+
};
|
|
61
|
+
}
|
|
62
|
+
export function dispatch(registry, argv, options = {}) {
|
|
63
|
+
// 第一段:并集 spec 解析,确定 command / positionals 并拒绝真正未知的选项。
|
|
64
|
+
const unionParsed = parseArguments(argv, registry.unionArgumentSpec());
|
|
65
|
+
if (unionParsed.flags.has('--help')) {
|
|
66
|
+
return { result: runHelp(registry, unionParsed), parsed: unionParsed, json: false, dryRun: false };
|
|
67
|
+
}
|
|
68
|
+
if (unionParsed.command === undefined) {
|
|
69
|
+
throw new YoyoAdminCliUsageError('missing-command', '缺少命令。运行 `yoyo-admin --help` 查看全部命令,或 `yoyo-admin capabilities --json` 获取机器可读清单。');
|
|
70
|
+
}
|
|
71
|
+
const command = registry.get(unionParsed.command);
|
|
72
|
+
if (command === undefined) {
|
|
73
|
+
throw new YoyoAdminCliUsageError('unknown-command', `未知命令 ${unionParsed.command}。运行 \`yoyo-admin --help\` 查看全部命令。`);
|
|
74
|
+
}
|
|
75
|
+
// 第二段:命令级 spec 解析,拒绝「存在但不属于本命令」的选项。
|
|
76
|
+
const commandSpec = mergeArgumentSpecs(globalArgumentSpec(), command.argumentSpec?.(unionParsed.positionals) ?? argumentSpecOf(command));
|
|
77
|
+
const parsed = parseArguments(argv, commandSpec);
|
|
78
|
+
const startDirectory = parsed.options.get('--repo') ?? options.startDirectory ?? process.cwd();
|
|
79
|
+
let workspace;
|
|
80
|
+
let workspaceError;
|
|
81
|
+
try {
|
|
82
|
+
workspace = resolveWorkspace(startDirectory);
|
|
83
|
+
}
|
|
84
|
+
catch (error) {
|
|
85
|
+
if (!(error instanceof YoyoAdminCliUsageError))
|
|
86
|
+
throw error;
|
|
87
|
+
workspaceError = error;
|
|
88
|
+
}
|
|
89
|
+
const context = {
|
|
90
|
+
...createCommandContext(parsed, startDirectory),
|
|
91
|
+
...(workspace === undefined ? {} : { workspace }),
|
|
92
|
+
...(workspaceError === undefined ? {} : { workspaceError }),
|
|
93
|
+
};
|
|
94
|
+
return {
|
|
95
|
+
result: command.run(context),
|
|
96
|
+
parsed,
|
|
97
|
+
json: context.json,
|
|
98
|
+
dryRun: context.dryRun,
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
/** `--help`:始终 exit 0;未知 recipe 回落到 create 级 help(help 请求本身永不失败)。 */
|
|
102
|
+
function runHelp(registry, parsed) {
|
|
103
|
+
if (parsed.command === undefined) {
|
|
104
|
+
return ok('help', [], { kind: 'help', text: renderRootHelp(registry.describeCapabilities()) });
|
|
105
|
+
}
|
|
106
|
+
const command = registry.get(parsed.command);
|
|
107
|
+
if (command === undefined) {
|
|
108
|
+
throw new YoyoAdminCliUsageError('unknown-command', `未知命令 ${parsed.command}。运行 \`yoyo-admin --help\` 查看全部命令。`);
|
|
109
|
+
}
|
|
110
|
+
const text = command.renderHelp?.(parsed.positionals) ?? renderCommandHelp(command.descriptor);
|
|
111
|
+
return ok('help', [], { kind: 'help', text });
|
|
112
|
+
}
|
|
113
|
+
export function createCommandContext(parsed, startDirectory) {
|
|
114
|
+
return {
|
|
115
|
+
parsed,
|
|
116
|
+
dryRun: parsed.flags.has('--dry-run'),
|
|
117
|
+
json: parsed.flags.has('--json'),
|
|
118
|
+
startDirectory,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件系统变更计划。
|
|
3
|
+
*
|
|
4
|
+
* 核心不变量:**先完整构建 plan,全部校验通过后再一次性落盘**。
|
|
5
|
+
* 因此不允许「生成器边算边写」,也不允许产生「创建了一半再失败」的部分状态。
|
|
6
|
+
*
|
|
7
|
+
* `--dry-run` 只渲染 plan,不调用 `applyFsPlan`。
|
|
8
|
+
*
|
|
9
|
+
* 路径约束:plan 里的每条 change 都是**仓库根下的相对路径**(`/` 分隔)。
|
|
10
|
+
* 绝对路径、drive path、UNC、`..`、NUL、空段等非法目标在构造 plan 时即被
|
|
11
|
+
* 拒绝(generator 自律),并由 `plan-runner` 的 preflight/apply 二次校验
|
|
12
|
+
* (kernel 兜底)—— 写入边界不依赖生成器的自觉。
|
|
13
|
+
*/
|
|
14
|
+
export interface CreateFileChange {
|
|
15
|
+
readonly kind: 'create';
|
|
16
|
+
/** 仓库根相对路径,始终使用 `/` 分隔。 */
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly content: string;
|
|
19
|
+
/** 该文件来自哪个 recipe,用于 dry-run 分组展示。 */
|
|
20
|
+
readonly origin: string;
|
|
21
|
+
}
|
|
22
|
+
export interface UpdateFileChange {
|
|
23
|
+
readonly kind: 'update';
|
|
24
|
+
readonly path: string;
|
|
25
|
+
readonly content: string;
|
|
26
|
+
readonly origin: string;
|
|
27
|
+
/** 人类可读的变更摘要,例如「在 appModuleManifests 注册 X」。 */
|
|
28
|
+
readonly summary: string;
|
|
29
|
+
}
|
|
30
|
+
export type FsChange = CreateFileChange | UpdateFileChange;
|
|
31
|
+
export declare function createFile(path: string, content: string, origin: string): CreateFileChange;
|
|
32
|
+
export declare function updateFile(path: string, content: string, origin: string, summary: string): UpdateFileChange;
|
|
33
|
+
export declare function normalizePath(path: string): string;
|
|
34
|
+
/**
|
|
35
|
+
* 拒绝一切可能落到仓库根之外的 plan 路径。
|
|
36
|
+
*
|
|
37
|
+
* 这是纯字符串判定(不依赖平台),因此 windows/posix 非法目标在同一处收敛。
|
|
38
|
+
*/
|
|
39
|
+
export declare function assertConfinedRelativePath(path: string): void;
|
|
40
|
+
/** plan 的呈现形态:`capabilities`/dry-run 输出共用的稳定结构。 */
|
|
41
|
+
export interface FsPlan {
|
|
42
|
+
readonly changes: readonly FsChange[];
|
|
43
|
+
readonly summary: {
|
|
44
|
+
readonly creates: number;
|
|
45
|
+
readonly updates: number;
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
export declare function createFsPlan(changes: readonly FsChange[]): FsPlan;
|
|
49
|
+
export declare function isFsPlan(value: unknown): value is FsPlan;
|
|
50
|
+
//# sourceMappingURL=fs-plan.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"fs-plan.d.ts","sourceRoot":"","sources":["../../src/kernel/fs-plan.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAIH,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,2BAA2B;IAC3B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,sCAAsC;IACtC,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;CACxB;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,IAAI,EAAE,QAAQ,CAAA;IACvB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAA;IACvB,+CAA+C;IAC/C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;CACzB;AAED,MAAM,MAAM,QAAQ,GAAG,gBAAgB,GAAG,gBAAgB,CAAA;AAE1D,wBAAgB,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,gBAAgB,CAI1F;AAED,wBAAgB,UAAU,CACxB,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,MAAM,EACf,MAAM,EAAE,MAAM,EACd,OAAO,EAAE,MAAM,GACd,gBAAgB,CAIlB;AAED,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,CAElD;AAED;;;;GAIG;AACH,wBAAgB,0BAA0B,CAAC,IAAI,EAAE,MAAM,GAAG,IAAI,CAqB7D;AAED,mDAAmD;AACnD,MAAM,WAAW,MAAM;IACrB,QAAQ,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,CAAA;IACrC,QAAQ,CAAC,OAAO,EAAE;QAChB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;QACxB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;KACzB,CAAA;CACF;AAED,wBAAgB,YAAY,CAAC,OAAO,EAAE,SAAS,QAAQ,EAAE,GAAG,MAAM,CAkBjE;AAED,wBAAgB,QAAQ,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,MAAM,CAQxD"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 文件系统变更计划。
|
|
3
|
+
*
|
|
4
|
+
* 核心不变量:**先完整构建 plan,全部校验通过后再一次性落盘**。
|
|
5
|
+
* 因此不允许「生成器边算边写」,也不允许产生「创建了一半再失败」的部分状态。
|
|
6
|
+
*
|
|
7
|
+
* `--dry-run` 只渲染 plan,不调用 `applyFsPlan`。
|
|
8
|
+
*
|
|
9
|
+
* 路径约束:plan 里的每条 change 都是**仓库根下的相对路径**(`/` 分隔)。
|
|
10
|
+
* 绝对路径、drive path、UNC、`..`、NUL、空段等非法目标在构造 plan 时即被
|
|
11
|
+
* 拒绝(generator 自律),并由 `plan-runner` 的 preflight/apply 二次校验
|
|
12
|
+
* (kernel 兜底)—— 写入边界不依赖生成器的自觉。
|
|
13
|
+
*/
|
|
14
|
+
import { YoyoAdminCliUsageError } from './result.js';
|
|
15
|
+
export function createFile(path, content, origin) {
|
|
16
|
+
const safePath = normalizePath(path);
|
|
17
|
+
assertConfinedRelativePath(safePath);
|
|
18
|
+
return { kind: 'create', path: safePath, content, origin };
|
|
19
|
+
}
|
|
20
|
+
export function updateFile(path, content, origin, summary) {
|
|
21
|
+
const safePath = normalizePath(path);
|
|
22
|
+
assertConfinedRelativePath(safePath);
|
|
23
|
+
return { kind: 'update', path: safePath, content, origin, summary };
|
|
24
|
+
}
|
|
25
|
+
export function normalizePath(path) {
|
|
26
|
+
return path.replace(/\\/g, '/').replace(/^\.\//, '');
|
|
27
|
+
}
|
|
28
|
+
/**
|
|
29
|
+
* 拒绝一切可能落到仓库根之外的 plan 路径。
|
|
30
|
+
*
|
|
31
|
+
* 这是纯字符串判定(不依赖平台),因此 windows/posix 非法目标在同一处收敛。
|
|
32
|
+
*/
|
|
33
|
+
export function assertConfinedRelativePath(path) {
|
|
34
|
+
const reject = (reason) => {
|
|
35
|
+
throw new YoyoAdminCliUsageError('unsafe-plan-path', `FsPlan 目标路径非法(${reason}):${JSON.stringify(path)}。` +
|
|
36
|
+
'plan 路径必须是仓库根下的相对路径,使用 / 分隔,且不得出现 .. 段。');
|
|
37
|
+
};
|
|
38
|
+
if (path.length === 0)
|
|
39
|
+
reject('空路径');
|
|
40
|
+
if (path.includes('\0'))
|
|
41
|
+
reject('包含 NUL 字符');
|
|
42
|
+
if (path.includes('\\'))
|
|
43
|
+
reject('包含反斜杠,必须使用 / 分隔');
|
|
44
|
+
if (path.startsWith('/'))
|
|
45
|
+
reject('绝对路径或 UNC 路径');
|
|
46
|
+
if (/^[A-Za-z]:/.test(path))
|
|
47
|
+
reject('Windows drive path');
|
|
48
|
+
if (/[:*?"<>|]/.test(path))
|
|
49
|
+
reject('包含 Windows 非法字符');
|
|
50
|
+
const segments = path.split('/');
|
|
51
|
+
if (segments.some(segment => segment === '..'))
|
|
52
|
+
reject('包含 .. 越界段');
|
|
53
|
+
if (segments.some(segment => segment.length === 0 || segment === '.')) {
|
|
54
|
+
reject('包含空段或 . 段');
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
export function createFsPlan(changes) {
|
|
58
|
+
const seen = new Set();
|
|
59
|
+
for (const change of changes) {
|
|
60
|
+
// 构造入口的二次防御:即使调用方绕过了 createFile/updateFile 的字面量,
|
|
61
|
+
// 也不会把非法路径传播到 plan-runner。
|
|
62
|
+
assertConfinedRelativePath(change.path);
|
|
63
|
+
if (seen.has(change.path)) {
|
|
64
|
+
throw new Error(`FsPlan contains duplicate target path: ${change.path}`);
|
|
65
|
+
}
|
|
66
|
+
seen.add(change.path);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
changes,
|
|
70
|
+
summary: {
|
|
71
|
+
creates: changes.filter(change => change.kind === 'create').length,
|
|
72
|
+
updates: changes.filter(change => change.kind === 'update').length,
|
|
73
|
+
},
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
export function isFsPlan(value) {
|
|
77
|
+
return (typeof value === 'object' &&
|
|
78
|
+
value !== null &&
|
|
79
|
+
'changes' in value &&
|
|
80
|
+
Array.isArray(value.changes) &&
|
|
81
|
+
'summary' in value);
|
|
82
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `--help` 渲染。
|
|
3
|
+
*
|
|
4
|
+
* 铁律:**help 从 registry 投影,不维护第二份命令清单**。
|
|
5
|
+
* 根 help 遍历 `registry.list()`;`create` 的 recipe help 遍历
|
|
6
|
+
* `create-registry`。新增命令/recipe 后 help 自动跟随,无需改本文件。
|
|
7
|
+
*/
|
|
8
|
+
import type { YoyoAdminCliCommandDescriptor } from './result.js';
|
|
9
|
+
/** help 的载荷:人类可读文本。`runCli` 原样写出,不做 JSON 包裹(即使带 --json)。 */
|
|
10
|
+
export interface HelpPayload {
|
|
11
|
+
readonly kind: 'help';
|
|
12
|
+
readonly text: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function helpPayload(text: string): HelpPayload;
|
|
15
|
+
export declare function isHelpPayload(value: unknown): value is HelpPayload;
|
|
16
|
+
/** 全局选项说明:与 `arguments.ts` 的全局 spec 一一对应,位置无关。 */
|
|
17
|
+
export declare const GLOBAL_OPTION_LINES: readonly [" --json 以 JSON 输出完整结果信封,供机器读取。", " --dry-run 仅展示计划变更,不写入任何文件(仅作者命令)。", " --help 展示帮助;`yoyo-admin --help` / `yoyo-admin create package --help`。", " --repo <path> 显式指定仓库查找起点;缺省从当前工作目录向上查找。"];
|
|
18
|
+
export interface HelpOptionDescriptor {
|
|
19
|
+
readonly flag: string;
|
|
20
|
+
readonly required: boolean;
|
|
21
|
+
readonly description: string;
|
|
22
|
+
readonly values?: readonly string[];
|
|
23
|
+
}
|
|
24
|
+
/** recipe 级 help 需要的最小投影(结构化类型,kernel 不依赖 commands 层)。 */
|
|
25
|
+
export interface HelpRecipeDescriptor {
|
|
26
|
+
readonly name: string;
|
|
27
|
+
readonly summary: string;
|
|
28
|
+
readonly usage: string;
|
|
29
|
+
readonly kinds: readonly string[];
|
|
30
|
+
readonly options: readonly HelpOptionDescriptor[];
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* 二级命令(`create <recipe>` / `inspect <subject>`)help 的投影面。
|
|
34
|
+
*
|
|
35
|
+
* help 文本只从这里 + 各自的 registry 生成,**不维护第二份清单**。
|
|
36
|
+
*/
|
|
37
|
+
export interface RecipeHelpSurface {
|
|
38
|
+
/** 二级命令名,例如 `create`。 */
|
|
39
|
+
readonly command: string;
|
|
40
|
+
readonly title: string;
|
|
41
|
+
/** 条目称呼,例如 `recipe` / `subject`。 */
|
|
42
|
+
readonly itemLabel: string;
|
|
43
|
+
/** 二级命令 usage 的尾部,例如 `[options] [--dry-run] [--json]`。 */
|
|
44
|
+
readonly usageSuffix: string;
|
|
45
|
+
/** 单条目 usage 的获取方式:直接读条目自带的 usage。 */
|
|
46
|
+
readonly itemUsageHint: string;
|
|
47
|
+
}
|
|
48
|
+
export declare function renderRootHelp(commands: readonly YoyoAdminCliCommandDescriptor[]): string;
|
|
49
|
+
export declare function renderCommandHelp(command: YoyoAdminCliCommandDescriptor): string;
|
|
50
|
+
/** `yoyo-admin create --help`:条目清单 + 各自的 usage。 */
|
|
51
|
+
export declare function renderRecipeListHelp(items: readonly HelpRecipeDescriptor[], surface: RecipeHelpSurface): string;
|
|
52
|
+
/** `yoyo-admin create <recipe> --help`:单条目的完整参数。 */
|
|
53
|
+
export declare function renderRecipeHelp(item: HelpRecipeDescriptor, surface: RecipeHelpSurface): string;
|
|
54
|
+
//# sourceMappingURL=help.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../../src/kernel/help.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,6BAA6B,EAAE,MAAM,aAAa,CAAA;AAEhE,4DAA4D;AAC5D,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;CACtB;AAED,wBAAgB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAErD;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,IAAI,WAAW,CAIlE;AAED,kDAAkD;AAClD,eAAO,MAAM,mBAAmB,uOAKtB,CAAA;AAEV,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,QAAQ,EAAE,OAAO,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,MAAM,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;CACpC;AAED,0DAA0D;AAC1D,MAAM,WAAW,oBAAoB;IACnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,QAAQ,CAAC,KAAK,EAAE,SAAS,MAAM,EAAE,CAAA;IACjC,QAAQ,CAAC,OAAO,EAAE,SAAS,oBAAoB,EAAE,CAAA;CAClD;AAED;;;;GAIG;AACH,MAAM,WAAW,iBAAiB;IAChC,yBAAyB;IACzB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAA;IACtB,oCAAoC;IACpC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,0DAA0D;IAC1D,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,sCAAsC;IACtC,QAAQ,CAAC,aAAa,EAAE,MAAM,CAAA;CAC/B;AAWD,wBAAgB,cAAc,CAAC,QAAQ,EAAE,SAAS,6BAA6B,EAAE,GAAG,MAAM,CA0BzF;AAED,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,6BAA6B,GAAG,MAAM,CA6BhF;AAED,mDAAmD;AACnD,wBAAgB,oBAAoB,CAClC,KAAK,EAAE,SAAS,oBAAoB,EAAE,EACtC,OAAO,EAAE,iBAAiB,GACzB,MAAM,CA4BR;AAED,oDAAoD;AACpD,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,oBAAoB,EAAE,OAAO,EAAE,iBAAiB,GAAG,MAAM,CAkB/F"}
|