Files
deepseek-harness/packages/sdk/plugin-fetch/README.md
T
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

33 lines
3.2 KiB
Markdown

# `@deepseek-ai/dsh-plugin-fetch`
Fetch an external Cordis plugin into a temp directory — pinned to an immutable commit or integrity and never executed — for the forthcoming `dsh-sdk create <source>` command.
The package parses a source spec into a `PluginSource`, dispatches to the matching `PluginFetcher`, and returns a common `FetchedPlugin` (temp dir + immutable provenance) that the wiring step pins into `package.json`, mounts in `cordis.yml`, and installs with `--ignore-scripts`.
| Export | Role |
|---|---|
| `resolvePluginSource(spec)``PluginSource` | Parse `owner/repo[/subdir]#ref` (github) or `pkg@version` (npm); fail loud on an ambiguous or malformed spec |
| `PluginFetcher<S>` | The fetch seam: resolve the pin BEFORE download, extract without executing pulled code |
| `GigetFetcher` / `createGigetFetcher()` | Github fetcher over `@bluwy/giget-core`: resolve `#ref` to a commit SHA, download that SHA |
| `PacoteFetcher` / `createPacoteFetcher()` | Npm fetcher over `pacote`: resolve the manifest, then extract the tarball verified against its integrity |
| `fetchPlugin(source, fetchers)``FetchedPlugin` | Dispatch one source to its fetcher by discriminant tag |
## Safety model — confirm-before-run, not run-on-fetch
A fetch only downloads and unpacks; it runs no install, no `postinstall`/`prepare`, and no degit-style template actions.
- **github** uses `@bluwy/giget-core` (one runtime dependency, `modern-tar`; no CLI, install, or JSON-registry surface) so a fetch can only download and untar a tarball. The commit is pinned first: `GigetFetcher` resolves `#ref` — or the default branch when absent — to an immutable SHA via the GitHub commits API, then downloads that SHA. Provenance carries the SHA so wiring pins `github:owner/repo#<sha>`.
- **npm** uses `pacote`. Registry-only is enforced upstream: `resolvePluginSource` produces only a `name@version` registry spec, so pacote never sees a git/file/dir spec whose lifecycle scripts would run, and a registry tarball extract is a plain untar. The manifest is resolved first so extract verifies the artifact against the registry-published integrity (a mismatch raises `EINTEGRITY`). Provenance carries the exact version, resolved URL, and integrity.
Both network boundaries (giget download, GitHub ref resolution, pacote, temp-dir allocation) are constructor-injected, so the fetch logic is unit-tested without network; the `create*Fetcher()` factories wire the real libraries.
## Model Experience
None, as this developer tooling acquires plugin sources for the SDK launcher and registers no live agent or model surface.
## Known Limitations and Deferred Work
- **Wiring is not here yet** — pinning `package.json`, mounting `cordis.yml` through `ProjectEditSession` with a confirmed diff, and `install --ignore-scripts` land with the `dsh-sdk create` command. This package stops at a fetched, pinned temp directory.
- **npm registry authentication** — `PacoteFetcher` targets a public or default-configured registry; private-registry auth beyond pacote's ambient npm config is deferred.
- **Template-repo initialization** — the whole-project init mode (`dsh-sdk create` from a template repository) is out of scope; this package fetches a single plugin into an existing project.