@dsh-workbench/module-tools 1.1.14-beta.1 → 1.1.14
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 +20 -3
- package/bin/dsh-wb-module.mjs +3 -1
- package/package.json +2 -2
- package/src/check.mjs +6 -0
- package/src/checks/seam.mjs +134 -0
- package/src/checks/source.mjs +1 -1
- package/src/release-util.mjs +2 -0
- package/src/release.mjs +113 -31
package/README.md
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
```bash
|
|
9
9
|
pnpm exec dsh-wb-module init my-unit # 建骨架(唯一模板,已默认)
|
|
10
10
|
pnpm build # dist/host.js + 套了 loader 壳的 client.js
|
|
11
|
-
pnpm check #
|
|
11
|
+
pnpm check # 十四条判据
|
|
12
12
|
pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
13
13
|
```
|
|
14
14
|
|
|
@@ -50,7 +50,7 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
50
50
|
| `dsh-wb-module init <unit> [--dir <目录>] [--template diy] [--description "一句话"]` | 建模块骨架(只有 `diy` 一个模板,也是默认) |
|
|
51
51
|
| `dsh-wb-module market-init <git 地址\|目录> [--ref <分支>] [--path <索引路径>]` | 初始化市场索引仓库:写 `market/` + README,推首个 commit |
|
|
52
52
|
| `dsh-wb-module build` | 出 `dist/host.js`(单文件 ESM)与 `client.js`(套 loader 壳) |
|
|
53
|
-
| `dsh-wb-module check [--only <判据>] [--package <包名或 unit>]` |
|
|
53
|
+
| `dsh-wb-module check [--only <判据>] [--package <包名或 unit>]` | 十四条判据;`--only` 单跑,也当查询口用;`--package` 只报这个模块的问题(见下);`--list [--json\|--markdown]` 只列判据元数据 |
|
|
54
54
|
| `dsh-wb-module lint [--fix]` | 用模块自己那份 `eslint.config.mjs`;`--fix` 写回文件 |
|
|
55
55
|
| `dsh-wb-module sync` | 按清单生成 `src/declared-tools.ts`,按 `design.json` 的 `key + recipe` 生成 `src/datasource.ts` |
|
|
56
56
|
| `dsh-wb-module design <init\|check\|render\|accept>` | 设计底稿:填、查空格、生成 README、验收打勾 |
|
|
@@ -68,7 +68,7 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
68
68
|
「我这个模块绿」不等于「工作区绿」。门禁 / 发布 / release 不要带它。要只让判据看见一个包,
|
|
69
69
|
用环境变量 `DSH_WB_ROOT=<包目录>`(工具链自己的自检就是这么隔离的)。
|
|
70
70
|
|
|
71
|
-
##
|
|
71
|
+
## 十四条判据
|
|
72
72
|
|
|
73
73
|
`dsh-wb-module check` 一次跑完,三态输出:`✔ 通过` / `○ 不适用` / `✖ 不过`;有任何不过就退出 1。
|
|
74
74
|
|
|
@@ -81,6 +81,7 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
81
81
|
| `protocol` | manifest | dsh.workbench 的 apiVersion、静态半形状与未知键 |
|
|
82
82
|
| `bundles` | build | 宿主产物不含浏览器侧、浏览器产物不内联外壳 CSS 与夹具 |
|
|
83
83
|
| `source` | source | 零注释、包与运行时入口分开、不手写进程/锁/原子写 |
|
|
84
|
+
| `seam` | source | 子进程只走 deps.seam 门面;不取原始 shell / 不裸 child_process;清单 writes 与请求一致 |
|
|
84
85
|
| `arch` | source | 模块自己的层序、运行时边界与设计系统配置 |
|
|
85
86
|
| `lint` | source | 模块自己的 eslint.config.mjs 与规则集 |
|
|
86
87
|
| `primitives` | build | dsh 包只从 kit 取,模块自己声明 external |
|
|
@@ -101,6 +102,22 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
101
102
|
现存违规收在各自的豁免表里,是棘轮:迁一处删一行,表里已经不再命中的行也会当场报红。
|
|
102
103
|
判据**故意窄** —— 模块自己用 div 拼一张不借槽类的表单不判,判宽了就是每次改界面都先撞门禁。
|
|
103
104
|
|
|
105
|
+
### `seam`:模块只走执行缝门面
|
|
106
|
+
|
|
107
|
+
模块在宿主侧起子进程,只能走外壳放进 `mount(deps)` 的门面 `deps.seam`。判据查三件事:
|
|
108
|
+
|
|
109
|
+
- 宿主侧可达的源码里不许出现 `runShell` / `shellOf`、`ctx.get('shell')` / `ctx.get('workbenchShell')` /
|
|
110
|
+
`ctx.get('seam')`、`node:child_process` / `child_process`;
|
|
111
|
+
- `dsh.workbench.host.writes` 只认 `"unit"` / `"workspace"` 两档(`protocol` 判据也查形状,这里再兜一次);
|
|
112
|
+
- 源码里以 `scope: 'unit'` / `scope: 'workspace'` 请求的档,清单 `host.writes` 里必须有。
|
|
113
|
+
|
|
114
|
+
修法:清单里写 `"host": { "inject": [], "writes": ["unit"] }`,代码里
|
|
115
|
+
`deps.seam?.run({ scope: 'unit', command })`。`unit` 档可写 `<运行时根>/units/<unit>/`(模块自己的数据目录,
|
|
116
|
+
`HOME` 与 npm/pnpm 缓存也注入在那里),`workspace` 档写当前会话工作区、还要带上会话对象;
|
|
117
|
+
不写 `writes` = 不给门面(`deps.seam` 是 `undefined`)。模块宿主代码与外壳同进程 —— 这层是
|
|
118
|
+
**约定 + 门禁 + 归因**,不是安全边界。完整写法见
|
|
119
|
+
[BUILD.md](../core/skills/workbench-module-diy/BUILD.md) §5。
|
|
120
|
+
|
|
104
121
|
### `arch`:模块包的分层方言
|
|
105
122
|
|
|
106
123
|
卡片一复杂就往 `src/client.tsx` 里堆,宿主侧顺手 import 一个组件(`dist/host.js` 在 Node 里
|
package/bin/dsh-wb-module.mjs
CHANGED
|
@@ -79,6 +79,8 @@ const RELEASE_HELP = `
|
|
|
79
79
|
--index auto|none|<目录|git 地址> 发布后要更新的索引源
|
|
80
80
|
--index-source <id> --index-ref <分支> --index-path <索引路径> --index-policy artifacts-in-repo|catalog-only
|
|
81
81
|
--resume 上次中断后接着发(用记录里的出口与版本)
|
|
82
|
+
--workspace-only 只做写工作区那几步(构建/检查/提交),索引与产物流给 --index-only
|
|
83
|
+
--index-only 接着 --workspace-only 的记录只做产品侧(索引/发布/产物)
|
|
82
84
|
--dry-run --verify --yes --json`;
|
|
83
85
|
|
|
84
86
|
const MARKET_INIT_HELP = `
|
|
@@ -97,7 +99,7 @@ release --changelog-note "这一版改了什么"(文件不在会先建)。`;
|
|
|
97
99
|
|
|
98
100
|
const COMMANDS = {
|
|
99
101
|
build: '出 dist/host.js(单文件 ESM)与 client.js(套 loader 壳)',
|
|
100
|
-
check: '
|
|
102
|
+
check: '十四条判据;--only 单跑,也当查询口用;--list 只列判据元数据',
|
|
101
103
|
init: '建模块骨架(--dir / --template diy / --description;只有 diy 一个模板)',
|
|
102
104
|
'market-init': '初始化市场索引仓库(写 market/ 与 README,推首个 commit)',
|
|
103
105
|
design: '设计底稿:init 填 / check 查空格 / render 生成 README / accept 打勾',
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/module-tools",
|
|
3
|
-
"version": "1.1.14
|
|
3
|
+
"version": "1.1.14",
|
|
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.14
|
|
36
|
+
"@dsh-workbench/shared": "1.1.14"
|
|
37
37
|
},
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
package/src/check.mjs
CHANGED
|
@@ -25,6 +25,12 @@ export const CHECKS = {
|
|
|
25
25
|
layer: 'source',
|
|
26
26
|
load: () => import('./checks/source.mjs'),
|
|
27
27
|
},
|
|
28
|
+
seam: {
|
|
29
|
+
title: '模块执行缝(写入域)',
|
|
30
|
+
summary: '子进程只走 deps.seam 门面;不取原始 shell / 不裸 child_process;清单 writes 与请求一致',
|
|
31
|
+
layer: 'source',
|
|
32
|
+
load: () => import('./checks/seam.mjs'),
|
|
33
|
+
},
|
|
28
34
|
arch: {
|
|
29
35
|
title: '模块架构(分层 / 边界 / 设计系统)',
|
|
30
36
|
summary: '模块自己的层序、运行时边界与设计系统配置',
|
|
@@ -0,0 +1,134 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { relative } from 'node:path';
|
|
3
|
+
|
|
4
|
+
import { discoverModulePackages } from '../discover.mjs';
|
|
5
|
+
import { reachableFrom } from './source.mjs';
|
|
6
|
+
|
|
7
|
+
export const title = '模块执行缝(写入域)';
|
|
8
|
+
|
|
9
|
+
const SCOPE_USE = /scope\s*:\s*['"](unit|workspace)['"]/g;
|
|
10
|
+
|
|
11
|
+
const RAW_SEAM = [
|
|
12
|
+
{ pattern: /\brunShell\b/, what: '用了原始 shell 的 runShell(导入或调用)' },
|
|
13
|
+
{ pattern: /\bshellOf\b/, what: '用了原始 shell 的 shellOf(导入或调用)' },
|
|
14
|
+
{
|
|
15
|
+
pattern: /ctx\s*\.\s*get\s*\(\s*['"`](shell|workbenchShell|seam)['"`]/,
|
|
16
|
+
what: '从外壳服务里取了原始执行缝',
|
|
17
|
+
},
|
|
18
|
+
{ pattern: /['"](?:node:)?child_process['"]/, what: '直接引了 child_process' },
|
|
19
|
+
{ pattern: /['"]execa['"]/, what: '直接引了 execa(模块不许自己起进程)' },
|
|
20
|
+
{ pattern: /kind\s*:\s*['"]host['"]/, what: '请求了宿主写入域(模块只有 unit / workspace 两档)' },
|
|
21
|
+
];
|
|
22
|
+
|
|
23
|
+
const SEAM_FIX =
|
|
24
|
+
'模块起子进程只走门面:mount(deps) 里的 `deps.seam.run({ scope: "unit" | "workspace", command })`,' +
|
|
25
|
+
'清单里用 `dsh.workbench.host.writes` 声明要哪几档(写法见 `@dsh-workbench/core` 的 ' +
|
|
26
|
+
'`skills/workbench-module-diy/BUILD.md`)';
|
|
27
|
+
|
|
28
|
+
const WRITE_SCOPES = ['unit', 'workspace'];
|
|
29
|
+
|
|
30
|
+
function lineOf(text, index) {
|
|
31
|
+
return text.slice(0, index).split('\n').length;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function isRecord(value) {
|
|
35
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function rawSeamProblemsOf(name, rel, text) {
|
|
39
|
+
const problems = [];
|
|
40
|
+
for (const entry of RAW_SEAM) {
|
|
41
|
+
const match = entry.pattern.exec(text);
|
|
42
|
+
if (match === null) continue;
|
|
43
|
+
problems.push(`${name}: ${rel}:${lineOf(text, match.index)} ${entry.what} —— ${SEAM_FIX}`);
|
|
44
|
+
}
|
|
45
|
+
return problems;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function declaredWritesOf(workbench) {
|
|
49
|
+
const host = isRecord(workbench) ? workbench.host : undefined;
|
|
50
|
+
const writes = isRecord(host) ? host.writes : undefined;
|
|
51
|
+
return new Set(Array.isArray(writes) ? writes.filter((scope) => WRITE_SCOPES.includes(scope)) : []);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export function declaredWritesProblemsOf(name, workbench) {
|
|
55
|
+
const host = isRecord(workbench) ? workbench.host : undefined;
|
|
56
|
+
if (!isRecord(host) || host.writes === undefined) return [];
|
|
57
|
+
const writes = host.writes;
|
|
58
|
+
if (!Array.isArray(writes)) {
|
|
59
|
+
return [
|
|
60
|
+
`${name}: dsh.workbench.host.writes 要是一串写入域("unit" / "workspace"),` +
|
|
61
|
+
`现在是 ${JSON.stringify(writes)}`,
|
|
62
|
+
];
|
|
63
|
+
}
|
|
64
|
+
const outside = writes.filter((scope) => !WRITE_SCOPES.includes(scope));
|
|
65
|
+
if (outside.length === 0) return [];
|
|
66
|
+
return [
|
|
67
|
+
`${name}: dsh.workbench.host.writes 里有不认的写入域 ` +
|
|
68
|
+
`${outside.map((scope) => JSON.stringify(scope)).join(' / ')} —— ` +
|
|
69
|
+
'只认 "unit"(模块自己的数据目录,<运行时根>/units/<unit>)与 "workspace"(当前会话工作区)两档;' +
|
|
70
|
+
'模块拿不到产品域与任意路径,别写别的名字(不写 = 不给执行缝门面)',
|
|
71
|
+
];
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function writesUseProblemsOf(name, rel, text, declared) {
|
|
75
|
+
if (!/\bseam\b/.test(text)) return [];
|
|
76
|
+
const problems = [];
|
|
77
|
+
const seen = new Set();
|
|
78
|
+
for (const match of text.matchAll(SCOPE_USE)) {
|
|
79
|
+
const scope = match[1];
|
|
80
|
+
if (seen.has(scope) || declared.has(scope)) continue;
|
|
81
|
+
seen.add(scope);
|
|
82
|
+
problems.push(
|
|
83
|
+
`${name}: ${rel}:${lineOf(text, match.index)} 请求了 ${scope} 档,但清单 ` +
|
|
84
|
+
`dsh.workbench.host.writes 没声明它 —— 外壳不会把门面交给这个模块(deps.seam 是 undefined):` +
|
|
85
|
+
`清单里补 "writes": ["${scope}"],或这一档别请求`,
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
return problems;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export async function run() {
|
|
92
|
+
const modulePackages = discoverModulePackages({ root: process.cwd() });
|
|
93
|
+
const problems = [];
|
|
94
|
+
let scanned = 0;
|
|
95
|
+
let scannedUnits = 0;
|
|
96
|
+
let seamUnits = 0;
|
|
97
|
+
|
|
98
|
+
for (const pkg of modulePackages) {
|
|
99
|
+
const name = pkg.manifest.name;
|
|
100
|
+
const workbench = pkg.manifest.dsh?.workbench;
|
|
101
|
+
problems.push(...declaredWritesProblemsOf(name, workbench));
|
|
102
|
+
const declared = declaredWritesOf(workbench);
|
|
103
|
+
if (declared.size > 0) seamUnits += 1;
|
|
104
|
+
const hostFace = pkg.manifest.dsh?.workbenchFaces?.host;
|
|
105
|
+
if (typeof hostFace !== 'string') continue;
|
|
106
|
+
const unit = typeof workbench?.unit === 'string' ? workbench.unit : '';
|
|
107
|
+
scannedUnits += 1;
|
|
108
|
+
for (const file of reachableFrom(pkg.dir, unit, hostFace)) {
|
|
109
|
+
const text = readFileSync(file, 'utf8');
|
|
110
|
+
const rel = relative(pkg.dir, file);
|
|
111
|
+
scanned += 1;
|
|
112
|
+
problems.push(...rawSeamProblemsOf(name, rel, text));
|
|
113
|
+
problems.push(...writesUseProblemsOf(name, rel, text, declared));
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
if (modulePackages.length === 0) {
|
|
118
|
+
return {
|
|
119
|
+
status: 'skip',
|
|
120
|
+
notes: ['没有模块包(没有声明 dsh.workbench 的包)—— 没有执行缝用法要查'],
|
|
121
|
+
problems: [],
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
if (problems.length > 0) return { status: 'fail', notes: [], problems };
|
|
125
|
+
return {
|
|
126
|
+
status: 'pass',
|
|
127
|
+
notes: [
|
|
128
|
+
`${modulePackages.length} 个模块包 · ${scannedUnits} 个宿主面 · ${scanned} 个宿主侧源文件 · ` +
|
|
129
|
+
`${seamUnits} 个模块声明了写入域(不取原始 shell / 不裸 child_process;` +
|
|
130
|
+
'请求的写入域都在清单里)',
|
|
131
|
+
],
|
|
132
|
+
problems: [],
|
|
133
|
+
};
|
|
134
|
+
}
|
package/src/checks/source.mjs
CHANGED
|
@@ -173,7 +173,7 @@ function resolveLocal(pkgDir, unit, fromFile, specifier) {
|
|
|
173
173
|
return undefined;
|
|
174
174
|
}
|
|
175
175
|
|
|
176
|
-
function reachableFrom(pkgDir, unit, entry) {
|
|
176
|
+
export function reachableFrom(pkgDir, unit, entry) {
|
|
177
177
|
const seen = new Set();
|
|
178
178
|
const start = join(pkgDir, entry);
|
|
179
179
|
if (!existsSync(start)) return seen;
|
package/src/release-util.mjs
CHANGED
|
@@ -81,6 +81,8 @@ export function dshHomeOf(home) {
|
|
|
81
81
|
}
|
|
82
82
|
|
|
83
83
|
export function workbenchRootOf(home) {
|
|
84
|
+
const staging = process.env.DSH_WB_RELEASE_HOME?.trim();
|
|
85
|
+
if (staging !== undefined && staging !== '') return staging;
|
|
84
86
|
const configured = process.env.DSH_WORKBENCH_HOME?.trim();
|
|
85
87
|
if (configured !== undefined && configured !== '') return configured;
|
|
86
88
|
return join(dshHomeOf(home), 'workbench');
|
package/src/release.mjs
CHANGED
|
@@ -151,6 +151,27 @@ function titleIn(segment, key) {
|
|
|
151
151
|
return match === null ? '' : match[1].trim();
|
|
152
152
|
}
|
|
153
153
|
|
|
154
|
+
function writeStateKeepingRun(home, unit, value) {
|
|
155
|
+
const existing = readState(home, unit);
|
|
156
|
+
const valueRun = value !== null && typeof value === 'object' ? value.run : undefined;
|
|
157
|
+
const run = valueRun ?? existing?.run;
|
|
158
|
+
writeState(home, unit, run === undefined ? value : { ...value, run });
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function stopSummaryOf(target, manifest, version, changes, changelog, labels) {
|
|
162
|
+
return {
|
|
163
|
+
ok: true,
|
|
164
|
+
stop: 'workspace-only',
|
|
165
|
+
target,
|
|
166
|
+
name: manifest.name,
|
|
167
|
+
version,
|
|
168
|
+
changes,
|
|
169
|
+
...(labels.length === 0 ? {} : { labels }),
|
|
170
|
+
...(changelog === undefined ? {} : { changelog }),
|
|
171
|
+
install: '',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
|
|
154
175
|
export function cardTitleFromMessages(root, unit) {
|
|
155
176
|
if (unit === '') return undefined;
|
|
156
177
|
const key = `${unit}.cardTitle`;
|
|
@@ -254,6 +275,9 @@ export async function runRelease({
|
|
|
254
275
|
const json = hasFlag(args, '--json');
|
|
255
276
|
const dryRun = hasFlag(args, '--dry-run');
|
|
256
277
|
const verify = hasFlag(args, '--verify');
|
|
278
|
+
const workspaceOnly = hasFlag(args, '--workspace-only');
|
|
279
|
+
const indexOnly = hasFlag(args, '--index-only');
|
|
280
|
+
if (workspaceOnly && indexOnly) throw new ReleaseError('--workspace-only 与 --index-only 不能同时用');
|
|
257
281
|
const askedAuthor = argValue(args, '--author');
|
|
258
282
|
const explicitAuthor = askedAuthor === undefined ? undefined : askedAuthor.trim();
|
|
259
283
|
if (askedAuthor !== undefined && (explicitAuthor === '' || /[\r\n]/u.test(explicitAuthor))) {
|
|
@@ -277,9 +301,12 @@ export async function runRelease({
|
|
|
277
301
|
throw new ReleaseError('package.json 里没有 version');
|
|
278
302
|
}
|
|
279
303
|
const earlyState = readState(home, unit);
|
|
280
|
-
const resume = hasFlag(args, '--resume');
|
|
281
|
-
const journal = resume ? resumableState(home, unit) : undefined;
|
|
282
|
-
if (
|
|
304
|
+
const resume = hasFlag(args, '--resume') || indexOnly;
|
|
305
|
+
const journal = indexOnly ? readState(home, unit) : resume ? resumableState(home, unit) : undefined;
|
|
306
|
+
if (indexOnly && (journal === undefined || journal.run === undefined)) {
|
|
307
|
+
throw new ReleaseError('没有可续跑的发布记录 —— 先正常发一次');
|
|
308
|
+
}
|
|
309
|
+
if (resume && !indexOnly && journal === undefined) {
|
|
283
310
|
throw new ReleaseError('没有可续跑的发布记录 —— 去掉 --resume 正常发一次');
|
|
284
311
|
}
|
|
285
312
|
const askedTarget = argValue(args, '--target');
|
|
@@ -291,7 +318,9 @@ export async function runRelease({
|
|
|
291
318
|
const resumeTarget = resume && askedTarget === undefined ? journal?.target : undefined;
|
|
292
319
|
const needPlan = !verify || stateTarget === 'market' || askedTarget === 'market';
|
|
293
320
|
let indexPlan;
|
|
294
|
-
if (
|
|
321
|
+
if (indexOnly) {
|
|
322
|
+
indexPlan = journal?.run?.indexPlan;
|
|
323
|
+
} else if (needPlan) {
|
|
295
324
|
try {
|
|
296
325
|
indexPlan = resolveIndexPlan({ args, home, exec });
|
|
297
326
|
} catch (error) {
|
|
@@ -306,10 +335,17 @@ export async function runRelease({
|
|
|
306
335
|
if (resume && indexPlan === undefined && journal?.run?.indexPlan !== undefined) {
|
|
307
336
|
indexPlan = journal.run.indexPlan;
|
|
308
337
|
}
|
|
309
|
-
const target =
|
|
338
|
+
const target = indexOnly
|
|
339
|
+
? typeof journal?.target === 'string'
|
|
340
|
+
? journal.target
|
|
341
|
+
: (() => {
|
|
342
|
+
throw new ReleaseError('发布记录里没有出口 —— 重新发一次');
|
|
343
|
+
})()
|
|
344
|
+
: (resumeTarget ?? stateTarget ?? targetOf(root, manifest, args, indexPlan));
|
|
310
345
|
if (target === 'local') {
|
|
311
346
|
const summary = {
|
|
312
347
|
ok: true,
|
|
348
|
+
...(workspaceOnly ? { stop: 'workspace-only' } : {}),
|
|
313
349
|
target: 'local',
|
|
314
350
|
name: manifest.name,
|
|
315
351
|
version: manifest.version,
|
|
@@ -324,7 +360,7 @@ export async function runRelease({
|
|
|
324
360
|
const labelsForIndex = labels.length > 0 ? labels : labelsOfManifest(manifest);
|
|
325
361
|
const labelsChange = verify || labels.length === 0 ? undefined : `标签 ← ${labels.join(' · ')}`;
|
|
326
362
|
const explicitApplied = explicitAuthor !== undefined && !verify && !dryRun;
|
|
327
|
-
if (explicitApplied) {
|
|
363
|
+
if (explicitApplied && !indexOnly) {
|
|
328
364
|
manifest.author = explicitAuthor;
|
|
329
365
|
writeManifest(root, manifest);
|
|
330
366
|
}
|
|
@@ -396,13 +432,13 @@ export async function runRelease({
|
|
|
396
432
|
return summary;
|
|
397
433
|
}
|
|
398
434
|
const authorChange = explicitAuthor === undefined ? authored.change : 'author ← --author';
|
|
399
|
-
if (!explicitApplied && !verify && !dryRun && authored.author !== undefined) {
|
|
435
|
+
if (!explicitApplied && !indexOnly && !verify && !dryRun && authored.author !== undefined) {
|
|
400
436
|
manifest.author = authored.author;
|
|
401
437
|
writeManifest(root, manifest);
|
|
402
438
|
}
|
|
403
439
|
const publisher = authorTextOf(manifest);
|
|
404
440
|
const writesIndex = indexPlan !== undefined && target !== 'local' && target !== 'tarball';
|
|
405
|
-
if (!verify && !dryRun && writesIndex) {
|
|
441
|
+
if (!workspaceOnly && !verify && !dryRun && writesIndex) {
|
|
406
442
|
if (publisher === '') throw new ReleaseError(missingPublisherText());
|
|
407
443
|
let claim;
|
|
408
444
|
try {
|
|
@@ -417,7 +453,7 @@ export async function runRelease({
|
|
|
417
453
|
throw new ReleaseError(claimProblemText(manifest.name, claim, unit));
|
|
418
454
|
}
|
|
419
455
|
}
|
|
420
|
-
const frozenVersion = resume && typeof journal?.version === 'string' ? journal.version : undefined;
|
|
456
|
+
const frozenVersion = (resume || indexOnly) && typeof journal?.version === 'string' ? journal.version : undefined;
|
|
421
457
|
const info =
|
|
422
458
|
target === 'market'
|
|
423
459
|
? { target: 'market', kind: 'market', url: indexPlan.source.url }
|
|
@@ -440,18 +476,20 @@ export async function runRelease({
|
|
|
440
476
|
}
|
|
441
477
|
return frozenVersion ?? nextVersion(manifest.version, askedVersion, () => false);
|
|
442
478
|
};
|
|
443
|
-
const releaseVersion = verify ? undefined : targetVersionOf();
|
|
444
|
-
|
|
445
|
-
|
|
479
|
+
const releaseVersion = indexOnly ? frozenVersion : verify ? undefined : targetVersionOf();
|
|
480
|
+
if (indexOnly && releaseVersion === undefined) throw new ReleaseError('发布记录里没有版本号 —— 重新发一次');
|
|
481
|
+
const changelogProblem = verify || dryRun || indexOnly ? undefined : changelogProblemOf(root, releaseVersion);
|
|
482
|
+
if (!indexOnly && changelogProblem !== undefined && changelogNote === '') {
|
|
446
483
|
throw new NeedsUser('changelog', changelogProblem.reason, changelogProblem.command, {
|
|
447
484
|
target,
|
|
448
485
|
version: releaseVersion,
|
|
449
486
|
});
|
|
450
487
|
}
|
|
451
488
|
const fingerprint = artifactFingerprintOf(root);
|
|
452
|
-
const resumeMatchesTree =
|
|
489
|
+
const resumeMatchesTree =
|
|
490
|
+
resume && frozenVersion !== undefined && (indexOnly || journal?.run?.fingerprint === fingerprint);
|
|
453
491
|
const tracking = !verify && !dryRun;
|
|
454
|
-
if (tracking) {
|
|
492
|
+
if (tracking && !indexOnly) {
|
|
455
493
|
startRun(home, unit, {
|
|
456
494
|
target,
|
|
457
495
|
name: manifest.name,
|
|
@@ -686,7 +724,7 @@ export async function runRelease({
|
|
|
686
724
|
}
|
|
687
725
|
const earlyLicense = info.kind === 'registry' ? licenseOf(root, manifest, target, args) : undefined;
|
|
688
726
|
if (info.kind === 'registry') checkLogin(exec, info);
|
|
689
|
-
const changelog = dryRun
|
|
727
|
+
const changelog = dryRun || indexOnly
|
|
690
728
|
? undefined
|
|
691
729
|
: applyChangelog(root, {
|
|
692
730
|
version: releaseVersion,
|
|
@@ -694,7 +732,7 @@ export async function runRelease({
|
|
|
694
732
|
note: changelogNote,
|
|
695
733
|
date: changelogDate(),
|
|
696
734
|
});
|
|
697
|
-
if (!dryRun && changelog === undefined) {
|
|
735
|
+
if (!indexOnly && !dryRun && changelog === undefined) {
|
|
698
736
|
const problem = changelogProblemOf(root, releaseVersion);
|
|
699
737
|
throw new NeedsUser(
|
|
700
738
|
'changelog',
|
|
@@ -739,6 +777,13 @@ export async function runRelease({
|
|
|
739
777
|
const next = { ...manifest, version };
|
|
740
778
|
const cardTitle = cardTitleFromMessages(root, unit);
|
|
741
779
|
const titled = cardTitle === undefined ? next : { ...next, title: cardTitle };
|
|
780
|
+
if (workspaceOnly) {
|
|
781
|
+
writeManifest(root, next);
|
|
782
|
+
if (tracking) advanceRun(home, unit, { phase: 'workspace', fingerprint: artifactFingerprintOf(root) });
|
|
783
|
+
const stopSummary = stopSummaryOf(target, next, version, changesOf(version), changelogReport, labels);
|
|
784
|
+
emit(out, json, stopSummary);
|
|
785
|
+
return stopSummary;
|
|
786
|
+
}
|
|
742
787
|
const index = updateMarketIndex({
|
|
743
788
|
plan: indexPlan,
|
|
744
789
|
root,
|
|
@@ -782,11 +827,11 @@ export async function runRelease({
|
|
|
782
827
|
...(index.branch === undefined ? {} : { branch: index.branch }),
|
|
783
828
|
});
|
|
784
829
|
}
|
|
785
|
-
writeManifest(root, next);
|
|
830
|
+
if (!indexOnly) writeManifest(root, next);
|
|
786
831
|
const path = index.path ?? marketArtifactPath(manifest.name);
|
|
787
832
|
const install = marketInstallOf(index.url, index.commit, path);
|
|
788
833
|
const journalNow = tracking ? readState(home, unit)?.run : undefined;
|
|
789
|
-
|
|
834
|
+
writeStateKeepingRun(home, unit, {
|
|
790
835
|
target: 'market',
|
|
791
836
|
name: next.name,
|
|
792
837
|
version,
|
|
@@ -828,17 +873,22 @@ export async function runRelease({
|
|
|
828
873
|
}
|
|
829
874
|
if (!resumeMatchesTree) runCliStage(exec, root, ['check'], stageOptions);
|
|
830
875
|
const next = { ...manifest, version };
|
|
831
|
-
writeManifest(root, next);
|
|
876
|
+
if (!indexOnly) writeManifest(root, next);
|
|
832
877
|
if (tracking) advanceRun(home, unit, { phase: 'version', version, fingerprint: artifactFingerprintOf(root) });
|
|
833
|
-
|
|
878
|
+
writeStateKeepingRun(home, unit, stateOf(info, next, version, 'intent'));
|
|
879
|
+
if (workspaceOnly) {
|
|
880
|
+
const stopSummary = stopSummaryOf(target, next, version, changesOf(version), changelogReport, labels);
|
|
881
|
+
emit(out, json, stopSummary);
|
|
882
|
+
return stopSummary;
|
|
883
|
+
}
|
|
834
884
|
let packed = false;
|
|
835
885
|
try {
|
|
836
886
|
const file = packTarball(exec, info, version);
|
|
837
887
|
packed = true;
|
|
838
|
-
|
|
888
|
+
writeStateKeepingRun(home, unit, { ...stateOf(info, next, version, 'published'), file });
|
|
839
889
|
const packedFiles = localFiles(exec, root);
|
|
840
890
|
checkPublished(packedFiles);
|
|
841
|
-
|
|
891
|
+
writeStateKeepingRun(home, unit, { ...stateOf(info, next, version, 'verified'), file });
|
|
842
892
|
if (tracking) finishRun(home, unit, { version });
|
|
843
893
|
summary.file = file;
|
|
844
894
|
summary.files = packedFiles;
|
|
@@ -847,7 +897,7 @@ export async function runRelease({
|
|
|
847
897
|
return summary;
|
|
848
898
|
} catch (error) {
|
|
849
899
|
if (!packed) {
|
|
850
|
-
writeManifest(root, manifest);
|
|
900
|
+
if (!indexOnly) writeManifest(root, manifest);
|
|
851
901
|
if (tracking) {
|
|
852
902
|
interruptRun(home, unit, { code: 'tarball', text: String(error?.message ?? error) });
|
|
853
903
|
} else {
|
|
@@ -881,19 +931,46 @@ export async function runRelease({
|
|
|
881
931
|
}
|
|
882
932
|
if (!resumeMatchesTree) runCliStage(exec, root, ['check'], stageOptions);
|
|
883
933
|
const next = { ...manifest, version };
|
|
884
|
-
writeManifest(root, next);
|
|
934
|
+
if (!indexOnly) writeManifest(root, next);
|
|
885
935
|
if (tracking) advanceRun(home, unit, { phase: 'version', version, fingerprint: artifactFingerprintOf(root) });
|
|
886
|
-
|
|
936
|
+
writeStateKeepingRun(home, unit, stateOf(info, next, version, 'intent'));
|
|
937
|
+
if (indexOnly) {
|
|
938
|
+
const state = readState(home, unit);
|
|
939
|
+
const checked = verifyGit(exec, info, version);
|
|
940
|
+
summary.files = checked.files;
|
|
941
|
+
summary.git.commit = checked.commit;
|
|
942
|
+
summary.market = indexOf(next, version, {
|
|
943
|
+
git: { url: info.url, commit: checked.commit, path: info.packagePath },
|
|
944
|
+
});
|
|
945
|
+
writeStateKeepingRun(home, unit, {
|
|
946
|
+
...stateOf(info, next, version, 'verified'),
|
|
947
|
+
commit: checked.commit,
|
|
948
|
+
...(tracking
|
|
949
|
+
? { run: { ...(state?.run ?? {}), state: 'done', phase: 'done', updatedAt: new Date().toISOString() } }
|
|
950
|
+
: {}),
|
|
951
|
+
});
|
|
952
|
+
emit(out, json, summary);
|
|
953
|
+
return summary;
|
|
954
|
+
}
|
|
887
955
|
const progress = { committed: false };
|
|
888
956
|
try {
|
|
889
957
|
const commit = gitCommitOf(exec, info, version, capture, progress, identityOf(next));
|
|
890
|
-
|
|
958
|
+
writeStateKeepingRun(home, unit, { ...stateOf(info, next, version, 'committed'), commit });
|
|
891
959
|
gitPushOf(exec, info, version, capture);
|
|
892
960
|
const checked = verifyGit(exec, info, version);
|
|
893
|
-
|
|
961
|
+
writeStateKeepingRun(home, unit, {
|
|
894
962
|
...stateOf(info, next, version, 'verified'),
|
|
895
963
|
commit: checked.commit,
|
|
896
964
|
});
|
|
965
|
+
if (workspaceOnly) {
|
|
966
|
+
const stopSummary = {
|
|
967
|
+
...stopSummaryOf(target, next, version, changesOf(version), changelogReport, labels),
|
|
968
|
+
files: checked.files,
|
|
969
|
+
git: { url: info.url, tag: info.tagOf(version), commit: checked.commit },
|
|
970
|
+
};
|
|
971
|
+
emit(out, json, stopSummary);
|
|
972
|
+
return stopSummary;
|
|
973
|
+
}
|
|
897
974
|
if (tracking) finishRun(home, unit, { version });
|
|
898
975
|
summary.files = checked.files;
|
|
899
976
|
summary.git.commit = checked.commit;
|
|
@@ -965,12 +1042,17 @@ export async function runRelease({
|
|
|
965
1042
|
emit(out, json, summary);
|
|
966
1043
|
return summary;
|
|
967
1044
|
}
|
|
968
|
-
writeManifest(root, next);
|
|
1045
|
+
if (!indexOnly) writeManifest(root, next);
|
|
969
1046
|
if (tracking) advanceRun(home, unit, { phase: 'version', version, fingerprint: artifactFingerprintOf(root) });
|
|
970
1047
|
let published = false;
|
|
971
1048
|
try {
|
|
972
1049
|
if (!resumeMatchesTree) runCliStage(exec, root, ['check'], stageOptions);
|
|
973
|
-
|
|
1050
|
+
writeStateKeepingRun(home, unit, stateOf(info, next, version, 'intent'));
|
|
1051
|
+
if (workspaceOnly) {
|
|
1052
|
+
const stopSummary = stopSummaryOf(target, next, version, changes, changelogReport, labels);
|
|
1053
|
+
emit(out, json, stopSummary);
|
|
1054
|
+
return stopSummary;
|
|
1055
|
+
}
|
|
974
1056
|
publishRegistry(exec, root, info, next, version);
|
|
975
1057
|
published = true;
|
|
976
1058
|
const files = verifyRegistry(exec, info, next.name, version);
|
|
@@ -993,14 +1075,14 @@ export async function runRelease({
|
|
|
993
1075
|
return summary;
|
|
994
1076
|
} catch (error) {
|
|
995
1077
|
if (!published && !(error instanceof NeedsUser && error.step === 'otp')) {
|
|
996
|
-
writeManifest(root, original);
|
|
1078
|
+
if (!indexOnly) writeManifest(root, original);
|
|
997
1079
|
if (tracking) {
|
|
998
1080
|
interruptRun(home, unit, { code: 'registry', text: String(error?.message ?? error) });
|
|
999
1081
|
} else {
|
|
1000
1082
|
removeState(home, unit);
|
|
1001
1083
|
}
|
|
1002
1084
|
}
|
|
1003
|
-
if (published)
|
|
1085
|
+
if (published) writeStateKeepingRun(home, unit, stateOf(info, next, version, 'published'));
|
|
1004
1086
|
throw error;
|
|
1005
1087
|
}
|
|
1006
1088
|
}
|