ssh-release 1.1.0 → 1.2.0

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 CHANGED
@@ -1,5 +1,15 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.2.0 - 2026-06-26
4
+
5
+ ### Added
6
+
7
+ - 增加首次发布指南,覆盖初始化、配置、预检、发布、列表和回滚流程。
8
+ - 增加平台依赖说明,补充 macOS、Ubuntu/Debian、Windows 和 CI 中的 `sshpass`、OpenSSH、`tar`、远端 hash 命令要求。
9
+ - `ssh-release init` 成功后输出下一步操作提示。
10
+ - 配置文件缺失和 `sshpass` 缺失时输出更具体的下一步提示。
11
+ - `ssh-release doctor` 增加本地 `tar`、`ssh`、`scp`、`sshpass` 和远端 hash 命令检查。
12
+
3
13
  ## 1.1.0 - 2026-06-26
4
14
 
5
15
  ### Added
package/README.md CHANGED
@@ -11,7 +11,7 @@
11
11
  已实现:
12
12
 
13
13
  - `ssh-release init`:生成 `ssh-release.config.ts` 配置模板。
14
- - `ssh-release doctor`:检查配置、本地源路径、SSH 连接、远程目录、远端锁和远端 `tar`。
14
+ - `ssh-release doctor`:检查配置、本地源路径、本地命令、SSH 连接、远程目录、远端 hash、远端锁和远端 `tar`。
15
15
  - `ssh-release deploy`:发布本地文件或目录。
16
16
  - `ssh-release list`:查看远程版本和当前版本。
17
17
  - `ssh-release rollback [version]`:回滚到上一个版本或指定版本。
@@ -55,6 +55,9 @@ ssh-release --help
55
55
  ssh-release --version
56
56
  ```
57
57
 
58
+ 首次接入步骤见 [docs/quick-start.md](https://github.com/JackEngineer/ssh-release/blob/main/docs/quick-start.md)。
59
+ 本机和 CI 依赖见 [docs/platform-requirements.md](https://github.com/JackEngineer/ssh-release/blob/main/docs/platform-requirements.md)。
60
+
58
61
  ## 本地开发
59
62
 
60
63
  ```bash
@@ -177,7 +180,9 @@ source files -> package -> upload -> release -> activate -> rollback
177
180
  ssh-release doctor
178
181
  ```
179
182
 
180
- `doctor` 会检查 `.ssh-release.lock`。没有锁时正常通过;如果发现锁,会以 `warn` 显示锁路径、pid、创建时间和安全清理提示。
183
+ `doctor` 会先检查本地 `tar`、`ssh`、`scp`,使用密码登录时还会检查 `sshpass`。本地检查失败时不会继续连接远端。
184
+
185
+ `doctor` 也会检查 `.ssh-release.lock`。没有锁时正常通过;如果发现锁,会以 `warn` 显示锁路径、pid、创建时间和安全清理提示。
181
186
 
182
187
  需要先查看发布计划但不修改服务器时:
183
188
 
@@ -359,13 +364,16 @@ export SSH_RELEASE_PASSWORD='your-password'
359
364
  - `tar`:本地打包发布内容。
360
365
  - `ssh`:执行远端目录、解压、软链接、列表和检查命令。
361
366
  - `scp`:上传压缩包和逐文件回退上传。
362
- - `sha256sum` 或 `shasum`:发布后校验远端 `manifest.json` hash。
363
367
  - `sshpass`:仅密码登录时需要;私钥登录不需要。
364
368
 
365
369
  在 macOS 上打包时会禁用 AppleDouble 和扩展属性元数据,避免把 `._*` 文件发布到 Linux 服务器。
366
370
 
371
+ 远端需要可运行 `sha256sum` 或 `shasum`,用于校验 `manifest.json` hash。
372
+
367
373
  远端 `tar` 是可选能力,不可用时会按配置回退逐文件上传。
368
374
 
375
+ Windows、macOS、Linux 和 CI 的依赖差异见 [docs/platform-requirements.md](https://github.com/JackEngineer/ssh-release/blob/main/docs/platform-requirements.md)。
376
+
369
377
  ## 开发命令
370
378
 
371
379
  ```bash
@@ -412,6 +420,10 @@ GitHub Actions 发布模板见 [docs/github-actions.md](https://github.com/JackE
412
420
 
413
421
  失败恢复指南见 [docs/recovery.md](https://github.com/JackEngineer/ssh-release/blob/main/docs/recovery.md)。
414
422
 
423
+ 首次发布指南见 [docs/quick-start.md](https://github.com/JackEngineer/ssh-release/blob/main/docs/quick-start.md)。
424
+
425
+ 平台依赖说明见 [docs/platform-requirements.md](https://github.com/JackEngineer/ssh-release/blob/main/docs/platform-requirements.md)。
426
+
415
427
  版本变更见 [CHANGELOG.md](https://github.com/JackEngineer/ssh-release/blob/main/CHANGELOG.md)。
416
428
 
417
429
  ## 项目结构
@@ -458,6 +470,8 @@ tests/
458
470
  docs/
459
471
  ├── contracts.md
460
472
  ├── github-actions.md
473
+ ├── platform-requirements.md
474
+ ├── quick-start.md
461
475
  ├── recovery.md
462
476
  ├── release-checklist.md
463
477
  └── superpowers/specs/2026-06-25-ssh-release-design.md
@@ -480,6 +494,7 @@ docs/
480
494
  - 发布和回滚锁获取、释放和锁冲突拦截。
481
495
  - 发布 manifest 生成、上传和远端 hash 校验。
482
496
  - `doctor` 远端锁状态检查和安全清理提示。
497
+ - `doctor` 本地命令和远端 hash 检查。
483
498
  - `unlock` 显式确认路径后删除远端锁。
484
499
  - 远端 `tar` 失败后的逐文件上传回退。
485
500
  - 远程版本列表读取和当前版本标记。
package/dist/cli.js CHANGED
@@ -54,6 +54,7 @@ export async function runCli(argv = process.argv.slice(2), options = {}) {
54
54
  return 0;
55
55
  }
56
56
  io.log(`已创建 ${parsed.configPath}`);
57
+ printInitNextSteps(parsed.configPath, io);
57
58
  return 0;
58
59
  }
59
60
  if (command === 'deploy') {
@@ -353,6 +354,13 @@ function printUnlockResult(result, io) {
353
354
  io.log('确认没有发布或回滚任务后再执行:');
354
355
  io.log(`ssh-release unlock --confirm ${result.lockPath}`);
355
356
  }
357
+ function printInitNextSteps(configPath, io) {
358
+ io.log('下一步:');
359
+ io.log('1. 设置 SSH_RELEASE_HOST、SSH_RELEASE_USER,并选择密码或私钥认证。');
360
+ io.log('2. 确认 source.path 和 target.path 指向要发布的本地目录和远端目录。');
361
+ io.log(`3. 运行 ssh-release doctor --config ${configPath} 检查配置和服务器连接。`);
362
+ io.log(`4. 运行 ssh-release deploy --plan --config ${configPath} 预览发布计划。`);
363
+ }
356
364
  function printJsonResult(command, result, exitCode, io) {
357
365
  io.log(JSON.stringify({
358
366
  ok: exitCode === 0,
@@ -415,6 +423,9 @@ function formatError(error) {
415
423
  return error instanceof Error ? error.message : String(error);
416
424
  }
417
425
  function createErrorHint(command, error) {
426
+ if (error.includes('配置文件不存在')) {
427
+ return '先运行 ssh-release init 生成配置文件,再填写 source.path、server 和 target.path 后执行 ssh-release doctor。';
428
+ }
418
429
  if (error.includes('远程已有发布任务正在运行') || error.includes('.ssh-release.lock')) {
419
430
  return '先运行 ssh-release unlock 查看远端锁,确认没有发布或回滚任务后再按提示删除锁。';
420
431
  }
@@ -435,12 +446,14 @@ function createErrorHint(command, error) {
435
446
  || error.includes('manifest.json hash'))) {
436
447
  return '先运行 ssh-release list --json 和 ssh-release doctor --json,确认 current、版本目录、manifest 和远端锁状态。';
437
448
  }
449
+ if (error.includes('sshpass')) {
450
+ return '当前配置使用密码登录,本机需要安装 sshpass;macOS 可运行 brew install hudochenkov/sshpass/sshpass,Ubuntu/Debian 可运行 sudo apt-get install sshpass,Windows 和 CI 推荐改用私钥登录。';
451
+ }
438
452
  if (command
439
453
  && command !== 'init'
440
454
  && (error.includes('Permission denied')
441
455
  || error.includes('Connection timed out')
442
456
  || error.includes('Could not resolve hostname')
443
- || error.includes('sshpass')
444
457
  || error.includes('SSH'))) {
445
458
  return '先运行 ssh-release doctor 检查 SSH 连接、认证信息和远端目录权限。';
446
459
  }
package/dist/doctor.js CHANGED
@@ -1,6 +1,7 @@
1
1
  import { access } from 'node:fs/promises';
2
2
  import { loadConfigFile } from './config.js';
3
3
  import { readRemoteLockStatus } from './lock.js';
4
+ import { runProcess } from './process.js';
4
5
  import { shellQuote } from './remote.js';
5
6
  export async function runDoctorFromFile(configPath, createClient) {
6
7
  let config;
@@ -32,8 +33,25 @@ export async function runDoctor(config, client, options = {}) {
32
33
  message: '配置字段有效',
33
34
  });
34
35
  await addSourcePathCheck(checks, config.source.path);
36
+ await addLocalDependencyChecks(checks, config, options.localCommandExists ?? defaultLocalCommandExists);
37
+ if (checks.some((check) => check.status === 'fail')) {
38
+ return {
39
+ ok: false,
40
+ checks,
41
+ };
42
+ }
35
43
  await addRemoteCheck(checks, 'SSH 连接', () => client.exec('true'), 'SSH 可连接');
36
44
  await addRemoteCheck(checks, '远程目录', () => client.exec(`mkdir -p ${shellQuote(config.target.path)} && test -w ${shellQuote(config.target.path)}`), '远程目录可创建且可写');
45
+ await addRemoteCheck(checks, '远端 hash', () => client.exec([
46
+ 'if command -v sha256sum >/dev/null 2>&1; then',
47
+ 'command -v sha256sum;',
48
+ 'elif command -v shasum >/dev/null 2>&1; then',
49
+ 'command -v shasum;',
50
+ 'else',
51
+ 'echo "远端缺少 sha256sum 或 shasum" >&2;',
52
+ 'exit 1;',
53
+ 'fi',
54
+ ].join(' ')), '远端 sha256sum 或 shasum 可用');
37
55
  await addRemoteLockCheck(checks, config, client);
38
56
  try {
39
57
  await client.exec('command -v tar');
@@ -127,6 +145,35 @@ async function addSourcePathCheck(checks, sourcePath) {
127
145
  });
128
146
  }
129
147
  }
148
+ async function addLocalDependencyChecks(checks, config, localCommandExists) {
149
+ await addLocalCommandCheck(checks, 'tar', '本地 tar', localCommandExists);
150
+ await addLocalCommandCheck(checks, 'ssh', '本地 ssh', localCommandExists);
151
+ await addLocalCommandCheck(checks, 'scp', '本地 scp', localCommandExists);
152
+ if (config.server.password) {
153
+ await addLocalCommandCheck(checks, 'sshpass', '本地 sshpass', localCommandExists, '本地 sshpass 不可用;密码登录需要安装 sshpass,或改用私钥登录');
154
+ }
155
+ }
156
+ async function addLocalCommandCheck(checks, command, name, localCommandExists, missingMessage = `${name} 不可用;请安装后重试`) {
157
+ const exists = await localCommandExists(command);
158
+ checks.push({
159
+ name,
160
+ status: exists ? 'pass' : 'fail',
161
+ message: exists ? `${name} 可用` : missingMessage,
162
+ });
163
+ }
164
+ async function defaultLocalCommandExists(command) {
165
+ try {
166
+ if (process.platform === 'win32') {
167
+ await runProcess('where.exe', [command]);
168
+ return true;
169
+ }
170
+ await runProcess('sh', ['-c', `command -v ${shellQuote(command)} >/dev/null 2>&1`]);
171
+ return true;
172
+ }
173
+ catch {
174
+ return false;
175
+ }
176
+ }
130
177
  async function addRemoteCheck(checks, name, run, successMessage) {
131
178
  try {
132
179
  await run();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ssh-release",
3
- "version": "1.1.0",
3
+ "version": "1.2.0",
4
4
  "description": "A general-purpose SSH file release CLI.",
5
5
  "type": "module",
6
6
  "bin": {