You can pin the direct dependency, but what if the packages you depend on don't pin their own dependencies? The standard (default behavior) is to use ^, which will automatically install new minor versions. Package.lock helps, but there's no sane way manage upgrades. Just running "npm audit fix" could result in pulling down a bad package.
Pretty sure pinning only pins the direct dependency. And most libraries do not "pin" their own dependencies, because it's more work to maintain. Security & bugs fixes that would otherwise be resolved via minor patches must be manually addressed. It also helps with resolving shared dependencies.
NPM is highly optimized to make sharing code as easily as possible, but that comes at a heavy price.