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-envinit

v0.1.3

Published

Plugin environment initialisation at startup: declare → probe → fetch → verify → report

Downloads

436

Readme

@avantf/dsh-envinit

插件启动期的环境初始化框架:插件声明它需要什么,框架负责把它装到盘上并报告。

插件在用户机器上启动时往往缺东西——运行期要用的 npm 包、要调用的命令行工具、要读的模型文件。 @avantf/dsh-envinit 让插件只写一份清单,其余交给框架:

声明 → 探测 → 获取 → 校验 → 报告
  • 已经在盘上 / PATH 上的直接用,不重复下载;
  • 需要安装的下载、校验完整性、原子落盘;
  • 每一项给出 present / installed / skipped / failed,失败带稳定 code。

运行期零依赖,要求 Node.js >= 22。

核心概念

| 概念 | 含义 | | --- | --- | | item | 插件声明的一条资源:id / kind / spec / target.root / onMissing / startup / needs | | kind | 资源种类。内置 npm-package、binary-archive、model-cache;自定义 kind 由 provider 认领 | | provider | 认领一种 kind、负责这类资源获取与校验的 npm 包 | | home | 受管族根,默认 ~/.avantf/env;所有受管资源都在它下面 | | key | 资源身份 kind + name,多个插件声明同一个资源时只落一份 |

怎么用

1. 声明依赖

插件把框架声明为 peer(不要放 dependencies,否则会装出多份框架副本):

{
  "peerDependencies": { "@avantf/dsh-envinit": "^0.1.3" },
  "devDependencies":  { "@avantf/dsh-envinit": "^0.1.3" }
}

2. 内联 bootstrap

唯一需要进入插件产物的是框架的零依赖 bootstrap:它只把这份框架解析出来并校验版本,不安装任何东西。

  • 有打包步骤:用框架的构建预设,它会强制内联 ./bootstrap、让框架本包保持外部,并在产物写出后断言这两条:

    import { envinitPreset, assertEnvinitArtifacts, assertEnvinitPresetChecks } from '@avantf/dsh-envinit/preset'
    
    const preset = envinitPreset({ external: [/* 自己的外部名白名单 */] })
    assertEnvinitPresetChecks(preset.checks)
    // bundler:external 用 preset.external,内联 preset.noExternal
    // 产物写出后:
    assertEnvinitPresetChecks(assertEnvinitArtifacts({ artifacts: ['dist/plugin.js'], clientArtifacts: ['dist/client.js'] }))
  • tsc 直出:把框架发布包里的 dist/bootstrap.js 拷进自己的产物,按相对路径 import。

3. 启动接线

import { fileURLToPath } from 'node:url'
import { homedir } from 'node:os'
import { join } from 'node:path'
import { loadFramework, readDependencyRange } from './bootstrap.js'   // 内联进来的那份
import type { Provisioner } from '@avantf/dsh-envinit'               // 只类型,会被擦除

export async function mount(logger: { warn(message: string): void }): Promise<void> {
  const home = join(homedir(), '.avantf', 'env')
  const range = await readDependencyRange(fileURLToPath(import.meta.url))  // 自己的 peerDependencies

  const framework = await loadFramework<typeof import('@avantf/dsh-envinit')>({ logger })
  if (framework === undefined) {
    logger.warn('预装设施不可用,降级挂载')   // 拿不到框架也照常挂载
    return
  }

  const provisioner: Provisioner = framework.createProvisioner({ home, logger, envinitRange: range })
  provisioner.register(framework.npmPackageProvider())
  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}`)
    },
  })
}

4. 写清单

{
  "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 端点、内容寻址 blobs 与原子落盘;config.json 在两种布局里都始终必需,spec.requiredFiles 在其上追加。已经落位但内容不对的条目(旧残留、指向错误 blob 的链接、同名目录)会在下次落位时被换成指向正确 blob 的链接。

probe 只做廉价的存在性判断(存在、不是目录、大小 > 0),不联网、不改盘;verify 才证明内容:

  • hub:snapshot 里的每一项都指向 blobs/<sha256>,逐项核对链接目标、blob 名与内容哈希一致;
  • flat:侧车记录里带每个文件的 sha256,逐项核对;只有文件名的旧记录仍然接受,不会把已经装好的树判成未安装。

落位与完成标记(hub 的 refs/<revision>、flat 的 record.json)在同一把族根锁内完成,所以多个进程安装不同 revision 时,记录与盘上的字节始终一致。

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

endpoint 默认是内置的模型 hub;policy.mirrors.model 可以给一组镜像端点(按序尝试,内置端点兜底),spec.endpoint 优先于镜像。显式给出空白的 endpoint、非法的 revision(空、.、..、绝对路径、反斜杠、控制字符)都会报 invalid-option;端点返回的 sha 必须是 40/64 位小写十六进制,内容长度与声明不符的下载会报 fetch/failed。例如:

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

清单可以离线检查:

provision lint manifest.json

5. 取用就绪资源

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 不把"还在后台装"算成失败。

6. 写 provider(可选)

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

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

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-envinit/conformance'

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

边界(不做的事)

  • 不做包管理器:不推导传递依赖、不生成 lockfile、不做版本求解;每个 item 自包含。
  • 不接管原生模块:binding.gyp / *.node / 依赖 postinstall 的包会被识别并拒绝。
  • 不跳过校验:npm 包核对 dist.integrity,归档核对 sha256。
  • 不做进程内预热:让文件在盘上是框架的事,读进内存是插件自己的事。
  • 框架自身零运行期依赖:dependencies 与 peerDependencies 均为空。

开发

pnpm check          # 类型检查 + 测试
pnpm build          # tsc → dist/
pnpm pack           # 打包到 release/ 并断言产物
pnpm release:check  # 发布门禁:typecheck → build → test → pack