@dsh-workbench/module-tools 1.1.0 → 1.1.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 +13 -4
- package/bin/dsh-wb-module.mjs +6 -5
- package/package.json +2 -2
- package/src/changelog.mjs +17 -69
- package/src/market-index.mjs +172 -34
- package/src/release-changelog.mjs +0 -7
- package/src/release.mjs +132 -41
package/README.md
CHANGED
|
@@ -19,15 +19,24 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
19
19
|
最后从出口侧核包(`--dry-run` 只预览,`--verify` 只核已发布的包)。配了可写 git 索引源时,
|
|
20
20
|
发完还会更新 `market.json`(`--index auto|none|<目录|git 地址>`,`artifacts-in-repo` 连产物一起入库,
|
|
21
21
|
`catalog-only` 只更索引);索引没更上不影响包已发布这件事,结果里会如实写。
|
|
22
|
+
发布可选 `--index-source <id>` 指定发到哪个索引源;不指定时用唯一可发布的源,有多个可发布源会要求指定。
|
|
22
23
|
空仓库先跑 `dsh-wb-module market-init <git 地址>`:生成 `market.json` 与 README 并推首个 commit
|
|
23
24
|
(用仓库声明的默认分支,读不到时用 `--ref` 指定)。在工作台里配了可写索引源之后,
|
|
24
25
|
`release` 默认出口就是 `market`:产物与索引一起进那个仓库。
|
|
25
26
|
发布过程中每一步都写进 `~/.dsh/workbench/releases/<unit>.json`;中断后带 `--resume` 接着发
|
|
26
27
|
(沿用记录里的出口、版本与索引计划,产物没变就不重跑门禁)。
|
|
27
|
-
这一版在 `CHANGELOG.md`
|
|
28
|
-
`
|
|
28
|
+
这一版在 `CHANGELOG.md` 里没有记录时,带 `--changelog-note "这一版改了什么"`
|
|
29
|
+
(`--changelog-type` 选分类,缺省 changed)发一次就写进去,文件不在会先建骨架;
|
|
30
|
+
没带 note 又没记录时 `release` 在跑任何门禁之前就拦下。
|
|
31
|
+
日志写在确认之后,发失败不回滚:这一版已经记下了,下一次发布直接用。
|
|
32
|
+
`package.json` 缺 `files` 里的 `CHANGELOG.md` 一样拦下;`author` 没写时从本机 `git config` 补进 manifest,索引与产物都带上。
|
|
33
|
+
模块的 `package.json` 可用 `dsh.workbench.market` 声明 `category` 与 `labels`(字符串数组);
|
|
34
|
+
不写就不带分类和标签,索引里只留 `metadata.workbench: true` 这条身份标记。
|
|
29
35
|
要换署名时带 `--author "姓名 <邮箱>"`(或只给姓名):它写回 `package.json`,索引、产物与提交都用它,
|
|
30
|
-
`--refresh`
|
|
36
|
+
`--refresh` 时也一样。一个包名归第一次发布的人(`market.json` 里的 `owner`):换署名发同名会被拒,
|
|
37
|
+
`--refresh` 也一样 —— 真要换人,先改那一条的 `owner`。
|
|
38
|
+
同一个市场里,一个模块 id(`dsh.workbench.unit`)只能被一个条目使用;产物目录按包名走
|
|
39
|
+
(`packages/<包名>/`):换一个包名沿用旧 unit、或两个条目撞同一个 id / 目录,都会被拒。
|
|
31
40
|
|
|
32
41
|
骨架自带脚本,日常不用记长命令:`build` · `typecheck` · `test` · `selfcheck` · `check` · `lint` · `design`。
|
|
33
42
|
|
|
@@ -42,7 +51,7 @@ pnpm design check # 设计底稿有没有空格、README 有没有落后
|
|
|
42
51
|
| `dsh-wb-module lint [--fix]` | 用模块自己那份 `eslint.config.mjs`;`--fix` 写回文件 |
|
|
43
52
|
| `dsh-wb-module sync` | 按清单生成 `src/declared-tools.ts`,按 `design.json` 的 `key + recipe` 生成 `src/datasource.ts` |
|
|
44
53
|
| `dsh-wb-module design <init\|check\|render\|accept>` | 设计底稿:填、查空格、生成 README、验收打勾 |
|
|
45
|
-
| `dsh-wb-module changelog
|
|
54
|
+
| `dsh-wb-module changelog check [版本]` | 查这一版有没有记录;发布时用 `release --changelog-note` 写进去 |
|
|
46
55
|
| `dsh-wb-module publish` | 发货前清单:门禁 + `files` + `npm pack`(只 dry run) |
|
|
47
56
|
| `dsh-wb-module release` | 真发布:team / public / tarball / git;全门禁 + 验收闸 + 预检接管 + 发后核包 + 更新市场索引(`--dry-run` / `--verify`) |
|
|
48
57
|
|
package/bin/dsh-wb-module.mjs
CHANGED
|
@@ -61,8 +61,10 @@ const RELEASE_HELP = `
|
|
|
61
61
|
--registry <url> --access restricted|public --npmrc <文件>
|
|
62
62
|
--license <id> --out <目录> --git <remote>
|
|
63
63
|
--author <署名> 换署名(姓名,或 姓名 <邮箱>);写回 package.json
|
|
64
|
+
--changelog-note <文字> --changelog-type added|changed|fixed|removed|security|deprecated
|
|
65
|
+
这一版还没有更新日志时写进 CHANGELOG.md(文件不在先建)
|
|
64
66
|
--index auto|none|<目录|git 地址> 发布后要更新的索引源
|
|
65
|
-
--index-ref <分支> --index-path <索引路径> --index-policy artifacts-in-repo|catalog-only
|
|
67
|
+
--index-source <id> --index-ref <分支> --index-path <索引路径> --index-policy artifacts-in-repo|catalog-only
|
|
66
68
|
--resume 上次中断后接着发(用记录里的出口与版本)
|
|
67
69
|
--dry-run --verify --yes --json`;
|
|
68
70
|
|
|
@@ -75,11 +77,10 @@ const MARKET_INIT_HELP = `
|
|
|
75
77
|
|
|
76
78
|
const CHANGELOG_HELP = `
|
|
77
79
|
用法:
|
|
78
|
-
changelog init
|
|
79
|
-
changelog add [版本] [--type added|changed|fixed|removed|security|deprecated] [--note <文字>] [--date <日期>]
|
|
80
80
|
changelog check [版本] [--json]
|
|
81
81
|
|
|
82
|
-
版本省略时用 package.json 里的 version
|
|
82
|
+
版本省略时用 package.json 里的 version。发布时这一版必须有记录:
|
|
83
|
+
release --changelog-note "这一版改了什么"(文件不在会先建)。`;
|
|
83
84
|
|
|
84
85
|
const COMMANDS = {
|
|
85
86
|
build: '出 dist/host.js(单文件 ESM)与 client.js(套 loader 壳)',
|
|
@@ -91,7 +92,7 @@ const COMMANDS = {
|
|
|
91
92
|
release: '真发布:全门禁 + 验收闸 + 预检接管 + 发到出口 + 发后核包 + 更新市场索引(中断可 --resume)',
|
|
92
93
|
sync: '按清单生成 src/declared-tools.ts,按 design.json 生成 src/datasource.ts',
|
|
93
94
|
lint: '用模块自己那份 eslint.config.mjs;--fix 写回文件',
|
|
94
|
-
changelog: '更新日志:
|
|
95
|
+
changelog: '更新日志:check 查这一版有没有;发布时用 release --changelog-note 写进去',
|
|
95
96
|
};
|
|
96
97
|
|
|
97
98
|
function flagValue(raw, flag) {
|
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.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.1.
|
|
36
|
+
"@dsh-workbench/shared": "1.1.2"
|
|
37
37
|
},
|
|
38
38
|
"repository": {
|
|
39
39
|
"type": "git",
|
package/src/changelog.mjs
CHANGED
|
@@ -1,27 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { readFileSync, writeFileSync } from 'node:fs';
|
|
2
2
|
import { join } from 'node:path';
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
CHANGELOG_FILE,
|
|
6
|
-
CHANGELOG_KINDS,
|
|
7
|
-
appendChangelogEntry,
|
|
8
6
|
changelogDate,
|
|
9
7
|
changelogHasEntry,
|
|
10
|
-
changelogSkeleton,
|
|
11
8
|
changelogVersion,
|
|
12
9
|
isValidVersion,
|
|
10
|
+
planChangelog,
|
|
13
11
|
} from '@dsh-workbench/shared/host';
|
|
14
12
|
|
|
15
13
|
const USAGE = `用法:
|
|
16
|
-
changelog
|
|
17
|
-
|
|
18
|
-
changelog check [版本] 查这一版有没有记录(省略版本用 package.json 的)
|
|
19
|
-
版本也可以省略:用 package.json 里的 version。发布时这一版必须有记录,否则拦下。`;
|
|
20
|
-
|
|
21
|
-
function argValue(args, flag) {
|
|
22
|
-
const at = args.indexOf(flag);
|
|
23
|
-
return at === -1 ? undefined : args[at + 1];
|
|
24
|
-
}
|
|
14
|
+
changelog check [版本] 查这一版有没有记录(省略版本用 package.json 的)
|
|
15
|
+
发布时把这一版写进去:release --changelog-note "这一版改了什么"(文件不在会先建)`;
|
|
25
16
|
|
|
26
17
|
function positional(args, at) {
|
|
27
18
|
const value = args[at];
|
|
@@ -54,25 +45,18 @@ export function changelogProblemOf(root, version) {
|
|
|
54
45
|
if (text.trim() === '') {
|
|
55
46
|
return {
|
|
56
47
|
reason: `包里还没有 ${CHANGELOG_FILE} —— 更新日志要随包发布`,
|
|
57
|
-
command: 'dsh-wb-module changelog
|
|
48
|
+
command: 'dsh-wb-module release --yes --changelog-note "这一版改了什么"',
|
|
58
49
|
};
|
|
59
50
|
}
|
|
60
51
|
if (!changelogHasEntry(text, version)) {
|
|
61
52
|
return {
|
|
62
|
-
reason: `${CHANGELOG_FILE} 里没有 ${version} 这一版的内容 ——
|
|
63
|
-
command:
|
|
53
|
+
reason: `${CHANGELOG_FILE} 里没有 ${version} 这一版的内容 —— 发布时把这一版写进去`,
|
|
54
|
+
command: 'dsh-wb-module release --yes --changelog-note "这一版改了什么"',
|
|
64
55
|
};
|
|
65
56
|
}
|
|
66
57
|
return undefined;
|
|
67
58
|
}
|
|
68
59
|
|
|
69
|
-
export function initChangelog(root) {
|
|
70
|
-
const file = changelogFileOf(root);
|
|
71
|
-
if (existsSync(file)) return { file, created: false };
|
|
72
|
-
writeFileSync(file, changelogSkeleton());
|
|
73
|
-
return { file, created: true };
|
|
74
|
-
}
|
|
75
|
-
|
|
76
60
|
export class ChangelogError extends Error {
|
|
77
61
|
constructor(message, code = 2) {
|
|
78
62
|
super(message);
|
|
@@ -81,54 +65,20 @@ export class ChangelogError extends Error {
|
|
|
81
65
|
}
|
|
82
66
|
}
|
|
83
67
|
|
|
84
|
-
export function
|
|
85
|
-
const text = (note ?? '').trim();
|
|
86
|
-
if (text === '') throw new ChangelogError('changelog add 要一句 --note:这一版改了什么');
|
|
87
|
-
if (!CHANGELOG_KINDS.includes(kind)) {
|
|
88
|
-
throw new ChangelogError(`--type 只认 ${CHANGELOG_KINDS.join(' / ')}(收到「${kind}」)`);
|
|
89
|
-
}
|
|
68
|
+
export function applyChangelog(root, { version, kind = 'changed', note, date }) {
|
|
90
69
|
const wanted = changelogVersion(version === undefined || version === '' ? manifestVersionOf(root) : version);
|
|
91
70
|
if (!isValidVersion(wanted)) {
|
|
92
71
|
throw new ChangelogError(`版本要写成 x.y.z(收到「${String(version)}」)—— 也可以省略,用 package.json 里的`);
|
|
93
72
|
}
|
|
94
|
-
const
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
console.log(JSON.stringify({ ok: true, ...result }));
|
|
104
|
-
return;
|
|
105
|
-
}
|
|
106
|
-
if (!result.created) {
|
|
107
|
-
console.log(`${CHANGELOG_FILE} 已经在:${result.file} —— 没动它`);
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
console.log(`✔ 建好了 ${result.file}`);
|
|
111
|
-
console.log(` 下一步:dsh-wb-module changelog add ${manifestVersionOf(root) || '<版本>'} --note "这一版改了什么"`);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
function runAdd(root, args) {
|
|
115
|
-
const version = positional(args, 1) ?? manifestVersionOf(root);
|
|
116
|
-
const kind = argValue(args, '--type') ?? 'changed';
|
|
117
|
-
const note = argValue(args, '--note') ?? '';
|
|
118
|
-
const date = (argValue(args, '--date') ?? changelogDate()).trim();
|
|
119
|
-
let result;
|
|
120
|
-
try {
|
|
121
|
-
result = addChangelog(root, { version, kind, note, date });
|
|
122
|
-
} catch (error) {
|
|
123
|
-
if (!(error instanceof ChangelogError)) throw error;
|
|
124
|
-
console.error(error.message);
|
|
125
|
-
process.exit(error.code);
|
|
126
|
-
}
|
|
127
|
-
if (args.includes('--json')) {
|
|
128
|
-
console.log(JSON.stringify({ ok: true, ...result, date }));
|
|
129
|
-
return;
|
|
130
|
-
}
|
|
131
|
-
console.log(`✔ ${CHANGELOG_FILE} 已记下 ${result.version}(${result.kind})`);
|
|
73
|
+
const plan = planChangelog(readChangelogText(root), {
|
|
74
|
+
version: wanted,
|
|
75
|
+
kind,
|
|
76
|
+
note: note ?? '',
|
|
77
|
+
date: date ?? changelogDate(),
|
|
78
|
+
});
|
|
79
|
+
if (plan === undefined) return undefined;
|
|
80
|
+
if (plan.action === 'written') writeFileSync(changelogFileOf(root), plan.text);
|
|
81
|
+
return { ...plan, file: changelogFileOf(root) };
|
|
132
82
|
}
|
|
133
83
|
|
|
134
84
|
function runCheck(root, args) {
|
|
@@ -157,8 +107,6 @@ function runCheck(root, args) {
|
|
|
157
107
|
|
|
158
108
|
export async function changelog({ root, args = [] }) {
|
|
159
109
|
const sub = args[0];
|
|
160
|
-
if (sub === 'init') return runInit(root, args);
|
|
161
|
-
if (sub === 'add') return runAdd(root, args);
|
|
162
110
|
if (sub === 'check') return runCheck(root, args);
|
|
163
111
|
console.error(`不认识的 changelog 子命令:${String(sub)}`);
|
|
164
112
|
console.error(USAGE);
|
package/src/market-index.mjs
CHANGED
|
@@ -5,15 +5,16 @@ import { isDeepStrictEqual } from 'node:util';
|
|
|
5
5
|
import {
|
|
6
6
|
MARKET_INDEX_README_LINES,
|
|
7
7
|
MARKET_INDEX_SEED,
|
|
8
|
+
claimOf,
|
|
8
9
|
gitUrlOk,
|
|
10
|
+
marketArtifactPath,
|
|
9
11
|
marketSourceKey,
|
|
10
12
|
parseMarketIndexJson,
|
|
11
13
|
upsertMarketEntry,
|
|
12
14
|
} from '@dsh-workbench/shared/host';
|
|
13
15
|
|
|
14
16
|
const MARKET_FILE = 'market.json';
|
|
15
|
-
const
|
|
16
|
-
const WORKBENCH_LABEL = 'workbench';
|
|
17
|
+
const SAFE_PACKAGE_NAME = /^(?:@[a-z0-9][a-z0-9._-]*\/)?[a-z0-9][a-z0-9._-]*$/;
|
|
17
18
|
const WORKSPACE_PREFIX = 'workspace:';
|
|
18
19
|
const ARTIFACT_DIRS = ['dist'];
|
|
19
20
|
const ARTIFACT_FILES = ['client.js', 'cordis.patch.yml', 'README.md', 'CHANGELOG.md'];
|
|
@@ -51,9 +52,8 @@ function workbenchRootOf(home) {
|
|
|
51
52
|
return explicit === undefined || explicit === '' ? join(home, 'workbench') : explicit;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
|
-
function
|
|
55
|
-
|
|
56
|
-
return fallback;
|
|
55
|
+
function statedPolicy(value) {
|
|
56
|
+
return value === 'artifacts-in-repo' || value === 'catalog-only' ? value : undefined;
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
export function sanitizeManifest(manifest) {
|
|
@@ -93,6 +93,54 @@ function authorOf(manifest) {
|
|
|
93
93
|
return '';
|
|
94
94
|
}
|
|
95
95
|
|
|
96
|
+
export function authorTextOf(manifest) {
|
|
97
|
+
const { name, email } = authorParts(manifest);
|
|
98
|
+
if (name === '') return '';
|
|
99
|
+
return email === '' ? name : `${name} <${email}>`;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function claimProblemText(name, claim, unit) {
|
|
103
|
+
if (claim.state === 'unit-taken') {
|
|
104
|
+
return (
|
|
105
|
+
`市场里 ${claim.entry} 已经用了模块 id ${unit} —— 两个模块不能共用一个 id,装在一起会撞。` +
|
|
106
|
+
'给这个模块换一个 unit(dsh.workbench.unit)再发;那个条目如果已经废弃,让索引维护者删掉或给它换一个 unit。'
|
|
107
|
+
);
|
|
108
|
+
}
|
|
109
|
+
if (claim.state === 'path-taken') {
|
|
110
|
+
return (
|
|
111
|
+
`市场仓库里的 ${marketArtifactPath(name)} 已经被 ${claim.entry} 占了 —— 一个产物目录只能放一个包。` +
|
|
112
|
+
'换一个包名再发;那个条目如果已经废弃,让索引维护者先处理掉。'
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return (
|
|
116
|
+
`市场里 ${name} 已经有主了(${claim.owner ?? '未署名'}),同一个名字不能再由别人发。` +
|
|
117
|
+
'要发就换个带团队 scope 的包名;你就是原来的作者(只是换了机器或署名),用 author 参数带上原来的署名再发一次(CLI 是 --author);' +
|
|
118
|
+
'确实是接手,让索引维护者把这一条的 owner 改成你。'
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
export function missingPublisherText() {
|
|
123
|
+
return '市场里一个名字要有主人:给 package.json 补上 author,或用 author 参数传一个署名(CLI 是 --author),再发。';
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function unitOf(manifest) {
|
|
127
|
+
const unit = manifest?.dsh?.workbench?.unit;
|
|
128
|
+
return typeof unit === 'string' ? unit.trim() : '';
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function claimOfIndexFile(workdir, indexPath, input) {
|
|
132
|
+
const file = join(workdir, indexPath);
|
|
133
|
+
if (!existsSync(file)) return { state: 'new' };
|
|
134
|
+
const parsed = parseMarketIndexJson(readFileSync(file, 'utf8'));
|
|
135
|
+
if (!parsed.ok) {
|
|
136
|
+
throw new MarketIndexError(
|
|
137
|
+
`${indexPath} 不合 format 1:${parsed.problems.map((one) => one.detail).join(';')}`,
|
|
138
|
+
'index-write',
|
|
139
|
+
);
|
|
140
|
+
}
|
|
141
|
+
return claimOf(parsed.index, input);
|
|
142
|
+
}
|
|
143
|
+
|
|
96
144
|
export function identityOf(manifest) {
|
|
97
145
|
const { name, email } = authorParts(manifest);
|
|
98
146
|
if (name === '') return undefined;
|
|
@@ -117,13 +165,22 @@ function titleOf(manifest) {
|
|
|
117
165
|
return { ...(zh === '' ? {} : { zh }), ...(en === '' ? {} : { en }) };
|
|
118
166
|
}
|
|
119
167
|
|
|
168
|
+
function textListOf(value) {
|
|
169
|
+
return Array.isArray(value)
|
|
170
|
+
? [...new Set(value.flatMap((one) => (typeof one === 'string' && one.trim() !== '' ? [one.trim()] : [])))]
|
|
171
|
+
: [];
|
|
172
|
+
}
|
|
173
|
+
|
|
120
174
|
export function entryMetaOf(manifest) {
|
|
121
175
|
const author = authorOf(manifest);
|
|
122
176
|
const homepage = typeof manifest.homepage === 'string' ? manifest.homepage.trim() : '';
|
|
123
|
-
const keywords =
|
|
124
|
-
|
|
125
|
-
|
|
177
|
+
const keywords = textListOf(manifest.keywords);
|
|
178
|
+
const market = manifest.dsh?.workbench?.market;
|
|
179
|
+
const marketRecord = market !== null && typeof market === 'object' && !Array.isArray(market) ? market : {};
|
|
180
|
+
const category = typeof marketRecord.category === 'string' ? marketRecord.category.trim() : '';
|
|
181
|
+
const labels = textListOf(marketRecord.labels);
|
|
126
182
|
const order = manifest.dsh?.workbench?.card?.order;
|
|
183
|
+
const unit = unitOf(manifest);
|
|
127
184
|
const title = titleOf(manifest);
|
|
128
185
|
const description = descriptionOf(manifest);
|
|
129
186
|
return {
|
|
@@ -132,8 +189,10 @@ export function entryMetaOf(manifest) {
|
|
|
132
189
|
...(author === '' ? {} : { author }),
|
|
133
190
|
...(homepage === '' ? {} : { homepage }),
|
|
134
191
|
metadata: {
|
|
135
|
-
|
|
136
|
-
|
|
192
|
+
workbench: true,
|
|
193
|
+
...(unit === '' ? {} : { unit }),
|
|
194
|
+
...(category === '' ? {} : { category }),
|
|
195
|
+
...(labels.length === 0 ? {} : { labels }),
|
|
137
196
|
...(keywords.length === 0 ? {} : { keywords }),
|
|
138
197
|
...(typeof order === 'number' ? { sortKeys: { order } } : {}),
|
|
139
198
|
},
|
|
@@ -143,8 +202,10 @@ export function entryMetaOf(manifest) {
|
|
|
143
202
|
export function indexEntryOf(input) {
|
|
144
203
|
const manifest = input.manifest;
|
|
145
204
|
const dsh = manifest.engines?.dsh;
|
|
205
|
+
const owner = authorTextOf(manifest);
|
|
146
206
|
return {
|
|
147
207
|
name: manifest.name,
|
|
208
|
+
...(owner === '' ? {} : { owner }),
|
|
148
209
|
...entryMetaOf(manifest),
|
|
149
210
|
versions: [
|
|
150
211
|
{
|
|
@@ -179,7 +240,7 @@ function readSources(file) {
|
|
|
179
240
|
}
|
|
180
241
|
}
|
|
181
242
|
|
|
182
|
-
function planOf(raw, args, fallbackPolicy) {
|
|
243
|
+
function planOf(raw, args, fallbackPolicy, origin) {
|
|
183
244
|
if (raw === null || typeof raw !== 'object') return undefined;
|
|
184
245
|
if (raw.kind !== 'git') return undefined;
|
|
185
246
|
const url = typeof raw.url === 'string' ? raw.url.trim() : '';
|
|
@@ -188,7 +249,8 @@ function planOf(raw, args, fallbackPolicy) {
|
|
|
188
249
|
if (url !== '' && !gitUrlOk(url)) return undefined;
|
|
189
250
|
const ref = (argValue(args, '--index-ref') ?? (typeof raw.ref === 'string' ? raw.ref : '')).trim();
|
|
190
251
|
const path = (argValue(args, '--index-path') ?? (typeof raw.path === 'string' ? raw.path : '')).trim();
|
|
191
|
-
const
|
|
252
|
+
const fromArgument = statedPolicy(argValue(args, '--index-policy'));
|
|
253
|
+
const fromSource = statedPolicy(raw.policy);
|
|
192
254
|
return {
|
|
193
255
|
source: {
|
|
194
256
|
kind: 'git',
|
|
@@ -197,7 +259,9 @@ function planOf(raw, args, fallbackPolicy) {
|
|
|
197
259
|
...(path === '' ? {} : { path }),
|
|
198
260
|
...(localPath === '' ? {} : { localPath }),
|
|
199
261
|
},
|
|
200
|
-
policy,
|
|
262
|
+
policy: fromArgument ?? fromSource ?? fallbackPolicy,
|
|
263
|
+
policyStated: fromArgument !== undefined || fromSource !== undefined,
|
|
264
|
+
origin,
|
|
201
265
|
};
|
|
202
266
|
}
|
|
203
267
|
|
|
@@ -234,7 +298,7 @@ function planFromArgument(asked, args, exec) {
|
|
|
234
298
|
if (existsSync(asDir)) {
|
|
235
299
|
const remote = exec('git', ['-C', asDir, 'remote', 'get-url', 'origin'], { capture: true, allowFailure: true });
|
|
236
300
|
const url = remote.status === 0 ? remote.stdout.trim() : '';
|
|
237
|
-
const plan = planOf({ kind: 'git', url, localPath: asDir }, args, 'artifacts-in-repo');
|
|
301
|
+
const plan = planOf({ kind: 'git', url, localPath: asDir }, args, 'artifacts-in-repo', 'argument');
|
|
238
302
|
if (plan === undefined) {
|
|
239
303
|
throw new MarketIndexError(`--index 指的目录不是 git 仓库或没有 origin:${asDir}`, 'index-write');
|
|
240
304
|
}
|
|
@@ -243,13 +307,45 @@ function planFromArgument(asked, args, exec) {
|
|
|
243
307
|
if (!gitUrlOk(asked)) {
|
|
244
308
|
throw new MarketIndexError(`--index 只认 none / 目录 / git 地址(收到「${asked}」)`, 'index-write');
|
|
245
309
|
}
|
|
246
|
-
return planOf({ kind: 'git', url: asked }, args, 'catalog-only');
|
|
310
|
+
return planOf({ kind: 'git', url: asked }, args, 'catalog-only', 'argument');
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
function findBySourceId(payload, id) {
|
|
314
|
+
const found = payload.sources.find((one) => one?.id === id);
|
|
315
|
+
if (found === undefined) {
|
|
316
|
+
const known = payload.sources.map((one) => one?.id).filter((one) => typeof one === 'string').join('、') || '(空)';
|
|
317
|
+
throw new MarketIndexError(`没有这个索引源:${id}。现有的:${known}`, 'index-write');
|
|
318
|
+
}
|
|
319
|
+
if (found.kind !== 'git') {
|
|
320
|
+
throw new MarketIndexError(`索引源 ${id} 是 http 地址,只能读,不能当发布目的地`, 'index-write');
|
|
321
|
+
}
|
|
322
|
+
return found;
|
|
247
323
|
}
|
|
248
324
|
|
|
249
325
|
function planFromSources(file, args) {
|
|
250
326
|
const payload = readSources(file);
|
|
251
|
-
const
|
|
252
|
-
|
|
327
|
+
const asked = (argValue(args, '--index-source') ?? '').trim();
|
|
328
|
+
let found;
|
|
329
|
+
if (asked !== '') {
|
|
330
|
+
found = findBySourceId(payload, asked);
|
|
331
|
+
} else {
|
|
332
|
+
const publishable = payload.sources.filter((one) => one?.kind === 'git' && one.policy === 'artifacts-in-repo');
|
|
333
|
+
if (publishable.length === 1) {
|
|
334
|
+
found = publishable[0];
|
|
335
|
+
} else if (publishable.length > 1) {
|
|
336
|
+
found = publishable.find((one) => one.id === payload.selected);
|
|
337
|
+
if (found === undefined) {
|
|
338
|
+
const ids = publishable.map((one) => one.id).join('、');
|
|
339
|
+
throw new MarketIndexError(`有多个可发布的索引源(${ids})—— 用 --index-source 指定发到哪一个`, 'index-write');
|
|
340
|
+
}
|
|
341
|
+
} else {
|
|
342
|
+
found = payload.sources.find((one) => one?.id === payload.selected);
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
const plan = planOf(found ?? null, args, 'catalog-only', 'sources');
|
|
346
|
+
return plan === undefined || found === undefined || found === null || typeof found.id !== 'string'
|
|
347
|
+
? plan
|
|
348
|
+
: { ...plan, sourceId: found.id };
|
|
253
349
|
}
|
|
254
350
|
|
|
255
351
|
function runGit(exec, cwd, argv, { allowFailure = false } = {}) {
|
|
@@ -331,25 +427,39 @@ function remoteHeadOf(exec, workdir, branch) {
|
|
|
331
427
|
return line.split(/\s+/)[0] ?? '';
|
|
332
428
|
}
|
|
333
429
|
|
|
334
|
-
function copyArtifacts(root, workdir,
|
|
335
|
-
|
|
430
|
+
function copyArtifacts(root, workdir, name, manifest) {
|
|
431
|
+
if (!SAFE_PACKAGE_NAME.test(name)) {
|
|
432
|
+
throw new MarketIndexError(`包名不是合法 npm 名,没法当产物目录:${JSON.stringify(name)}`, 'index-write');
|
|
433
|
+
}
|
|
434
|
+
const path = marketArtifactPath(name);
|
|
435
|
+
const dest = join(workdir, path);
|
|
336
436
|
rmSync(dest, { recursive: true, force: true });
|
|
337
437
|
mkdirSync(dest, { recursive: true });
|
|
338
438
|
for (const dir of ARTIFACT_DIRS) {
|
|
339
439
|
const src = join(root, dir);
|
|
340
440
|
if (existsSync(src)) cpSync(src, join(dest, dir), { recursive: true });
|
|
341
441
|
}
|
|
342
|
-
for (const
|
|
343
|
-
const src = join(root,
|
|
344
|
-
if (existsSync(src)) copyFileSync(src, join(dest,
|
|
442
|
+
for (const file of ARTIFACT_FILES) {
|
|
443
|
+
const src = join(root, file);
|
|
444
|
+
if (existsSync(src)) copyFileSync(src, join(dest, file));
|
|
345
445
|
}
|
|
346
446
|
writeFileSync(join(dest, 'package.json'), `${JSON.stringify(sanitizeManifest(manifest), null, 2)}\n`);
|
|
347
|
-
return
|
|
447
|
+
return path;
|
|
348
448
|
}
|
|
349
449
|
|
|
350
|
-
function writeEntry(workdir, indexPath, entry) {
|
|
450
|
+
function writeEntry(workdir, indexPath, entry, publisher, unit) {
|
|
351
451
|
const file = join(workdir, indexPath);
|
|
352
452
|
const current = existsSync(file) ? readFileSync(file, 'utf8') : '';
|
|
453
|
+
if (publisher === '') throw new MarketIndexError(missingPublisherText(), 'market-owner');
|
|
454
|
+
const claim = claimOfIndexFile(workdir, indexPath, {
|
|
455
|
+
name: entry.name,
|
|
456
|
+
publisher,
|
|
457
|
+
unit,
|
|
458
|
+
artifactPath: entry.versions[0]?.source?.type === 'git' ? entry.versions[0].source.path : undefined,
|
|
459
|
+
});
|
|
460
|
+
if (claim.state === 'conflict' || claim.state === 'unit-taken' || claim.state === 'path-taken') {
|
|
461
|
+
throw new MarketIndexError(claimProblemText(entry.name, claim, unit), 'market-owner');
|
|
462
|
+
}
|
|
353
463
|
const merged = mergeIndexText(current, entry);
|
|
354
464
|
if (!merged.ok) {
|
|
355
465
|
throw new MarketIndexError(
|
|
@@ -385,8 +495,20 @@ function publishSourceOf(publish) {
|
|
|
385
495
|
return undefined;
|
|
386
496
|
}
|
|
387
497
|
|
|
498
|
+
export function marketClaim({ plan, exec, home, name, unit, publisher }) {
|
|
499
|
+
const workdir = worktreeOf(plan, exec, home);
|
|
500
|
+
return claimOfIndexFile(workdir, plan.source.path ?? MARKET_FILE, {
|
|
501
|
+
name,
|
|
502
|
+
publisher,
|
|
503
|
+
unit,
|
|
504
|
+
artifactPath: marketArtifactPath(name),
|
|
505
|
+
});
|
|
506
|
+
}
|
|
507
|
+
|
|
388
508
|
export function updateMarketIndex(options) {
|
|
389
|
-
const { plan, exec, home, root, manifest,
|
|
509
|
+
const { plan, exec, home, root, manifest, version, publish, now, resume, onStep } = options;
|
|
510
|
+
const unit = options.unit ?? unitOf(manifest);
|
|
511
|
+
const publisher = options.publisher ?? authorTextOf(manifest);
|
|
390
512
|
const entryName = `${manifest.name}@${version}`;
|
|
391
513
|
let base = { kind: 'git', url: plan.source.url, entry: entryName };
|
|
392
514
|
let phase = 'start';
|
|
@@ -403,7 +525,9 @@ export function updateMarketIndex(options) {
|
|
|
403
525
|
const indexPath = plan.source.path ?? MARKET_FILE;
|
|
404
526
|
if (resume !== undefined && plan.source.localPath === undefined) {
|
|
405
527
|
runGit(exec, workdir, ['reset', '--hard', 'HEAD'], { allowFailure: true });
|
|
406
|
-
runGit(exec, workdir, ['clean', '-fd', '--',
|
|
528
|
+
runGit(exec, workdir, ['clean', '-fd', '--', marketArtifactPath(manifest.name), indexPath], {
|
|
529
|
+
allowFailure: true,
|
|
530
|
+
});
|
|
407
531
|
} else {
|
|
408
532
|
assertClean(exec, workdir);
|
|
409
533
|
}
|
|
@@ -422,11 +546,21 @@ export function updateMarketIndex(options) {
|
|
|
422
546
|
};
|
|
423
547
|
}
|
|
424
548
|
}
|
|
549
|
+
if (publisher === '') throw new MarketIndexError(missingPublisherText(), 'market-owner');
|
|
550
|
+
const claim = claimOfIndexFile(workdir, indexPath, {
|
|
551
|
+
name: manifest.name,
|
|
552
|
+
publisher,
|
|
553
|
+
unit,
|
|
554
|
+
artifactPath: marketArtifactPath(manifest.name),
|
|
555
|
+
});
|
|
556
|
+
if (claim.state === 'conflict' || claim.state === 'unit-taken' || claim.state === 'path-taken') {
|
|
557
|
+
throw new MarketIndexError(claimProblemText(manifest.name, claim, unit), 'market-owner');
|
|
558
|
+
}
|
|
425
559
|
let source;
|
|
426
560
|
let artifactPath;
|
|
427
561
|
if (plan.policy === 'artifacts-in-repo') {
|
|
428
562
|
report({ phase: 'artifacts' });
|
|
429
|
-
artifactPath = copyArtifacts(root, workdir,
|
|
563
|
+
artifactPath = copyArtifacts(root, workdir, manifest.name, manifest);
|
|
430
564
|
report({ phase: 'artifacts-copied' });
|
|
431
565
|
const sha = commitOf(exec, workdir, `chore(release): ${entryName}`, identityOf(manifest));
|
|
432
566
|
report({ phase: 'artifacts-committed', artifactCommit: sha });
|
|
@@ -448,7 +582,7 @@ export function updateMarketIndex(options) {
|
|
|
448
582
|
}
|
|
449
583
|
const entry = indexEntryOf({ manifest, version, source, at: now() });
|
|
450
584
|
report({ phase: 'index' });
|
|
451
|
-
writeEntry(workdir, indexPath, entry);
|
|
585
|
+
writeEntry(workdir, indexPath, entry, publisher, unit);
|
|
452
586
|
let indexSha = commitOf(exec, workdir, `chore(market): ${entryName}`, identityOf(manifest));
|
|
453
587
|
report({ phase: 'index-committed', indexCommit: indexSha });
|
|
454
588
|
const pushed = pushOf(exec, workdir, branch);
|
|
@@ -460,7 +594,7 @@ export function updateMarketIndex(options) {
|
|
|
460
594
|
`模块已进仓库、索引没更新(本地 commit ${indexSha}):${tailOf(`${pushed.stdout}\n${pushed.stderr}`)}`,
|
|
461
595
|
);
|
|
462
596
|
}
|
|
463
|
-
writeEntry(workdir, indexPath, entry);
|
|
597
|
+
writeEntry(workdir, indexPath, entry, publisher, unit);
|
|
464
598
|
indexSha = commitOf(exec, workdir, `chore(market): ${entryName}`, identityOf(manifest));
|
|
465
599
|
report({ phase: 'index-committed', indexCommit: indexSha });
|
|
466
600
|
const retried = pushOf(exec, workdir, branch);
|
|
@@ -502,11 +636,14 @@ export function updateMarketIndex(options) {
|
|
|
502
636
|
|
|
503
637
|
export function refreshMarketEntry(options) {
|
|
504
638
|
const { plan, exec, home, manifest } = options;
|
|
639
|
+
const publisher = options.publisher ?? authorTextOf(manifest);
|
|
640
|
+
const unit = options.unit ?? unitOf(manifest);
|
|
505
641
|
let base = { kind: 'git', url: plan.source.url, entry: manifest.name };
|
|
506
642
|
let phase = 'start';
|
|
507
643
|
try {
|
|
508
644
|
const workdir = worktreeOf(plan, exec, home);
|
|
509
645
|
const indexPath = plan.source.path ?? MARKET_FILE;
|
|
646
|
+
if (publisher === '') throw new MarketIndexError(missingPublisherText(), 'market-owner');
|
|
510
647
|
assertClean(exec, workdir);
|
|
511
648
|
const branch = plan.source.ref ?? currentBranch(exec, workdir);
|
|
512
649
|
base = { ...base, branch };
|
|
@@ -533,7 +670,7 @@ export function refreshMarketEntry(options) {
|
|
|
533
670
|
if (isDeepStrictEqual(next, existing)) {
|
|
534
671
|
return { ...base, updated: false, unchanged: true, version: latest, phase: 'done' };
|
|
535
672
|
}
|
|
536
|
-
writeEntry(workdir, indexPath, next);
|
|
673
|
+
writeEntry(workdir, indexPath, next, publisher, unit);
|
|
537
674
|
let indexSha = commitOf(exec, workdir, `chore(market): refresh ${manifest.name}`, identityOf(manifest));
|
|
538
675
|
const pushed = pushOf(exec, workdir, branch);
|
|
539
676
|
if (pushed.status !== 0) {
|
|
@@ -541,7 +678,7 @@ export function refreshMarketEntry(options) {
|
|
|
541
678
|
if (!replayed) {
|
|
542
679
|
throw new MarketIndexError(`元数据没推上去(本地 commit ${indexSha}):${tailOf(pushed.stderr)}`, 'index-write');
|
|
543
680
|
}
|
|
544
|
-
writeEntry(workdir, indexPath, next);
|
|
681
|
+
writeEntry(workdir, indexPath, next, publisher, unit);
|
|
545
682
|
indexSha = commitOf(exec, workdir, `chore(market): refresh ${manifest.name}`, identityOf(manifest));
|
|
546
683
|
const retried = pushOf(exec, workdir, branch);
|
|
547
684
|
if (retried.status !== 0) {
|
|
@@ -666,7 +803,7 @@ export function marketVersionExists({ plan, exec, home, name, version }) {
|
|
|
666
803
|
}
|
|
667
804
|
}
|
|
668
805
|
|
|
669
|
-
export function verifyMarketArtifact({ plan, exec, home,
|
|
806
|
+
export function verifyMarketArtifact({ plan, exec, home, name, path, commit }) {
|
|
670
807
|
const workdir = worktreeOf(plan, exec, home);
|
|
671
808
|
const found = runGit(exec, workdir, ['cat-file', '-e', `${commit}^{commit}`], { allowFailure: true });
|
|
672
809
|
if (found.status !== 0) {
|
|
@@ -676,13 +813,14 @@ export function verifyMarketArtifact({ plan, exec, home, unit, commit }) {
|
|
|
676
813
|
if (tree.status !== 0) {
|
|
677
814
|
throw new MarketIndexError(`读不了 commit ${commit} 的内容:${tailOf(tree.stderr)}`, 'market-verify');
|
|
678
815
|
}
|
|
679
|
-
const
|
|
816
|
+
const artifactPath = typeof path === 'string' && path !== '' ? path : marketArtifactPath(name);
|
|
817
|
+
const prefix = `${artifactPath}/`;
|
|
680
818
|
const files = tree.stdout
|
|
681
819
|
.split('\n')
|
|
682
820
|
.map((one) => one.trim())
|
|
683
821
|
.filter((one) => one.startsWith(prefix))
|
|
684
822
|
.map((one) => one.slice(prefix.length));
|
|
685
|
-
return { path:
|
|
823
|
+
return { path: artifactPath, files };
|
|
686
824
|
}
|
|
687
825
|
|
|
688
826
|
export { MARKET_FILE };
|
|
@@ -1,5 +1,3 @@
|
|
|
1
|
-
import { changelogProblemOf } from './changelog.mjs';
|
|
2
|
-
|
|
3
1
|
function manifestAuthor(manifest) {
|
|
4
2
|
const author = manifest?.author;
|
|
5
3
|
if (typeof author === 'string') return author.trim();
|
|
@@ -22,8 +20,3 @@ export function authorChangeOf(root, manifest, exec) {
|
|
|
22
20
|
change: 'author ← git config',
|
|
23
21
|
};
|
|
24
22
|
}
|
|
25
|
-
|
|
26
|
-
export function changelogGateOf(root, version) {
|
|
27
|
-
const problem = changelogProblemOf(root, version);
|
|
28
|
-
return problem === undefined ? { ok: true } : { ok: false, ...problem };
|
|
29
|
-
}
|
package/src/release.mjs
CHANGED
|
@@ -17,7 +17,9 @@ import {
|
|
|
17
17
|
writeState,
|
|
18
18
|
} from './release-journal.mjs';
|
|
19
19
|
import { emit, installOf, marketInstallOf, stateOf } from './release-report.mjs';
|
|
20
|
-
import { authorChangeOf
|
|
20
|
+
import { authorChangeOf } from './release-changelog.mjs';
|
|
21
|
+
import { CHANGELOG_FILE, CHANGELOG_KINDS, changelogDate, marketArtifactPath } from '@dsh-workbench/shared/host';
|
|
22
|
+
import { applyChangelog, changelogProblemOf } from './changelog.mjs';
|
|
21
23
|
import {
|
|
22
24
|
NeedsUser,
|
|
23
25
|
ReleaseError,
|
|
@@ -47,8 +49,12 @@ import {
|
|
|
47
49
|
} from './release-publish.mjs';
|
|
48
50
|
import {
|
|
49
51
|
MarketIndexError,
|
|
52
|
+
authorTextOf,
|
|
53
|
+
claimProblemText,
|
|
50
54
|
identityOf,
|
|
55
|
+
marketClaim,
|
|
51
56
|
marketVersionExists,
|
|
57
|
+
missingPublisherText,
|
|
52
58
|
refreshMarketEntry,
|
|
53
59
|
resolveIndexPlan,
|
|
54
60
|
updateMarketIndex,
|
|
@@ -251,6 +257,11 @@ export async function runRelease({
|
|
|
251
257
|
if (askedAuthor !== undefined && (explicitAuthor === '' || /[\r\n]/u.test(explicitAuthor))) {
|
|
252
258
|
throw new ReleaseError('--author 只认一行署名:姓名,或 姓名 <邮箱>');
|
|
253
259
|
}
|
|
260
|
+
const changelogNote = (argValue(args, '--changelog-note') ?? '').trim();
|
|
261
|
+
const changelogKind = (argValue(args, '--changelog-type') ?? 'changed').trim();
|
|
262
|
+
if (!CHANGELOG_KINDS.includes(changelogKind)) {
|
|
263
|
+
throw new ReleaseError(`--changelog-type 只认 ${CHANGELOG_KINDS.join(' / ')}(收到「${changelogKind}」)`);
|
|
264
|
+
}
|
|
254
265
|
const manifest = readManifest(root);
|
|
255
266
|
const unit = manifest?.dsh?.workbench?.unit;
|
|
256
267
|
if (typeof unit !== 'string' || unit === '') throw new ReleaseError('这不是一个 wb 模块包(没有 dsh.workbench.unit)');
|
|
@@ -312,9 +323,25 @@ export async function runRelease({
|
|
|
312
323
|
throw new ReleaseError('没有可写的 git 索引源 —— 用 --index <仓库> 指定,或先在「模块市场 → 发现」里初始化索引');
|
|
313
324
|
}
|
|
314
325
|
if (indexPlan.policy !== 'artifacts-in-repo') {
|
|
326
|
+
if (indexPlan.policyStated !== true) {
|
|
327
|
+
throw new NeedsUser(
|
|
328
|
+
'index-write',
|
|
329
|
+
indexPlan.origin === 'argument'
|
|
330
|
+
? '这个索引仓库默认只当目录。要往里发产物,加 --index-policy artifacts-in-repo 再跑一次。'
|
|
331
|
+
: '这个索引源还没设为可发布。到「模块市场 → 发现 → 索引源」点「设为可发布」,再发一次。',
|
|
332
|
+
undefined,
|
|
333
|
+
{
|
|
334
|
+
target: 'market',
|
|
335
|
+
...(indexPlan.origin === 'argument' ? {} : { enableTool: 'workbench_market_enable' }),
|
|
336
|
+
...(indexPlan.sourceId === undefined ? {} : { sourceId: indexPlan.sourceId }),
|
|
337
|
+
...(indexPlan.source.url === '' ? {} : { sourceUrl: indexPlan.source.url }),
|
|
338
|
+
},
|
|
339
|
+
);
|
|
340
|
+
}
|
|
315
341
|
throw new ReleaseError('这个索引源是 catalog-only:它只收目录、不放产物。用 --index-policy artifacts-in-repo 或换一个可写仓库');
|
|
316
342
|
}
|
|
317
343
|
}
|
|
344
|
+
const authored = authorChangeOf(root, manifest, exec);
|
|
318
345
|
if (hasFlag(args, '--refresh')) {
|
|
319
346
|
if (dryRun) throw new ReleaseError('--refresh 没有预览:它只改索引里的标题 / 作者 / 说明');
|
|
320
347
|
if (target !== 'market') throw new ReleaseError('--refresh 只对市场索引有意义(--target market)');
|
|
@@ -323,7 +350,8 @@ export async function runRelease({
|
|
|
323
350
|
}
|
|
324
351
|
const cardTitle = cardTitleFromMessages(root, unit);
|
|
325
352
|
const titled = cardTitle === undefined ? manifest : { ...manifest, title: cardTitle };
|
|
326
|
-
const
|
|
353
|
+
const publisher = authorTextOf(manifest) || authored.author || '';
|
|
354
|
+
const result = refreshMarketEntry({ plan: indexPlan, exec, home, manifest: titled, publisher });
|
|
327
355
|
if (result.updated !== true && result.reason !== undefined) {
|
|
328
356
|
throw new ReleaseError(`刷新市集元数据没走通:${result.reason}`);
|
|
329
357
|
}
|
|
@@ -352,13 +380,59 @@ export async function runRelease({
|
|
|
352
380
|
emit(out, json, summary);
|
|
353
381
|
return summary;
|
|
354
382
|
}
|
|
355
|
-
const authored = authorChangeOf(root, manifest, exec);
|
|
356
383
|
const authorChange = explicitAuthor === undefined ? authored.change : 'author ← --author';
|
|
357
384
|
if (!explicitApplied && !verify && !dryRun && authored.author !== undefined) {
|
|
358
385
|
manifest.author = authored.author;
|
|
359
386
|
writeManifest(root, manifest);
|
|
360
387
|
}
|
|
388
|
+
const publisher = authorTextOf(manifest);
|
|
389
|
+
const writesIndex = indexPlan !== undefined && target !== 'local' && target !== 'tarball';
|
|
390
|
+
if (!verify && !dryRun && writesIndex) {
|
|
391
|
+
if (publisher === '') throw new ReleaseError(missingPublisherText());
|
|
392
|
+
let claim;
|
|
393
|
+
try {
|
|
394
|
+
claim = marketClaim({ plan: indexPlan, exec, home, name: manifest.name, unit, publisher });
|
|
395
|
+
} catch (error) {
|
|
396
|
+
if (error instanceof MarketIndexError) {
|
|
397
|
+
throw new NeedsUser(error.step ?? 'index-write', error.message, error.command, { target });
|
|
398
|
+
}
|
|
399
|
+
throw error;
|
|
400
|
+
}
|
|
401
|
+
if (claim.state === 'conflict' || claim.state === 'unit-taken' || claim.state === 'path-taken') {
|
|
402
|
+
throw new ReleaseError(claimProblemText(manifest.name, claim, unit));
|
|
403
|
+
}
|
|
404
|
+
}
|
|
361
405
|
const frozenVersion = resume && typeof journal?.version === 'string' ? journal.version : undefined;
|
|
406
|
+
const info =
|
|
407
|
+
target === 'market'
|
|
408
|
+
? { target: 'market', kind: 'market', url: indexPlan.source.url }
|
|
409
|
+
: targetInfoOf(root, manifest, args, home, exec, target, earlyState?.registry);
|
|
410
|
+
const askedVersion = argValue(args, '--version');
|
|
411
|
+
const targetVersionOf = () => {
|
|
412
|
+
if (info.kind === 'tarball' || info.kind === 'git') {
|
|
413
|
+
return frozenVersion ?? nextVersion(manifest.version, askedVersion, info.exists);
|
|
414
|
+
}
|
|
415
|
+
if (info.kind === 'registry') {
|
|
416
|
+
return frozenVersion ?? nextVersion(manifest.version, askedVersion, (one) => registryExists(exec, info, manifest.name, one));
|
|
417
|
+
}
|
|
418
|
+
if (info.kind === 'market' && indexPlan !== undefined) {
|
|
419
|
+
return (
|
|
420
|
+
frozenVersion ??
|
|
421
|
+
nextVersion(manifest.version, askedVersion, (one) =>
|
|
422
|
+
marketVersionExists({ plan: indexPlan, exec, home, name: manifest.name, version: one }),
|
|
423
|
+
)
|
|
424
|
+
);
|
|
425
|
+
}
|
|
426
|
+
return frozenVersion ?? nextVersion(manifest.version, askedVersion, () => false);
|
|
427
|
+
};
|
|
428
|
+
const releaseVersion = verify ? undefined : targetVersionOf();
|
|
429
|
+
const changelogProblem = verify || dryRun ? undefined : changelogProblemOf(root, releaseVersion);
|
|
430
|
+
if (changelogProblem !== undefined && changelogNote === '') {
|
|
431
|
+
throw new NeedsUser('changelog', changelogProblem.reason, changelogProblem.command, {
|
|
432
|
+
target,
|
|
433
|
+
version: releaseVersion,
|
|
434
|
+
});
|
|
435
|
+
}
|
|
362
436
|
const fingerprint = artifactFingerprintOf(root);
|
|
363
437
|
const resumeMatchesTree = resume && frozenVersion !== undefined && journal?.run?.fingerprint === fingerprint;
|
|
364
438
|
const tracking = !verify && !dryRun;
|
|
@@ -376,10 +450,6 @@ export async function runRelease({
|
|
|
376
450
|
},
|
|
377
451
|
});
|
|
378
452
|
}
|
|
379
|
-
const info =
|
|
380
|
-
target === 'market'
|
|
381
|
-
? { target: 'market', kind: 'market', url: indexPlan.source.url }
|
|
382
|
-
: targetInfoOf(root, manifest, args, home, exec, target, earlyState?.registry);
|
|
383
453
|
const indexPreviewOf = (name, version) =>
|
|
384
454
|
indexPlan === undefined
|
|
385
455
|
? undefined
|
|
@@ -409,39 +479,18 @@ export async function runRelease({
|
|
|
409
479
|
unit,
|
|
410
480
|
version,
|
|
411
481
|
publish,
|
|
482
|
+
publisher: authorTextOf(manifestNow),
|
|
412
483
|
exec,
|
|
413
484
|
home,
|
|
414
485
|
now: () => Date.now(),
|
|
415
486
|
});
|
|
416
487
|
};
|
|
488
|
+
let changelogChange;
|
|
417
489
|
const changesOf = (version) => [
|
|
418
490
|
...(authorChange === undefined ? [] : [authorChange]),
|
|
419
491
|
...(version === manifest.version ? [] : [`version → ${version}`]),
|
|
492
|
+
...(changelogChange === undefined ? [] : [changelogChange]),
|
|
420
493
|
];
|
|
421
|
-
const gateOf = (version) => {
|
|
422
|
-
if (verify || dryRun) return;
|
|
423
|
-
const gate = changelogGateOf(root, version);
|
|
424
|
-
if (gate.ok !== true) throw new NeedsUser('changelog', gate.reason, gate.command, { target, version });
|
|
425
|
-
};
|
|
426
|
-
const askedVersion = argValue(args, '--version');
|
|
427
|
-
const targetVersionOf = () => {
|
|
428
|
-
if (info.kind === 'tarball' || info.kind === 'git') {
|
|
429
|
-
return frozenVersion ?? nextVersion(manifest.version, askedVersion, info.exists);
|
|
430
|
-
}
|
|
431
|
-
if (info.kind === 'registry') {
|
|
432
|
-
return frozenVersion ?? nextVersion(manifest.version, askedVersion, (one) => registryExists(exec, info, manifest.name, one));
|
|
433
|
-
}
|
|
434
|
-
if (info.kind === 'market' && indexPlan !== undefined) {
|
|
435
|
-
return (
|
|
436
|
-
frozenVersion ??
|
|
437
|
-
nextVersion(manifest.version, askedVersion, (one) =>
|
|
438
|
-
marketVersionExists({ plan: indexPlan, exec, home, name: manifest.name, version: one }),
|
|
439
|
-
)
|
|
440
|
-
);
|
|
441
|
-
}
|
|
442
|
-
return frozenVersion ?? nextVersion(manifest.version, askedVersion, () => false);
|
|
443
|
-
};
|
|
444
|
-
if (!verify && !dryRun) gateOf(targetVersionOf());
|
|
445
494
|
if (verify) {
|
|
446
495
|
const state = earlyState;
|
|
447
496
|
const version = argValue(args, '--version') ?? state?.version ?? manifest.version;
|
|
@@ -451,7 +500,14 @@ export async function runRelease({
|
|
|
451
500
|
if (commit === '') throw new ReleaseError('没有记录上次发布的 commit —— 先正常发一次再核');
|
|
452
501
|
let checked;
|
|
453
502
|
try {
|
|
454
|
-
checked = verifyMarketArtifact({
|
|
503
|
+
checked = verifyMarketArtifact({
|
|
504
|
+
plan: indexPlan,
|
|
505
|
+
exec,
|
|
506
|
+
home,
|
|
507
|
+
name: manifest.name,
|
|
508
|
+
path: typeof state?.path === 'string' ? state.path : undefined,
|
|
509
|
+
commit,
|
|
510
|
+
});
|
|
455
511
|
} catch (error) {
|
|
456
512
|
throw new ReleaseError(error instanceof Error ? error.message : String(error));
|
|
457
513
|
}
|
|
@@ -612,16 +668,35 @@ export async function runRelease({
|
|
|
612
668
|
}
|
|
613
669
|
const earlyLicense = info.kind === 'registry' ? licenseOf(root, manifest, target, args) : undefined;
|
|
614
670
|
if (info.kind === 'registry') checkLogin(exec, info);
|
|
671
|
+
const changelog = dryRun
|
|
672
|
+
? undefined
|
|
673
|
+
: applyChangelog(root, {
|
|
674
|
+
version: releaseVersion,
|
|
675
|
+
kind: changelogKind,
|
|
676
|
+
note: changelogNote,
|
|
677
|
+
date: changelogDate(),
|
|
678
|
+
});
|
|
679
|
+
if (!dryRun && changelog === undefined) {
|
|
680
|
+
const problem = changelogProblemOf(root, releaseVersion);
|
|
681
|
+
throw new NeedsUser(
|
|
682
|
+
'changelog',
|
|
683
|
+
problem?.reason ?? `${CHANGELOG_FILE} 里没有 ${String(releaseVersion)} 这一版`,
|
|
684
|
+
problem?.command,
|
|
685
|
+
{ target, version: releaseVersion },
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
if (changelog !== undefined && changelog.action === 'written') {
|
|
689
|
+
changelogChange = `CHANGELOG ← ${changelog.version}`;
|
|
690
|
+
}
|
|
691
|
+
const changelogReport =
|
|
692
|
+
changelog === undefined ? undefined : { version: changelog.version, action: changelog.action };
|
|
615
693
|
const capture = json;
|
|
616
694
|
const stageOptions = { cwd: root, capture, err };
|
|
617
695
|
if (!resumeMatchesTree) runGate(exec, root, stageOptions);
|
|
618
696
|
if (tracking) advanceRun(home, unit, { phase: 'gates' });
|
|
619
697
|
|
|
620
698
|
if (info.kind === 'market') {
|
|
621
|
-
const
|
|
622
|
-
marketVersionExists({ plan: indexPlan, exec, home, name: manifest.name, version: one });
|
|
623
|
-
const version =
|
|
624
|
-
frozenVersion ?? nextVersion(manifest.version, argValue(args, '--version'), exists);
|
|
699
|
+
const version = releaseVersion;
|
|
625
700
|
const files = localFiles(exec, root);
|
|
626
701
|
checkPublished(files);
|
|
627
702
|
const summary = {
|
|
@@ -632,6 +707,7 @@ export async function runRelease({
|
|
|
632
707
|
version,
|
|
633
708
|
files,
|
|
634
709
|
changes: changesOf(version),
|
|
710
|
+
...(changelogReport === undefined ? {} : { changelog: changelogReport }),
|
|
635
711
|
install: '',
|
|
636
712
|
market: indexPreviewOf(manifest.name, version),
|
|
637
713
|
};
|
|
@@ -651,6 +727,7 @@ export async function runRelease({
|
|
|
651
727
|
unit,
|
|
652
728
|
version,
|
|
653
729
|
publish: {},
|
|
730
|
+
publisher,
|
|
654
731
|
exec,
|
|
655
732
|
home,
|
|
656
733
|
now: () => Date.now(),
|
|
@@ -658,6 +735,17 @@ export async function runRelease({
|
|
|
658
735
|
...(tracking ? { onStep: (step) => advanceRun(home, unit, step) } : {}),
|
|
659
736
|
});
|
|
660
737
|
if (index.updated !== true || index.commit === undefined || index.commit === '') {
|
|
738
|
+
if (index.code === 'market-owner') {
|
|
739
|
+
if (tracking) {
|
|
740
|
+
interruptRun(home, unit, {
|
|
741
|
+
class: 'human',
|
|
742
|
+
code: 'market-owner',
|
|
743
|
+
text: index.reason ?? missingPublisherText(),
|
|
744
|
+
phase: index.phase ?? 'claim',
|
|
745
|
+
});
|
|
746
|
+
}
|
|
747
|
+
throw new ReleaseError(index.reason ?? missingPublisherText());
|
|
748
|
+
}
|
|
661
749
|
const failure = {
|
|
662
750
|
class: index.code === 'git-market-protected' ? 'human' : 'transient',
|
|
663
751
|
code: index.code ?? 'index-write',
|
|
@@ -674,7 +762,7 @@ export async function runRelease({
|
|
|
674
762
|
});
|
|
675
763
|
}
|
|
676
764
|
writeManifest(root, next);
|
|
677
|
-
const path = index.path ??
|
|
765
|
+
const path = index.path ?? marketArtifactPath(manifest.name);
|
|
678
766
|
const install = marketInstallOf(index.url, index.commit, path);
|
|
679
767
|
const journalNow = tracking ? readState(home, unit)?.run : undefined;
|
|
680
768
|
writeState(home, unit, {
|
|
@@ -698,7 +786,7 @@ export async function runRelease({
|
|
|
698
786
|
}
|
|
699
787
|
|
|
700
788
|
if (info.kind === 'tarball') {
|
|
701
|
-
const version =
|
|
789
|
+
const version = releaseVersion;
|
|
702
790
|
const files = localFiles(exec, root);
|
|
703
791
|
checkPublished(files);
|
|
704
792
|
const summary = {
|
|
@@ -710,6 +798,7 @@ export async function runRelease({
|
|
|
710
798
|
file: info.fileOf(version),
|
|
711
799
|
files,
|
|
712
800
|
changes: changesOf(version),
|
|
801
|
+
...(changelogReport === undefined ? {} : { changelog: changelogReport }),
|
|
713
802
|
install: installOf(info, manifest.name, version),
|
|
714
803
|
};
|
|
715
804
|
if (dryRun) {
|
|
@@ -749,7 +838,7 @@ export async function runRelease({
|
|
|
749
838
|
}
|
|
750
839
|
|
|
751
840
|
if (info.kind === 'git') {
|
|
752
|
-
const version =
|
|
841
|
+
const version = releaseVersion;
|
|
753
842
|
const files = localFiles(exec, root);
|
|
754
843
|
checkPublished(files);
|
|
755
844
|
const summary = {
|
|
@@ -760,6 +849,7 @@ export async function runRelease({
|
|
|
760
849
|
version,
|
|
761
850
|
files,
|
|
762
851
|
changes: changesOf(version),
|
|
852
|
+
...(changelogReport === undefined ? {} : { changelog: changelogReport }),
|
|
763
853
|
git: { url: info.url, tag: info.tagOf(version) },
|
|
764
854
|
install: installOf(info, manifest.name, version),
|
|
765
855
|
...(dryRun ? { market: indexPreviewOf(manifest.name, version) } : {}),
|
|
@@ -829,12 +919,12 @@ export async function runRelease({
|
|
|
829
919
|
delete next.private;
|
|
830
920
|
changes.push('摘掉 private');
|
|
831
921
|
}
|
|
832
|
-
const version =
|
|
833
|
-
frozenVersion ?? nextVersion(next.version, argValue(args, '--version'), (one) => registryExists(exec, info, next.name, one));
|
|
922
|
+
const version = releaseVersion;
|
|
834
923
|
if (version !== next.version) {
|
|
835
924
|
next.version = version;
|
|
836
925
|
changes.push(`version → ${version}`);
|
|
837
926
|
}
|
|
927
|
+
if (changelogChange !== undefined) changes.push(changelogChange);
|
|
838
928
|
if (dryRun) {
|
|
839
929
|
if (!resumeMatchesTree) runCliStage(exec, root, ['check'], stageOptions);
|
|
840
930
|
const files = localFiles(exec, root);
|
|
@@ -874,6 +964,7 @@ export async function runRelease({
|
|
|
874
964
|
registry: info.registry,
|
|
875
965
|
changes,
|
|
876
966
|
files,
|
|
967
|
+
...(changelogReport === undefined ? {} : { changelog: changelogReport }),
|
|
877
968
|
install: installOf(info, next.name, version),
|
|
878
969
|
market: indexOf(next, version, { registry: info.registry }),
|
|
879
970
|
};
|