Files
deepseek-harness/packages/sdk/plugin-fetch/tests/fetcher.spec.ts
imccyu 825a63ab01 feat(sdk): add dsh-plugin-fetch source + fetcher seam
Greenfield #2 "建插件" modules for the forthcoming `dsh-sdk create <source>`
command, in a new foundation-independent package so it touches none of the
dsh-scripts / dsh-helper / create-sdk hotspots the foundation refactor edits.

- `resolvePluginSource(spec)` parses `owner/repo[/subdir]#ref` (github) or
  `pkg@version` (npm) into a `PluginSource` discriminated union, failing loud on
  an ambiguous or malformed spec.
- `PluginFetcher<S>` seam + `fetchPlugin` tag dispatch returning a common
  `FetchedPlugin` (temp dir + immutable provenance).
- `GigetFetcher` (github) over @bluwy/giget-core: resolve `#ref` to a commit SHA
  first, download that SHA; provenance pins the SHA. Chosen over unjs/giget for
  its single runtime dep and absent install/action surface.
- `PacoteFetcher` (npm) over pacote: resolve the manifest, then extract the
  tarball verified against its registry integrity. Registry-only is enforced by
  the source resolver; extract runs no lifecycle scripts.
- Branded `CommitSha`/`Integrity`; network + temp-dir boundaries are injected so
  the logic is unit-tested at 100% per-file coverage without network.

Wiring (package.json pin, cordis.yml via ProjectEditSession with a confirmed
diff, install --ignore-scripts) and the launcher command registration land
later with the foundation.
2026-07-18 16:08:12 +08:00

65 lines
2.3 KiB
TypeScript

import { rm, stat } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { describe, expect, it, vi } from 'vitest'
import {
createTempDir,
fetchPlugin,
type FetchedPlugin,
type PluginFetchers,
} from '../src/fetcher.ts'
import { commitSha } from '../src/ids.ts'
import type { GithubSource, NpmSource, PluginSource } from '../src/source.ts'
function stubFetchers(): { fetchers: PluginFetchers; github: ReturnType<typeof vi.fn>; npm: ReturnType<typeof vi.fn> } {
const result = (dir: string): FetchedPlugin => ({
dir,
source: { kind: 'github', owner: 'o', repo: 'r' },
provenance: { kind: 'github', sha: commitSha('a'.repeat(40)) },
})
const github = vi.fn(async (source: GithubSource) => result(`github:${source.repo}`))
const npm = vi.fn(async (source: NpmSource) => result(`npm:${source.name}`))
return {
fetchers: { github: { kind: 'github', fetch: github }, npm: { kind: 'npm', fetch: npm } },
github,
npm,
}
}
describe('fetchPlugin', () => {
it('routes a github source to the github fetcher', async () => {
const { fetchers, github, npm } = stubFetchers()
const source: GithubSource = { kind: 'github', owner: 'o', repo: 'r' }
const result = await fetchPlugin(source, fetchers)
expect(github).toHaveBeenCalledWith(source)
expect(npm).not.toHaveBeenCalled()
expect(result.dir).toBe('github:r')
})
it('routes an npm source to the npm fetcher', async () => {
const { fetchers, github, npm } = stubFetchers()
const source: NpmSource = { kind: 'npm', name: 'plugin', version: '1.0.0' }
const result = await fetchPlugin(source, fetchers)
expect(npm).toHaveBeenCalledWith(source)
expect(github).not.toHaveBeenCalled()
expect(result.dir).toBe('npm:plugin')
})
it('throws on an unknown source kind', () => {
const { fetchers } = stubFetchers()
const bogus = { kind: 'svn' } as unknown as PluginSource
expect(() => fetchPlugin(bogus, fetchers)).toThrow(/unreachable variant in fetchPlugin/)
})
})
describe('createTempDir', () => {
it('creates a fresh empty directory under the OS temp root', async () => {
const dir = await createTempDir('dsh-plugin-fetch-test-')
try {
expect(dir.startsWith(tmpdir())).toBe(true)
expect((await stat(dir)).isDirectory()).toBe(true)
} finally {
await rm(dir, { recursive: true, force: true })
}
})
})