@dsh-workbench/module-tools 1.1.1 → 1.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -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` 里没有记录、或 `package.json` 缺 `files` 里的 `CHANGELOG.md` 时,
28
- `release` 直接拦下并给出补的命令;`author` 没写时从本机 `git config` 补进 manifest,索引与产物都带上。
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 <init\|add\|check>` | 更新日志:建 `CHANGELOG.md`、记一版、查这一版有没有;`release` 要求当前版本必须有记录 |
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
 
@@ -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。发布时这一版必须在 CHANGELOG.md 里有记录,否则 release 会拦下。`;
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: '更新日志:init 建 / add 记一版 / check 查这一版有没有(发布前必须有)',
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.1",
3
+ "version": "1.1.3",
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.1"
36
+ "@dsh-workbench/shared": "1.1.3"
37
37
  },
38
38
  "repository": {
39
39
  "type": "git",
package/src/changelog.mjs CHANGED
@@ -1,27 +1,18 @@
1
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
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 init 建一份 ${CHANGELOG_FILE}(已有就不动)
17
- changelog add <版本> [--type ${CHANGELOG_KINDS.join('|')}] [--note <文字>] [--date <日期>]
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 init',
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: `dsh-wb-module changelog add ${version} --note "这一版改了什么"`,
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 addChangelog(root, { version, kind = 'changed', note, date }) {
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 file = changelogFileOf(root);
95
- const body = appendChangelogEntry(readChangelogText(root), { version: wanted, kind, note: text, date });
96
- writeFileSync(file, body);
97
- return { file, version: wanted, kind };
98
- }
99
-
100
- function runInit(root, args) {
101
- const result = initChangelog(root);
102
- if (args.includes('--json')) {
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);
@@ -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 PACKAGES_DIR = 'packages';
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 normalizedPolicy(value, fallback) {
55
- if (value === 'artifacts-in-repo' || value === 'catalog-only') return value;
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 = Array.isArray(manifest.keywords)
124
- ? manifest.keywords.flatMap((one) => (typeof one === 'string' && one.trim() !== '' ? [one.trim()] : []))
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
- category: WORKBENCH_LABEL,
136
- labels: [WORKBENCH_LABEL],
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 policy = normalizedPolicy(argValue(args, '--index-policy'), normalizedPolicy(raw.policy, fallbackPolicy));
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 found = payload.sources.find((one) => one?.id === payload.selected);
252
- return planOf(found ?? null, args, 'catalog-only');
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, unit, manifest) {
335
- const dest = join(workdir, PACKAGES_DIR, unit);
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 name of ARTIFACT_FILES) {
343
- const src = join(root, name);
344
- if (existsSync(src)) copyFileSync(src, join(dest, name));
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 `${PACKAGES_DIR}/${unit}`;
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, unit, version, publish, now, resume, onStep } = options;
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', '--', `${PACKAGES_DIR}/${unit}`, indexPath], { allowFailure: true });
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, unit, manifest);
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, unit, commit }) {
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 prefix = `${PACKAGES_DIR}/${unit}/`;
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: `${PACKAGES_DIR}/${unit}`, files };
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, changelogGateOf } from './release-changelog.mjs';
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 result = refreshMarketEntry({ plan: indexPlan, exec, home, manifest: titled });
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({ plan: indexPlan, exec, home, unit, commit });
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 exists = (one) =>
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 ?? `packages/${unit}`;
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 = frozenVersion ?? nextVersion(manifest.version, argValue(args, '--version'), info.exists);
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 = frozenVersion ?? nextVersion(manifest.version, argValue(args, '--version'), info.exists);
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
  };