@logicseek/hello-cc 0.1.0 → 0.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/CHANGELOG.md +81 -0
- package/README.md +69 -378
- package/README.zh-CN.md +49 -325
- package/assets/logo.svg +84 -0
- package/bin/hcc.mjs +106 -11
- package/docs/README.md +22 -0
- package/docs/README.zh-CN.md +22 -0
- package/docs/commands.md +104 -0
- package/docs/commands.zh-CN.md +101 -0
- package/docs/guide.md +339 -0
- package/docs/guide.zh-CN.md +292 -0
- package/package.json +6 -2
- package/scripts/regression.mjs +78 -3
- package/scripts/release-notes.mjs +112 -0
package/scripts/regression.mjs
CHANGED
|
@@ -473,6 +473,31 @@ async function expectWebSocketMarker(peer, marker) {
|
|
|
473
473
|
});
|
|
474
474
|
}
|
|
475
475
|
|
|
476
|
+
async function expectResizeReplaceSnapshot(peer, marker) {
|
|
477
|
+
await new Promise((resolve, reject) => {
|
|
478
|
+
let sawSnapshot = false;
|
|
479
|
+
const ws = new WebSocket(`ws://127.0.0.1:${port}/ws/terminal/${encodeURIComponent(peer)}`);
|
|
480
|
+
const timer = setTimeout(() => reject(new Error(`${peer} resize replace timeout`)), 5000);
|
|
481
|
+
ws.on('open', () => {
|
|
482
|
+
const result = hccMaybe(['inject', peer, `echo ${marker}`]);
|
|
483
|
+
if (result.status !== 0) reject(new Error(result.stderr || result.stdout || 'inject failed'));
|
|
484
|
+
});
|
|
485
|
+
ws.on('message', (raw) => {
|
|
486
|
+
const msg = JSON.parse(String(raw));
|
|
487
|
+
if (msg.type === 'snapshot') {
|
|
488
|
+
sawSnapshot = true;
|
|
489
|
+
ws.send(JSON.stringify({ type: 'resize', cols: 96, rows: 28 }));
|
|
490
|
+
}
|
|
491
|
+
if (sawSnapshot && msg.type === 'replace' && String(msg.data || '').includes(marker)) {
|
|
492
|
+
clearTimeout(timer);
|
|
493
|
+
ws.close();
|
|
494
|
+
resolve();
|
|
495
|
+
}
|
|
496
|
+
});
|
|
497
|
+
ws.on('error', reject);
|
|
498
|
+
});
|
|
499
|
+
}
|
|
500
|
+
|
|
476
501
|
function tmuxStreamNodes() {
|
|
477
502
|
const dir = path.join(root, '.hello-cc', 'bufs');
|
|
478
503
|
if (!fs.existsSync(dir)) return [];
|
|
@@ -871,6 +896,7 @@ async function tmuxBackedStartWorkflow() {
|
|
|
871
896
|
hcc(['inject', 'shell-a', `echo PTY_OK > ${file}`]);
|
|
872
897
|
await waitForFile(file, 'PTY_OK', 'pty injection');
|
|
873
898
|
await expectWebSocketMarker('shell-a', 'WS_PTY_OK');
|
|
899
|
+
await expectResizeReplaceSnapshot('shell-a', 'WS_RESIZE_OK');
|
|
874
900
|
await expectBoundedTmuxStream('tmux-backed FIFO stream');
|
|
875
901
|
|
|
876
902
|
await stopRuntime();
|
|
@@ -1098,7 +1124,17 @@ async function downGcPackWorkflow() {
|
|
|
1098
1124
|
await stopRuntime();
|
|
1099
1125
|
await waitFor(() => !fs.existsSync(path.join(root, '.hello-cc', 'runtime.json')), 'runtime cleanup', 5000);
|
|
1100
1126
|
hcc(['gc', '--older-than', '0', '--yes']);
|
|
1101
|
-
run('npm', ['pack', '--dry-run']);
|
|
1127
|
+
const pack = JSON.parse(run('npm', ['pack', '--dry-run', '--json']));
|
|
1128
|
+
const files = new Set(pack[0]?.files?.map((entry) => entry.path) || []);
|
|
1129
|
+
for (const file of ['assets/logo.svg', 'CHANGELOG.md', 'docs/commands.md', 'docs/commands.zh-CN.md']) {
|
|
1130
|
+
if (!files.has(file)) fail(`npm package missing ${file}`);
|
|
1131
|
+
}
|
|
1132
|
+
const releaseCheck = run('npm', ['run', 'release:check']);
|
|
1133
|
+
if (!releaseCheck.includes('release notes ok: 0.1.2')) fail(`release check output wrong:\n${releaseCheck}`);
|
|
1134
|
+
const releaseNotes = run('npm', ['run', 'release:notes']);
|
|
1135
|
+
if (!releaseNotes.includes('## 0.1.2') || !releaseNotes.includes('### Summary')) {
|
|
1136
|
+
fail(`release notes output wrong:\n${releaseNotes}`);
|
|
1137
|
+
}
|
|
1102
1138
|
}
|
|
1103
1139
|
|
|
1104
1140
|
function oldNameScan() {
|
|
@@ -1131,12 +1167,51 @@ function syntaxAndHelp() {
|
|
|
1131
1167
|
run(process.execPath, ['--check', path.join(repoRoot, 'lib', 'setup.mjs')]);
|
|
1132
1168
|
run(process.execPath, ['--check', path.join(repoRoot, 'lib', 'discover.mjs')]);
|
|
1133
1169
|
const mainHelp = run(process.execPath, [hccBin, '--help']);
|
|
1134
|
-
if (mainHelp.includes('setup') || mainHelp.includes('
|
|
1170
|
+
if (mainHelp.includes('setup') || mainHelp.includes('--web-managed')) {
|
|
1135
1171
|
fail(`public help exposes maintenance or removed commands:\n${mainHelp}`);
|
|
1136
1172
|
}
|
|
1173
|
+
if (!mainHelp.includes(' update Update the global npm install of hello-cc')) {
|
|
1174
|
+
fail(`main help missing update command:\n${mainHelp}`);
|
|
1175
|
+
}
|
|
1176
|
+
if (!mainHelp.includes(' uninstall Remove hooks, shims, and optional project data')) {
|
|
1177
|
+
fail(`main help missing uninstall command:\n${mainHelp}`);
|
|
1178
|
+
}
|
|
1179
|
+
const updateHelp = run(process.execPath, [hccBin, 'update', '--help']);
|
|
1180
|
+
if (!updateHelp.includes('hcc update') || !updateHelp.includes('npm install -g @logicseek/hello-cc@TAG')) {
|
|
1181
|
+
fail(`update help missing expected content:\n${updateHelp}`);
|
|
1182
|
+
}
|
|
1183
|
+
const uninstallHelp = run(process.execPath, [hccBin, 'uninstall', '--help']);
|
|
1184
|
+
if (!uninstallHelp.includes('hcc uninstall') || !uninstallHelp.includes('hcc uninstall [--purge --yes]')) {
|
|
1185
|
+
fail(`uninstall help missing expected content:\n${uninstallHelp}`);
|
|
1186
|
+
}
|
|
1187
|
+
const updateDryRun = run(process.execPath, [hccBin, 'update', '--dry-run']);
|
|
1188
|
+
if (!updateDryRun.includes('would run: npm install -g @logicseek/hello-cc@latest')) {
|
|
1189
|
+
fail(`update dry-run output wrong:\n${updateDryRun}`);
|
|
1190
|
+
}
|
|
1191
|
+
const updateJson = JSON.parse(run(process.execPath, [hccBin, '--json', 'update', '--dry-run', '--tag', '0.1.2']));
|
|
1192
|
+
if (!updateJson.ok || updateJson.data.command !== 'npm install -g @logicseek/hello-cc@0.1.2') {
|
|
1193
|
+
fail(`update json dry-run output wrong:\n${JSON.stringify(updateJson)}`);
|
|
1194
|
+
}
|
|
1195
|
+
const updateBuildJson = JSON.parse(run(process.execPath, [hccBin, '--json', 'update', '--dry-run', '--tag', '1.2.3+build.1']));
|
|
1196
|
+
if (!updateBuildJson.ok || updateBuildJson.data.command !== 'npm install -g @logicseek/hello-cc@1.2.3+build.1') {
|
|
1197
|
+
fail(`update build-metadata dry-run output wrong:\n${JSON.stringify(updateBuildJson)}`);
|
|
1198
|
+
}
|
|
1137
1199
|
const runHelp = run(process.execPath, [hccBin, 'run', '--help']);
|
|
1138
1200
|
if (runHelp.includes('--web-managed')) fail(`run help exposes removed --web-managed:\n${runHelp}`);
|
|
1139
|
-
|
|
1201
|
+
const subcommandHelpCases = [
|
|
1202
|
+
['task', 'done', 'hcc task done'],
|
|
1203
|
+
['msg', 'ack', 'hcc msg ack'],
|
|
1204
|
+
['peer', 'attach', 'peer attach'],
|
|
1205
|
+
['lock', 'release', 'hcc lock release'],
|
|
1206
|
+
['handoff', 'create', 'hcc handoff create'],
|
|
1207
|
+
['event', 'tail', 'hcc event tail']
|
|
1208
|
+
];
|
|
1209
|
+
for (const [group, subcommand, expected] of subcommandHelpCases) {
|
|
1210
|
+
const help = run(process.execPath, [hccBin, group, subcommand, '--help']);
|
|
1211
|
+
if (!help.includes(expected)) {
|
|
1212
|
+
fail(`${group} ${subcommand} help missing expected content:\n${help}`);
|
|
1213
|
+
}
|
|
1214
|
+
}
|
|
1140
1215
|
const removed = runMaybe(process.execPath, [hccBin, '--root', root, 'run', '--peer', 'bad', '--kind', 'shell', '--web-managed', '--', 'bash']);
|
|
1141
1216
|
if (removed.status === 0 || !String(removed.stderr || removed.stdout).includes('unknown option --web-managed')) {
|
|
1142
1217
|
fail(`run --web-managed was not rejected:\n${removed.stdout}\n${removed.stderr}`);
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'node:fs';
|
|
4
|
+
import path from 'node:path';
|
|
5
|
+
import process from 'node:process';
|
|
6
|
+
|
|
7
|
+
const repoRoot = path.resolve(import.meta.dirname, '..');
|
|
8
|
+
|
|
9
|
+
function usage() {
|
|
10
|
+
console.log(`release-notes
|
|
11
|
+
|
|
12
|
+
Usage:
|
|
13
|
+
node scripts/release-notes.mjs [--version VERSION] [--check]
|
|
14
|
+
|
|
15
|
+
Reads CHANGELOG.md and prints the section for VERSION. If VERSION is omitted,
|
|
16
|
+
the current package.json version is used.
|
|
17
|
+
|
|
18
|
+
Keep one top-level CHANGELOG.md file. For each release, add a new section:
|
|
19
|
+
|
|
20
|
+
## 0.1.3
|
|
21
|
+
|
|
22
|
+
The printed section is the source for GitHub Release notes and detailed npm
|
|
23
|
+
package release descriptions.
|
|
24
|
+
`);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function readJson(file) {
|
|
28
|
+
return JSON.parse(fs.readFileSync(file, 'utf8'));
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function parseArgs(argv) {
|
|
32
|
+
const opts = { check: false, help: false, version: null };
|
|
33
|
+
for (let i = 0; i < argv.length; i += 1) {
|
|
34
|
+
const arg = argv[i];
|
|
35
|
+
if (arg === '--help' || arg === '-h') {
|
|
36
|
+
opts.help = true;
|
|
37
|
+
} else if (arg === '--check') {
|
|
38
|
+
opts.check = true;
|
|
39
|
+
} else if (arg === '--version') {
|
|
40
|
+
opts.version = argv[++i];
|
|
41
|
+
if (opts.version === undefined) throw new Error('Missing value for --version');
|
|
42
|
+
} else if (arg.startsWith('--version=')) {
|
|
43
|
+
opts.version = arg.slice('--version='.length);
|
|
44
|
+
} else {
|
|
45
|
+
throw new Error(`Unknown argument: ${arg}`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return opts;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function releaseSection(markdown, version) {
|
|
52
|
+
const lines = markdown.split(/\r?\n/);
|
|
53
|
+
const heading = `## ${version}`;
|
|
54
|
+
const start = lines.findIndex((line) => line.trim() === heading);
|
|
55
|
+
if (start < 0) return null;
|
|
56
|
+
|
|
57
|
+
let end = lines.length;
|
|
58
|
+
for (let i = start + 1; i < lines.length; i += 1) {
|
|
59
|
+
if (/^##\s+/.test(lines[i])) {
|
|
60
|
+
end = i;
|
|
61
|
+
break;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return lines.slice(start, end).join('\n').trimEnd();
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function validateSection(section, version) {
|
|
68
|
+
const problems = [];
|
|
69
|
+
if (!section) {
|
|
70
|
+
problems.push(`CHANGELOG.md is missing section ## ${version}`);
|
|
71
|
+
return problems;
|
|
72
|
+
}
|
|
73
|
+
if (!section.includes('### Summary')) problems.push(`## ${version} is missing ### Summary`);
|
|
74
|
+
if (!section.includes('### Highlights')) problems.push(`## ${version} is missing ### Highlights`);
|
|
75
|
+
if (section.includes('TODO') || section.includes('TBD')) problems.push(`## ${version} still contains TODO/TBD text`);
|
|
76
|
+
const bulletCount = (section.match(/^- /gm) || []).length;
|
|
77
|
+
if (bulletCount < 2) problems.push(`## ${version} should include at least two release-note bullets`);
|
|
78
|
+
return problems;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function main() {
|
|
82
|
+
const opts = parseArgs(process.argv.slice(2));
|
|
83
|
+
if (opts.help) {
|
|
84
|
+
usage();
|
|
85
|
+
return;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const pkg = readJson(path.join(repoRoot, 'package.json'));
|
|
89
|
+
const version = opts.version || pkg.version;
|
|
90
|
+
const changelog = fs.readFileSync(path.join(repoRoot, 'CHANGELOG.md'), 'utf8');
|
|
91
|
+
const section = releaseSection(changelog, version);
|
|
92
|
+
const problems = validateSection(section, version);
|
|
93
|
+
|
|
94
|
+
if (opts.check) {
|
|
95
|
+
if (problems.length) {
|
|
96
|
+
console.error(problems.join('\n'));
|
|
97
|
+
process.exitCode = 1;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
console.log(`release notes ok: ${version}`);
|
|
101
|
+
return;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (problems.length) {
|
|
105
|
+
console.error(problems.join('\n'));
|
|
106
|
+
process.exitCode = 1;
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
console.log(section);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
main();
|