fix(plugins): keep aliases and whole user information in bundle sources

Address the second ds-review-bot round on the bundle Source section:

- remove an http(s) URL's user information up to its last `@`, so a
  username containing `@` no longer leaves the password behind;
- keep the dependency name before a local or git spec when the package's
  own manifest name differs, so reinstalling restores the alias;
- accept scp-like git addresses with any user name;
- show Built in for a profile dependency whose loaded copy the
  installation supplies, instead of hiding the section;
- align the PackageView, subsystem page, and README wording with the
  shadowing rule and the kept query string, and assert the source of
  not-bundle and unreadable bundles.
This commit is contained in:
turtle1999 committed 2026-09-29 13:27:43 +08:00
1 parent 6d1e12b237
commit 9b79f97010
13 files changed
+51 -32

No files matched your search

+2 -2
View File
@@ -6,5 +6,5 @@
en: 962b4398c9052959
zh: 14471c340a9f6e60
/profile-management/management-records:
en: 6295412615ed2d56
zh: f02b40d4ad5fc7e4
en: 6929d1001684c024
zh: 06d3d3f6eea848ec
+1 -1
View File
@@ -10,7 +10,7 @@ The [boot package group](../../packages/boot/README.md) owns launcher-provided p
`PluginInfo` carries module identity, effective enablement, fiber phase and optional display `meta`, plus a unique `patchId` or a `readOnlyReason`.
`BundleInfo` carries the package name, optional installed version, selected enablement, removal availability, optional resolution error, and, for a bundle the profile's own dependency supplies, `source`: that dependency as a spec `pnpm add` accepts. Its optional `meta` and each `BundleRowInfo.meta` contain display text or a metadata diagnostic; Clients select a language at render time.
`BundleInfo` carries the package name, optional installed version, selected enablement, removal availability, optional resolution error, and, for a bundle the profile's own dependency supplies and the installation does not, `source`: that dependency as a spec `pnpm add` accepts. Its optional `meta` and each `BundleRowInfo.meta` contain display text or a metadata diagnostic; Clients select a language at render time.
`InstallBundleOptions.enabled` defaults to true. False installs without selecting the bundle layer. `approvedBuilds` grants persistent script permission to the supplied pending package names before installation. `registry` names the registry asked first; absent, the configured one.
+1 -1
View File
@@ -10,7 +10,7 @@
`PluginInfo` 包含模块标识、实际启停状态、fiber 阶段和可选的展示 `meta`,以及唯一的 `patchId` 或 `readOnlyReason`。
`BundleInfo` 包含包名、可选的安装版本、组合层选择状态、删除可用性、可选的解析错误;由 profile 自身依赖提供的组合包还带有 `source`,即该依赖在 `pnpm add` 中可用的 spec。它的可选 `meta` 与各行的 `BundleRowInfo.meta` 包含展示文本或元信息诊断;Client 在渲染时选择语言。
`BundleInfo` 包含包名、可选的安装版本、组合层选择状态、删除可用性、可选的解析错误;由 profile 自身依赖提供、且安装不提供的组合包还带有 `source`,即该依赖在 `pnpm add` 中可用的 spec。它的可选 `meta` 与各行的 `BundleRowInfo.meta` 包含展示文本或元信息诊断;Client 在渲染时选择语言。
`InstallBundleOptions.enabled` 默认为 true,false 表示安装但不选择组合包层。`approvedBuilds` 在安装前向指定的待审批包名授予持久脚本权限。`registry` 指定首先询问的注册表;缺省为配置的那个。
@@ -15,8 +15,8 @@
en: 87679c51aa3693f5
zh: 11684a48a5806afb
/deepseek-ai-dsh-plugin-manager/use-this-package:
en: 22d07e11dba5e843
zh: b7d548b6a3f70b98
en: d0f7d42dffe6d3c7
zh: b4bf81958cda876d
/deepseek-ai-dsh-plugin-manager/use-this-package/version-compatibility-and-exemptions:
en: 920c0acb3b9feaf3
zh: ba6e832a256880a7
+1 -1
View File
@@ -41,7 +41,7 @@ A plugin toggle updates only `disabled` in the last matching override in the pro
A selected bundle that cannot load remains in `listBundles` with an `error`; `enabled` records the saved selection, not successful loading. The plugin page shows the error and allows deselection. A broken bundle cannot be enabled. Management bundles remain protected if their files become unreadable.
`listBundles` includes optional display `meta` for each bundle and its declared plugin rows, including disabled bundles. The Client selects a language from these values. The separate `description` field is the bundle's raw `package.json.description`; a metadata diagnostic does not block management. `source` gives a profile dependency as a spec `pnpm add` accepts: a git address or URL as recorded without the credentials of an http(s) URL, a `file:` or `link:` path made absolute against the profile or the home directory for `~`, and a registry range, tag, or alias after the package name; a bundle the installation supplies has none, even when the profile also depends on it, because the installation's copy is the one that loads. The `plugin_manager` tool omits UI display metadata from list results.
`listBundles` includes optional display `meta` for each bundle and its declared plugin rows, including disabled bundles. The Client selects a language from these values. The separate `description` field is the bundle's raw `package.json.description`; a metadata diagnostic does not block management. `source` gives a profile dependency as a spec `pnpm add` accepts: a git address or URL as recorded without the user information of an http(s) URL (a query string stays as recorded), a `file:` or `link:` path made absolute against the profile or the home directory for `~`, and a registry range, tag, or alias after the package name, as is any spec of a package installed under another name; a bundle the installation supplies has none, even when the profile also depends on it, because the installation's copy is the one that loads. The `plugin_manager` tool omits UI display metadata from list results.
`inspect(spec, options)` reads what a spec names before anything installs: a registry name is asked of the registry through `pnpm view`, run in the profile directory so the same proxy and authentication settings apply as to the install; an absolute path has its `package.json` read; a git address or tarball answers only its form and the `host` it is fetched from. The answer carries the name, version, description, whether the package declares a bundle, and the `registry` that answered, or a `problem`: `invalid-spec`, `already-installed`, `not-found`, `not-a-package`, `not-a-bundle`, `network`, or `unknown`, with the `registries` asked. A caller's `signal` or `inspectTimeoutMs` ends the lookup.
+1 -1
View File
@@ -41,7 +41,7 @@ kind: "package-reference"
已选择但无法加载的组合包仍会出现在 `listBundles` 中,并携带 `error`;`enabled` 表示保存的选择,不代表加载成功。插件页面显示错误并允许取消选择。损坏的组合包无法启用。管理组合包的文件变得不可读后仍受保护。
`listBundles` 为各组合包及其声明的插件行提供可选的展示 `meta`,包括已禁用的组合包。Client 从这些值中选择语言。单独的 `description` 字段是该组合包原始的 `package.json.description`;元信息诊断不会阻止管理操作。`source` 以 `pnpm add` 接受的 spec 给出 profile 依赖:git 地址或 URL 按记录给出,但去掉 http(s) URL 中的凭据;`file:` 或 `link:` 路径按 profile 目录解析为绝对路径,`~` 展开为主目录;注册表版本范围、标签或别名接在包名的 `@` 之后。安装提供的组合包没有该字段,即使 profile 也依赖它,因为实际加载的是安装中的副本。`plugin_manager` 工具的列表结果不包含 UI 展示元信息。
`listBundles` 为各组合包及其声明的插件行提供可选的展示 `meta`,包括已禁用的组合包。Client 从这些值中选择语言。单独的 `description` 字段是该组合包原始的 `package.json.description`;元信息诊断不会阻止管理操作。`source` 以 `pnpm add` 接受的 spec 给出 profile 依赖:git 地址或 URL 按记录给出,但去掉 http(s) URL 的 user information(查询字符串保持原样);`file:` 或 `link:` 路径按 profile 目录解析为绝对路径,`~` 展开为主目录;注册表版本范围、标签或别名接在包名的 `@` 之后,以别名安装的包的 spec 也是如此。安装提供的组合包没有该字段,即使 profile 也依赖它,因为实际加载的是安装中的副本。`plugin_manager` 工具的列表结果不包含 UI 展示元信息。
`inspect(spec, options)` 在任何东西安装之前读出 spec 指向什么:注册表包名通过 `pnpm view` 询问注册表,在 profile 目录中运行,因而与安装使用同样的代理与认证设置;绝对路径读取其 `package.json`;git 地址或 tarball 只答复自己的形式和它被拉取的 `host`。答复携带名称、版本、描述、该包是否声明组合包,以及作答的 `registry`,否则给出 `problem`:`invalid-spec`、`already-installed`、`not-found`、`not-a-package`、`not-a-bundle`、`network` 或 `unknown`,并附上问过的 `registries`。调用方的 `signal` 或 `inspectTimeoutMs` 会结束查询。
+8 -5
View File
@@ -290,14 +290,17 @@ export class PluginManager extends TypertRemoteService {
const installed = dependencies.includes(name)
const optional = OPTIONAL_BUNDLES.includes(name)
const removable = installed && !Object.hasOwn(installation.dependencies ?? {}, name)
// Bundle resolution reads the installation first, so a profile dependency it also supplies is not the loaded copy.
const source = removable ? { source: dependencySpec(name, recorded[name] as string, this.profile.dir) } : {}
// Bundle resolution reads the installation first, so a profile dependency the installation manifest also names,
// like one it forbids removing, is not the loaded copy.
const sourceOf = (packageName?: string): { source?: string } =>
removable ? { source: dependencySpec(name, recorded[name] as string, this.profile.dir, packageName) } : {}
const enabled = selected.includes(name)
const readOnlyReason = this.protectsManager(name) ? 'management-required' as const : undefined
try {
const info = bundleManifest(name, this.profile.dir, this.profile.installAnchor)
if (info === undefined) {
if (enabled) bundles.push({ name, ...source, enabled, installed, optional, removable: removable && readOnlyReason === undefined,
if (enabled) bundles.push({ name, ...sourceOf(), enabled, installed, optional,
removable: removable && readOnlyReason === undefined,
...(readOnlyReason === undefined ? {} : { readOnlyReason }), error: { code: 'not-bundle' }, rows: [], overrides: [] })
continue
}
@@ -308,12 +311,12 @@ export class PluginManager extends TypertRemoteService {
bundles.push({ name, ...(info.version === undefined ? {} : { version: info.version }),
...(info.description === undefined || info.description === '' ? {} : { description: info.description }),
...meta === undefined ? {} : { meta },
...source, enabled, installed, optional, removable: removable && readOnlyReason === undefined,
...sourceOf(info.name), enabled, installed, optional, removable: removable && readOnlyReason === undefined,
...(readOnlyReason === undefined ? {} : { readOnlyReason }),
...this.declaredRows(name, info) })
} catch (error) {
if (enabled || installed) {
bundles.push({ name, ...source, enabled, installed, optional, removable: removable && readOnlyReason === undefined,
bundles.push({ name, ...sourceOf(), enabled, installed, optional, removable: removable && readOnlyReason === undefined,
...(readOnlyReason === undefined ? {} : { readOnlyReason }), error: managementError(error), rows: [], overrides: [] })
}
}
@@ -92,25 +92,32 @@ export function parseInstallSpec(raw: string): ParsedInstallSpec {
}
/** A recorded dependency value pnpm resolves outside a registry: a protocol other than a registry alias, or an scp-like git address. */
const NON_REGISTRY_VALUE = /^(?!(?:npm|jsr|workspace|catalog):)[a-z][a-z0-9+.-]*:|^git@/i
/** The user information of an http(s) URL, which holds a password or access token when present. */
const HTTP_CREDENTIALS = /^((?:git\+)?https?:\/\/)[^/@]*@/i
const NON_REGISTRY_VALUE = /^(?!(?:npm|jsr|workspace|catalog):)[a-z][a-z0-9+.-]*:|^[^@/:\s]+@[^:/\s]+:/i
/** The user information of an http(s) URL, up to the last `@` before the path, which holds a password or access token when present. */
const HTTP_USER_INFO = /^((?:git\+)?https?:\/\/)[^/]*@/i
/**
* The spec `pnpm add` accepts for a dependency the profile manifest records. A git address, URL, or other
* non-registry protocol is the recorded value without the credentials of an http(s) URL; a `file:` or `link:`
* path, which pnpm records relative to the profile or with `~` for the home directory, becomes absolute; a
* registry range, tag, or alias follows the dependency name after `@`.
* non-registry protocol is the recorded value without the user information of an http(s) URL, whose query string
* is kept; a `file:` or `link:` path, which pnpm records relative to the profile or with `~` for the home
* directory, becomes absolute. A registry range, tag, or alias follows the dependency name after `@`, as does a
* non-registry value installed under a name other than the package's own.
* @param name - the dependency name.
* @param recorded - the value the profile manifest records for it.
* @param profileDir - the profile directory a relative local path is resolved against.
* @param packageName - the name the installed package's manifest declares, when it could be read.
* @returns the installable spec.
*/
export function dependencySpec(name: string, recorded: string, profileDir: string): string {
export function dependencySpec(name: string, recorded: string, profileDir: string, packageName = name): string {
const local = /^(file|link):(.*)$/s.exec(recorded)
let spec: string
if (local !== null) {
const path = (local[2] as string).replace(/^~(?=$|[\\/])/, () => homedir())
return `${local[1]}:${resolve(profileDir, path)}`
spec = `${local[1]}:${resolve(profileDir, path)}`
} else if (NON_REGISTRY_VALUE.test(recorded)) {
spec = recorded.replace(HTTP_USER_INFO, '$1')
} else {
return `${name}@${recorded}`
}
return NON_REGISTRY_VALUE.test(recorded) ? recorded.replace(HTTP_CREDENTIALS, '$1') : `${name}@${recorded}`
return packageName === name ? spec : `${name}@${spec}`
}
+1 -1
View File
@@ -57,7 +57,7 @@ export interface BundleInfo {
installed: boolean
/**
* Present for a profile dependency the installation does not also supply: the spec `pnpm add` accepts, with local
* paths made absolute and http(s) credentials removed.
* paths made absolute and the user information of an http(s) URL removed.
*/
source?: string
/**
@@ -241,7 +241,7 @@ it.each(['missing package', 'invalid manifest', 'not a bundle', 'missing patch',
})
expect([...ctx.loader.entries()].some(entry => entry.id === 'include:managed')).toBe(false)
expect((await manager.listBundles()).find(row => row.name === 'extra')).toMatchObject({
enabled: true, error: { code: failure === 'not a bundle' ? 'not-bundle' : 'operation-error' }, rows: [],
enabled: true, source: '[email protected]', error: { code: failure === 'not a bundle' ? 'not-bundle' : 'operation-error' }, rows: [],
})
expect(await manager.setBundleEnabled('extra', false)).toMatchObject({ changed: true, application: 'applied' })
expect(readProfileManifest('test', dir).dsh?.profile?.bundles).toEqual(['core'])
@@ -340,13 +340,16 @@ it('names where each installed bundle comes from as a spec pnpm installs', async
const { manager, dir, bundle, profile } = await fixture()
const recorded: Record<string, string> = {
extra: '^1.0.0', tagged: 'latest', aliased: 'npm:@acme/aliased@2', jsr: 'jsr:@acme/jsr@^1', github: 'github:someone/dsh-plugin#v1',
ssh: '[email protected]:someone/dsh-plugin.git', sshUrl: 'git+ssh://[email protected]/someone/dsh-plugin.git',
ssh: '[email protected]:someone/dsh-plugin.git', deploy: '[email protected]:team/dsh-plugin.git',
sshUrl: 'git+ssh://[email protected]/someone/dsh-plugin.git', email: 'git+http://[email protected]:[email protected]/repo.git',
tarball: 'https://cdn.example.com/dsh-x-1.0.0.tgz', token: 'https://[email protected]/dsh-x-1.0.0.tgz',
password: 'git+https://someone:[email protected]/someone/dsh-plugin.git#main',
relative: 'file:../plugins/relative', linked: 'link:/plugins/linked', home: 'file:~/plugins/home',
shadowed: '1.0.0',
aliasLocal: 'file:../plugins/original', shadowed: '1.0.0',
}
for (const name of Object.keys(recorded)) bundle(name, [])
// Installed under a name other than its own, the package keeps that name only when the spec carries it.
writeFileSync(join(dir, 'node_modules', 'aliasLocal', 'package.json'), JSON.stringify({ name: 'original', version: '1.0.0', dsh: { bundle: { patch: './cordis.patch.yml' } } }))
const manifest = readProfileManifest('test', dir)
manifest.dependencies = recorded
writeFileSync(join(dir, 'package.json'), JSON.stringify(manifest))
@@ -354,11 +357,12 @@ it('names where each installed bundle comes from as a spec pnpm installs', async
writeFileSync(profile.installAnchor, JSON.stringify({ name: 'installation', dependencies: { shadowed: '1.0.0' } }))
expect(Object.fromEntries((await manager.listBundles()).map(row => [row.name, row.source]))).toEqual({
core: undefined, extra: 'extra@^1.0.0', tagged: 'tagged@latest', aliased: 'aliased@npm:@acme/aliased@2', jsr: 'jsr@jsr:@acme/jsr@^1',
github: 'github:someone/dsh-plugin#v1', ssh: '[email protected]:someone/dsh-plugin.git', sshUrl: 'git+ssh://[email protected]/someone/dsh-plugin.git',
github: 'github:someone/dsh-plugin#v1', ssh: '[email protected]:someone/dsh-plugin.git', deploy: '[email protected]:team/dsh-plugin.git',
sshUrl: 'git+ssh://[email protected]/someone/dsh-plugin.git', email: 'git+http://git.example.com/repo.git',
tarball: 'https://cdn.example.com/dsh-x-1.0.0.tgz', token: 'https://cdn.example.com/dsh-x-1.0.0.tgz',
password: 'git+https://git.example.com/someone/dsh-plugin.git#main',
relative: `file:${resolve(dir, '../plugins/relative')}`, linked: `link:${resolve(dir, '/plugins/linked')}`,
home: `file:${resolve(homedir(), 'plugins/home')}`, shadowed: undefined,
home: `file:${resolve(homedir(), 'plugins/home')}`, aliasLocal: `aliasLocal@file:${resolve(dir, '../plugins/original')}`, shadowed: undefined,
})
})
@@ -271,11 +271,12 @@ function RowsSection({ rows, t, resolveText, toggle, configure }: {
/**
* Where a bundle comes from: the spec that installs it elsewhere, or built in
* for an official one the installation ships, and its version. A selected
* bundle that neither the profile nor the installation holds has no section.
* for one whose loaded copy the installation supplies, and its version. A
* selected bundle that neither the profile nor the installation holds has no
* section.
*/
function SourceSection({ pkg, t }: { readonly pkg: PackageView; readonly t: Translate }): ReactNode {
if (pkg.source === undefined && !pkg.optional) return null
if (pkg.source === undefined && !pkg.installed && !pkg.optional) return null
return (
<section className={css.detailSection} data-plugin-source>
<h4 className={css.sectionTitle}>{t('sourceTitle')}</h4>
@@ -85,7 +85,7 @@ export interface PackageView {
readonly meta?: PluginLocalizedMeta
/** Whether the profile's own dependencies hold the package; false for a bundle the installation supplies. */
readonly installed: boolean
/** Present when `installed`: the profile dependency as a spec `pnpm add` accepts. */
/** Present for a profile dependency the installation does not also supply: the spec `pnpm add` accepts. */
readonly source?: string
/** Whether the installation ships the bundle for the person to switch on: official, off until selected, never removable. */
readonly optional: boolean
@@ -205,6 +205,7 @@ describe('PluginManagerPage', () => {
const b = renderTab({ packages: [
pkg({ source: 'github:someone/dsh-better-sidebar' }),
{ name: 'dsh-official', installed: false, optional: true, enabled: false, rows: [] },
{ name: 'dsh-shadowed', installed: true, optional: false, enabled: true, rows: [] },
{ name: 'dsh-missing', installed: false, optional: false, enabled: true, error: { code: 'unknown-plugin' }, rows: [] },
] })
const facts = (): string[] => [...document.querySelectorAll('[data-plugin-source] dt, [data-plugin-source] dd')].map(node => node.textContent)
@@ -213,6 +214,9 @@ describe('PluginManagerPage', () => {
expect(facts()).toEqual([en.sourceSpec, 'github:someone/dsh-better-sidebar', en.sourceVersion, '0.16.0'])
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-official' }) })
expect(facts()).toEqual([en.sourceSpec, en.sourceBuiltIn])
// A profile dependency the installation also supplies loads the installation's copy.
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-shadowed' }) })
expect(facts()).toEqual([en.sourceSpec, en.sourceBuiltIn])
act(() => { b.navigation.actions.setView({ kind: 'package', name: 'dsh-missing' }) })
expect(document.querySelector('[data-plugin-source]')).toBeNull()
})