@dsh-workbench/module-tools 1.1.18 → 1.1.19-beta.0
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 +4 -2
- package/bin/dsh-wb-module.mjs +6 -3
- package/package.json +2 -2
- package/src/arch.mjs +1 -3
- package/src/build.mjs +4 -3
- package/src/checks/contracts.mjs +4 -4
- package/src/checks/style.mjs +4 -2
- package/src/faces.mjs +7 -0
- package/src/font-scale.mjs +25 -6
- package/src/market-index.mjs +29 -2
- package/src/migrate.mjs +3 -3
- package/src/release-target.mjs +2 -1
- package/src/release.mjs +28 -6
- package/template-diy/AGENTS.md.tpl +1 -1
- package/template-diy/README.md.tpl +2 -2
- package/template-diy/scripts/smoke.assert.mjs.tpl +1 -1
- package/template-diy/scripts/smoke.mjs.tpl +1 -1
- package/template-diy/src/components/SetupFlow.tsx.tpl +71 -0
- package/template-diy/src/messages.ts.tpl +20 -0
package/README.md
CHANGED
|
@@ -31,8 +31,10 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
31
31
|
`package.json` 缺 `files` 里的 `CHANGELOG.md` 一样拦下;`author` 没写时从本机 `git config` 补进 manifest,索引与产物都带上。
|
|
32
32
|
标签只住在市集条目上(`metadata.labels`),**不写进 `package.json`**:清单的键是协议定死的
|
|
33
33
|
(`apiVersion` / `unit` / `title` / `card` / `tools` / `host` / `needs`),往里塞私货 check 会判红。
|
|
34
|
-
|
|
34
|
+
标签可选:`--label <短词>`(可重复,最多两个)并进索引条目的 `metadata.labels`;
|
|
35
35
|
不传就沿用老清单里已经写过的(`dsh.workbench.market.labels`,只在读那一侧认)。
|
|
36
|
+
分类 `--category <短词>` 是市场发布的要求:条目还没有分类时必须给一个(工作台的发布审批会拦),
|
|
37
|
+
已有分类可以不带;它写进 `metadata.category`,发现页的分类 chip 就是按它汇总的,新模块带一个新分类,页面自动多一个。
|
|
36
38
|
标题 / 署名 / 说明仍保持索引里现有的那份不动。
|
|
37
39
|
要换署名时带 `--author "姓名 <邮箱>"`(或只给姓名):它写回 `package.json`,索引、产物与提交都用它,
|
|
38
40
|
`--refresh` 时也一样。**发布本身不改市集里的标题 / 署名 / 说明**:这些元数据只由 `--refresh`(或显式 `--author`)写,
|
|
@@ -57,7 +59,7 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
57
59
|
| `dsh-wb-module migrate [--dry-run]` | 把模块清单迁到当前代:`apiVersion` → 3、缺/空的 `workbenchFaces` 补默认;源码类问题只给提示(check 会点名) |
|
|
58
60
|
| `dsh-wb-module changelog check [版本]` | 查这一版有没有记录;发布时用 `release --changelog-note` 写进去 |
|
|
59
61
|
| `dsh-wb-module publish` | 发货前清单:门禁 + `files` + `npm pack`(只 dry run) |
|
|
60
|
-
| `dsh-wb-module release` | 真发布:team / public / tarball / git;全门禁 + 验收闸 + 预检接管 + 发后核包 + 更新市场索引(`--dry-run` / `--verify`) |
|
|
62
|
+
| `dsh-wb-module release` | 真发布:team / public / tarball / git / market / local;全门禁 + 验收闸 + 预检接管 + 发后核包 + 更新市场索引(`--dry-run` / `--verify`) |
|
|
61
63
|
|
|
62
64
|
`--cwd <目录>` 指定**工作目录**,默认当前目录。注意判据仍按**工作区**发现模块:往上找
|
|
63
65
|
`pnpm-workspace.yaml`(或 `package.json` 的 `workspaces`),按它声明的 glob 展开包目录
|
package/bin/dsh-wb-module.mjs
CHANGED
|
@@ -2,6 +2,8 @@
|
|
|
2
2
|
import { existsSync } from 'node:fs';
|
|
3
3
|
import { join, resolve } from 'node:path';
|
|
4
4
|
import { Command } from 'commander';
|
|
5
|
+
import { WORKBENCH_API_VERSION } from '@dsh-workbench/shared/host';
|
|
6
|
+
import { RELEASE_TARGETS } from '@dsh-workbench/shared/contract/protocol/release';
|
|
5
7
|
import { build } from '../src/build.mjs';
|
|
6
8
|
import { CHECKS, check } from '../src/check.mjs';
|
|
7
9
|
import { changelog } from '../src/changelog.mjs';
|
|
@@ -63,17 +65,18 @@ const MIGRATE_HELP = `
|
|
|
63
65
|
用法:
|
|
64
66
|
migrate [--cwd <模块目录>] [--dry-run]
|
|
65
67
|
|
|
66
|
-
把模块清单迁到当前代:apiVersion →
|
|
68
|
+
把模块清单迁到当前代:apiVersion → ${WORKBENCH_API_VERSION}、缺/空的 workbenchFaces 补默认入口。
|
|
67
69
|
源码类问题(kit 成员移除、裸控件、card: null 对账)不自动改 —— check 会逐条点名。`;
|
|
68
70
|
|
|
69
71
|
const RELEASE_HELP = `
|
|
70
72
|
选项:
|
|
71
|
-
--target
|
|
73
|
+
--target ${RELEASE_TARGETS.join('|')} 出口;省略按配置默认(可写索引优先 market)
|
|
72
74
|
--version patch|minor|major|x.y.z 版本;省略时已存在就自动 patch
|
|
73
75
|
--registry <url> --access restricted|public --npmrc <文件>
|
|
74
76
|
--license <id> --out <目录> --git <remote>
|
|
75
77
|
--author <署名> 换署名(姓名,或 姓名 <邮箱>);写回 package.json
|
|
76
|
-
--label <短词> 给市集条目打标签(1–2
|
|
78
|
+
--label <短词> 给市集条目打标签(1–2 个,可重复);只进索引条目
|
|
79
|
+
--category <短词> 给市集条目定分类(一个短词);只进索引条目,不传沿用已有的
|
|
77
80
|
--changelog-note <文字> --changelog-type added|changed|fixed|removed|security|deprecated
|
|
78
81
|
这一版还没有更新日志时写进 CHANGELOG.md(文件不在先建)
|
|
79
82
|
--index auto|none|<目录|git 地址> 发布后要更新的索引源
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/module-tools",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.19-beta.0",
|
|
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.1.
|
|
36
|
+
"@dsh-workbench/shared": "1.1.19-beta.0"
|
|
37
37
|
},
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
package/src/arch.mjs
CHANGED
|
@@ -13,9 +13,7 @@ import {
|
|
|
13
13
|
import { createRule } from '@arch-guard/core';
|
|
14
14
|
import { DECLARED_TOOLS_FILE } from './declared-tools.mjs';
|
|
15
15
|
import { SEED_SPECIFIERS } from './seed.mjs';
|
|
16
|
-
|
|
17
|
-
const HOST_FACE = 'src/host.ts';
|
|
18
|
-
const CLIENT_FACE = 'src/client.tsx';
|
|
16
|
+
import { CLIENT_FACE, HOST_FACE } from './faces.mjs';
|
|
19
17
|
|
|
20
18
|
const LAYERS = { model: 1, components: 2, host: 10 };
|
|
21
19
|
|
package/src/build.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { isAbsolute, join, relative } from 'node:path';
|
|
|
5
5
|
import { build as esbuild } from 'esbuild';
|
|
6
6
|
import { SEED_SPECIFIERS } from './seed.mjs';
|
|
7
7
|
import { cssInjectionCode } from './css-injection.mjs';
|
|
8
|
+
import { CLIENT_ENTRY_CANDIDATES, HOST_ENTRY_CANDIDATES } from './faces.mjs';
|
|
8
9
|
|
|
9
10
|
const SHELL_CLIENT = '@dsh-workbench/core/client';
|
|
10
11
|
const RESOLVE_EXTENSIONS = ['.ts', '.tsx', '.js', '.json'];
|
|
@@ -113,7 +114,7 @@ export async function build({ root }) {
|
|
|
113
114
|
const plugins = [aliasResolver({ root, unit })];
|
|
114
115
|
const nodeBuiltins = [...builtinModules, ...builtinModules.map((name) => `node:${name}`)];
|
|
115
116
|
|
|
116
|
-
const hostEntry = pickEntry(root,
|
|
117
|
+
const hostEntry = pickEntry(root, HOST_ENTRY_CANDIDATES);
|
|
117
118
|
if (hostEntry !== undefined) {
|
|
118
119
|
await esbuild({
|
|
119
120
|
absWorkingDir: root,
|
|
@@ -130,8 +131,8 @@ export async function build({ root }) {
|
|
|
130
131
|
});
|
|
131
132
|
}
|
|
132
133
|
|
|
133
|
-
const clientEntry = pickEntry(root,
|
|
134
|
-
if (clientEntry === undefined) throw new Error('
|
|
134
|
+
const clientEntry = pickEntry(root, CLIENT_ENTRY_CANDIDATES);
|
|
135
|
+
if (clientEntry === undefined) throw new Error(`找不到浏览器侧入口(${CLIENT_ENTRY_CANDIDATES.join(' 或 ')})`);
|
|
135
136
|
const client = await esbuild({
|
|
136
137
|
absWorkingDir: root,
|
|
137
138
|
entryPoints: [clientEntry],
|
package/src/checks/contracts.mjs
CHANGED
|
@@ -4,6 +4,8 @@ import { homedir } from 'node:os';
|
|
|
4
4
|
import { join, relative } from 'node:path';
|
|
5
5
|
import { fileURLToPath, pathToFileURL } from 'node:url';
|
|
6
6
|
import {
|
|
7
|
+
OPENCLI_CATALOG_ENV,
|
|
8
|
+
OPENCLI_NPM_PACKAGE,
|
|
7
9
|
buildSupplyIndex,
|
|
8
10
|
createHostKit,
|
|
9
11
|
hostSurface,
|
|
@@ -18,6 +20,7 @@ import {
|
|
|
18
20
|
} from '../declared-tools.mjs';
|
|
19
21
|
import { discoverPackages, workspaceRootOf } from '../discover.mjs';
|
|
20
22
|
import { readDesign } from '../design.mjs';
|
|
23
|
+
import { DEFAULT_FACES } from '../faces.mjs';
|
|
21
24
|
import { datasourceProblemOf } from '../datasource-file.mjs';
|
|
22
25
|
import { SEED_SHORT } from '../seed.mjs';
|
|
23
26
|
|
|
@@ -65,9 +68,6 @@ async function unitFaceOf(dir) {
|
|
|
65
68
|
}
|
|
66
69
|
}
|
|
67
70
|
|
|
68
|
-
const OPENCLI_CATALOG_ENV = 'DSH_WB_OPENCLI_CATALOG';
|
|
69
|
-
const OPENCLI_NPM_PACKAGE = '@jackwener/opencli';
|
|
70
|
-
|
|
71
71
|
function workbenchRootOf() {
|
|
72
72
|
const configured = process.env.DSH_WORKBENCH_HOME?.trim();
|
|
73
73
|
if (configured !== undefined && configured !== '') return configured;
|
|
@@ -540,7 +540,7 @@ export async function run() {
|
|
|
540
540
|
problems.push(
|
|
541
541
|
`${name}: dsh.workbenchFaces.${face} 没声明 —— 这两个源码入口是工具链字段` +
|
|
542
542
|
`(不在 dsh.workbench 协议里):source 判据靠它们判"哪一半",缺了它判不了` +
|
|
543
|
-
`(\`dsh-wb-module migrate\` 会补默认的 ${face
|
|
543
|
+
`(\`dsh-wb-module migrate\` 会补默认的 ${DEFAULT_FACES[face]})`,
|
|
544
544
|
);
|
|
545
545
|
} else if (!existsSync(join(pkg.dir, rel))) {
|
|
546
546
|
problems.push(`${name}: dsh.workbenchFaces.${face} 指的文件不存在 —— ${rel}`);
|
package/src/checks/style.mjs
CHANGED
|
@@ -289,6 +289,7 @@ export async function run() {
|
|
|
289
289
|
const anchorHits = new Set();
|
|
290
290
|
const modules = discoverModulePackages({ root: process.cwd() });
|
|
291
291
|
const contracts = new Map(modules.map((pkg) => [pkg.dir, contractOf(pkg.dir)]));
|
|
292
|
+
let dshTokens = null;
|
|
292
293
|
if (modules.length > 0) {
|
|
293
294
|
const searched = [
|
|
294
295
|
process.cwd(),
|
|
@@ -297,6 +298,7 @@ export async function run() {
|
|
|
297
298
|
join(dshHome(), 'workbench', 'diy'),
|
|
298
299
|
];
|
|
299
300
|
const tokens = readTokens(searched);
|
|
301
|
+
dshTokens = tokens.dsw;
|
|
300
302
|
const dswNames = (tokens.dsw?.entries ?? []).map((one) => one.name);
|
|
301
303
|
const wbNames = (tokens.wb?.entries ?? []).map((one) => one.name);
|
|
302
304
|
const known = new Set([...dswNames, ...wbNames]);
|
|
@@ -675,11 +677,11 @@ export async function run() {
|
|
|
675
677
|
? '没有手写的整张表单(没有裸 <form>,也没有借表单槽类手摆行)'
|
|
676
678
|
: `表单这一层还有 ${formHits.size} 个文件在 FORM_EXEMPT 里等外壳发版后迁(棘轮:迁一处删一行)`;
|
|
677
679
|
|
|
678
|
-
const fontScale = fontScaleOf(SHELL_FONT_SCALE_FILES);
|
|
680
|
+
const fontScale = fontScaleOf(SHELL_FONT_SCALE_FILES, dshTokens);
|
|
679
681
|
const fontNotes = fontScaleNotes(
|
|
680
682
|
auditFontSizes({ roots: fontRootsIn(modules), scale: fontScale }),
|
|
681
683
|
fontScale,
|
|
682
|
-
{ doc: FONT_DOC },
|
|
684
|
+
{ doc: FONT_DOC, dshMissing: dshTokens === null },
|
|
683
685
|
);
|
|
684
686
|
return {
|
|
685
687
|
status: 'pass',
|
package/src/faces.mjs
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export const HOST_FACE = 'src/host.ts';
|
|
2
|
+
export const CLIENT_FACE = 'src/client.tsx';
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_FACES = { host: HOST_FACE, client: CLIENT_FACE };
|
|
5
|
+
|
|
6
|
+
export const HOST_ENTRY_CANDIDATES = ['src/plugin.host.ts', HOST_FACE];
|
|
7
|
+
export const CLIENT_ENTRY_CANDIDATES = ['src/plugin.client.ts', CLIENT_FACE];
|
package/src/font-scale.mjs
CHANGED
|
@@ -1,8 +1,6 @@
|
|
|
1
1
|
import { existsSync, readFileSync, readdirSync, statSync } from 'node:fs';
|
|
2
2
|
import { join, relative } from 'node:path';
|
|
3
3
|
|
|
4
|
-
export const DSH_ALIAS_FONT_SIZES = [11, 12, 13, 14, 16, 20, 24];
|
|
5
|
-
|
|
6
4
|
const SHELL_FONT_TOKEN = /--wb-font-[a-z0-9-]+\s*:\s*([0-9]+(?:\.[0-9]+)?)px/g;
|
|
7
5
|
|
|
8
6
|
export function shellFontScaleIn(files) {
|
|
@@ -16,8 +14,26 @@ export function shellFontScaleIn(files) {
|
|
|
16
14
|
return sizes;
|
|
17
15
|
}
|
|
18
16
|
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
const TOKEN_FONT_SIZE = /([0-9]*\.?[0-9]+)(px|rem)/g;
|
|
18
|
+
|
|
19
|
+
export function tokenFontSizesIn(source) {
|
|
20
|
+
const sizes = new Set();
|
|
21
|
+
if (source === null || source === undefined) return sizes;
|
|
22
|
+
for (const entry of source.entries ?? []) {
|
|
23
|
+
if (!entry.name.includes('font-size')) continue;
|
|
24
|
+
for (const value of [entry.values?.light, entry.values?.dark]) {
|
|
25
|
+
if (typeof value !== 'string') continue;
|
|
26
|
+
for (const hit of value.matchAll(TOKEN_FONT_SIZE)) {
|
|
27
|
+
const number = Number(hit[1]);
|
|
28
|
+
sizes.add(hit[2] === 'rem' ? number * 16 : number);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
return sizes;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function fontScaleOf(shellTokenFiles = [], dshTokens = null) {
|
|
36
|
+
return new Set([...shellFontScaleIn(shellTokenFiles), ...tokenFontSizesIn(dshTokens)]);
|
|
21
37
|
}
|
|
22
38
|
|
|
23
39
|
const CSS_FONT_SIZE = /(?:^|[;{\s])font-size\s*:\s*([^;{}]+)/g;
|
|
@@ -131,10 +147,13 @@ function detailOf(hit, verdict) {
|
|
|
131
147
|
: `· ${head} —— ${formatSize(hit.px)} 在刻度上,可收进令牌(但不是错)`;
|
|
132
148
|
}
|
|
133
149
|
|
|
134
|
-
export function fontScaleNotes(report, scale, { doc }) {
|
|
150
|
+
export function fontScaleNotes(report, scale, { doc, dshMissing = false }) {
|
|
135
151
|
const sizes = [...scale].sort((a, b) => a - b).map(formatSize).join(' / ');
|
|
152
|
+
const source = dshMissing
|
|
153
|
+
? '外壳 --wb-font-* 的定义(dsh 主题没读到,--dsw-font-* 未纳入)'
|
|
154
|
+
: '外壳 --wb-font-* 与 dsh --dsw-font-* 的定义';
|
|
136
155
|
const head =
|
|
137
|
-
`字号口径(黄,不判红):刻度 ${sizes}(从
|
|
156
|
+
`字号口径(黄,不判红):刻度 ${sizes}(从 ${source}读出来,` +
|
|
138
157
|
`出处见 ${doc});引用令牌 ${report.refs.length} 处 / ${report.refFiles.size} 文件(合规,不列);` +
|
|
139
158
|
`刻度外字面值 ${report.offScale.length} 处 / ${report.litFiles.size} 文件;` +
|
|
140
159
|
`刻度上字面值 ${report.onScale.length} 处;相对单位(em/%)判不了 ${report.relative.length} 处`;
|
package/src/market-index.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { cpSync, copyFileSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { dirname, join, resolve } from 'node:path';
|
|
3
3
|
import { isDeepStrictEqual } from 'node:util';
|
|
4
|
+
import { MARKET_INDEX_DIR, MARKET_INDEX_PATH } from '@dsh-workbench/shared/contract/protocol/market-index';
|
|
4
5
|
|
|
5
6
|
import {
|
|
6
7
|
MARKET_ENTRIES_README_LINES,
|
|
@@ -18,8 +19,8 @@ import {
|
|
|
18
19
|
sortMarketVersions,
|
|
19
20
|
} from '@dsh-workbench/shared/host';
|
|
20
21
|
|
|
21
|
-
const MARKET_DIR =
|
|
22
|
-
const LEGACY_FILE =
|
|
22
|
+
const MARKET_DIR = MARKET_INDEX_DIR;
|
|
23
|
+
const LEGACY_FILE = MARKET_INDEX_PATH;
|
|
23
24
|
const SAFE_PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
24
25
|
const WORKSPACE_PREFIX = 'workspace:';
|
|
25
26
|
const ARTIFACT_DIRS = ['dist'];
|
|
@@ -321,6 +322,19 @@ export function mergeEntryLabels(entry, labels) {
|
|
|
321
322
|
return { ...entry, metadata: { ...(entry.metadata ?? {}), labels: wanted } };
|
|
322
323
|
}
|
|
323
324
|
|
|
325
|
+
export function categoryOfManifest(manifest) {
|
|
326
|
+
const dsh = recordOf(recordOf(manifest).dsh);
|
|
327
|
+
const workbench = recordOf(dsh.workbench);
|
|
328
|
+
const category = recordOf(workbench.market).category;
|
|
329
|
+
return typeof category === 'string' ? category.trim() : '';
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
export function mergeEntryCategory(entry, category) {
|
|
333
|
+
if (typeof category !== 'string' || category === '') return entry;
|
|
334
|
+
if (entry.metadata?.category === category) return entry;
|
|
335
|
+
return { ...entry, metadata: { ...(entry.metadata ?? {}), category } };
|
|
336
|
+
}
|
|
337
|
+
|
|
324
338
|
export function entryMetaOf(manifest) {
|
|
325
339
|
const author = authorOf(manifest);
|
|
326
340
|
const homepage = typeof manifest.homepage === 'string' ? manifest.homepage.trim() : '';
|
|
@@ -741,6 +755,7 @@ export function updateMarketIndex(options) {
|
|
|
741
755
|
}
|
|
742
756
|
}
|
|
743
757
|
next = mergeEntryLabels(next, options.labels);
|
|
758
|
+
next = mergeEntryCategory(next, options.category);
|
|
744
759
|
writeEntryFile(workdir, dir, next);
|
|
745
760
|
indexSha = commitOf(exec, workdir, `chore(market): ${entryName}`, identityOf(manifest));
|
|
746
761
|
report({ phase: 'index-committed', indexCommit: indexSha });
|
|
@@ -815,6 +830,18 @@ export function refreshMarketEntry(options) {
|
|
|
815
830
|
}
|
|
816
831
|
const latest = existing.versions[0]?.version ?? '';
|
|
817
832
|
const meta = entryMetaOf(manifest);
|
|
833
|
+
const keptLabels = Array.isArray(options.labels) && options.labels.length > 0
|
|
834
|
+
? [...options.labels]
|
|
835
|
+
: (existing.metadata?.labels ?? []);
|
|
836
|
+
const keptCategory = typeof options.category === 'string' && options.category !== ''
|
|
837
|
+
? options.category
|
|
838
|
+
: (existing.metadata?.category ?? '');
|
|
839
|
+
const metadata = { ...(meta.metadata ?? {}) };
|
|
840
|
+
if (keptLabels.length === 0) delete metadata.labels;
|
|
841
|
+
else metadata.labels = keptLabels;
|
|
842
|
+
if (keptCategory === '') delete metadata.category;
|
|
843
|
+
else metadata.category = keptCategory;
|
|
844
|
+
meta.metadata = metadata;
|
|
818
845
|
const next = { ...existing };
|
|
819
846
|
for (const key of ['title', 'description', 'author', 'homepage', 'metadata']) {
|
|
820
847
|
if (Object.hasOwn(meta, key)) next[key] = meta[key];
|
package/src/migrate.mjs
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { existsSync, readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
|
+
import { WORKBENCH_API_VERSION } from '@dsh-workbench/shared/host';
|
|
4
|
+
import { DEFAULT_FACES } from './faces.mjs';
|
|
3
5
|
|
|
4
|
-
const API_VERSION =
|
|
5
|
-
|
|
6
|
-
const DEFAULT_FACES = { host: 'src/host.ts', client: 'src/client.tsx' };
|
|
6
|
+
const API_VERSION = WORKBENCH_API_VERSION;
|
|
7
7
|
|
|
8
8
|
export async function migrate({ root, args = [] }) {
|
|
9
9
|
const dryRun = args.includes('--dry-run');
|
package/src/release-target.mjs
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from 'node:fs';
|
|
2
|
+
import { RELEASE_TARGETS } from '@dsh-workbench/shared/contract/protocol/release';
|
|
2
3
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
3
4
|
|
|
4
5
|
import {
|
|
@@ -11,7 +12,7 @@ import {
|
|
|
11
12
|
tailOf,
|
|
12
13
|
} from './release-util.mjs';
|
|
13
14
|
|
|
14
|
-
const TARGETS =
|
|
15
|
+
const TARGETS = RELEASE_TARGETS;
|
|
15
16
|
const PUBLIC_REGISTRIES = ['https://registry.npmjs.org/', 'https://registry.npmmirror.com/'];
|
|
16
17
|
|
|
17
18
|
function isPublicRegistry(registry) {
|
package/src/release.mjs
CHANGED
|
@@ -51,6 +51,7 @@ import {
|
|
|
51
51
|
import {
|
|
52
52
|
MarketIndexError,
|
|
53
53
|
authorTextOf,
|
|
54
|
+
categoryOfManifest,
|
|
54
55
|
claimProblemText,
|
|
55
56
|
identityOf,
|
|
56
57
|
labelsOfManifest,
|
|
@@ -158,7 +159,7 @@ function writeStateKeepingRun(home, unit, value) {
|
|
|
158
159
|
writeState(home, unit, run === undefined ? value : { ...value, run });
|
|
159
160
|
}
|
|
160
161
|
|
|
161
|
-
function stopSummaryOf(target, manifest, version, changes, changelog, labels) {
|
|
162
|
+
function stopSummaryOf(target, manifest, version, changes, changelog, labels, category = '') {
|
|
162
163
|
return {
|
|
163
164
|
ok: true,
|
|
164
165
|
stop: 'workspace-only',
|
|
@@ -167,6 +168,7 @@ function stopSummaryOf(target, manifest, version, changes, changelog, labels) {
|
|
|
167
168
|
version,
|
|
168
169
|
changes,
|
|
169
170
|
...(labels.length === 0 ? {} : { labels }),
|
|
171
|
+
...(category === '' ? {} : { category }),
|
|
170
172
|
...(changelog === undefined ? {} : { changelog }),
|
|
171
173
|
install: '',
|
|
172
174
|
};
|
|
@@ -293,6 +295,10 @@ export async function runRelease({
|
|
|
293
295
|
throw new ReleaseError('--label 只认一行短词:不能是空的');
|
|
294
296
|
}
|
|
295
297
|
if (askedLabels.length > 2) throw new ReleaseError('--label 最多两个 —— 一个模块的标签是 1–2 个短词');
|
|
298
|
+
const askedCategory = (argValue(args, '--category') ?? '').trim();
|
|
299
|
+
if (askedCategory !== '' && /[\r\n]/u.test(askedCategory)) {
|
|
300
|
+
throw new ReleaseError('--category 只认一个短词:不能换行');
|
|
301
|
+
}
|
|
296
302
|
const manifest = readManifest(root);
|
|
297
303
|
const unit = manifest?.dsh?.workbench?.unit;
|
|
298
304
|
if (typeof unit !== 'string' || unit === '') throw new ReleaseError('这不是一个 wb 模块包(没有 dsh.workbench.unit)');
|
|
@@ -359,6 +365,8 @@ export async function runRelease({
|
|
|
359
365
|
const labels = askedLabels;
|
|
360
366
|
const labelsForIndex = labels.length > 0 ? labels : labelsOfManifest(manifest);
|
|
361
367
|
const labelsChange = verify || labels.length === 0 ? undefined : `标签 ← ${labels.join(' · ')}`;
|
|
368
|
+
const categoryForIndex = askedCategory !== '' ? askedCategory : categoryOfManifest(manifest);
|
|
369
|
+
const categoryChange = verify || askedCategory === '' ? undefined : `分类 ← ${askedCategory}`;
|
|
362
370
|
const explicitApplied = explicitAuthor !== undefined && !verify && !dryRun;
|
|
363
371
|
if (explicitApplied && !indexOnly) {
|
|
364
372
|
manifest.author = explicitAuthor;
|
|
@@ -397,7 +405,15 @@ export async function runRelease({
|
|
|
397
405
|
const cardTitle = cardTitleFromMessages(root, unit);
|
|
398
406
|
const titled = cardTitle === undefined ? manifest : { ...manifest, title: cardTitle };
|
|
399
407
|
const publisher = authorTextOf(manifest) || authored.author || '';
|
|
400
|
-
const result = refreshMarketEntry({
|
|
408
|
+
const result = refreshMarketEntry({
|
|
409
|
+
plan: indexPlan,
|
|
410
|
+
exec,
|
|
411
|
+
home,
|
|
412
|
+
manifest: titled,
|
|
413
|
+
publisher,
|
|
414
|
+
...(labelsForIndex.length === 0 ? {} : { labels: labelsForIndex }),
|
|
415
|
+
...(categoryForIndex === '' ? {} : { category: categoryForIndex }),
|
|
416
|
+
});
|
|
401
417
|
if (result.updated !== true && result.reason !== undefined) {
|
|
402
418
|
throw new ReleaseError(`刷新市集元数据没走通:${result.reason}`);
|
|
403
419
|
}
|
|
@@ -419,9 +435,11 @@ export async function runRelease({
|
|
|
419
435
|
'市集元数据已刷新',
|
|
420
436
|
...(explicitAuthor === undefined ? [] : ['author ← --author']),
|
|
421
437
|
...(labelsChange === undefined ? [] : [labelsChange]),
|
|
438
|
+
...(categoryChange === undefined ? [] : [categoryChange]),
|
|
422
439
|
]
|
|
423
440
|
: [],
|
|
424
441
|
...(labels.length === 0 ? {} : { labels }),
|
|
442
|
+
...(categoryForIndex === '' ? {} : { category: categoryForIndex }),
|
|
425
443
|
install: '',
|
|
426
444
|
text:
|
|
427
445
|
result.updated === true
|
|
@@ -535,6 +553,7 @@ export async function runRelease({
|
|
|
535
553
|
publisher: authorTextOf(manifestNow),
|
|
536
554
|
metadata: authorChange !== undefined,
|
|
537
555
|
...(labelsForIndex.length === 0 ? {} : { labels: labelsForIndex }),
|
|
556
|
+
...(categoryForIndex === '' ? {} : { category: categoryForIndex }),
|
|
538
557
|
exec,
|
|
539
558
|
home,
|
|
540
559
|
now: () => Date.now(),
|
|
@@ -544,6 +563,7 @@ export async function runRelease({
|
|
|
544
563
|
const changesOf = (version) => [
|
|
545
564
|
...(authorChange === undefined ? [] : [authorChange]),
|
|
546
565
|
...(labelsChange === undefined ? [] : [labelsChange]),
|
|
566
|
+
...(categoryChange === undefined ? [] : [categoryChange]),
|
|
547
567
|
...(version === manifest.version ? [] : [`version → ${version}`]),
|
|
548
568
|
...(changelogChange === undefined ? [] : [changelogChange]),
|
|
549
569
|
];
|
|
@@ -764,6 +784,7 @@ export async function runRelease({
|
|
|
764
784
|
files,
|
|
765
785
|
changes: changesOf(version),
|
|
766
786
|
...(labels.length === 0 ? {} : { labels }),
|
|
787
|
+
...(categoryForIndex === '' ? {} : { category: categoryForIndex }),
|
|
767
788
|
...(changelogReport === undefined ? {} : { changelog: changelogReport }),
|
|
768
789
|
install: '',
|
|
769
790
|
market: indexPreviewOf(manifest.name, version),
|
|
@@ -780,7 +801,7 @@ export async function runRelease({
|
|
|
780
801
|
if (workspaceOnly) {
|
|
781
802
|
writeManifest(root, next);
|
|
782
803
|
if (tracking) advanceRun(home, unit, { phase: 'workspace', fingerprint: artifactFingerprintOf(root) });
|
|
783
|
-
const stopSummary = stopSummaryOf(target, next, version, changesOf(version), changelogReport, labels);
|
|
804
|
+
const stopSummary = stopSummaryOf(target, next, version, changesOf(version), changelogReport, labels, categoryForIndex);
|
|
784
805
|
emit(out, json, stopSummary);
|
|
785
806
|
return stopSummary;
|
|
786
807
|
}
|
|
@@ -794,6 +815,7 @@ export async function runRelease({
|
|
|
794
815
|
publisher,
|
|
795
816
|
metadata: authorChange !== undefined,
|
|
796
817
|
...(labelsForIndex.length === 0 ? {} : { labels: labelsForIndex }),
|
|
818
|
+
...(categoryForIndex === '' ? {} : { category: categoryForIndex }),
|
|
797
819
|
exec,
|
|
798
820
|
home,
|
|
799
821
|
now: () => Date.now(),
|
|
@@ -877,7 +899,7 @@ export async function runRelease({
|
|
|
877
899
|
if (tracking) advanceRun(home, unit, { phase: 'version', version, fingerprint: artifactFingerprintOf(root) });
|
|
878
900
|
writeStateKeepingRun(home, unit, stateOf(info, next, version, 'intent'));
|
|
879
901
|
if (workspaceOnly) {
|
|
880
|
-
const stopSummary = stopSummaryOf(target, next, version, changesOf(version), changelogReport, labels);
|
|
902
|
+
const stopSummary = stopSummaryOf(target, next, version, changesOf(version), changelogReport, labels, categoryForIndex);
|
|
881
903
|
emit(out, json, stopSummary);
|
|
882
904
|
return stopSummary;
|
|
883
905
|
}
|
|
@@ -964,7 +986,7 @@ export async function runRelease({
|
|
|
964
986
|
});
|
|
965
987
|
if (workspaceOnly) {
|
|
966
988
|
const stopSummary = {
|
|
967
|
-
...stopSummaryOf(target, next, version, changesOf(version), changelogReport, labels),
|
|
989
|
+
...stopSummaryOf(target, next, version, changesOf(version), changelogReport, labels, categoryForIndex),
|
|
968
990
|
files: checked.files,
|
|
969
991
|
git: { url: info.url, tag: info.tagOf(version), commit: checked.commit },
|
|
970
992
|
};
|
|
@@ -1049,7 +1071,7 @@ export async function runRelease({
|
|
|
1049
1071
|
if (!resumeMatchesTree) runCliStage(exec, root, ['check'], stageOptions);
|
|
1050
1072
|
writeStateKeepingRun(home, unit, stateOf(info, next, version, 'intent'));
|
|
1051
1073
|
if (workspaceOnly) {
|
|
1052
|
-
const stopSummary = stopSummaryOf(target, next, version, changes, changelogReport, labels);
|
|
1074
|
+
const stopSummary = stopSummaryOf(target, next, version, changes, changelogReport, labels, categoryForIndex);
|
|
1053
1075
|
emit(out, json, stopSummary);
|
|
1054
1076
|
return stopSummary;
|
|
1055
1077
|
}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
改 `src/**` 后:
|
|
8
8
|
|
|
9
9
|
1. `pnpm build` —— 产出 `dist/host.js` + 套了 loader 壳的 `client.js`;产物由它生成。
|
|
10
|
-
2. `pnpm check` ——
|
|
10
|
+
2. `pnpm check` —— 十四条判据,读的就是上一步的产物。单跑一条用 `pnpm check --only <判据名>`;
|
|
11
11
|
**全绿**才算这件活做完。怎么读红、常见红:`@dsh-workbench/core/skills/workbench-module-diy/REVIEW.md`。
|
|
12
12
|
3. `pnpm test` —— `src/**/__tests__/` 下、与改动同目录的 `*.test.ts`;新逻辑配对应用例,**全绿**才算完。
|
|
13
13
|
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
```bash
|
|
10
10
|
dsh-wb-module init <unit> --dir <目录> # 生成这份骨架(只有这一个模板)
|
|
11
11
|
dsh-wb-module build # 出 dist/host.js 与套了 loader 壳的 client.js
|
|
12
|
-
dsh-wb-module check #
|
|
13
|
-
dsh-wb-module check --only cards #
|
|
12
|
+
dsh-wb-module check # 十四条判据:契约 / 声明协议 / 产物边界 / 源码边界 / 执行缝 / 架构 / dsh 原语 / kit 面 / 卡片渲染 / 独立构建 / 内部标识 / 样式归属 / 不指外壳 / 代码卫生(eslint)
|
|
13
|
+
dsh-wb-module check --only cards # 十四条可以单跑
|
|
14
14
|
```
|
|
15
15
|
|
|
16
16
|
包自带的六个脚本(`build` / `typecheck` / `test` / `selfcheck` 是 `check` 的契约判据要求的那四个;`check` 跑全部判据,`lint` 只跑源码卫生那一条):
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// 这份模块自己的真读断言:换数据源时改这一份,不要删 smoke.mjs 里那一整段。
|
|
2
2
|
//
|
|
3
3
|
// `assertModule()` 只在这里拿到真东西(路由表与 check),想验什么就写什么:
|
|
4
|
-
// 卡片画的数从哪来,这里真的调一次 —— `check`
|
|
4
|
+
// 卡片画的数从哪来,这里真的调一次 —— `check` 的十四条判据一条都不碰这条。
|
|
5
5
|
|
|
6
6
|
import { readdirSync } from 'node:fs';
|
|
7
7
|
import { dirname } from 'node:path';
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
//
|
|
7
7
|
// ★ 这份脚本验的是**形状**:它认不出成员名,所以换数据源、换卡片内容都不用改它。
|
|
8
8
|
// 模板自己的"真读"与"真跑"在 `scripts/smoke.assert.mjs` 里:换数据源时改那一份,
|
|
9
|
-
// 不要删这里的调用 —— 卡片画的数从哪来,`check`
|
|
9
|
+
// 不要删这里的调用 —— 卡片画的数从哪来,`check` 的十四条判据一条都不碰,只有这里会真调一次。
|
|
10
10
|
//
|
|
11
11
|
// 负例在最后一段:服务不在的时候必须**报出来**,不许静默通过 —— 静默的模块是最难查的那种坏。
|
|
12
12
|
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { createElement as h } from 'react';
|
|
2
|
+
import { kit } from '@dsh-workbench/core/client';
|
|
3
|
+
|
|
4
|
+
const UNIT_ID = '__UNIT__';
|
|
5
|
+
const SETUP_ROUTE = `${UNIT_ID}/setup`;
|
|
6
|
+
|
|
7
|
+
const { SetupShell, TextField } = kit.components;
|
|
8
|
+
const { useUnitSetup } = kit.hooks;
|
|
9
|
+
|
|
10
|
+
interface SetupPayload {
|
|
11
|
+
readonly configured?: boolean;
|
|
12
|
+
readonly values?: { readonly name?: string };
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface SetupFlowProps {
|
|
16
|
+
readonly t: (key: string) => string;
|
|
17
|
+
readonly path: string;
|
|
18
|
+
readonly onDone: () => void;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export function SetupFlow({ t, path, onDone }: SetupFlowProps): ReturnType<typeof h> {
|
|
22
|
+
const setup = useUnitSetup({
|
|
23
|
+
steps: [
|
|
24
|
+
{
|
|
25
|
+
id: 'name',
|
|
26
|
+
title: t(`${UNIT_ID}.setup.title`),
|
|
27
|
+
render: (ctx) =>
|
|
28
|
+
h(TextField, {
|
|
29
|
+
value: String(ctx.values['name'] ?? ''),
|
|
30
|
+
placeholder: t(`${UNIT_ID}.setup.namePlaceholder`),
|
|
31
|
+
disabled: ctx.busy,
|
|
32
|
+
onChange: (event) => {
|
|
33
|
+
ctx.setValues({ ...ctx.values, name: event.target.value });
|
|
34
|
+
},
|
|
35
|
+
}),
|
|
36
|
+
submit: async (values) => {
|
|
37
|
+
const response = await kit.api.apiPost<{ ok: boolean }>(SETUP_ROUTE, {
|
|
38
|
+
path,
|
|
39
|
+
name: String(values['name'] ?? ''),
|
|
40
|
+
});
|
|
41
|
+
return response.ok ? { ok: true } : { ok: false, message: response.error.message };
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
read: async () => {
|
|
46
|
+
const response = await kit.api.apiGet<SetupPayload>(SETUP_ROUTE, { path });
|
|
47
|
+
if (!response.ok) return { configured: false, values: {} };
|
|
48
|
+
return {
|
|
49
|
+
configured: response.configured === true,
|
|
50
|
+
values: { name: response.values?.name ?? '' },
|
|
51
|
+
};
|
|
52
|
+
},
|
|
53
|
+
onReady: () => {
|
|
54
|
+
onDone();
|
|
55
|
+
},
|
|
56
|
+
});
|
|
57
|
+
return h(SetupShell, {
|
|
58
|
+
setup,
|
|
59
|
+
labels: {
|
|
60
|
+
back: t(`${UNIT_ID}.setup.back`),
|
|
61
|
+
next: t(`${UNIT_ID}.setup.next`),
|
|
62
|
+
finish: t(`${UNIT_ID}.setup.finish`),
|
|
63
|
+
save: t(`${UNIT_ID}.setup.save`),
|
|
64
|
+
saving: t(`${UNIT_ID}.setup.saving`),
|
|
65
|
+
cancel: t(`${UNIT_ID}.setup.cancel`),
|
|
66
|
+
reading: t(`${UNIT_ID}.setup.reading`),
|
|
67
|
+
failed: t(`${UNIT_ID}.setup.failed`),
|
|
68
|
+
},
|
|
69
|
+
renderHome: () => null,
|
|
70
|
+
});
|
|
71
|
+
}
|
|
@@ -24,6 +24,16 @@ export const messages = {
|
|
|
24
24
|
'__UNIT__.prefsLabel': '最大的几个文件',
|
|
25
25
|
'__UNIT__.prefsHint': '这个选择按项目存(刷新还在);只影响卡片显示,不改读到的数据',
|
|
26
26
|
'__UNIT__.prefsUnit': ' 个',
|
|
27
|
+
'__UNIT__.setup.title': '显示名',
|
|
28
|
+
'__UNIT__.setup.namePlaceholder': '给这个模块起个显示名',
|
|
29
|
+
'__UNIT__.setup.back': '上一步',
|
|
30
|
+
'__UNIT__.setup.next': '下一步',
|
|
31
|
+
'__UNIT__.setup.finish': '完成',
|
|
32
|
+
'__UNIT__.setup.save': '保存',
|
|
33
|
+
'__UNIT__.setup.saving': '保存中',
|
|
34
|
+
'__UNIT__.setup.cancel': '取消',
|
|
35
|
+
'__UNIT__.setup.reading': '正在读配置…',
|
|
36
|
+
'__UNIT__.setup.failed': '读不到配置',
|
|
27
37
|
},
|
|
28
38
|
en: {
|
|
29
39
|
'__UNIT__.cardTitle': 'Directory overview',
|
|
@@ -50,5 +60,15 @@ export const messages = {
|
|
|
50
60
|
'__UNIT__.prefsLabel': 'Largest files shown',
|
|
51
61
|
'__UNIT__.prefsHint': 'Stored per project (survives refresh); affects display only, not the data',
|
|
52
62
|
'__UNIT__.prefsUnit': '',
|
|
63
|
+
'__UNIT__.setup.title': 'Display name',
|
|
64
|
+
'__UNIT__.setup.namePlaceholder': 'Give this module a display name',
|
|
65
|
+
'__UNIT__.setup.back': 'Back',
|
|
66
|
+
'__UNIT__.setup.next': 'Next',
|
|
67
|
+
'__UNIT__.setup.finish': 'Finish',
|
|
68
|
+
'__UNIT__.setup.save': 'Save',
|
|
69
|
+
'__UNIT__.setup.saving': 'Saving',
|
|
70
|
+
'__UNIT__.setup.cancel': 'Cancel',
|
|
71
|
+
'__UNIT__.setup.reading': 'Reading config…',
|
|
72
|
+
'__UNIT__.setup.failed': 'Cannot read config',
|
|
53
73
|
},
|
|
54
74
|
};
|