@dsh-workbench/module-tools 1.2.8-beta.0 → 1.2.8-beta.1

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/README.md CHANGED
@@ -196,7 +196,7 @@ export default [
196
196
  `dsh-wb-module lint --fix` 先写回再跑一遍看还剩什么。**`check` 永远不动手改文件** ——
197
197
  门禁是判据,不是修复器。
198
198
 
199
- 模板生成三份文档:`readme.json` 是终端用户文案源(标题、简介、会看到什么、能做什么、agent 能做什么、数据从哪来、排障、FAQ),`README.md` 只由它生成;`DEVELOPING.md` 是作者文档(实现、路由、判据、数据源与三张表),由 `design.json` 生成。`design render` 刷新这两份;`design check` 会拦住空文案、占位符和技术词;旧模块先跑 `design migrate`,它把旧 `README.md` 原样保存为 `DEVELOPING.md`,再生成 `readme.json` 与用户 README。
199
+ 模板生成三份文档:`readme.json` 是终端用户文案源(标题、简介、卡片布局草图 `shots[]`、会看到什么、能做什么、agent 能做什么、数据从哪来、排障、FAQ,后五段是短条目),`README.md` 只由它生成;`DEVELOPING.md` 是作者文档(实现、路由、判据、数据源与三张表),由 `design.json` 生成。`design render` 刷新这两份;`design check` 会拦住空文案、占位符和技术词;旧模块先跑 `design migrate`,它把旧 `README.md` 原样保存为 `DEVELOPING.md`,再生成 `readme.json` 与用户 README。
200
200
 
201
201
  ## `design.json`:三张表
202
202
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/module-tools",
3
- "version": "1.2.8-beta.0",
3
+ "version": "1.2.8-beta.1",
4
4
  "description": "模块包的构建与门禁工具:build(单文件 ESM 宿主侧 + 套 loader 壳的浏览器侧)、check(契约/产物边界/源码边界/卡片渲染)。模块包不再各带一份构建脚本。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -33,7 +33,7 @@
33
33
  "semver": "^7.8.5",
34
34
  "typescript": "5.9.3",
35
35
  "typescript-eslint": "^8.71.0",
36
- "@dsh-workbench/shared": "1.2.8-beta.0"
36
+ "@dsh-workbench/shared": "1.2.8-beta.1"
37
37
  },
38
38
  "repository": {
39
39
  "type": "git",
package/src/design.mjs CHANGED
@@ -432,9 +432,30 @@ export function renderDesign(design) {
432
432
  ].join('\n');
433
433
  }
434
434
 
435
- function sectionOf(heading, body) {
436
- const text0 = text(body);
437
- return text0 === '' ? [] : [`## ${heading}`, '', text0, ''];
435
+ function bodyLinesOf(value) {
436
+ if (Array.isArray(value)) {
437
+ return value.flatMap((one) => {
438
+ const line = text(one);
439
+ return line === '' ? [] : [`- ${line}`];
440
+ });
441
+ }
442
+ const line = text(value);
443
+ return line === '' ? [] : [line];
444
+ }
445
+
446
+ function sectionOf(heading, value) {
447
+ const lines = bodyLinesOf(value);
448
+ return lines.length === 0 ? [] : [`## ${heading}`, '', ...lines, ''];
449
+ }
450
+
451
+ function shotsOf(source) {
452
+ const listed = Array.isArray(source?.shots) ? source.shots : [];
453
+ const single = text(source?.ascii) === '' ? [] : [{ caption: '', ascii: source.ascii }];
454
+ return [...single, ...listed].flatMap((one) => {
455
+ const drawing = typeof one?.ascii === 'string' ? one.ascii.replace(/\s+$/, '') : '';
456
+ if (drawing.trim() === '') return [];
457
+ return [{ caption: text(one?.caption), drawing }];
458
+ });
438
459
  }
439
460
 
440
461
  export function renderUserReadme(source) {
@@ -443,7 +464,21 @@ export function renderUserReadme(source) {
443
464
  const faq = asRows(source?.faq);
444
465
  const install = text(source?.install) === ''
445
466
  ? '在市场「已安装」里装或卸。装完在当前项目的面板里勾选这个模块,卡片才会出现;卸掉后卡片消失。'
446
- : text(source?.install);
467
+ : source?.install;
468
+ const shots = shotsOf(source);
469
+ const shotLines = shots.length === 0
470
+ ? []
471
+ : [
472
+ '## 卡片长这样',
473
+ '',
474
+ ...shots.flatMap((one) => [
475
+ ...(one.caption === '' ? [] : [`**${one.caption}**`, '']),
476
+ '```text',
477
+ one.drawing,
478
+ '```',
479
+ '',
480
+ ]),
481
+ ];
447
482
  const faqLines = faq.length === 0
448
483
  ? []
449
484
  : [
@@ -457,6 +492,7 @@ export function renderUserReadme(source) {
457
492
  '',
458
493
  summary,
459
494
  '',
495
+ ...shotLines,
460
496
  ...sectionOf('你会看到什么', source?.see),
461
497
  ...sectionOf('你能做什么', source?.do),
462
498
  ...sectionOf('agent 能替你做什么', source?.agent),
@@ -499,12 +535,18 @@ export function userSourceSkeleton(manifest, design) {
499
535
  return {
500
536
  title: manifestTitleOf(manifest) || text(design?.name) || '模块',
501
537
  summary: text(design?.oneLine) || text(manifest?.description),
502
- see: '(待填:终端用户会在卡片上看到什么;不要写组件名、图号、像素值)',
503
- do: '(待填:终端用户自己能点什么、能改什么)',
504
- agent: '(待填:agent 能替用户做什么;哪些动作会先弹审批)',
505
- data: '(待填:数据从哪个系统来、用谁的登录、会不会写数据)',
538
+ shots: [
539
+ {
540
+ caption: '(可选:图注,比如「卡片」「设置面」「审批」)',
541
+ ascii: '(待填:一张卡片布局草图,用界面真实文案,按屏幕从上到下的顺序画)',
542
+ },
543
+ ],
544
+ see: ['(待填:用户在卡片上看到的第一样东西)'],
545
+ do: ['(待填:用户自己能点的一个按钮或能改的一项设置)'],
546
+ agent: ['(待填:agent 能替用户做的一件事;写之前要不要批准)'],
547
+ data: ['(待填:数据从哪来、用谁的登录、读还是写)'],
506
548
  install: '',
507
- trouble: '(待填:没登录、超时、读不到时用户会看到什么、下一步点哪)',
549
+ trouble: ['(待填:出问题时用户看到什么、下一步点哪)'],
508
550
  faq: [],
509
551
  };
510
552
  }
@@ -523,18 +565,37 @@ const USER_COPY_FORBIDDEN = [
523
565
  ['平台内部字段', /\bglobal_key\b/],
524
566
  ];
525
567
 
568
+ const BULLET_LIMIT = 70;
569
+
570
+ function copyTextsOf(value) {
571
+ if (Array.isArray(value)) return value.map(text).filter((one) => one !== '');
572
+ const one = text(value);
573
+ return one === '' ? [] : [one];
574
+ }
575
+
526
576
  export function userCopyProblemsOf(source) {
527
577
  const problems = [];
528
578
  if (source === undefined) return [`没有 ${README_SOURCE_FILE} —— 先跑 dsh-wb-module design init 或 design migrate`];
529
579
  for (const field of USER_COPY_FIELDS) {
530
- const value = text(source?.[field]);
531
- if (value === '') problems.push(`${field} 是空的 —— README 的「${field}」还没有用户文案`);
532
- else if (value.includes('(待填')) problems.push(`${field} 还留着「(待填)」`);
580
+ const values = copyTextsOf(source?.[field]);
581
+ if (values.length === 0) problems.push(`${field} 是空的 —— README 的「${field}」还没有用户文案`);
582
+ for (const value of values) {
583
+ if (value.includes('(待填')) problems.push(`${field} 还留着「(待填)」`);
584
+ if (Array.isArray(source?.[field]) && value.length > BULLET_LIMIT) {
585
+ problems.push(`${field} 有一条 ${String(value.length)} 字 —— 超过 ${String(BULLET_LIMIT)} 字就拆成两条,或者把细节放进图里`);
586
+ }
587
+ }
588
+ }
589
+ const shots = asRows(source?.shots);
590
+ if (shots.length === 0) problems.push('shots 是空的 —— 至少画一张卡片布局图');
591
+ for (const [index, one] of shots.entries()) {
592
+ if (text(one?.ascii).includes('(待填')) problems.push(`shots[${index}] 还留着「(待填)」`);
533
593
  }
534
594
  for (const field of ['title', 'summary', 'see', 'do', 'agent', 'data', 'install', 'trouble']) {
535
- const value = text(source?.[field]);
536
- for (const [label, pattern] of USER_COPY_FORBIDDEN) {
537
- if (pattern.test(value)) problems.push(`${field} 里有${label} —— 这段只给终端用户看`);
595
+ for (const value of copyTextsOf(source?.[field])) {
596
+ for (const [label, pattern] of USER_COPY_FORBIDDEN) {
597
+ if (pattern.test(value)) problems.push(`${field} 里有${label} —— 这段只给终端用户看`);
598
+ }
538
599
  }
539
600
  }
540
601
  for (const [index, one] of asRows(source?.faq).entries()) {
@@ -1,11 +1,27 @@
1
1
  {
2
2
  "title": "__UNIT__ 模块",
3
3
  "summary": "__DESC__",
4
- "see": "(待填:用户在卡片上会看到什么。写画面和数字,不写组件名、图号、像素值)",
5
- "do": "(待填:用户自己能点什么、能改什么;没有就写「只能看,不能改」)",
6
- "agent": "(待填:agent 能替用户做什么;哪些动作会先弹审批。没有就写「这个模块没有给 agent 开放工具」)",
7
- "data": "(待填:数据从哪个系统来、用谁的登录、会不会写数据。没有数据源就写「没有数据源」)",
4
+ "shots": [
5
+ {
6
+ "caption": "卡片",
7
+ "ascii": "(待填:一张卡片布局草图,用界面真实文案,按屏幕从上到下的顺序画)"
8
+ }
9
+ ],
10
+ "see": [
11
+ "(待填:用户在卡片上看到的第一样东西)"
12
+ ],
13
+ "do": [
14
+ "(待填:用户自己能点的一个按钮或能改的一项设置)"
15
+ ],
16
+ "agent": [
17
+ "(待填:agent 能替用户做的一件事;写之前要不要批准)"
18
+ ],
19
+ "data": [
20
+ "(待填:数据从哪来、用谁的登录、读还是写)"
21
+ ],
8
22
  "install": "",
9
- "trouble": "(待填:没登录、超时、读不到时用户会看到什么,下一步点哪)",
23
+ "trouble": [
24
+ "(待填:出问题时用户看到什么、下一步点哪)"
25
+ ],
10
26
  "faq": []
11
27
  }