@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,358 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yoyo-admin create frontend-module --kind standard-crud`
|
|
3
|
+
*
|
|
4
|
+
* **Contract-driven**:从生成的 CRUD resource catalog 读取该资源的完整
|
|
5
|
+
* descriptor(operations / binding),actions、权限与最小表单字段全部由
|
|
6
|
+
* 真实契约派生 —— 不猜业务事实:
|
|
7
|
+
* - 资源没有 `remove` operation → 绝不生成 delete action / Delete 权限;
|
|
8
|
+
* - 字段取自 `binding.create.fieldMappings[].source`(排除系统字段),
|
|
9
|
+
* 生命周期按 `binding.update.fieldMappings` 派生;
|
|
10
|
+
* 不再硬编码任何资源特定字段(v1 的 name/enabled/creationTime 已移除);
|
|
11
|
+
* - 表格列只含 rowKey(来自 `binding.getForEdit.idParameter`),
|
|
12
|
+
* 查询字段与展示列由作者补齐。
|
|
13
|
+
*
|
|
14
|
+
* 生成**当前正式作者面**,不多不少:
|
|
15
|
+
* - 模块 `manifest.ts`(纯 standard-crud definition)
|
|
16
|
+
* - feature `schema.ts`(唯一的页面契约作者面)
|
|
17
|
+
* - 并接线 `app/manifests.ts`
|
|
18
|
+
*
|
|
19
|
+
* 明确不生成(治理裁决):
|
|
20
|
+
* `page-contract.ts`、通用 `service.ts`、通用 `validation.ts`、
|
|
21
|
+
* 空 `index.vue`、binding。这些属于 typed extension,
|
|
22
|
+
* 只在真实复杂业务需要时作为独立 recipe/option 增加。
|
|
23
|
+
*
|
|
24
|
+
* Plan-time fail-closed 校验:
|
|
25
|
+
* - resourceKey 必须存在于生成的 CRUD resource catalog;
|
|
26
|
+
* - 权限前缀派生的全部权限必须已存在于 capability catalog
|
|
27
|
+
* (admin-vue 的 YoyoAdminPermissionKey 是类型化字面量联合,假前缀在
|
|
28
|
+
* vue-tsc 下必然编译失败,提前到计划期 fail-closed);
|
|
29
|
+
* - moduleKey / pageKey / routePath 不得与既有模块重复。
|
|
30
|
+
*/
|
|
31
|
+
import { readdirSync, readFileSync } from 'node:fs';
|
|
32
|
+
import { join } from 'node:path';
|
|
33
|
+
import { readRepoFile } from '../kernel/plan-runner.js';
|
|
34
|
+
import { createFile, createFsPlan, updateFile, } from '../kernel/fs-plan.js';
|
|
35
|
+
import { buildManifestsWiring, parseManifestsAnchors, renderWiredManifests, } from '../kernel/manifests-wiring.js';
|
|
36
|
+
import { YoyoAdminCliUsageError } from '../kernel/result.js';
|
|
37
|
+
import { loadCrudResourceDescriptor } from '../catalogs/crud.js';
|
|
38
|
+
import { loadKnownPermissionNames } from '../catalogs/capability.js';
|
|
39
|
+
import { deriveStandardCrudShape } from '../catalogs/standard-crud.js';
|
|
40
|
+
import { ADMIN_VUE_APP_ROOT_RELATIVE, APP_MANIFESTS_RELATIVE, } from '../kernel/workspace.js';
|
|
41
|
+
export const STANDARD_CRUD_KIND = 'standard-crud';
|
|
42
|
+
// standard-crud 的形状派生活动在共享事实层(`catalogs/standard-crud.ts`):
|
|
43
|
+
// generator 与 `inspect resource` 用同一份逻辑,只是 generator 把
|
|
44
|
+
// `eligible: false` 转成 plan-time fail-closed。这里原样再导出,保持既有消费面稳定。
|
|
45
|
+
export { deriveStandardCrudShape, RUNTIME_SUPPLIED_FIELDS, } from '../catalogs/standard-crud.js';
|
|
46
|
+
const KEBAB_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
47
|
+
const CAMEL_PATTERN = /^[a-z][A-Za-z0-9]*$/;
|
|
48
|
+
/** 生成合法的 TypeScript 单引号字符串字面量(转义 '/\/控制字符/行分隔符)。 */
|
|
49
|
+
export function toTsStringLiteral(value) {
|
|
50
|
+
const escaped = value
|
|
51
|
+
.replace(/\\/g, '\\\\')
|
|
52
|
+
.replace(/'/g, "\\'")
|
|
53
|
+
.replace(/\r/g, '\\r')
|
|
54
|
+
.replace(/\n/g, '\\n')
|
|
55
|
+
.replace(/\u2028/g, '\\u2028')
|
|
56
|
+
.replace(/\u2029/g, '\\u2029')
|
|
57
|
+
.replace(
|
|
58
|
+
// eslint-disable-next-line no-control-regex
|
|
59
|
+
/[\u0000-\u0008\u000B\u000C\u000E-\u001F]/g, (character) => `\\u${character.charCodeAt(0).toString(16).padStart(4, '0')}`);
|
|
60
|
+
return `'${escaped}'`;
|
|
61
|
+
}
|
|
62
|
+
export function validateFrontendModuleRequest(request) {
|
|
63
|
+
if (request.virtualization !== undefined && request.virtualization !== 'fixed') {
|
|
64
|
+
throw new YoyoAdminCliUsageError('unsupported-virtualization-mode', '--virtualization 仅支持 fixed;省略该参数以关闭虚拟化。');
|
|
65
|
+
}
|
|
66
|
+
if (request.kind !== STANDARD_CRUD_KIND) {
|
|
67
|
+
throw new YoyoAdminCliUsageError('unsupported-recipe-kind', `当前仅支持 --kind ${STANDARD_CRUD_KIND};收到 ${request.kind}。` +
|
|
68
|
+
'如需 typed extension 等其他形态,请作为独立 recipe 提出,不要套用相近配方。');
|
|
69
|
+
}
|
|
70
|
+
if (!KEBAB_PATTERN.test(request.moduleKey)) {
|
|
71
|
+
throw new YoyoAdminCliUsageError('invalid-module-key', `--module 必须是 kebab-case,例如 basic-data;收到 ${request.moduleKey}。`);
|
|
72
|
+
}
|
|
73
|
+
if (!CAMEL_PATTERN.test(request.resourceKey)) {
|
|
74
|
+
throw new YoyoAdminCliUsageError('invalid-resource-key', `--resource 必须是 camelCase,与生成的 CRUD resource catalog 一致(例如 measuringUnit);收到 ${request.resourceKey}。`);
|
|
75
|
+
}
|
|
76
|
+
if (!request.routePath.startsWith('/')) {
|
|
77
|
+
throw new YoyoAdminCliUsageError('invalid-route-path', `--route 必须以 / 开头;收到 ${request.routePath}。`);
|
|
78
|
+
}
|
|
79
|
+
if (!Number.isInteger(request.order)) {
|
|
80
|
+
throw new YoyoAdminCliUsageError('invalid-order', `--order 必须是整数;收到 ${request.order}。`);
|
|
81
|
+
}
|
|
82
|
+
if (request.permissionPrefix.length === 0) {
|
|
83
|
+
throw new YoyoAdminCliUsageError('invalid-permission-prefix', '--permission-prefix 不能为空字符串。');
|
|
84
|
+
}
|
|
85
|
+
if (request.datasource !== undefined && request.datasource.length === 0) {
|
|
86
|
+
throw new YoyoAdminCliUsageError('invalid-datasource', '--datasource 不能为空字符串。');
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
/** 既有的全部模块 manifest 文件(用于重复检测)。 */
|
|
90
|
+
function collectExistingManifestSources(workspace) {
|
|
91
|
+
const appRoot = join(workspace.adminVueHost, 'src/app');
|
|
92
|
+
const results = [];
|
|
93
|
+
const walk = (directory) => {
|
|
94
|
+
for (const entry of readdirSync(directory, { withFileTypes: true })) {
|
|
95
|
+
const absolute = join(directory, entry.name);
|
|
96
|
+
if (entry.isDirectory()) {
|
|
97
|
+
walk(absolute);
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
if (entry.name === 'manifest.ts') {
|
|
101
|
+
results.push({
|
|
102
|
+
path: `${ADMIN_VUE_APP_ROOT_RELATIVE}${absolute.slice(appRoot.length).replace(/\\/g, '/')}`,
|
|
103
|
+
content: readFileSync(absolute, 'utf8'),
|
|
104
|
+
});
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
};
|
|
108
|
+
walk(appRoot);
|
|
109
|
+
return results;
|
|
110
|
+
}
|
|
111
|
+
/** 对 moduleKey / pageKey / routePath 做既有清单重复检测(fail-closed)。 */
|
|
112
|
+
export function assertNoDuplicates(workspace, request, pageKey) {
|
|
113
|
+
const routePattern = new RegExp(`(?:routePath|path):\\s*['"\`]${escapeRegExp(request.routePath)}['"\`]`);
|
|
114
|
+
const moduleKeyPattern = new RegExp(`moduleKey:\\s*['"\`]${escapeRegExp(request.moduleKey)}['"\`]`);
|
|
115
|
+
const pageKeyPattern = new RegExp(`['"\`]${escapeRegExp(pageKey)}['"\`]`);
|
|
116
|
+
for (const manifest of collectExistingManifestSources(workspace)) {
|
|
117
|
+
if (manifest.path === APP_MANIFESTS_RELATIVE)
|
|
118
|
+
continue;
|
|
119
|
+
if (moduleKeyPattern.test(manifest.content)) {
|
|
120
|
+
throw new YoyoAdminCliUsageError('duplicate-module-key', `moduleKey "${request.moduleKey}" 已被 ${manifest.path} 使用;拒绝生成。`);
|
|
121
|
+
}
|
|
122
|
+
if (routePattern.test(manifest.content)) {
|
|
123
|
+
throw new YoyoAdminCliUsageError('duplicate-route', `路由 "${request.routePath}" 已被 ${manifest.path} 使用;拒绝生成。`);
|
|
124
|
+
}
|
|
125
|
+
if (pageKeyPattern.test(manifest.content)) {
|
|
126
|
+
throw new YoyoAdminCliUsageError('duplicate-page-key', `pageKey "${pageKey}" 已被 ${manifest.path} 使用;拒绝生成。`);
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
function escapeRegExp(value) {
|
|
131
|
+
return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
132
|
+
}
|
|
133
|
+
export function camelCase(value) {
|
|
134
|
+
return value.replace(/-([a-z0-9])/g, (_, character) => character.toUpperCase());
|
|
135
|
+
}
|
|
136
|
+
export function kebabCase(value) {
|
|
137
|
+
return value
|
|
138
|
+
.replace(/([a-z0-9])([A-Z])/g, '$1-$2')
|
|
139
|
+
.replace(/([A-Z]+)([A-Z][a-z])/g, '$1-$2')
|
|
140
|
+
.toLowerCase();
|
|
141
|
+
}
|
|
142
|
+
/** datasourceId 的唯一推导点:schema 与 manifest 共用,不可能漂移。 */
|
|
143
|
+
export function datasourceIdFor(request) {
|
|
144
|
+
return request.datasource ?? `${camelCase(request.moduleKey)}.${request.resourceKey}`;
|
|
145
|
+
}
|
|
146
|
+
export function pageKeyFor(request) {
|
|
147
|
+
return `${request.moduleKey}-${kebabCase(request.resourceKey)}`;
|
|
148
|
+
}
|
|
149
|
+
export function deriveStandardCrudPermissions(permissionPrefix, shape) {
|
|
150
|
+
return {
|
|
151
|
+
page: [`${permissionPrefix}.Query`],
|
|
152
|
+
...(shape.canCreate ? { create: `${permissionPrefix}.Create` } : {}),
|
|
153
|
+
...(shape.canUpdate ? { update: `${permissionPrefix}.Edit` } : {}),
|
|
154
|
+
...(shape.canSetEnabled ? { setEnabled: `${permissionPrefix}.Edit` } : {}),
|
|
155
|
+
...(shape.canRemove ? { remove: `${permissionPrefix}.Delete` } : {}),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
/** 平铺权限集合(plan-time capability catalog 校验用)。 */
|
|
159
|
+
export function permissionNamesOf(permissions) {
|
|
160
|
+
return [
|
|
161
|
+
...permissions.page,
|
|
162
|
+
...(permissions.create === undefined ? [] : [permissions.create]),
|
|
163
|
+
...(permissions.update === undefined ? [] : [permissions.update]),
|
|
164
|
+
...(permissions.setEnabled === undefined ? [] : [permissions.setEnabled]),
|
|
165
|
+
...(permissions.remove === undefined ? [] : [permissions.remove]),
|
|
166
|
+
];
|
|
167
|
+
}
|
|
168
|
+
export function planStandardCrudFrontendModule(workspace, request) {
|
|
169
|
+
validateFrontendModuleRequest(request);
|
|
170
|
+
const descriptor = loadCrudResourceDescriptor(workspace.repoRoot, request.resourceKey);
|
|
171
|
+
const shape = deriveStandardCrudShape(descriptor);
|
|
172
|
+
const permissions = deriveStandardCrudPermissions(request.permissionPrefix, shape);
|
|
173
|
+
const knownPermissions = loadKnownPermissionNames(workspace.repoRoot);
|
|
174
|
+
const missingPermissions = permissionNamesOf(permissions).filter((permission) => !knownPermissions.includes(permission));
|
|
175
|
+
if (missingPermissions.length > 0) {
|
|
176
|
+
throw new YoyoAdminCliUsageError('unknown-permission-key', `权限前缀 "${request.permissionPrefix}" 派生的权限不在 capability catalog 中:` +
|
|
177
|
+
`${missingPermissions.join(', ')}。` +
|
|
178
|
+
'YoyoAdminPermissionKey 是类型化字面量联合,假前缀会导致 admin-vue vue-tsc 编译失败。' +
|
|
179
|
+
'请先在 aspnet-core 的授权 Provider 中定义这些权限,再运行 CapabilityExporter 刷新生成物。');
|
|
180
|
+
}
|
|
181
|
+
const resourceVariable = request.resourceKey.charAt(0).toLowerCase() + request.resourceKey.slice(1);
|
|
182
|
+
const featureDirectory = kebabCase(request.resourceKey);
|
|
183
|
+
const pageKey = pageKeyFor(request);
|
|
184
|
+
const datasourceId = datasourceIdFor(request);
|
|
185
|
+
assertNoDuplicates(workspace, request, pageKey);
|
|
186
|
+
const moduleDirectory = `${ADMIN_VUE_APP_ROOT_RELATIVE}/${request.moduleKey}`;
|
|
187
|
+
const manifestPath = `${moduleDirectory}/manifest.ts`;
|
|
188
|
+
const schemaPath = `${moduleDirectory}/${featureDirectory}/schema.ts`;
|
|
189
|
+
// 导出名按 moduleKey 推导:manifests.ts 显式收集纯 definition,
|
|
190
|
+
// moduleKey 唯一性已由 assertNoDuplicates 保证,标识符冲突在构造上不可能发生。
|
|
191
|
+
const manifestExportName = `${camelCase(request.moduleKey)}Definition`;
|
|
192
|
+
const schemaFactoryName = `create${request.resourceKey.charAt(0).toUpperCase()}${request.resourceKey.slice(1)}PageSchema`;
|
|
193
|
+
const changes = [
|
|
194
|
+
createFile(manifestPath, renderModuleManifest({
|
|
195
|
+
request,
|
|
196
|
+
pageKey,
|
|
197
|
+
datasourceId,
|
|
198
|
+
manifestExportName,
|
|
199
|
+
schemaFactoryName,
|
|
200
|
+
resourceVariable,
|
|
201
|
+
featureDirectory,
|
|
202
|
+
permissions,
|
|
203
|
+
}), 'frontend-module/manifest'),
|
|
204
|
+
createFile(schemaPath, renderFeatureSchema({ request, datasourceId, schemaFactoryName, shape }), 'frontend-module/schema'),
|
|
205
|
+
];
|
|
206
|
+
const manifestsSource = readRepoFile(workspace.repoRoot, APP_MANIFESTS_RELATIVE);
|
|
207
|
+
const anchors = parseManifestsAnchors(manifestsSource, 'yCrudDefinitions');
|
|
208
|
+
const wiring = buildManifestsWiring({
|
|
209
|
+
manifestExportName,
|
|
210
|
+
importPath: `./${request.moduleKey}/manifest`,
|
|
211
|
+
});
|
|
212
|
+
const wired = renderWiredManifests(manifestsSource, anchors, wiring);
|
|
213
|
+
changes.push(updateFile(APP_MANIFESTS_RELATIVE, wired, 'frontend-module/wiring', `import ${manifestExportName} 并注册进 yCrudDefinitions`));
|
|
214
|
+
return {
|
|
215
|
+
...createFsPlan(changes),
|
|
216
|
+
moduleKey: request.moduleKey,
|
|
217
|
+
directory: moduleDirectory,
|
|
218
|
+
pageKey,
|
|
219
|
+
datasourceId,
|
|
220
|
+
shape,
|
|
221
|
+
permissions,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
function renderFeatureSchema(options) {
|
|
225
|
+
const { request, datasourceId, schemaFactoryName, shape } = options;
|
|
226
|
+
const actions = [
|
|
227
|
+
" { key: 'refresh', kind: 'refresh', label: '刷新' },",
|
|
228
|
+
" { key: 'reset', kind: 'reset', label: '重置' },",
|
|
229
|
+
];
|
|
230
|
+
if (shape.canCreate)
|
|
231
|
+
actions.push(" { key: 'create', kind: 'create', label: '新增' },");
|
|
232
|
+
if (shape.canUpdate)
|
|
233
|
+
actions.push(" { key: 'edit', kind: 'edit', label: '编辑' },");
|
|
234
|
+
if (shape.canSetEnabled)
|
|
235
|
+
actions.push(" { key: 'disable', kind: 'disable', label: '切换启用状态' },");
|
|
236
|
+
if (shape.canRemove) {
|
|
237
|
+
actions.push(" { key: 'delete', kind: 'delete', label: '删除', confirmation: { intent: 'destructive' } },");
|
|
238
|
+
}
|
|
239
|
+
const formFields = shape.formFields
|
|
240
|
+
.map((field) => field.editableOn === 'both'
|
|
241
|
+
? ` { key: ${toTsStringLiteral(field.key)}, kind: 'text', label: ${toTsStringLiteral(field.key)} },`
|
|
242
|
+
: ` { key: ${toTsStringLiteral(field.key)}, kind: 'text', label: ${toTsStringLiteral(field.key)}, lifecycle: { editableOn: 'create' } },`)
|
|
243
|
+
.join('\n');
|
|
244
|
+
return `import { defineYoyoQueryPage } from '@yoyoboot/yoyo-admin-schema'
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* 页面契约的唯一作者面。
|
|
248
|
+
*
|
|
249
|
+
* 本模块由 yoyo-admin CLI 按 CRUD resource catalog 的真实契约生成:
|
|
250
|
+
* - actions 与资源 operations 一一对应(无对应 operation 的动作不会出现);
|
|
251
|
+
* - form 字段取自 create fieldMappings(排除系统字段),生命周期按 update 契约
|
|
252
|
+
* 派生(create∩update → both;create-only → editableOn: 'create');
|
|
253
|
+
* - structural-ready ≠ product-content-ready:rowKey 仅是稳定记录 key;
|
|
254
|
+
* 作者必须补真实业务 identity/headline、查询项和展示列语义,CLI 不猜业务展示。
|
|
255
|
+
*/
|
|
256
|
+
export function ${schemaFactoryName}(pageKey: string) {
|
|
257
|
+
return defineYoyoQueryPage({
|
|
258
|
+
schemaVersion: 1,
|
|
259
|
+
pageKind: 'crud',
|
|
260
|
+
pageKey,
|
|
261
|
+
title: ${toTsStringLiteral(request.title)},
|
|
262
|
+
query: {
|
|
263
|
+
datasource: ${toTsStringLiteral(datasourceId)},
|
|
264
|
+
presentation: {
|
|
265
|
+
pageSize: 20,
|
|
266
|
+
allowedUrlParams: ['page', 'pageSize', 'sort'],
|
|
267
|
+
},
|
|
268
|
+
},
|
|
269
|
+
table: {
|
|
270
|
+
rowKey: ${toTsStringLiteral(shape.rowKey)},
|
|
271
|
+
columns: [
|
|
272
|
+
{
|
|
273
|
+
key: ${toTsStringLiteral(shape.rowKey)},
|
|
274
|
+
title: ${toTsStringLiteral(shape.rowKey)},
|
|
275
|
+
renderer: 'text',
|
|
276
|
+
sortable: false,
|
|
277
|
+
role: 'key',
|
|
278
|
+
priority: 'high',
|
|
279
|
+
mobilePlacement: 'supporting',
|
|
280
|
+
},
|
|
281
|
+
],
|
|
282
|
+
${request.virtualization === 'fixed' ? " virtualization: { mode: 'fixed' },\n" : ''} },
|
|
283
|
+
actions: [
|
|
284
|
+
${actions.join('\n')}
|
|
285
|
+
],
|
|
286
|
+
form: {
|
|
287
|
+
fields: [
|
|
288
|
+
${formFields}
|
|
289
|
+
],
|
|
290
|
+
},
|
|
291
|
+
})
|
|
292
|
+
}
|
|
293
|
+
`;
|
|
294
|
+
}
|
|
295
|
+
function renderModuleManifest(options) {
|
|
296
|
+
const { request, pageKey, datasourceId, manifestExportName, schemaFactoryName, resourceVariable, featureDirectory, permissions, } = options;
|
|
297
|
+
const permissionEntries = [
|
|
298
|
+
` page: [${permissions.page.map((permission) => toTsStringLiteral(permission)).join(', ')}],`,
|
|
299
|
+
];
|
|
300
|
+
if (permissions.create !== undefined)
|
|
301
|
+
permissionEntries.push(` create: ${toTsStringLiteral(permissions.create)},`);
|
|
302
|
+
if (permissions.update !== undefined)
|
|
303
|
+
permissionEntries.push(` update: ${toTsStringLiteral(permissions.update)},`);
|
|
304
|
+
if (permissions.setEnabled !== undefined)
|
|
305
|
+
permissionEntries.push(` setEnabled: ${toTsStringLiteral(permissions.setEnabled)},`);
|
|
306
|
+
if (permissions.remove !== undefined)
|
|
307
|
+
permissionEntries.push(` remove: ${toTsStringLiteral(permissions.remove)},`);
|
|
308
|
+
// icon 选择约束:必须同时存在于 @lucide/vue 与 admin-vue 的 vitest lucide
|
|
309
|
+
// mock 集合(tests/unit/setup/vitest.setup.ts 的 lucideIconNames),
|
|
310
|
+
// 否则接入 app 后宿主测试会因 mock 缺导出整体失败。Boxes 满足两者。
|
|
311
|
+
return `import { Boxes } from '@lucide/vue'
|
|
312
|
+
import type { YCrudDefinition } from '@/shared/standard-management/definition'
|
|
313
|
+
import { ${schemaFactoryName} } from './${featureDirectory}/schema'
|
|
314
|
+
|
|
315
|
+
const ${resourceVariable}PageKey = ${toTsStringLiteral(pageKey)}
|
|
316
|
+
|
|
317
|
+
export const ${manifestExportName} = {
|
|
318
|
+
moduleKey: ${toTsStringLiteral(request.moduleKey)},
|
|
319
|
+
schemaSourcePath: ${toTsStringLiteral(`app/${request.moduleKey}/${featureDirectory}/schema.ts`)},
|
|
320
|
+
pageKey: ${resourceVariable}PageKey,
|
|
321
|
+
resourceKey: ${toTsStringLiteral(request.resourceKey)},
|
|
322
|
+
datasourceId: ${toTsStringLiteral(datasourceId)},
|
|
323
|
+
routePath: ${toTsStringLiteral(request.routePath)},
|
|
324
|
+
title: ${toTsStringLiteral(request.title)},
|
|
325
|
+
description: ${toTsStringLiteral(request.description)},
|
|
326
|
+
order: ${request.order},
|
|
327
|
+
schema: ${schemaFactoryName}(${resourceVariable}PageKey),
|
|
328
|
+
menu: {
|
|
329
|
+
group: ${toTsStringLiteral(request.group)},
|
|
330
|
+
description: ${toTsStringLiteral(request.description)},
|
|
331
|
+
icon: Boxes,
|
|
332
|
+
badge: 'standard',
|
|
333
|
+
},
|
|
334
|
+
page: {
|
|
335
|
+
description: ${toTsStringLiteral(request.description)},
|
|
336
|
+
},
|
|
337
|
+
permissions: {
|
|
338
|
+
${permissionEntries.join('\n')}
|
|
339
|
+
},
|
|
340
|
+
presentation: {
|
|
341
|
+
eyebrow: ${toTsStringLiteral(request.group)},
|
|
342
|
+
description: ${toTsStringLiteral(request.description)},
|
|
343
|
+
editorDescription: ${toTsStringLiteral(request.description)},
|
|
344
|
+
loadErrorMessage: ${toTsStringLiteral(`${request.title}加载失败,请重试。`)},
|
|
345
|
+
headerStatusTestId: \`\${${resourceVariable}PageKey}__header-status\`,
|
|
346
|
+
feedbackMessage: (event) => event.outcome === 'success'
|
|
347
|
+
? event.operation === 'softDelete'
|
|
348
|
+
? ${toTsStringLiteral(`${request.title}已删除。`)}
|
|
349
|
+
: event.operation === 'setEnabled'
|
|
350
|
+
? ${toTsStringLiteral(`${request.title}状态已更新。`)}
|
|
351
|
+
: ${toTsStringLiteral(`${request.title}已保存。`)}
|
|
352
|
+
: event.message,
|
|
353
|
+
// 中性删除文案:不引用行业务字段(字段名由各资源的 schema 作者决定)。
|
|
354
|
+
deleteDescription: () => ${toTsStringLiteral('确认删除该条目吗?删除后该条目将不再出现在正常列表中。')},
|
|
355
|
+
},
|
|
356
|
+
} satisfies YCrudDefinition
|
|
357
|
+
`;
|
|
358
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yoyo-admin create package --kind ts`
|
|
3
|
+
*
|
|
4
|
+
* 在 `packages/yoyo-admin/` 下生成一个**标准纯 TypeScript 私有包骨架**。
|
|
5
|
+
*
|
|
6
|
+
* 只承诺骨架本身:package.json / tsconfig 对 / src 根入口 / 一个真实可跑的
|
|
7
|
+
* smoke test / README。**不声称与 admin-core / admin-schema / admin-kit
|
|
8
|
+
* 「完全同构」**——这三个包的职责、边界与验证项由各自 README/AGENTS 拥有,
|
|
9
|
+
* 骨架不复制也不暗示它们。
|
|
10
|
+
*
|
|
11
|
+
* 生成物必须 fresh-create 后**直接通过自己的 `package:verify`**,因此
|
|
12
|
+
* `tests/` 里会落一个真实的最小 smoke test(`vitest run` 无测试文件会失败,
|
|
13
|
+
* 不能用降低 test gate 的方式绕过)。
|
|
14
|
+
*
|
|
15
|
+
* Plan-time fail-closed:kind / 目录名 / 包名 / 描述 / 目录冲突 / 包名冲突
|
|
16
|
+
* 全部在计划期拒绝,重复执行必然 exit 2。
|
|
17
|
+
*/
|
|
18
|
+
import { type FsPlan } from '../kernel/fs-plan.js';
|
|
19
|
+
import { type YoyoAdminWorkspace } from '../kernel/workspace.js';
|
|
20
|
+
/** 当前唯一支持的 package profile。 */
|
|
21
|
+
export declare const PACKAGE_KIND_TS = "ts";
|
|
22
|
+
export interface PackageRequest {
|
|
23
|
+
/** 配方 kind;当前仅支持 `ts`。 */
|
|
24
|
+
readonly kind: string;
|
|
25
|
+
/** 包目录名,例如 admin-runtime。 */
|
|
26
|
+
readonly directory: string;
|
|
27
|
+
/** npm 包名,例如 @yoyoboot/yoyo-admin-runtime。 */
|
|
28
|
+
readonly packageName: string;
|
|
29
|
+
readonly description: string;
|
|
30
|
+
}
|
|
31
|
+
export declare function validatePackageRequest(request: PackageRequest): void;
|
|
32
|
+
/**
|
|
33
|
+
* 目录与包名冲突检测:两者都在 plan 期 fail-closed,重复执行必然被拒绝。
|
|
34
|
+
*
|
|
35
|
+
* 包名冲突的作用域 = workspace 的全部 importer(根 + 直接子目录),
|
|
36
|
+
* 因为 `pnpm-workspace.yaml` 把 `'.'` 与 `'./*'` 都算作 workspace 成员。
|
|
37
|
+
*/
|
|
38
|
+
export declare function assertNoPackageConflicts(workspace: YoyoAdminWorkspace, request: PackageRequest): void;
|
|
39
|
+
export interface PackagePlan extends FsPlan {
|
|
40
|
+
readonly kind: typeof PACKAGE_KIND_TS;
|
|
41
|
+
/** 仓库根相对目录,例如 packages/yoyo-admin/admin-runtime。 */
|
|
42
|
+
readonly directory: string;
|
|
43
|
+
readonly packageName: string;
|
|
44
|
+
}
|
|
45
|
+
export declare function planAdminPackage(workspace: YoyoAdminWorkspace, request: PackageRequest): PackagePlan;
|
|
46
|
+
//# sourceMappingURL=package.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"package.d.ts","sourceRoot":"","sources":["../../src/generators/package.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAKH,OAAO,EAA4B,KAAK,MAAM,EAAE,MAAM,sBAAsB,CAAA;AAE5E,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,wBAAwB,CAAA;AAE1F,+BAA+B;AAC/B,eAAO,MAAM,eAAe,OAAO,CAAA;AAKnC,MAAM,WAAW,cAAc;IAC7B,0BAA0B;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAA;IACrB,6BAA6B;IAC7B,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,8CAA8C;IAC9C,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;IAC5B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,cAAc,GAAG,IAAI,CAkCpE;AAwDD;;;;;GAKG;AACH,wBAAgB,wBAAwB,CACtC,SAAS,EAAE,kBAAkB,EAC7B,OAAO,EAAE,cAAc,GACtB,IAAI,CAiBN;AAED,MAAM,WAAW,WAAY,SAAQ,MAAM;IACzC,QAAQ,CAAC,IAAI,EAAE,OAAO,eAAe,CAAA;IACrC,oDAAoD;IACpD,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAA;IAC1B,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAA;CAC7B;AAED,wBAAgB,gBAAgB,CAC9B,SAAS,EAAE,kBAAkB,EAC7B,OAAO,EAAE,cAAc,GACtB,WAAW,CA2Bb"}
|
|
@@ -0,0 +1,230 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `yoyo-admin create package --kind ts`
|
|
3
|
+
*
|
|
4
|
+
* 在 `packages/yoyo-admin/` 下生成一个**标准纯 TypeScript 私有包骨架**。
|
|
5
|
+
*
|
|
6
|
+
* 只承诺骨架本身:package.json / tsconfig 对 / src 根入口 / 一个真实可跑的
|
|
7
|
+
* smoke test / README。**不声称与 admin-core / admin-schema / admin-kit
|
|
8
|
+
* 「完全同构」**——这三个包的职责、边界与验证项由各自 README/AGENTS 拥有,
|
|
9
|
+
* 骨架不复制也不暗示它们。
|
|
10
|
+
*
|
|
11
|
+
* 生成物必须 fresh-create 后**直接通过自己的 `package:verify`**,因此
|
|
12
|
+
* `tests/` 里会落一个真实的最小 smoke test(`vitest run` 无测试文件会失败,
|
|
13
|
+
* 不能用降低 test gate 的方式绕过)。
|
|
14
|
+
*
|
|
15
|
+
* Plan-time fail-closed:kind / 目录名 / 包名 / 描述 / 目录冲突 / 包名冲突
|
|
16
|
+
* 全部在计划期拒绝,重复执行必然 exit 2。
|
|
17
|
+
*/
|
|
18
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
19
|
+
import { join } from 'node:path';
|
|
20
|
+
import { createFile, createFsPlan } from '../kernel/fs-plan.js';
|
|
21
|
+
import { YoyoAdminCliUsageError } from '../kernel/result.js';
|
|
22
|
+
import { ADMIN_WORKSPACE_RELATIVE } from '../kernel/workspace.js';
|
|
23
|
+
/** 当前唯一支持的 package profile。 */
|
|
24
|
+
export const PACKAGE_KIND_TS = 'ts';
|
|
25
|
+
const PACKAGE_DIRECTORY_PATTERN = /^[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
26
|
+
const PACKAGE_NAME_PATTERN = /^@yoyoboot\/[a-z][a-z0-9]*(?:-[a-z0-9]+)*$/;
|
|
27
|
+
export function validatePackageRequest(request) {
|
|
28
|
+
if (request.kind !== PACKAGE_KIND_TS) {
|
|
29
|
+
throw new YoyoAdminCliUsageError('unsupported-recipe-kind', `create package 目前仅支持 --kind ${PACKAGE_KIND_TS}(标准纯 TS 包骨架);收到 ${request.kind}。` +
|
|
30
|
+
'其他包形态请作为独立 recipe 提出,不要套用相近配方。');
|
|
31
|
+
}
|
|
32
|
+
if (!PACKAGE_DIRECTORY_PATTERN.test(request.directory)) {
|
|
33
|
+
throw new YoyoAdminCliUsageError('invalid-package-directory', `包目录名必须是 kebab-case(例如 admin-runtime);收到 ${request.directory}。`);
|
|
34
|
+
}
|
|
35
|
+
if (!PACKAGE_NAME_PATTERN.test(request.packageName)) {
|
|
36
|
+
throw new YoyoAdminCliUsageError('invalid-package-name', `包名必须是 @yoyoboot/<kebab-case>(例如 @yoyoboot/yoyo-admin-runtime);收到 ${request.packageName}。`);
|
|
37
|
+
}
|
|
38
|
+
if (request.description.trim().length === 0) {
|
|
39
|
+
throw new YoyoAdminCliUsageError('invalid-package-description', '--description 不能是空字符串。');
|
|
40
|
+
}
|
|
41
|
+
// 描述会同时进入 package.json 与 README:控制字符/换行会破坏生成物结构,plan 期拒绝。
|
|
42
|
+
// eslint-disable-next-line no-control-regex
|
|
43
|
+
if (/[\u0000-\u001F\u007F]/.test(request.description)) {
|
|
44
|
+
throw new YoyoAdminCliUsageError('invalid-package-description', '--description 不能包含换行或控制字符。');
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 读取 workspace 内既有的 importer 包名:`packages/yoyo-admin/package.json` 本身
|
|
49
|
+
* (`pnpm-workspace.yaml` 明确包含 `'.'`)以及它的每一个直接子目录。
|
|
50
|
+
* manifest 不可解析/缺 name 一律 fail-closed —— 无法确认无冲突就不许生成。
|
|
51
|
+
*/
|
|
52
|
+
function collectExistingPackages(workspace) {
|
|
53
|
+
const candidates = [
|
|
54
|
+
{
|
|
55
|
+
manifestPath: join(workspace.adminWorkspace, 'package.json'),
|
|
56
|
+
location: `${ADMIN_WORKSPACE_RELATIVE}(workspace 根)`,
|
|
57
|
+
},
|
|
58
|
+
...readdirSync(workspace.adminWorkspace, { withFileTypes: true })
|
|
59
|
+
.filter((entry) => entry.isDirectory())
|
|
60
|
+
.map((entry) => ({
|
|
61
|
+
manifestPath: join(workspace.adminWorkspace, entry.name, 'package.json'),
|
|
62
|
+
location: `${ADMIN_WORKSPACE_RELATIVE}/${entry.name}`,
|
|
63
|
+
})),
|
|
64
|
+
];
|
|
65
|
+
const results = [];
|
|
66
|
+
for (const candidate of candidates) {
|
|
67
|
+
if (!existsSync(candidate.manifestPath))
|
|
68
|
+
continue;
|
|
69
|
+
let manifest;
|
|
70
|
+
try {
|
|
71
|
+
manifest = JSON.parse(readFileSync(candidate.manifestPath, 'utf8'));
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
throw new YoyoAdminCliUsageError('package-manifest-unreadable', `${candidate.location}/package.json 无法解析,` +
|
|
75
|
+
`无法确认包名是否冲突;请先修复该 manifest。(${error instanceof Error ? error.message : String(error)})`);
|
|
76
|
+
}
|
|
77
|
+
if (typeof manifest.name !== 'string' || manifest.name.length === 0) {
|
|
78
|
+
throw new YoyoAdminCliUsageError('package-manifest-unreadable', `${candidate.location}/package.json 缺少 name,无法确认包名是否冲突。`);
|
|
79
|
+
}
|
|
80
|
+
results.push({ location: candidate.location, packageName: manifest.name });
|
|
81
|
+
}
|
|
82
|
+
return results;
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* 目录与包名冲突检测:两者都在 plan 期 fail-closed,重复执行必然被拒绝。
|
|
86
|
+
*
|
|
87
|
+
* 包名冲突的作用域 = workspace 的全部 importer(根 + 直接子目录),
|
|
88
|
+
* 因为 `pnpm-workspace.yaml` 把 `'.'` 与 `'./*'` 都算作 workspace 成员。
|
|
89
|
+
*/
|
|
90
|
+
export function assertNoPackageConflicts(workspace, request) {
|
|
91
|
+
if (existsSync(join(workspace.adminWorkspace, request.directory))) {
|
|
92
|
+
throw new YoyoAdminCliUsageError('package-directory-exists', `目录已存在,拒绝覆盖:${ADMIN_WORKSPACE_RELATIVE}/${request.directory}。`);
|
|
93
|
+
}
|
|
94
|
+
const conflict = collectExistingPackages(workspace).find((existing) => existing.packageName === request.packageName);
|
|
95
|
+
if (conflict !== undefined) {
|
|
96
|
+
throw new YoyoAdminCliUsageError('duplicate-package-name', `包名 ${request.packageName} 已被 ${conflict.location} 使用;拒绝生成。`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
export function planAdminPackage(workspace, request) {
|
|
100
|
+
validatePackageRequest(request);
|
|
101
|
+
assertNoPackageConflicts(workspace, request);
|
|
102
|
+
const base = `${ADMIN_WORKSPACE_RELATIVE}/${request.directory}`;
|
|
103
|
+
return {
|
|
104
|
+
...createFsPlan([
|
|
105
|
+
createFile(`${base}/package.json`, renderPackageJson(request), 'package/package.json'),
|
|
106
|
+
createFile(`${base}/tsconfig.json`, renderTsconfig(), 'package/tsconfig.json'),
|
|
107
|
+
createFile(`${base}/tsconfig.build.json`, renderTsconfigBuild(), 'package/tsconfig.build.json'),
|
|
108
|
+
createFile(`${base}/src/index.ts`, renderIndex(request), 'package/src/index.ts'),
|
|
109
|
+
createFile(`${base}/tests/index.spec.ts`, renderSmokeTest(request), 'package/tests/index.spec.ts'),
|
|
110
|
+
createFile(`${base}/README.md`, renderReadme(request), 'package/README.md'),
|
|
111
|
+
]),
|
|
112
|
+
kind: PACKAGE_KIND_TS,
|
|
113
|
+
directory: base,
|
|
114
|
+
packageName: request.packageName,
|
|
115
|
+
};
|
|
116
|
+
}
|
|
117
|
+
function renderPackageJson(request) {
|
|
118
|
+
return `${JSON.stringify({
|
|
119
|
+
name: request.packageName,
|
|
120
|
+
private: true,
|
|
121
|
+
version: '0.0.0-private',
|
|
122
|
+
type: 'module',
|
|
123
|
+
description: request.description,
|
|
124
|
+
files: ['dist'],
|
|
125
|
+
exports: {
|
|
126
|
+
'.': {
|
|
127
|
+
types: './dist/index.d.ts',
|
|
128
|
+
import: './dist/index.js',
|
|
129
|
+
},
|
|
130
|
+
},
|
|
131
|
+
types: './dist/index.d.ts',
|
|
132
|
+
scripts: {
|
|
133
|
+
build: 'tsc -p ./tsconfig.build.json',
|
|
134
|
+
typecheck: 'tsc -p ./tsconfig.json --noEmit',
|
|
135
|
+
'test:run': 'vitest run',
|
|
136
|
+
'package:verify': 'pnpm run typecheck && pnpm run test:run && pnpm run build',
|
|
137
|
+
},
|
|
138
|
+
devDependencies: {
|
|
139
|
+
'@types/node': '^24.13.2',
|
|
140
|
+
typescript: '^6.0.2',
|
|
141
|
+
vitest: '^4.1.10',
|
|
142
|
+
},
|
|
143
|
+
packageManager: 'pnpm@11.28.0',
|
|
144
|
+
}, null, 2)}\n`;
|
|
145
|
+
}
|
|
146
|
+
function renderTsconfig() {
|
|
147
|
+
return `${JSON.stringify({
|
|
148
|
+
compilerOptions: {
|
|
149
|
+
target: 'ES2022',
|
|
150
|
+
module: 'ESNext',
|
|
151
|
+
moduleResolution: 'Bundler',
|
|
152
|
+
strict: true,
|
|
153
|
+
exactOptionalPropertyTypes: true,
|
|
154
|
+
noUncheckedIndexedAccess: true,
|
|
155
|
+
noEmit: true,
|
|
156
|
+
skipLibCheck: true,
|
|
157
|
+
ignoreDeprecations: '6.0',
|
|
158
|
+
lib: ['ES2022', 'DOM', 'DOM.Iterable'],
|
|
159
|
+
baseUrl: '.',
|
|
160
|
+
types: ['vitest/globals', 'node'],
|
|
161
|
+
},
|
|
162
|
+
include: ['src/**/*.ts', 'tests/**/*.ts'],
|
|
163
|
+
}, null, 2)}\n`;
|
|
164
|
+
}
|
|
165
|
+
function renderTsconfigBuild() {
|
|
166
|
+
return `${JSON.stringify({
|
|
167
|
+
extends: './tsconfig.json',
|
|
168
|
+
compilerOptions: {
|
|
169
|
+
noEmit: false,
|
|
170
|
+
declaration: true,
|
|
171
|
+
declarationMap: true,
|
|
172
|
+
emitDeclarationOnly: false,
|
|
173
|
+
outDir: 'dist',
|
|
174
|
+
rootDir: 'src',
|
|
175
|
+
},
|
|
176
|
+
include: ['src/**/*.ts'],
|
|
177
|
+
}, null, 2)}\n`;
|
|
178
|
+
}
|
|
179
|
+
function renderIndex(request) {
|
|
180
|
+
return `/**
|
|
181
|
+
* 包根入口。
|
|
182
|
+
*
|
|
183
|
+
* 只从根入口导出稳定 API;内部实现不构成契约。
|
|
184
|
+
* 不要在此处使用 \`export *\` 泄漏内部目录。
|
|
185
|
+
*/
|
|
186
|
+
|
|
187
|
+
/**
|
|
188
|
+
* 包身份。
|
|
189
|
+
*
|
|
190
|
+
* 这是骨架里唯一的真实导出:它让 \`tests/index.spec.ts\` 成为可执行的真实断言
|
|
191
|
+
* (而不是空占位),保证 fresh-create 后 \`package:verify\` 直接通过。
|
|
192
|
+
* 首个业务导出落地后,可以连同 smoke test 一起替换成真正的契约断言。
|
|
193
|
+
*/
|
|
194
|
+
export const packageName = '${request.packageName}'
|
|
195
|
+
`;
|
|
196
|
+
}
|
|
197
|
+
function renderSmokeTest(request) {
|
|
198
|
+
return `import { describe, expect, it } from 'vitest'
|
|
199
|
+
|
|
200
|
+
import { packageName } from '../src/index.js'
|
|
201
|
+
|
|
202
|
+
describe('${request.packageName} skeleton', () => {
|
|
203
|
+
it('exposes its package identity through the root entry', () => {
|
|
204
|
+
expect(packageName).toBe('${request.packageName}')
|
|
205
|
+
})
|
|
206
|
+
})
|
|
207
|
+
`;
|
|
208
|
+
}
|
|
209
|
+
function renderReadme(request) {
|
|
210
|
+
return `# ${request.packageName}
|
|
211
|
+
|
|
212
|
+
${request.description}
|
|
213
|
+
|
|
214
|
+
## 职责
|
|
215
|
+
|
|
216
|
+
(待补充:本包拥有什么、不拥有什么。)
|
|
217
|
+
|
|
218
|
+
## 边界
|
|
219
|
+
|
|
220
|
+
本骨架是标准的纯 TypeScript 私有包:\`src/\` 只通过包根入口导出稳定 API,
|
|
221
|
+
\`tests/\` 由 vitest 执行。它不声称与 \`admin-core\` / \`admin-schema\` /
|
|
222
|
+
\`admin-kit\` 同构——职责、依赖边界与验证项由作者按真实需要补齐。
|
|
223
|
+
|
|
224
|
+
## 验证
|
|
225
|
+
|
|
226
|
+
\`\`\`powershell
|
|
227
|
+
pnpm --dir .\\${ADMIN_WORKSPACE_RELATIVE.replace(/\//g, '\\')}\\${request.directory} run package:verify
|
|
228
|
+
\`\`\`
|
|
229
|
+
`;
|
|
230
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
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, type RunCliOptions } from './cli.js';
|
|
8
|
+
export { defineCommand, dispatch, requireWorkspace, YoyoAdminCliRegistry, createCommandContext, type CommandContext, type DispatchOptions, type DispatchOutcome, type RegisteredCommand, } from './kernel/command.js';
|
|
9
|
+
export { GLOBAL_BOOLEAN_FLAGS, GLOBAL_VALUE_OPTIONS, globalArgumentSpec, hasFlag, mergeArgumentSpecs, optionalOption, parseArguments, requireOption, requirePositional, type ArgumentSpec, type ParsedArguments, } from './kernel/arguments.js';
|
|
10
|
+
export { helpPayload, isHelpPayload, renderCommandHelp, renderRecipeHelp, renderRecipeListHelp, renderRootHelp, type HelpPayload, type HelpRecipeDescriptor, type RecipeHelpSurface, } from './kernel/help.js';
|
|
11
|
+
export { assertConfinedRelativePath, createFile, createFsPlan, isFsPlan, normalizePath, updateFile, type CreateFileChange, type FsChange, type FsPlan, type UpdateFileChange, } from './kernel/fs-plan.js';
|
|
12
|
+
export { applyFsPlan, applyFsPlanTransaction, confineRepoPath, planRunner, preflightFsPlan, readRepoFile, type AppliedFsPlan, type PlanPreflightReport, } from './kernel/plan-runner.js';
|
|
13
|
+
export { LOCKFILE_REFRESH_ARGS, LOCKFILE_RELATIVE, lockfileHasImporter, refreshWorkspaceLockfile, type LockfileRefreshOptions, type LockfileRefreshOutcome, type PnpmRunResult, type PnpmRunner, } from './kernel/lockfile.js';
|
|
14
|
+
export { assertWiringIsSafe, buildManifestsWiring, parseManifestsAnchors, renderWiredManifests, type ManifestsFileAnchors, type ManifestsWiringRequest, } from './kernel/manifests-wiring.js';
|
|
15
|
+
export { finding, failure, hasError, ok, warn, YoyoAdminCliUsageError, type YoyoAdminCliCommandDescriptor, type YoyoAdminCliFinding, type YoyoAdminCliResult, type YoyoAdminCliStatus, } 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, type YoyoAdminWorkspace, } from './kernel/workspace.js';
|
|
17
|
+
export { runDoctor, MINIMUM_NODE_MAJOR } from './commands/doctor.js';
|
|
18
|
+
export { buildCapabilities, CAPABILITIES_SCHEMA_VERSION, type CapabilitiesPayload } from './commands/capabilities.js';
|
|
19
|
+
export { inspectCommand, isInspectPayload, listInspectSubjects, renderInspectPayload, resolveInspectSubject, runInspect, type InspectPayload, type InspectPermissionPayload, type InspectPermissionsPayload, type InspectResourcePayload, type InspectResourcesPayload, type InspectSubject, } from './commands/inspect.js';
|
|
20
|
+
export { CRUD_CATALOG_RELATIVE, loadCrudCatalog, loadCrudResourceDescriptor, loadCrudResourceDescriptors, loadKnownResourceKeys, type CrudCatalogDocument, type CrudFieldMapping, type CrudResourceDescriptor, } from './catalogs/crud.js';
|
|
21
|
+
export { CAPABILITY_CATALOG_RELATIVE, loadCapabilityCatalog, loadKnownPermissionNames, loadPermissionDescriptor, matchPermissionPrefix, type CapabilityCatalogDocument, type CapabilityFeatureDescriptor, type CapabilityPermissionDescriptor, } from './catalogs/capability.js';
|
|
22
|
+
export { deriveStandardCrudCompatibility, deriveStandardCrudShape, RUNTIME_SUPPLIED_FIELDS, type StandardCrudCompatibility, type StandardCrudShape, type StandardCrudShapeField, } from './catalogs/standard-crud.js';
|
|
23
|
+
export { type CatalogSource } from './catalogs/source.js';
|
|
24
|
+
export { planStandardCrudFrontendModule, validateFrontendModuleRequest, STANDARD_CRUD_KIND, type FrontendModulePlan, type FrontendModuleRequest, } from './generators/frontend-module.js';
|
|
25
|
+
export { assertNoPackageConflicts, planAdminPackage, validatePackageRequest, PACKAGE_KIND_TS, type PackagePlan, type PackageRequest, } from './generators/package.js';
|
|
26
|
+
//# sourceMappingURL=index.d.ts.map
|