@dsh-workbench/module-tools 1.0.0-beta.0 → 1.0.0-beta.1
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/package.json +6 -2
- package/src/changelog.mjs +3 -4
- package/src/checks/contracts.mjs +20 -2
- package/src/checks/style.mjs +24 -0
- package/src/publish.mjs +5 -1
- package/src/release-util.mjs +7 -0
- package/src/release.mjs +14 -15
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dsh-workbench/module-tools",
|
|
3
|
-
"version": "1.0.0-beta.
|
|
3
|
+
"version": "1.0.0-beta.1",
|
|
4
4
|
"description": "模块包的构建与门禁工具:build(单文件 ESM 宿主侧 + 套 loader 壳的浏览器侧)、check(契约/产物边界/源码边界/卡片渲染)。模块包不再各带一份构建脚本。",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"type": "module",
|
|
@@ -30,9 +30,10 @@
|
|
|
30
30
|
"eslint-plugin-react": "^7.37.5",
|
|
31
31
|
"eslint-plugin-react-hooks": "^7.1.1",
|
|
32
32
|
"execa": "^10.2.0",
|
|
33
|
+
"semver": "^7.8.5",
|
|
33
34
|
"typescript": "5.9.3",
|
|
34
35
|
"typescript-eslint": "^8.71.0",
|
|
35
|
-
"@dsh-workbench/shared": "1.0.0-beta.
|
|
36
|
+
"@dsh-workbench/shared": "1.0.0-beta.1"
|
|
36
37
|
},
|
|
37
38
|
"repository": {
|
|
38
39
|
"type": "git",
|
|
@@ -43,6 +44,9 @@
|
|
|
43
44
|
"bugs": {
|
|
44
45
|
"url": "https://github.com/novaeth/dsh-workbench/issues"
|
|
45
46
|
},
|
|
47
|
+
"devDependencies": {
|
|
48
|
+
"@types/semver": "^7.8.0"
|
|
49
|
+
},
|
|
46
50
|
"scripts": {
|
|
47
51
|
"lint": "eslint src bin scripts",
|
|
48
52
|
"arch": "arch-guard",
|
package/src/changelog.mjs
CHANGED
|
@@ -9,10 +9,9 @@ import {
|
|
|
9
9
|
changelogHasEntry,
|
|
10
10
|
changelogSkeleton,
|
|
11
11
|
changelogVersion,
|
|
12
|
+
isValidVersion,
|
|
12
13
|
} from '@dsh-workbench/shared/host';
|
|
13
14
|
|
|
14
|
-
const SEMVER = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
15
|
-
|
|
16
15
|
const USAGE = `用法:
|
|
17
16
|
changelog init 建一份 ${CHANGELOG_FILE}(已有就不动)
|
|
18
17
|
changelog add <版本> [--type ${CHANGELOG_KINDS.join('|')}] [--note <文字>] [--date <日期>]
|
|
@@ -89,7 +88,7 @@ export function addChangelog(root, { version, kind = 'changed', note, date }) {
|
|
|
89
88
|
throw new ChangelogError(`--type 只认 ${CHANGELOG_KINDS.join(' / ')}(收到「${kind}」)`);
|
|
90
89
|
}
|
|
91
90
|
const wanted = changelogVersion(version === undefined || version === '' ? manifestVersionOf(root) : version);
|
|
92
|
-
if (!
|
|
91
|
+
if (!isValidVersion(wanted)) {
|
|
93
92
|
throw new ChangelogError(`版本要写成 x.y.z(收到「${String(version)}」)—— 也可以省略,用 package.json 里的`);
|
|
94
93
|
}
|
|
95
94
|
const file = changelogFileOf(root);
|
|
@@ -134,7 +133,7 @@ function runAdd(root, args) {
|
|
|
134
133
|
|
|
135
134
|
function runCheck(root, args) {
|
|
136
135
|
const version = changelogVersion(positional(args, 1) ?? manifestVersionOf(root));
|
|
137
|
-
if (!
|
|
136
|
+
if (!isValidVersion(version)) {
|
|
138
137
|
console.error('changelog check 要知道查哪一版:给一个 x.y.z,或先让 package.json 有 version');
|
|
139
138
|
process.exit(2);
|
|
140
139
|
}
|
package/src/checks/contracts.mjs
CHANGED
|
@@ -253,6 +253,17 @@ function mountOf(face, created, manifest, tools) {
|
|
|
253
253
|
},
|
|
254
254
|
manifests: () => [],
|
|
255
255
|
opencliCommand: 'opencli',
|
|
256
|
+
env: {},
|
|
257
|
+
git: {
|
|
258
|
+
origin: async () => ({ kind: 'no-origin' }),
|
|
259
|
+
branch: async () => ({ kind: 'no-branch' }),
|
|
260
|
+
head: async () => ({ kind: 'no-head' }),
|
|
261
|
+
},
|
|
262
|
+
libs: {
|
|
263
|
+
load: async () => {
|
|
264
|
+
throw new Error('gate: libs 未装');
|
|
265
|
+
},
|
|
266
|
+
},
|
|
256
267
|
store: fakeStore(),
|
|
257
268
|
longTasks: fakeLongTasks(created),
|
|
258
269
|
});
|
|
@@ -603,9 +614,16 @@ export async function run() {
|
|
|
603
614
|
}
|
|
604
615
|
}
|
|
605
616
|
|
|
606
|
-
|
|
617
|
+
const usesLibPort = sourceFilesOf(pkg.dir).some((file) =>
|
|
618
|
+
/(?:\blibs\.load\b|\bworkflowFromLibs\b|\bLibReader\b)/.test(readFileSync(file, 'utf8')),
|
|
619
|
+
);
|
|
620
|
+
if (
|
|
621
|
+
needs.some((need) => need?.kind === 'lib') &&
|
|
622
|
+
!products.includes('libInstallCommand') &&
|
|
623
|
+
!usesLibPort
|
|
624
|
+
) {
|
|
607
625
|
problems.push(
|
|
608
|
-
`${name}: 声明了 lib 类 need
|
|
626
|
+
`${name}: 声明了 lib 类 need 却既没用 libInstallCommand,也没走 core 的 LibReader(libs.load)—— ` +
|
|
609
627
|
`lib 没装好时用户只看到"没装",没有能照做的那条安装命令`,
|
|
610
628
|
);
|
|
611
629
|
}
|
package/src/checks/style.mjs
CHANGED
|
@@ -125,6 +125,30 @@ const ANCHOR_EXEMPT = new Map([
|
|
|
125
125
|
'@dsh-workbench/module-demo-anchor-exempt:src/components/IssueRow.tsx',
|
|
126
126
|
'自检探针:整行链形态的裸 `<a>`(只在这个临时探针包里出现)',
|
|
127
127
|
],
|
|
128
|
+
[
|
|
129
|
+
'@pagoda-dsh-workbench/module-argocd:src/components/views/EnvRows.tsx',
|
|
130
|
+
'图标钮 / 按钮皮 / 整行链:外观与交互沿用既有视图,文字链已走 kit 的 Link',
|
|
131
|
+
],
|
|
132
|
+
[
|
|
133
|
+
'@pagoda-dsh-workbench/module-argocd:src/components/views/IdentityFields.tsx',
|
|
134
|
+
'图标钮 / 按钮皮:登录入口沿用既有视图,文字链已走 kit 的 Link',
|
|
135
|
+
],
|
|
136
|
+
[
|
|
137
|
+
'@pagoda-dsh-workbench/module-coding-analysis:src/components/views/AnalysisBody.tsx',
|
|
138
|
+
'图标钮:卡片角上的外链按钮,不是正文文字链',
|
|
139
|
+
],
|
|
140
|
+
[
|
|
141
|
+
'@pagoda-dsh-workbench/module-coding-analysis:src/components/views/AnalysisStates.tsx',
|
|
142
|
+
'按钮皮的链:空态行动区,沿用既有视图',
|
|
143
|
+
],
|
|
144
|
+
[
|
|
145
|
+
'@pagoda-dsh-workbench/module-coding-analysis:src/components/views/EnvSettings.tsx',
|
|
146
|
+
'按钮皮的链:设置区行动入口,沿用既有视图',
|
|
147
|
+
],
|
|
148
|
+
[
|
|
149
|
+
'@pagoda-dsh-workbench/module-coding-issues:src/components/IssueRow.tsx',
|
|
150
|
+
'整行链:缺陷行整行可点,不是正文文字链',
|
|
151
|
+
],
|
|
128
152
|
]);
|
|
129
153
|
|
|
130
154
|
function bareControlsIn(text) {
|
package/src/publish.mjs
CHANGED
|
@@ -5,6 +5,7 @@ import { fileURLToPath } from 'node:url';
|
|
|
5
5
|
import { execaSync } from 'execa';
|
|
6
6
|
|
|
7
7
|
import { DESIGN_FILE } from './design.mjs';
|
|
8
|
+
import { publishTagOf } from './release-util.mjs';
|
|
8
9
|
|
|
9
10
|
const CLI = fileURLToPath(new URL('../bin/dsh-wb-module.mjs', import.meta.url));
|
|
10
11
|
|
|
@@ -111,6 +112,9 @@ export async function publish({ root }) {
|
|
|
111
112
|
return;
|
|
112
113
|
}
|
|
113
114
|
console.log('✔ 清单干净。真要发就自己跑(凭据在你手上):');
|
|
114
|
-
|
|
115
|
+
const tag = publishTagOf(manifest.version);
|
|
116
|
+
console.log(
|
|
117
|
+
` npm publish${manifest.publishConfig?.registry === undefined ? '' : ` --registry ${manifest.publishConfig.registry}`}${tag === undefined ? '' : ` --tag ${tag}`}`,
|
|
118
|
+
);
|
|
115
119
|
console.log(` 包目录:${relative(process.cwd(), root) === '' ? '.' : relative(process.cwd(), root)}`);
|
|
116
120
|
}
|
package/src/release-util.mjs
CHANGED
|
@@ -2,6 +2,7 @@ import { mkdirSync } from 'node:fs';
|
|
|
2
2
|
import { tmpdir } from 'node:os';
|
|
3
3
|
import { join } from 'node:path';
|
|
4
4
|
import { execaSync } from 'execa';
|
|
5
|
+
import semver from 'semver';
|
|
5
6
|
|
|
6
7
|
export class ReleaseError extends Error {
|
|
7
8
|
constructor(message, code = 1) {
|
|
@@ -52,6 +53,12 @@ export function tailOf(text) {
|
|
|
52
53
|
.join(' | ');
|
|
53
54
|
}
|
|
54
55
|
|
|
56
|
+
export function publishTagOf(version) {
|
|
57
|
+
const prerelease = semver.prerelease(String(version ?? '').trim());
|
|
58
|
+
if (prerelease === null) return undefined;
|
|
59
|
+
return typeof prerelease[0] === 'string' ? prerelease[0] : 'next';
|
|
60
|
+
}
|
|
61
|
+
|
|
55
62
|
export function defaultExec(command, argv, { cwd, capture = false, allowFailure = false } = {}) {
|
|
56
63
|
const cache = join(tmpdir(), 'dsh-wb-npm-cache');
|
|
57
64
|
const logs = join(tmpdir(), 'dsh-wb-npm-logs');
|
package/src/release.mjs
CHANGED
|
@@ -3,6 +3,7 @@ import { homedir, tmpdir } from 'node:os';
|
|
|
3
3
|
import { dirname, join, relative, resolve, sep } from 'node:path';
|
|
4
4
|
import { fileURLToPath } from 'node:url';
|
|
5
5
|
import { isDeepStrictEqual } from 'node:util';
|
|
6
|
+
import semver from 'semver';
|
|
6
7
|
|
|
7
8
|
import { DESIGN_FILE, acceptanceOf } from './design.mjs';
|
|
8
9
|
import {
|
|
@@ -18,7 +19,7 @@ import {
|
|
|
18
19
|
} from './release-journal.mjs';
|
|
19
20
|
import { emit, installOf, marketInstallOf, stateOf } from './release-report.mjs';
|
|
20
21
|
import { authorChangeOf, changelogGateOf } from './release-changelog.mjs';
|
|
21
|
-
import { NeedsUser, ReleaseError, ResumableError, defaultExec, tailOf } from './release-util.mjs';
|
|
22
|
+
import { NeedsUser, ReleaseError, ResumableError, defaultExec, publishTagOf, tailOf } from './release-util.mjs';
|
|
22
23
|
import {
|
|
23
24
|
MarketIndexError,
|
|
24
25
|
marketVersionExists,
|
|
@@ -33,7 +34,6 @@ const CLI = fileURLToPath(new URL('../bin/dsh-wb-module.mjs', import.meta.url));
|
|
|
33
34
|
const SCRIPTS = ['typecheck', 'test', 'selfcheck'];
|
|
34
35
|
const TARGETS = ['team', 'public', 'tarball', 'git', 'market', 'local'];
|
|
35
36
|
const PUBLIC_REGISTRIES = ['https://registry.npmjs.org/', 'https://registry.npmmirror.com/'];
|
|
36
|
-
const SEMVER = /^\d+\.\d+\.\d+(?:[-+][0-9A-Za-z.-]+)?$/;
|
|
37
37
|
const REQUIRED_FILES = ['dist/host.js', 'client.js', 'cordis.patch.yml', 'package.json', 'CHANGELOG.md'];
|
|
38
38
|
|
|
39
39
|
function argValue(args, flag) {
|
|
@@ -190,20 +190,15 @@ function repositoryOf(root, exec) {
|
|
|
190
190
|
}
|
|
191
191
|
|
|
192
192
|
function bumpVersion(version, kind) {
|
|
193
|
-
const
|
|
194
|
-
if (
|
|
195
|
-
|
|
196
|
-
const minor = Number(match[2]);
|
|
197
|
-
const patch = Number(match[3]);
|
|
198
|
-
if (kind === 'major') return `${major + 1}.0.0`;
|
|
199
|
-
if (kind === 'minor') return `${major}.${minor + 1}.0`;
|
|
200
|
-
return `${major}.${minor}.${patch + 1}`;
|
|
193
|
+
const next = semver.inc(String(version), kind);
|
|
194
|
+
if (next === null) throw new ReleaseError(`当前版本号读不出来:${String(version)}`);
|
|
195
|
+
return next;
|
|
201
196
|
}
|
|
202
197
|
|
|
203
198
|
function nextVersion(current, asked, exists) {
|
|
204
199
|
if (asked !== undefined) {
|
|
205
200
|
const version = /^(major|minor|patch)$/.test(asked) ? bumpVersion(current, asked) : asked;
|
|
206
|
-
if (
|
|
201
|
+
if (semver.valid(version) === null) {
|
|
207
202
|
throw new ReleaseError(`--version 只认 patch / minor / major / x.y.z(收到「${asked}」)`);
|
|
208
203
|
}
|
|
209
204
|
if (exists(version)) throw new ReleaseError(`${version} 已经被占了 —— 换一个版本`);
|
|
@@ -436,7 +431,8 @@ function registryExists(exec, info, name, version) {
|
|
|
436
431
|
throw new ReleaseError(`查团队源失败(${info.registry}):${tailOf(text)}`);
|
|
437
432
|
}
|
|
438
433
|
|
|
439
|
-
function publishArgsOf(info) {
|
|
434
|
+
function publishArgsOf(info, version) {
|
|
435
|
+
const tag = publishTagOf(version);
|
|
440
436
|
return [
|
|
441
437
|
'publish',
|
|
442
438
|
'--registry',
|
|
@@ -444,11 +440,13 @@ function publishArgsOf(info) {
|
|
|
444
440
|
'--access',
|
|
445
441
|
info.access,
|
|
446
442
|
'--ignore-scripts',
|
|
443
|
+
...(tag === undefined ? [] : ['--tag', tag]),
|
|
447
444
|
...npmrcArgs(info.npmrc),
|
|
448
445
|
];
|
|
449
446
|
}
|
|
450
447
|
|
|
451
|
-
function otpCommandOf(root, info) {
|
|
448
|
+
function otpCommandOf(root, info, version) {
|
|
449
|
+
const tag = publishTagOf(version);
|
|
452
450
|
return [
|
|
453
451
|
`cd ${quote(root)}`,
|
|
454
452
|
'&&',
|
|
@@ -459,19 +457,20 @@ function otpCommandOf(root, info) {
|
|
|
459
457
|
'--access',
|
|
460
458
|
info.access,
|
|
461
459
|
'--ignore-scripts',
|
|
460
|
+
...(tag === undefined ? [] : ['--tag', tag]),
|
|
462
461
|
...npmrcArgs(info.npmrc),
|
|
463
462
|
].join(' ');
|
|
464
463
|
}
|
|
465
464
|
|
|
466
465
|
function publishRegistry(exec, root, info, manifest, version) {
|
|
467
|
-
const result = exec('npm', publishArgsOf(info), { cwd: root, capture: true, allowFailure: true });
|
|
466
|
+
const result = exec('npm', publishArgsOf(info, version), { cwd: root, capture: true, allowFailure: true });
|
|
468
467
|
if (result.status === 0) return;
|
|
469
468
|
const text = `${result.stdout}\n${result.stderr}`;
|
|
470
469
|
if (/EOTP|one-time password|OTP/i.test(text)) {
|
|
471
470
|
throw new NeedsUser(
|
|
472
471
|
'otp',
|
|
473
472
|
'源要求一次性验证码。构建和门禁都过了,请在终端跑下面这条,按提示输入验证码;完了跟我说「好了」,我来核对。',
|
|
474
|
-
otpCommandOf(root, info),
|
|
473
|
+
otpCommandOf(root, info, version),
|
|
475
474
|
{ target: info.target, name: manifest.name, version, registry: info.registry },
|
|
476
475
|
);
|
|
477
476
|
}
|