feat(plugin): add registry name handling and release age exclusion for pnpm workspace
CI / python runtime / release-shaped Linux x64 (push) Has been skipped
CI / node 24 / static (push) Has been cancelled
CI / node 24 / coverage (push) Has been cancelled
CI / node 24 / snapshots and artifacts (push) Has been cancelled
CI / node 22.19 (push) Has been cancelled
CI / node 26 (push) Has been cancelled
CI / python 3.10 / keyless SDK (push) Has been cancelled
CI / windows node 24 / wine blocking (push) Has been cancelled
CI / wine apt cache (push) Has been cancelled
CI / windows node 24 / native complete (push) Has been cancelled
CI / serial / linux (push) Has been cancelled
CI / serial / linux (self-hosted standby) (push) Has been cancelled
CI / serial / macos (push) Has been cancelled
CI / serial / windows (self-hosted standby) (push) Has been cancelled
CI / larger-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (16, windows, dsh-windows-2025-16core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (32, windows, dsh-windows-2025-32core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (4, windows, dsh-windows-2025-4core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (64, windows, dsh-windows-2025-64core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (8, windows, dsh-windows-2025-8core, production-site) (push) Has been cancelled
CI / larger-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, typecheck) (push) Has been cancelled
CI / larger-runner-benchmark (96, windows, dsh-windows-2025-96core, production-site) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, linux, dsh-ubuntu-24-04-16core, 16) (push) Has been cancelled
CI / consolidated-runner-benchmark (16, windows, dsh-windows-2025-16core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, linux, dsh-ubuntu-24-04-32core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (32, windows, dsh-windows-2025-32core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, linux, dsh-ubuntu-24-04-4core, 4) (push) Has been cancelled
CI / consolidated-runner-benchmark (4, windows, dsh-windows-2025-4core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, linux, dsh-ubuntu-24-04-64core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (64, windows, dsh-windows-2025-64core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, linux, dsh-ubuntu-24-04-8core, 8) (push) Has been cancelled
CI / consolidated-runner-benchmark (8, windows, dsh-windows-2025-8core, 2) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, linux, dsh-ubuntu-24-04-96core, 32) (push) Has been cancelled
CI / consolidated-runner-benchmark (96, windows, dsh-windows-2025-96core, 2) (push) Has been cancelled
E2E (real DeepSeek API) / e2e (push) Has been cancelled
Release (vendor) / Pack npm tarballs (push) Has been cancelled
Release (dsh) / Pack npm tarballs (push) Has been cancelled
Sandbox / sandbox e2e (seatbelt, macos-latest) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04) (push) Has been cancelled
Sandbox / sandbox e2e (landlock, ubuntu-24.04-arm) (push) Has been cancelled
Sandbox / sandbox e2e (bwrap, ubuntu-latest) (push) Has been cancelled
CI / all checks passed (push) Has been cancelled
Release (vendor) / Publish to npm (push) Has been cancelled
Release (dsh) / Publish to npm (push) Has been cancelled

This commit is contained in:
Pine
2026-08-16 16:11:22 +08:00
parent 43c4b825e8
commit 98b3221eec
5 changed files with 158 additions and 49 deletions
+54 -1
View File
@@ -11,8 +11,9 @@
*/
import { spawnSync } from 'node:child_process'
import { existsSync } from 'node:fs'
import { existsSync, readFileSync, writeFileSync } from 'node:fs'
import { join, resolve } from 'node:path'
import { dump, load } from 'js-yaml'
import {
DEFAULT_PROFILE_BUNDLES,
initProfile,
@@ -26,6 +27,52 @@ import { INSTALL_ANCHOR } from './profile-boot.ts'
const NAME = 'dsh'
/**
* The bare package name when `spec` is a registry specifier, else undefined —
* the same shape the in-app install uses to decide registry participation.
* Everything pnpm treats as a non-registry source (paths, `file:`/`link:`/
* `github:`/`git+`, tarballs, http(s) repo URLs, `.git` markers) is not a
* registry spec.
* @param spec - the pnpm specifier verbatim.
*/
export function registryNameOf(spec: string): string | undefined {
const trimmed = spec.trim()
if (trimmed.length === 0) return undefined
if (/^(?:file:|link:|github:|gitlab:|bitbucket:|git\+|git@)/.test(trimmed)) return undefined
if (/^(?:\.{1,2}|~|[/\\])/.test(trimmed) || /^[a-zA-Z]:[\\/]/.test(trimmed)) return undefined
if (/\.(?:tgz|tar\.gz)(?:[?#]|$)/.test(trimmed)) return undefined
if (/^https?:\/\//.test(trimmed) && trimmed.replace(/^https?:\/\//, '').split('/').length > 1) return undefined
if (/\.git(?:[#@]|$)/.test(trimmed)) return undefined
return trimmed.replace(/@[^/@]+$/, '')
}
/**
* Exempt registry package names from pnpm's `minimumReleaseAge` check by writing
* the `minimumReleaseAgeExclude` setting into the profile's `pnpm-workspace.yaml`
* (honored by pnpm ≥10.16; an older pnpm ignores the key rather than aborting,
* unlike the `--minimum-release-age-exclude` CLI flag). This mirrors the in-app
* install so an external `dsh plugin add <name>` gets the same latest-version
* behavior as the desktop's install, not a stale age-blocked fallback.
* @param profileDir - the profile directory.
* @param names - the registry package names to exempt.
*/
export function writeReleaseAgeExclude(profileDir: string, names: readonly string[]): void {
const workspacePath = join(profileDir, 'pnpm-workspace.yaml')
let doc: Record<string, unknown>
try {
const parsed = load(readFileSync(workspacePath, 'utf8'))
doc = parsed !== null && typeof parsed === 'object'
? parsed as Record<string, unknown>
: {}
} catch {
doc = {}
}
const excluded = new Set<string>((doc.minimumReleaseAgeExclude ?? []) as string[])
for (const name of names) excluded.add(name)
if (excluded.size > 0) doc.minimumReleaseAgeExclude = [...excluded]
writeFileSync(workspacePath, dump(doc))
}
/**
* Rewrite relative filesystem specs against the user's invoking directory.
* pnpm runs with cwd = the profile directory, so a bare `.` or `../plugin`
@@ -66,6 +113,12 @@ export function runPlugin(profile: string, args: readonly string[]): number {
// its .cmd shim, which spawn() refuses without a shell since the
// CVE-2024-27980 hardening; the vendored path runs node against pnpm.cjs.
const anchored = args.map(argument => anchorPathSpec(argument, process.cwd()))
// An external `add <registry-name>` gets the same minimum-release-age exemption
// the in-app install applies, so a just-published plugin installs at latest.
if (args[0] === 'add') {
const registryNames = args.slice(1).map(registryNameOf).filter((name): name is string => name !== undefined)
if (registryNames.length > 0) writeReleaseAgeExclude(dir, registryNames)
}
const vendored = resolvePnpm(process.execPath)
const result = vendored === undefined
? spawnSync('pnpm', anchored, { cwd: dir, stdio: 'inherit', shell: process.platform === 'win32' })