| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 |
- /** Installed profile dependencies and their bundle activation after package-manager operations. */
- import { join } from 'node:path'
- import { readProfileManifest, resolveBundleDir, writeProfileManifest, type ProfileManifest } from './profile.ts'
- /** Profile directory and installation used by the shared bundle resolver. */
- export interface ProfilePluginLocation {
- /** Diagnostic prefix for profile manifest failures. */
- readonly binName: string
- /** Profile package directory managed by pnpm. */
- readonly profileDir: string
- /** Absolute package.json path of the owning dsh installation. */
- readonly installAnchor: string
- }
- /** One dependency under its package-manager key, including npm aliases. */
- export interface ProfilePluginDependency {
- /** Dependency key used in package.json and the bundle list. */
- readonly name: string
- /** Installed version, or the dependency spec when installed metadata is unavailable. */
- readonly version: string
- /** Whether the package selected by the bundle resolver declares a patch. */
- readonly bundle: boolean
- /** Whether the dependency appears in the profile's active bundle list. */
- readonly enabled: boolean
- }
- /** Profile manifest and dependencies in manifest order. */
- export interface ProfilePluginInventory {
- readonly manifest: ProfileManifest
- readonly dependencies: readonly ProfilePluginDependency[]
- }
- /** Result of reconciling bundle activation after a successful package operation. */
- export interface ProfilePluginReconciliation {
- readonly plugins: ProfilePluginInventory
- /** Newly added ordinary dependencies, for optional caller-owned diagnostics. */
- readonly addedPlainDependencies: readonly string[]
- }
- /** Unavailable installed metadata must not prevent listing or removing dependencies. */
- function optionalManifest(binName: string, packageDir: string): ProfileManifest | undefined {
- try { return readProfileManifest(binName, packageDir) } catch { return undefined }
- }
- /** Resolve activation metadata with the same installation precedence as profile loading. */
- function bundleManifest(location: ProfilePluginLocation, name: string): ProfileManifest | undefined {
- let packageDir: string
- try {
- packageDir = resolveBundleDir(location.binName, name, location.installAnchor, location.profileDir)
- } catch {
- // An unresolved dependency remains visible as an ordinary package.
- return undefined
- }
- return optionalManifest(location.binName, packageDir)
- }
- /**
- * Read installed versions and bundle declarations without requiring loadable plugin code.
- * Missing or unreadable installed metadata leaves the dependency visible for repair or removal.
- * @param location - profile and installation resolution inputs.
- * @returns dependency records in package.json order and the profile manifest.
- */
- export function readProfilePlugins(location: ProfilePluginLocation): ProfilePluginInventory {
- const manifest = readProfileManifest(location.binName, location.profileDir)
- const bundles = manifest.dsh?.profile?.bundles ?? []
- const dependencies = Object.entries(manifest.dependencies ?? {}).map(([name, spec]) => {
- const installed = optionalManifest(location.binName, join(location.profileDir, 'node_modules', name))
- return {
- name,
- version: typeof installed?.version === 'string' ? installed.version : spec,
- bundle: bundleManifest(location, name)?.dsh?.bundle?.patch !== undefined,
- enabled: bundles.includes(name),
- }
- })
- return { manifest, dependencies }
- }
- /**
- * Write a bundle list while preserving the supplied profile's other metadata.
- * @param profileDir - directory whose package.json is updated.
- * @param manifest - current profile manifest, read after the package operation when applicable.
- * @param bundles - ordered active bundle names, including any template entries.
- * @returns the written manifest.
- */
- export function writeProfileBundles(
- profileDir: string, manifest: ProfileManifest, bundles: readonly string[],
- ): ProfileManifest {
- const updated = { ...manifest, dsh: { ...manifest.dsh, profile: { ...manifest.dsh?.profile, bundles: [...bundles] } } }
- writeProfileManifest(profileDir, updated)
- return updated
- }
- /**
- * Reconcile installed bundle declarations after a successful package-manager operation.
- * Dependency-managed entries disappear when removed or when their package loses its declaration;
- * template entries retain their order and duplicates. New bundle dependencies activate automatically.
- * @param options - location, inventory captured before pnpm, and whether explicitly disabled bundles remain disabled.
- * @returns updated inventory and newly added ordinary dependencies for caller-owned warnings.
- */
- export function reconcileProfilePlugins(options: ProfilePluginLocation & {
- readonly before: ProfilePluginInventory
- readonly preserveDisabled: boolean
- }): ProfilePluginReconciliation {
- const after = readProfilePlugins(options)
- const beforeNames = new Set(options.before.dependencies.map(dependency => dependency.name))
- const afterNames = new Set(after.dependencies.map(dependency => dependency.name))
- const bundleNames = new Set(after.dependencies.filter(dependency => dependency.bundle).map(dependency => dependency.name))
- const disabled = new Set(options.preserveDisabled
- ? options.before.dependencies.filter(dependency => dependency.bundle && !dependency.enabled).map(dependency => dependency.name)
- : [])
- const previous = after.manifest.dsh?.profile?.bundles ?? []
- const bundles = previous.filter(name => !(beforeNames.has(name) || afterNames.has(name)) || bundleNames.has(name))
- for (const dependency of after.dependencies) {
- if (dependency.bundle && !disabled.has(dependency.name) && !bundles.includes(dependency.name)) bundles.push(dependency.name)
- }
- const changed = bundles.length !== previous.length || bundles.some((name, index) => name !== previous[index])
- const manifest = changed ? writeProfileBundles(options.profileDir, after.manifest, bundles) : after.manifest
- return {
- plugins: {
- manifest,
- dependencies: after.dependencies.map(dependency => ({ ...dependency, enabled: bundles.includes(dependency.name) })),
- },
- addedPlainDependencies: after.dependencies.filter(dependency => !dependency.bundle && !beforeNames.has(dependency.name))
- .map(dependency => dependency.name),
- }
- }
|