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

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.2",
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.2"
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,22 @@ 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 shotsHeading = text(source?.shotsTitle) || '长这样';
470
+ const shotLines = shots.length === 0
471
+ ? []
472
+ : [
473
+ `## ${shotsHeading}`,
474
+ '',
475
+ ...shots.flatMap((one) => [
476
+ ...(one.caption === '' ? [] : [`**${one.caption}**`, '']),
477
+ '```',
478
+ one.drawing,
479
+ '```',
480
+ '',
481
+ ]),
482
+ ];
447
483
  const faqLines = faq.length === 0
448
484
  ? []
449
485
  : [
@@ -457,6 +493,7 @@ export function renderUserReadme(source) {
457
493
  '',
458
494
  summary,
459
495
  '',
496
+ ...shotLines,
460
497
  ...sectionOf('你会看到什么', source?.see),
461
498
  ...sectionOf('你能做什么', source?.do),
462
499
  ...sectionOf('agent 能替你做什么', source?.agent),
@@ -499,12 +536,19 @@ export function userSourceSkeleton(manifest, design) {
499
536
  return {
500
537
  title: manifestTitleOf(manifest) || text(design?.name) || '模块',
501
538
  summary: text(design?.oneLine) || text(manifest?.description),
502
- see: '(待填:终端用户会在卡片上看到什么;不要写组件名、图号、像素值)',
503
- do: '(待填:终端用户自己能点什么、能改什么)',
504
- agent: '(待填:agent 能替用户做什么;哪些动作会先弹审批)',
505
- data: '(待填:数据从哪个系统来、用谁的登录、会不会写数据)',
539
+ shotsTitle: '',
540
+ shots: [
541
+ {
542
+ caption: '(可选:图注,比如「卡片」「设置面」「审批」)',
543
+ ascii: '(待填:一张卡片布局草图,用界面真实文案,按屏幕从上到下的顺序画)',
544
+ },
545
+ ],
546
+ see: ['(待填:用户在卡片上看到的第一样东西)'],
547
+ do: ['(待填:用户自己能点的一个按钮或能改的一项设置)'],
548
+ agent: ['(待填:agent 能替用户做的一件事;写之前要不要批准)'],
549
+ data: ['(待填:数据从哪来、用谁的登录、读还是写)'],
506
550
  install: '',
507
- trouble: '(待填:没登录、超时、读不到时用户会看到什么、下一步点哪)',
551
+ trouble: ['(待填:出问题时用户看到什么、下一步点哪)'],
508
552
  faq: [],
509
553
  };
510
554
  }
@@ -523,18 +567,37 @@ const USER_COPY_FORBIDDEN = [
523
567
  ['平台内部字段', /\bglobal_key\b/],
524
568
  ];
525
569
 
570
+ const BULLET_LIMIT = 70;
571
+
572
+ function copyTextsOf(value) {
573
+ if (Array.isArray(value)) return value.map(text).filter((one) => one !== '');
574
+ const one = text(value);
575
+ return one === '' ? [] : [one];
576
+ }
577
+
526
578
  export function userCopyProblemsOf(source) {
527
579
  const problems = [];
528
580
  if (source === undefined) return [`没有 ${README_SOURCE_FILE} —— 先跑 dsh-wb-module design init 或 design migrate`];
529
581
  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} 还留着「(待填)」`);
582
+ const values = copyTextsOf(source?.[field]);
583
+ if (values.length === 0) problems.push(`${field} 是空的 —— README 的「${field}」还没有用户文案`);
584
+ for (const value of values) {
585
+ if (value.includes('(待填')) problems.push(`${field} 还留着「(待填)」`);
586
+ if (Array.isArray(source?.[field]) && value.length > BULLET_LIMIT) {
587
+ problems.push(`${field} 有一条 ${String(value.length)} 字 —— 超过 ${String(BULLET_LIMIT)} 字就拆成两条,或者把细节放进图里`);
588
+ }
589
+ }
590
+ }
591
+ const shots = asRows(source?.shots);
592
+ if (shots.length === 0) problems.push('shots 是空的 —— 至少画一张卡片布局图');
593
+ for (const [index, one] of shots.entries()) {
594
+ if (text(one?.ascii).includes('(待填')) problems.push(`shots[${index}] 还留着「(待填)」`);
533
595
  }
534
596
  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} —— 这段只给终端用户看`);
597
+ for (const value of copyTextsOf(source?.[field])) {
598
+ for (const [label, pattern] of USER_COPY_FORBIDDEN) {
599
+ if (pattern.test(value)) problems.push(`${field} 里有${label} —— 这段只给终端用户看`);
600
+ }
538
601
  }
539
602
  }
540
603
  for (const [index, one] of asRows(source?.faq).entries()) {
@@ -1,11 +1,28 @@
1
1
  {
2
2
  "title": "__UNIT__ 模块",
3
3
  "summary": "__DESC__",
4
- "see": "(待填:用户在卡片上会看到什么。写画面和数字,不写组件名、图号、像素值)",
5
- "do": "(待填:用户自己能点什么、能改什么;没有就写「只能看,不能改」)",
6
- "agent": "(待填:agent 能替用户做什么;哪些动作会先弹审批。没有就写「这个模块没有给 agent 开放工具」)",
7
- "data": "(待填:数据从哪个系统来、用谁的登录、会不会写数据。没有数据源就写「没有数据源」)",
4
+ "shotsTitle": "",
5
+ "shots": [
6
+ {
7
+ "caption": "卡片",
8
+ "ascii": "(待填:一张卡片布局草图,用界面真实文案,按屏幕从上到下的顺序画)"
9
+ }
10
+ ],
11
+ "see": [
12
+ "(待填:用户在卡片上看到的第一样东西)"
13
+ ],
14
+ "do": [
15
+ "(待填:用户自己能点的一个按钮或能改的一项设置)"
16
+ ],
17
+ "agent": [
18
+ "(待填:agent 能替用户做的一件事;写之前要不要批准)"
19
+ ],
20
+ "data": [
21
+ "(待填:数据从哪来、用谁的登录、读还是写)"
22
+ ],
8
23
  "install": "",
9
- "trouble": "(待填:没登录、超时、读不到时用户会看到什么,下一步点哪)",
24
+ "trouble": [
25
+ "(待填:出问题时用户看到什么、下一步点哪)"
26
+ ],
10
27
  "faq": []
11
28
  }