npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@avantf/dsh-plugin-base

v0.7.1

Published

The @avantf family base for DSH plugins: startup environment initialisation (declare → probe → fetch → verify → report), the host compatibility gate (provision / verdictOf / probes / report / post-registration verification), and one content-addressed shar

Readme

@avantf/dsh-plugin-base

DSH 插件共用的底座(共享库)。插件在运行时按 file URL 动态加载它——不静态 import、不打包进插件, 拿不到就降级挂载——把四件容易各写一遍的事收在一起:启动期资源预装、共享运行期依赖、 宿主兼容门禁、一组与 DSH 无关的共享工具。

  • 运行期零依赖:dependencies 为空,源码里连 zod 都不 import。
  • 唯一一条 peer 是 zod(>=4.4.3 <5),由宿主 / 上层提供,好让调用方、本包与宿主解析到同一份 zod —— 两份物理副本的 schema 身份不同,strict codec 会因此判不兼容。
  • 要求 Node.js ≥ 22。

主要功能

1. 资源预装(声明式 provisioner)

调用方只写一份清单,其余交给框架:

声明 → 探测 → 获取 → 校验 → 报告
  • 已经在盘上 / PATH 上的直接用,不重复下载;
  • 需要安装的由框架下载、校验完整性、原子落盘(同目录临时文件 + rename;跨进程一把族根锁);
  • 锁被活体进程长期持有(锁文件 mtime 越过 staleMs 而 holder 的 pid 仍活着)时不抢锁:打一条用户可见的 warn(写明 holder 的 pid、已持有时长、本次已等待时长)后继续按预算等待;等不到就报 lock/timeout, 消息里带 holder 与 mtime 诊断;
  • 每一项给出 present / installed / skipped / failed,失败带稳定 code;
  • 四种内置资源种类:npm-package、binary-archive、model-cache、runtime-deps。新的种类 = 一个新的 provider,核心不动;
  • 策略可配:离线、镜像、每项的并发与超时、启动预算、下载总闸。

2. 共享运行期依赖(runtime-deps)

插件需要自己 install 树里没有的运行期 npm 包(例如某个带原生模块的推理库),但各插件不该各装一份、 更不该把宿主单例(zod、DSH 包)装成两份。base 提供一个按 spec 寻址(spec-addressed)、装一次就不再 改写、全局共享的依赖根:

插件声明(packages / sources / attempts / verify / peers)
  → key = sha256(packages + verify + lockfile + platform/arch/Node ABI + manager 版本
                 + attempts 的 source/env/scripts + peer 名/区间 + 解析到的宿主副本)
  → 在 <home>/deps/runtime/<key>/ 只装一次(per-key 锁 + staging + 原子 rename)
  → 插件用 createRequire(<根>/package.json) 加载
  • key 是声明的摘要,不是取回字节的摘要:不给 lockfile 时,同一个 key 在不同时间/机器上可以装出不同 版本;根保证的是“装一次,之后本地不再改写”。要字节级可复现就把 lockfile 传进来;
  • 同 key ⇒ 同一份物理安装,所有插件共用;改了包、verify、lockfile、平台、manager、env,或 peer 的区间/ 解析到的副本 ⇒ 新 key、新根,旧根永不被改写;
  • sources + attempts 按顺序尝试(registry / cache / 离线 / 脚本策略);每个 attempt 都记录,全部失败报 runtime-deps/install-failed,插件照常挂载(failed 只是一条报告);
  • verify 的每一项在写 .ready 之前用 createRequire(...).resolve() 验过;失败换下一个 attempt;
  • peers 只链接不安装:宿主单例写在 peerDependencies(optional),装完从插件自己的树链接进来;复用 时同时核对链接目标与链接到的版本,同版本的另一个目录不算本 key 的根(没有 symlink 时的复制回退是 版本级身份,记录在 .runtime.json 的 copiedPeers);
  • base 不认识任何包名:装什么、从哪装、验什么,全部来自插件的 RuntimeDepsSpec;
  • GC 显式(./internal 的 gcRuntimeDeps),按 status.json 里记录的 key 保留,绝不在启动时自动回收。

3. 宿主兼容门禁

在挂载之前判断"当前宿主能不能跑这段代码",语义是只有被证实的不兼容才拒绝:

  • 调用方交出自己真实的 contribution 与契约(服务、工具、方法、wire schema),由探针在真实宿主上验;
  • 说不清只是一条备注,版本差异只是警告,绝不抛错;
  • compatReport(verdict, words) 把裁决渲染成一份报告:结构归本包,措辞归调用方;
  • registerMegaphone 在拒绝加载的路径上留一条用户可见的命令(那时它是唯一的信息渠道);
  • verifyRegisteredFaces 在挂载后复查"声明的每个 schema / 工具真的注册上了"(只告警)。

4. 共享工具(kit)

与 DSH 无关的纯逻辑,调用方在运行时复用:

| 工具 | 作用 | | --- | --- | | PromptFiles | "缺失或空白就写入默认、有内容则逐字读回"的用户可编辑文本文件层(含崩溃残留的死临时文件清理)。可传 namespace 把 <dataHome>/prompts 里属于本插件的 mem-* / mission-* 前缀变成 base 校验的字段(不传则行为不变) | | familyHome / familyToolsDir / familyModelsDir / resolveDataHome / expandHome | 家族根与数据根解析:⑤ 显式 → ④ 环境变量 → ② 配置值 → 默认,一律传具名 slot | | wellFormedText / wellFormedDeep | 出站文本的良构修复:孤立代理(lone surrogate)会被 JSON.stringify 原样写出、被严格解析器整篇拒收,这里统一换成 U+FFFD;引擎没有 String.prototype.toWellFormed 时走等价扫描。递归版只修字符串、数组与普通对象(含键),Date / 类实例等一律按同一性原样返回 | | openSqlite / probeSqlite / describeSqlite / migrateSqlite / readSqliteUserVersion | 通用 SQLite 机制(v7):打开(含只读)、busy_timeout / WAL / synchronous 等 PRAGMA、位置或命名参数绑定(未用命名参数被忽略)、BLOB → Buffer、预编译语句缓存、BEGIN IMMEDIATE 事务(拒绝嵌套)、健康/完整性检查与带版本的迁移执行器。不含 schema、表名或产品错误分类——SQL 与文案归调用方 |

SQLite kit 用的是运行时自带的 node:sqlite(经 createRequire 惰性加载、按能力探测),不是第三方 运行依赖;宿主没有它时 probeSqlite() 返回降级原因、openSqlite() 抛结构化错误,插件照常挂载。加载时的 ExperimentalWarning 是 stderr 噪声,不是错误。详见 docs/INTERFACE.md §13。

没有运行期消费者的构件(如 createPluginLogger、Typert 符号、compat 的单个探针、npmPackageProvider) 放在不承诺兼容的 ./internal 子路径,不属于 . 接口。

怎么用

安装

npm install @avantf/dsh-plugin-base

自己的插件把它声明成普通运行期依赖,装上插件就会带上它;插件在运行时从自己的依赖树解析它。

最小示例(完整,可直接抄)

下面是一个插件从构建期准备到启动期取用资源的完整路径。四步都要,缺一步会出现"能跑但拿不到底座"的 隐性降级。

// ── ① 构建期:把零依赖的 bootstrap 拷进自己的源码树,按相对路径 import ──────────────
//    cp node_modules/@avantf/dsh-plugin-base/lib/bootstrap.js src/envinit-bootstrap.js
//    (有打包步骤时用 ./preset 自动内联,见「运行时装载」。)
import { loadFramework } from './envinit-bootstrap.js'

// ── ② 启动期:解析底座 → 校验版本 → 动态 import() ─────────────────────────────────
//    底座缺失或版本超出 supportedRange 时只返回 undefined(并打一条 warn),
//    插件照常挂载、走自己的降级路径。
const framework = await loadFramework<typeof import('@avantf/dsh-plugin-base')>({
  logger: { warn: (message) => { log.warn(`envinit: ${message}`) } },
})

if (framework === undefined) {
  log.warn('envinit: @avantf/dsh-plugin-base 不可用,改用内置默认路径继续')
} else {
  // ── ③ 取用一项就绪资源(完整调用;清单的形状见「写清单」)───────────────────────
  const home = framework.familyHome()                       // 受管族根(受 AVANTF_HOME 约束)
  const provisioner = framework.createProvisioner({ home, logger: log })
  provisioner.register(framework.binaryArchiveProvider())
  provisioner.register(framework.modelCacheProvider())
  provisioner.declare({
    plugin: 'my-plugin',
    items: [{
      id: 'my-plugin:tool',
      kind: 'binary-archive',
      spec: {
        id: 'tool',
        version: '1.2.3',
        packs: { 'linux-x64': { url: 'https://example.com/tool.tar.gz', sha256: '<hex>' } },
      },
      target: { root: 'tools' },
      schemaVersion: 1,
    }],
  })
  await provisioner.ensure({ only: ['my-plugin:tool'], deadlineMs: 30_000 })
  const state = provisioner.resolve('my-plugin:tool')
  if (state.state === 'ready') {
    // handle.dir 是可用入口目录(归档:可执行文件所在目录);handle.env 是 PATH 等增量。
    process.env.PATH = [state.handle.dir, process.env.PATH].filter(Boolean).join(delimiter)
  } else {
    // pending / failed / skipped / missing 都走这里:用内置默认实现,不取消挂载。
    log.warn(`my-plugin:tool 未就绪(${state.state}),改用内置默认路径`)
  }

  // ── ④ 跑一次兼容门禁,读 compatReport 的结论(可选,但拒绝加载前必须有)──────────
  //    ctx 是插件自己的宿主 ctx,spec 是本插件的契约清单(服务 / 工具 / schema / 版本)。
  const verdict = framework.verdictOf(framework.gatherEvidence(ctx, spec))
  if (!verdict.load) {
    log.error(framework.compatReport(verdict, {
      heading: '插件未加载:与当前宿主的兼容性检查未通过。',
      warningsLabel: '风险提示:',
      warnings: verdict.warnings,
      fix: '升级宿主,或按本插件声明的版本重建。',
      logPointer: prefix => `完整诊断见日志里 ${prefix} 开头的行。`,
    }))
  } else if (verdict.warning !== undefined) {
    log.warn(`compat: ${verdict.warning}`)
  }
}

delimiter 来自 node:path(import { delimiter } from 'node:path'),log / ctx / spec 是插件自己的 对象——它们不来自底座,所以底座缺席时照样存在。

预装资源(逐项)

import { homedir } from 'node:os'
import { join } from 'node:path'
import type { Provisioner } from '@avantf/dsh-plugin-base'

const home = join(homedir(), '.avantf', 'env')

const provisioner: Provisioner = framework.createProvisioner({ home, logger })
provisioner.register(framework.binaryArchiveProvider())
provisioner.register(framework.modelCacheProvider())
provisioner.declare(manifest)

// 挂载前必须就绪的项:阻塞等待
await provisioner.ensure({ only: ['my-plugin:compat'], deadlineMs: 30_000 })

// 其余项(含 background)后台预装,到达终态时回调
void provisioner.ensure({
  onSettled: entry => {
    if (entry.id === 'my-plugin:model' && entry.action === 'installed') useModel()
    if (entry.action === 'failed') logger.warn(`${entry.id}: ${entry.code}`)
  },
})

写清单

{
  "plugin": "my-plugin",
  "items": [
    {
      "id": "my-plugin:compat",
      "kind": "npm-package",
      "spec": { "name": "@scope/compat", "range": "^1.0.0" },
      "target": { "root": "runtime" },
      "startup": "blocking",
      "schemaVersion": 1
    },
    {
      "id": "my-plugin:tool",
      "kind": "binary-archive",
      "spec": {
        "id": "tool", "version": "1.2.3",
        "packs": { "linux-x64": { "url": "https://…/tool.tar.gz", "sha256": "<hex>" } }
      },
      "target": { "root": "tools" },
      "onMissing": { "atStartup": "degrade", "atUse": "error" },
      "schemaVersion": 1
    },
    {
      "id": "my-plugin:model",
      "kind": "model-cache",
      "spec": { "repo": "org/name", "revision": "main", "layout": "flat", "files": ["config.json"] },
      "target": { "root": "models" },
      "startup": "background",
      "schemaVersion": 1
    }
  ]
}
  • startup:blocking(缺省,ensure 等它)或 background(派发后不占启动预算)。
  • onMissing.atStartup:degrade(缺了也继续)/ refuse;onMissing.atUse:degrade / error。
  • needs:同一清单内其它 item 的 id。

model-cache 的 spec.layout 决定文件怎么落盘:

| layout | 落盘形态 | 谁读 | | --- | --- | --- | | hub(缺省) | <root>/models--<org>--<name>/{blobs,refs,snapshots/<sha>/<file>} 的内容寻址快照树 | 认这套缓存形状的客户端 | | flat | <root>/<org>/<name>/<file>,文件直接可读;revision 记录与 blob 在 <root>/.envinit/ 侧车目录 | 直接按 <repo>/<file> 路径读取的运行时 |

两种布局共用同一套 revision 解析、文件列表、模型 hub 端点、内容寻址 blob 与原子落盘;config.json 在两种布局里都必需,spec.requiredFiles 在其上追加。已经落位但内容不对的条目(旧残留、指向错误 blob 的链接、同名目录)会在下次落位时被换成指向正确 blob 的链接。

probe 只做廉价的存在性判断(存在、不是目录、大小 > 0),不联网、不改盘;verify 才证明内容: hub 逐项核对链接目标、blob 名与内容哈希;flat 按侧车记录里的 sha256 逐项核对(只有文件名的旧记录 仍然接受,不会把装好的树判成未安装)。落位与完成标记在同一把族根锁内完成。

只增不减:受管的模型数据不会回收。blobs 与 snapshots 永不删除,换一个 revision 就会再存一份; experimental().prune() 只是打一条告警的 no-op。

endpoint 默认是内置的模型 hub;policy.mirrors.model 可给一组镜像端点(按序尝试、内置端点兜底), spec.endpoint 优先于镜像。显式给出空白的 endpoint、非法的 revision(空、.、..、绝对路径、 反斜杠、控制字符)都会报 invalid-option。

framework.createProvisioner({
  home,
  logger,
  policy: { mirrors: { archive: [], model: ['https://mirror.example'] } },
})

取用就绪资源

const state = provisioner.resolve('my-plugin:tool')
if (state.state === 'ready') {
  const entry = state.handle.dir      // 可用入口目录(归档:可执行文件所在目录)
  const env = state.handle.env        // PATH 等环境增量
}

resolve() 的五个状态:ready / pending / failed / skipped / missing。ensure() 返回的报告里 ok 不把"还在后台装"算成失败。

跑兼容门禁

import { compatReport, gatherEvidence, verdictOf } from '@avantf/dsh-plugin-base'

const verdict = verdictOf(gatherEvidence(ctx, spec))
if (!verdict.load) {
  log.error(compatReport(verdict, {
    heading: '插件未加载:与当前宿主的兼容性检查未通过。',
    warningsLabel: '风险提示:',
    warnings: verdict.warnings,
    fix: '升级宿主,或按本插件声明的版本重建。',
    logPointer: prefix => `完整诊断见日志里 ${prefix} 开头的行。`,
  }))
}

取用共享运行期依赖(runtime-deps)

// 注册 provider(它和别的 provider 一样进 provisioner)
provisioner.register(framework.runtimeDepsProvider())

provisioner.declare({
  plugin: 'my-plugin',
  items: [{
    id: 'my-plugin:transformers',
    kind: framework.RUNTIME_DEPS_KIND,           // 'runtime-deps'
    spec: {
      packages: { '@huggingface/transformers': '^4.2.0' },
      sources: [
        { id: 'npmmirror', registry: 'https://registry.npmmirror.com' },
        { id: 'npmjs', registry: 'https://registry.npmjs.org' },
      ],
      attempts: [
        { source: 'npmmirror' },
        { source: 'npmmirror', env: { ONNXRUNTIME_NODE_INSTALL: 'skip' } },
        { source: 'npmjs', manager: 'npm' },
      ],
      verify: ['@huggingface/transformers'],
      peers: { zod: '>=4.4.3 <5' },              // 宿主单例:只链接,不从 registry 装
    },
    target: { root: 'deps/runtime' },            // 规范位置;换 root 就是另一个共享根
    onMissing: { atStartup: 'degrade', atUse: 'error' },
    schemaVersion: 1,
  }],
})

const report = await provisioner.ensure({ only: ['my-plugin:transformers'], deadlineMs: 120_000 })
if (!report.ok) logger.warn('运行期依赖未就绪,用内置降级路径继续')

const state = provisioner.resolve('my-plugin:transformers')
if (state.state === 'ready') {
  const dir = state.handle.dir                   // <home>/deps/runtime/<key>
  const req = createRequire(join(dir, 'package.json'))
  const transformers = await import(req.resolve('@huggingface/transformers'))
  // 完整记录(key / attempts / integrity / installedAt)在 <dir>/.runtime.json
}

用 helper 声明运行期依赖 + 会话提示(v6)

上面那种接线(注册 provider、拼 item、ensure、createRequire)可以缩成一次调用。helper 只是糖,底层 runtimeDepsProvider() + createProvisioner() 的行为一字未改:

import { createRuntimeDeps } from '@avantf/dsh-plugin-base'

const runtime = createRuntimeDeps({
  namespace: 'my-plugin',                       // item id 默认 my-plugin:runtime-deps
  packages: { 'some-runtime-pkg': '^1.2.0' },
  sources: [{ id: 'npmmirror', registry: 'https://registry.npmmirror.com' }],
  attempts: [{ source: 'npmmirror' }],
  verify: ['some-runtime-pkg'],
  // 文案永远是插件的(base 里没有任何包名/命令/句子);when 决定它对应哪种状态
  notices: [{
    id: 'my-plugin:runtime-pkg',
    severity: 'degraded',
    when: 'failed',
    body: '[my-plugin] 可选能力未就绪,已降级为内置路径。',
  }],
})

provisioner.declare({ plugin: 'my-plugin', items: [runtime.item] })
const entry = await runtime.ensure(provisioner)          // 这个 item 的报告条目
if (entry?.action === 'failed') logger.warn(entry.reason ?? '')

// 自己的 pre-step 里(owner 会话):
const lines = runtime.notices.consume(sessionId, { ownerOnly: true })
if (lines.length > 0) return [...lines, existingHint].join('\n')   // 与已有提示合并成 ONE 条

// 就绪后从共享根加载:不复制包、不写 node_modules
const mod = await runtime.load('some-runtime-pkg')

notices 的状态是每次 consume 重新判断的(依赖修好了就不再提),一个 id 对每个 session 最多出一次, 多条同时成立时按 degraded > warning > info 排序合并;consume 永不抛。

取用共享工具

import { PromptFiles } from '@avantf/dsh-plugin-base'

// 插件自己的 logger(base 解析之前就要用);提示词层传 `namespace` 让 base 校验前缀。
const files = new PromptFiles({ dir: join(dataHome, 'prompts'), namespace: 'my-plugin', logger })
  .load([{ file: 'my-plugin-prompt.md', fallback: '内置默认正文\n' }])
// files[0].text —— 用户写的正文(逐字),或刚写下的默认正文

运行时装载(./bootstrap)

如果调用方要在运行时装载本包(而不是让它出现在自己的 import 图里),本包提供零依赖的 ./bootstrap:解析本包在哪 → 校验版本落在给定区间 → 动态 import()。它不安装任何东西, 失败只返回 undefined 并给出原因。把它拷进自己的源码、按相对路径 import(上面的最小示例), 本包缺席时调用方仍能加载,只是走降级路径。

import { loadFramework, readDependencyRange } from './envinit-bootstrap.js'

const range = await readDependencyRange(import.meta.url)   // 调用方自己声明的区间
const framework = await loadFramework<typeof import('@avantf/dsh-plugin-base')>({ logger })
if (framework === undefined) {
  logger.warn('本包不可用,按自己的默认路径继续')
} else {
  // 用 framework.xxx
}

有打包步骤时,本包提供构建预设把 ./bootstrap 内联进产物、并断言产物里没有本包的静态引用:

import { envinitPreset, assertEnvinitArtifacts, assertEnvinitPresetChecks } from '@avantf/dsh-plugin-base/preset'

const preset = envinitPreset({ external: [/* 自己的外部名白名单 */] })
assertEnvinitPresetChecks(preset.checks)
// bundler:external 用 preset.external,内联 preset.noExternal;产物写出后再断言:
assertEnvinitPresetChecks(assertEnvinitArtifacts({
  artifacts: ['dist/plugin.js'],
  clientArtifacts: ['dist/client.js'],
}))

写 provider(可选)

新增资源种类 = 新增一个 provider(不动核心)。provider 是实现五个动作的 npm 包:

import type { Provider } from '@avantf/dsh-plugin-base'

export const myProvider: Provider = {
  id: '@scope/my-provider',
  kinds: ['@scope/my-kind'],
  identify: item => ({ name: '…', range: '…' }),
  probe: async (item, ctx) => ({ found: false }),          // 只读、不联网
  plan: () => ({ action: 'install' }),                     // 纯函数
  targetDir: (_item, ref) => `${ref.name}/${ref.segment}`, // 安全相对路径
  install: async (item, ctx) => ctx.publish(await ctx.stage(), { name: '…', version: '…' }),
  verify: async (item, resolved, ctx) => { /* 证明能用 */ },
}

用一致性套件跑一遍再发布:

import { assertProviderConformance, runProviderConformance } from '@avantf/dsh-plugin-base/conformance'

const report = await runProviderConformance({ provider: myProvider, items: [...], packageName: '@scope/my-provider' })
assertProviderConformance(report)

接口要点

  • v7(0.7.1,本地已改版本号,未发 npm)新增:通用 SQLite kit —— openSqlite / probeSqlite / describeSqlite / migrateSqlite / readSqliteUserVersion 五个值,加 SqliteDatabase / SqliteStatement / SqliteRunResult / SqliteTransaction / SqliteTransactionMode / SqliteOpenOptions / SqliteMigration / SqliteMigrationResult / SqliteMigrationOptions 九个类型;v7 是 v6 的增量世代 (BaseRuntimeV7 extends BaseRuntimeV6),没有拿走任何旧名字。详见 docs/INTERFACE.md §13。
  • v6(随 0.6.0 对外)新增:会话提示 registry createSessionNotices(+ SessionNoticeSpec / SessionNoticeRegistry)与 runtime-deps 的薄 helper createRuntimeDeps(+ RuntimeDepsConfig / RuntimeDepsHandle);v6 是 v5 的增量世代(BaseRuntimeV6 extends BaseRuntimeV5),没有拿走任何旧名字。
  • v5(0.6.0)新增:RUNTIME_DEPS_KIND / runtimeDepsProvider 与 DependencySource / InstallAttempt / RuntimeDepsSpec;v5 是 v4 的增量世代(BaseRuntimeV5 extends BaseRuntimeV4)。 BuiltinKind 增加 'runtime-deps'、ProvisionCode 增加 runtime-deps/install-failed(都是并集加宽)。

. 上的导出(值 + 类型)构成本包的接口,由世代号冻结;./preset / ./conformance / ./bootstrap 三个子路径也在承诺内,./internal 从不承诺稳定。

  • INTERFACE_VERSION(正整数)标识当前世代;每一代的名字面有一份随包的快照,名称与导出对不上就是 接口变更,必须升世代号。
  • checkInterface(required, module):把"调用方构建时所对的世代"与"运行时加载到的本包报告的世代"比一比, 返回 ok / incompatible / cannot-tell。纯函数、非对称、读属性有守卫(取属性就抛的对象读成 "没报")、永不抛。只有加载到的世代更旧(成员可能缺失)才是 incompatible;加载到的世代 更新是 ok + 一句话的 warning(新世代不拿走有消费者的成员),相等是 ok。
  • readInterfaceRequirement(url):读调用方产物里烘着的那条记录 { baseVersion, interfaceVersion }; 缺失或畸形返回 undefined("没烘"),不抛。
  • verifyRegisteredFaces({ ctx, packageId, schemaNames?, toolNames, log, scope? }):挂载之后复查每条声明的 schema 与工具是否真的可见,返回没看到的那些名字(warn-only)。scope 是可选的不透明值(本包不认识 作用域的类型),会原样传给 tools.get(name, scope);不传就按全局视图 tools.get(name) 读。
  • 拿到 incompatible 时的建议动作是降级(不使用本包的共享能力)而不是拒绝挂载;拿到 ok + warning 时照常使用、只把那条警告记下来 —— 本包只把裁决与一句话的 reason / warning 交出去,怎么处理由 调用方决定。

边界(不做的事)

  • 不自己当包管理器:不推导传递依赖、不做版本求解;runtime-deps 把安装交给调用方声明的 npm / pnpm, 依赖图与 lockfile 由 manager 负责,base 只做 spec 寻址、锁、校验与失败报告。
  • npm-package provider 不接管原生模块:binding.gyp / *.node / 依赖 postinstall 的包会被识别并 拒绝;需要原生模块时走 runtime-deps(由 manager 安装)或 binary-archive。
  • 不跳过校验:npm 包核对 dist.integrity,归档核对 sha256。
  • 不做进程内预热:让文件在盘上是框架的事,读进内存是调用方自己的事。

版本承诺

  • 接口面是 . 上的导出(值 + 类型)加上 ./preset / ./conformance / ./bootstrap 三个子路径;./internal 是内部件,从不承诺稳定。
  • 当前这一代(v6)是 v5 的增量:. 仍然只列"插件真取用、或插件作者被期望调用"的成员,其余进 ./internal。v6 新增会话提示 registry 与 runtime-deps helper(2 个值名 + 4 个类型名),没有拿走 v5 的 任何名字;BaseRuntimeV6 extends BaseRuntimeV5,所以 v1–v5 构建遇到 v6 base 仍判 ok + warning。 v5 本身是 v4 的增量(共享运行期依赖 provider:2 个值名 + 3 个类型名,外加 BuiltinKind / ProvisionCode 两个并集加宽);v4 则是一个名字都没动的换代 —— 它加宽的是既有成员接受的入参: verifyRegisteredFaces({ …, scope? }) 多了个可选、不透明的 scope,原样透传给 tools.get(name, scope),不传时行为与 v3 逐字相同。
  • v3 是第一次破坏性换代:v2 的成员如果有消费者,v3 一个不少;但 v3 不再对 v2 做结构超集。旧调用方 遇到更新的 base 仍判 ok + warning,遇到更旧的 base 才降级。
  • 接口与包版本是两条轴:接口换代只升 INTERFACE_VERSION 并新增快照;包版本是普通 semver,只表达 包自身(行为变更 minor、修复 patch)。
  • 顺序敏感的参数一律传具名对象(如 resolveDataHome({ explicit, env, configured }));可观察的文案 由调用方给(如 compatReport 的 words)。

许可

MIT