@dsh-workbench/shared 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.
@@ -32,7 +32,10 @@ export const MARKET_INDEX_README_LINES: readonly string[] = [
32
32
  'dsh-wb-module release --index <本仓库地址> --index-policy artifacts-in-repo',
33
33
  '```',
34
34
  '',
35
- '产物会提交到 `packages/<unit>/`,并更新 `market.json`。',
35
+ '产物会提交到 `packages/<包名>/`(带 scope 时是 `packages/@scope/名字/`),并更新 `market.json`。',
36
+ '',
37
+ '一个包名归第一次发布的人(`market.json` 里的 `owner`)。换一个署名发同名会被拒;要换人,先把这一条的 `owner` 改成新的人。',
38
+ '同一个市场里,一个模块 id(`metadata.unit`)只能被一个条目使用;另一个包名想用同一个 id / 产物目录也会被拒。',
36
39
  '',
37
40
  '## 使用这个索引',
38
41
  '',
@@ -152,9 +155,20 @@ function iconProblem(icon: string): MarketIndexProblem | undefined {
152
155
  return problem('icon', 'icon 只能是 base64 data URL 或 http(s) URL');
153
156
  }
154
157
 
158
+ const MAX_OWNER_CHARS = 200;
159
+ const UNIT_ID = /^[a-z][a-z0-9-]*$/;
160
+
161
+ function ownerProblem(owner: string): MarketIndexProblem | undefined {
162
+ if (/[\r\n]/u.test(owner)) return problem('owner', 'owner 只能是一行署名');
163
+ if (owner.length > MAX_OWNER_CHARS) return problem('owner', 'owner 超过 200 字');
164
+ return undefined;
165
+ }
166
+
155
167
  function metadataOf(raw: unknown): MarketIndexEntry['metadata'] {
156
168
  const record = asRecord(raw);
157
169
  if (record === undefined) return undefined;
170
+ const workbench = record['workbench'] === true;
171
+ const unit = asText(record['unit']);
158
172
  const category = asText(record['category']);
159
173
  const labels = asTextArray(record['labels']);
160
174
  const keywords = asTextArray(record['keywords']);
@@ -168,6 +182,8 @@ function metadataOf(raw: unknown): MarketIndexEntry['metadata'] {
168
182
  ),
169
183
  );
170
184
  return {
185
+ ...(workbench ? { workbench: true } : {}),
186
+ ...(unit === '' ? {} : { unit }),
171
187
  ...(category === '' ? {} : { category }),
172
188
  ...(labels.length === 0 ? {} : { labels }),
173
189
  ...(keywords.length === 0 ? {} : { keywords }),
@@ -192,14 +208,23 @@ function entryOf(raw: unknown, at: string): MarketIndexEntry | MarketIndexProble
192
208
  const title = asLocalized(record['title']);
193
209
  const description = asLocalized(record['description']);
194
210
  const author = asText(record['author']);
211
+ const owner = asText(record['owner']);
212
+ if (owner !== '') {
213
+ const badOwner = ownerProblem(owner);
214
+ if (badOwner !== undefined) return badOwner;
215
+ }
195
216
  const homepage = asText(record['homepage']);
196
217
  const metadata = metadataOf(record['metadata']);
218
+ if (metadata?.unit !== undefined && !UNIT_ID.test(metadata.unit)) {
219
+ return problem('unit', `metadata.unit 只能是小写字母开头的 a-z0-9-(现在是 ${JSON.stringify(metadata.unit)})`, at);
220
+ }
197
221
  return {
198
222
  name,
199
223
  versions,
200
224
  ...(title === undefined ? {} : { title }),
201
225
  ...(description === undefined ? {} : { description }),
202
226
  ...(author === '' ? {} : { author }),
227
+ ...(owner === '' ? {} : { owner }),
203
228
  ...(homepage === '' ? {} : { homepage }),
204
229
  ...(icon === '' ? {} : { icon }),
205
230
  ...(metadata === undefined ? {} : { metadata }),
@@ -272,8 +297,14 @@ export function upsertMarketEntry(index: MarketIndex, entry: MarketIndexEntry):
272
297
  const merged = new Map<string, MarketIndexVersion>();
273
298
  for (const one of current.versions) merged.set(one.version, one);
274
299
  for (const one of entry.versions) merged.set(one.version, one);
300
+ const owner = current.owner ?? entry.owner;
275
301
  const plugins = [...index.plugins];
276
- plugins[found] = { ...current, ...entry, versions: sortMarketVersions([...merged.values()]) };
302
+ plugins[found] = {
303
+ ...current,
304
+ ...entry,
305
+ ...(owner === undefined ? {} : { owner }),
306
+ versions: sortMarketVersions([...merged.values()]),
307
+ };
277
308
  return { ...index, plugins };
278
309
  }
279
310
 
package/src/host.ts CHANGED
@@ -72,4 +72,5 @@ export * from './host/extension.js';
72
72
  export * from './host/port-types.js';
73
73
  export * from './host/version-range.js';
74
74
  export * from './host/market-index.js';
75
+ export * from './host/market-index-claim.js';
75
76
  export * from './host/changelog.js';
package/src/i18n/en.ts CHANGED
@@ -299,12 +299,19 @@ export const en: Record<keyof WorkbenchMessages, string> = {
299
299
  'panels.discover.addingSource': 'Adding…',
300
300
  'panels.discover.addFirstSource': 'Add index source',
301
301
  'panels.discover.removeSource': 'Remove',
302
- 'panels.discover.removeSourceNamed': 'Remove {url}',
303
- 'panels.discover.useSource': 'Use',
304
- 'panels.discover.useSourceNamed': 'Use {label}',
305
- 'panels.discover.sourceCurrent': 'In use',
302
+ 'panels.discover.removeTitle': 'Remove this index source?',
303
+ 'panels.discover.removeBody': '"{label}" will be removed from this list; the repository itself is not deleted, and you can add it back later.',
304
+ 'panels.discover.sourceActions': 'Actions for {label}',
305
+ 'panels.discover.sourceStateWritable': 'Publish target',
306
+ 'panels.discover.sourceStateReadOnly': 'Read-only list',
307
+ 'panels.discover.sourceEnable': 'Set up for publishing',
308
+ 'panels.discover.sourceDisable': 'Set to read-only',
309
+ 'panels.discover.sourceWorking': 'Updating…',
310
+ 'panels.discover.sourceEnabled': '"{label}" is ready for publishing',
311
+ 'panels.discover.sourceReadOnly': '"{label}" is now read-only',
306
312
  'panels.discover.refresh': 'Refresh',
307
313
  'panels.discover.search': 'Search modules',
314
+ 'panels.discover.clearSearch': 'Clear search',
308
315
  'panels.discover.all': 'All',
309
316
  'panels.discover.sourceLabel': 'Name',
310
317
  'panels.discover.sourceKind': 'Type',
@@ -328,7 +335,7 @@ export const en: Record<keyof WorkbenchMessages, string> = {
328
335
  'panels.discover.missingIndex': 'This repository has no {path} yet.',
329
336
  'panels.discover.initTitle': 'Initialize this repository as a module market index?',
330
337
  'panels.discover.initCta': 'Initialize market index',
331
- 'panels.discover.initWhat': 'It will write market.json and README.md on {branch}, commit once, and push. Modules you publish from this workbench will commit their build files under packages/<unit>/. The repository stays owned by its owner.',
338
+ 'panels.discover.initWhat': 'It will write market.json and README.md on {branch}, commit once, and push. Modules you publish from this workbench will commit their build files under packages/<package-name>/. The repository stays owned by its owner.',
332
339
  'panels.discover.initBranch': 'Branch',
333
340
  'panels.discover.initBranchHint': 'The index and build files go to this branch; an empty repository gets it created.',
334
341
  'panels.discover.initBranchBad': 'Use only letters, digits, dots, underscores, slashes, or dashes in a branch name.',
@@ -646,6 +653,13 @@ export const en: Record<keyof WorkbenchMessages, string> = {
646
653
  'approval.publish.version': 'Version',
647
654
  'approval.publish.author': 'Author',
648
655
  'approval.publish.authorMissing': 'unsigned',
656
+ 'approval.publish.owner': 'Owner',
657
+ 'approval.publish.ownerUnowned': 'originally unattributed',
658
+ 'approval.publish.ownerTaken': 'Owned by {owner}',
659
+ 'approval.publish.unit': 'Module id',
660
+ 'approval.publish.unitTaken': 'Taken by {entry}',
661
+ 'approval.publish.path': 'Artifact dir',
662
+ 'approval.publish.pathTaken': 'Taken by {entry}',
649
663
  'approval.publish.registry': 'Registry',
650
664
  'approval.publish.repo': 'Repository',
651
665
  'approval.publish.branch': 'Branch',
@@ -656,6 +670,7 @@ export const en: Record<keyof WorkbenchMessages, string> = {
656
670
  'approval.publish.twoCommits': 'build files + market.json in 2 commits, one push',
657
671
  'approval.publish.reading': 'Reading…',
658
672
  'approval.publish.changelog': 'Changelog for {version}',
673
+ 'approval.publish.changelogPending': 'This release writes the {version} changelog entry',
659
674
  'approval.publish.changelogMissing': 'No changelog entry for {version}; publishing will be blocked',
660
675
  'approval.publish.targetAuto': 'configured default',
661
676
  'approval.publish.targetTeam': 'team registry',
@@ -664,6 +679,15 @@ export const en: Record<keyof WorkbenchMessages, string> = {
664
679
  'approval.publish.targetGit': 'git repository',
665
680
  'approval.publish.targetMarket': 'module market index',
666
681
  'approval.publish.targetLocal': 'keep local',
682
+ 'approval.market.source': 'Index source',
683
+ 'approval.market.url': 'URL',
684
+ 'approval.market.action': 'This action',
685
+ 'approval.market.policy': 'Current state',
686
+ 'approval.market.enable': 'make it publishable',
687
+ 'approval.market.disable': 'make it read-only',
688
+ 'approval.market.writable': 'publishable',
689
+ 'approval.market.readOnly': 'read-only module list',
690
+ 'approval.market.reading': 'Reading…',
667
691
  'markdown.copy': 'Copy',
668
692
  'markdown.copied': 'Copied',
669
693
  'markdown.footnotes': 'Footnotes',
package/src/i18n/zh.ts CHANGED
@@ -296,12 +296,19 @@ export const zh = {
296
296
  'panels.discover.addingSource': '正在添加…',
297
297
  'panels.discover.addFirstSource': '添加索引源',
298
298
  'panels.discover.removeSource': '移除',
299
- 'panels.discover.removeSourceNamed': '移除 {url}',
300
- 'panels.discover.useSource': '使用',
301
- 'panels.discover.useSourceNamed': '使用 {label}',
302
- 'panels.discover.sourceCurrent': '当前使用',
299
+ 'panels.discover.removeTitle': '移除这个索引源?',
300
+ 'panels.discover.removeBody': '会把「{label}」从这份列表里移除;仓库本身不会被删,之后还能再添加。',
301
+ 'panels.discover.sourceActions': '「{label}」的操作',
302
+ 'panels.discover.sourceStateWritable': '可发布',
303
+ 'panels.discover.sourceStateReadOnly': '仅读取模块列表',
304
+ 'panels.discover.sourceEnable': '设为可发布',
305
+ 'panels.discover.sourceDisable': '设为只读',
306
+ 'panels.discover.sourceWorking': '设置中…',
307
+ 'panels.discover.sourceEnabled': '已把「{label}」设为可发布',
308
+ 'panels.discover.sourceReadOnly': '已把「{label}」设为只读',
303
309
  'panels.discover.refresh': '刷新',
304
310
  'panels.discover.search': '搜索模块',
311
+ 'panels.discover.clearSearch': '清除搜索',
305
312
  'panels.discover.all': '全部',
306
313
  'panels.discover.sourceLabel': '名字',
307
314
  'panels.discover.sourceKind': '类型',
@@ -325,7 +332,7 @@ export const zh = {
325
332
  'panels.discover.missingIndex': '仓库里还没有索引文件 {path}',
326
333
  'panels.discover.initTitle': '把这个仓库初始化成模块市场索引?',
327
334
  'panels.discover.initCta': '初始化成市场索引',
328
- 'panels.discover.initWhat': '会在 {branch} 分支写 market.json 与 README.md,提交一次并推上去;之后从这个工作台发布的模块会把打包好的文件提交到 packages/<unit>/。仓库的写权限归它的所有者。',
335
+ 'panels.discover.initWhat': '会在 {branch} 分支写 market.json 与 README.md,提交一次并推上去;之后从这个工作台发布的模块会把打包好的文件提交到按包名分的 packages/<包名>/。仓库的写权限归它的所有者。',
329
336
  'panels.discover.initBranch': '分支',
330
337
  'panels.discover.initBranchHint': '索引和打包文件都发到这个分支;空仓库会新建它。',
331
338
  'panels.discover.initBranchBad': '分支名只能用字母、数字、点、下划线、斜杠或短横线',
@@ -641,6 +648,13 @@ export const zh = {
641
648
  'approval.publish.version': '版本',
642
649
  'approval.publish.author': '作者',
643
650
  'approval.publish.authorMissing': '未署名',
651
+ 'approval.publish.owner': '认领人',
652
+ 'approval.publish.ownerUnowned': '原来没署名',
653
+ 'approval.publish.ownerTaken': '已有主人:{owner}',
654
+ 'approval.publish.unit': '模块 id',
655
+ 'approval.publish.unitTaken': '已被 {entry} 占用',
656
+ 'approval.publish.path': '产物目录',
657
+ 'approval.publish.pathTaken': '已被 {entry} 占用',
644
658
  'approval.publish.registry': '源',
645
659
  'approval.publish.repo': '仓库',
646
660
  'approval.publish.branch': '分支',
@@ -651,6 +665,7 @@ export const zh = {
651
665
  'approval.publish.twoCommits': '打包文件 + market.json 两次提交,一次推送',
652
666
  'approval.publish.reading': '读取中…',
653
667
  'approval.publish.changelog': '{version} 的更新日志',
668
+ 'approval.publish.changelogPending': '{version} 这一版会写进更新日志,随包发布',
654
669
  'approval.publish.changelogMissing': '这一版还没有更新日志({version}),发布会被拦下',
655
670
  'approval.publish.targetAuto': '按配置默认',
656
671
  'approval.publish.targetTeam': '团队源',
@@ -659,6 +674,15 @@ export const zh = {
659
674
  'approval.publish.targetGit': 'git 仓库',
660
675
  'approval.publish.targetMarket': '模块市场索引',
661
676
  'approval.publish.targetLocal': '留在本机',
677
+ 'approval.market.source': '索引源',
678
+ 'approval.market.url': '地址',
679
+ 'approval.market.action': '这次操作',
680
+ 'approval.market.policy': '当前状态',
681
+ 'approval.market.enable': '设为可发布',
682
+ 'approval.market.disable': '改回只读',
683
+ 'approval.market.writable': '可发布',
684
+ 'approval.market.readOnly': '仅读取模块列表',
685
+ 'approval.market.reading': '正在读取…',
662
686
  'markdown.copy': '复制',
663
687
  'markdown.copied': '已复制',
664
688
  'markdown.footnotes': '脚注',
@@ -34,7 +34,8 @@ describe('模块作者写用例时读自己那份声明', () => {
34
34
 
35
35
  expect(manifest.id).toBe('demo');
36
36
  expect(manifest.title).toEqual({ zh: '演示', en: 'Demo' });
37
- expect(manifest.card).toEqual({ titleKey: unitTitleKey('demo'), order: 7 });
37
+ expect(manifest.titleKey).toBe(unitTitleKey('demo'));
38
+ expect(manifest.card).toEqual({ order: 7 });
38
39
  expect(manifest.tools).toEqual([{ name: 'workbench_demo_list', writes: false }]);
39
40
  });
40
41
 
@@ -10,6 +10,7 @@ import type {
10
10
  import type { NeedState } from '../contract/unit-capability.js';
11
11
  import type { UnitCapability } from '../contract/unit-capability.js';
12
12
  import type { UnitManifest } from '../contract/unit.js';
13
+ import { unitTitleKey } from '../contract/unit.js';
13
14
 
14
15
  export const fakeNpmSource: SupplySource = {
15
16
  kind: 'npm',
@@ -47,9 +48,11 @@ export function manifestOf(
47
48
  readonly card?: UnitManifest['card'];
48
49
  } = {},
49
50
  ): UnitManifest {
51
+ const id = options.id ?? 'fixture';
50
52
  return {
51
- id: options.id ?? 'fixture',
52
- card: options.card === undefined ? { titleKey: `${options.id ?? 'fixture'}.mine`, order: 10 } : options.card,
53
+ id,
54
+ titleKey: unitTitleKey(id),
55
+ card: options.card === undefined ? { order: 10 } : options.card,
53
56
  tools: [],
54
57
  host: { inject: [] },
55
58
  ...(options.needs === undefined ? {} : { needs: options.needs }),