Prechádzať zdrojové kódy

test(plugins): verify CLI interruption and document recovery

Yichen Jiang 2 týždňov pred
rodič
commit
f511455e14

+ 2 - 2
apps/cli/README.i18n.yaml

@@ -2,5 +2,5 @@
 # side as of the last confirmed-consistent state. Both languages carry equal authority;
 # after editing either side, bring the other along and re-record with:
 #   pnpm run verify-translation-pairing --write apps/cli/README.md
-README.md: 44dfb27275e29295f8c63134ab833a2bff86e1ed
-README.zh.md: b2158681d8ab1a4e11e31155e8f970539573d457
+README.md: b7cddfb46203f40fa68b223931b745a75750ee5a
+README.zh.md: 626be009f179d45a1a478856cde8db6de67a39cb

+ 2 - 0
apps/cli/README.md

@@ -23,6 +23,8 @@ The invoking directory is the default workspace root. The `web`, `headless`, `sd
 
 The launcher parses only its own flags and hands everything after them to the booted profile, where any injected app plugin may parse the shared immutable snapshot ([`dsh-cmdline`](../../packages/boot/cmdline/README.md)). The first token the launcher does not recognize starts the app's arguments:
 
+Managed `dsh plugin add/remove` commands forward interruption to their package process and wait for it to stop. An interrupted add restores `package.json` and `pnpm-lock.yaml`; downloaded or unpacked files may remain. SIGINT returns exit code 130 and SIGTERM returns 143.
+
 ```sh
 dsh --profile web --port 8080       # --port belongs to the web app
 dsh --profile tui --resume <id>     # example, assuming the tui profile is installed; --resume belongs to the terminal app

+ 2 - 0
apps/cli/README.zh.md

@@ -23,6 +23,8 @@
 
 启动器只解析自身的 flag,并将其后的所有内容交给已启动的 profile;注入该 profile 的任意应用插件都可以解析这份共享的不可变快照([`dsh-cmdline`](../../packages/boot/cmdline/README.zh.md))。启动器无法识别的第一个 token 标志着应用参数的开始:
 
+由安装器处理的 `dsh plugin add/remove` 会将中断传给包管理进程,并等待它停止。被中断的 add 会恢复 `package.json` 与 `pnpm-lock.yaml`;下载或解包文件可能保留。SIGINT 返回退出码 130,SIGTERM 返回 143。
+
 ```sh
 dsh --profile web --port 8080       # --port belongs to the web app
 dsh --profile tui --resume <id>     # example, assuming the tui profile is installed; --resume belongs to the terminal app

+ 6 - 6
apps/cli/tests/plugin.spec.ts

@@ -104,8 +104,8 @@ const fakeMetadata: typeof readPackageMetadata = (options) => {
 }
 
 describe('dsh plugin', () => {
-  it('waits for its interrupted install and removes its signal listeners', async () => {
-    const before = process.listeners('SIGINT')
+  it.each(['SIGINT', 'SIGTERM'] as const)('waits for an install interrupted by %s and removes its listener', async (signal) => {
+    const before = process.listeners(signal)
     const done = Promise.withResolvers<{ exitCode: null; signal: 'SIGTERM' }>()
     const started = Promise.withResolvers<undefined>()
     const stdout = new PassThrough()
@@ -119,12 +119,12 @@ describe('dsh plugin', () => {
       }
     } })
     await started.promise
-    const owned = process.listeners('SIGINT').find(listener => !before.includes(listener))
+    const owned = process.listeners(signal).find(listener => !before.includes(listener))
     expect(owned).toBeDefined()
     // Invoke only this command's listener; do not signal the test runner or other listeners.
-    owned?.('SIGINT')
-    expect(await run).toBe(130)
-    expect(process.listeners('SIGINT')).toEqual(before)
+    owned?.(signal)
+    expect(await run).toBe(signal === 'SIGINT' ? 130 : 143)
+    expect(process.listeners(signal)).toEqual(before)
   })
 
   it('lets pnpm colour its output when stdout is a terminal', async () => {