0.0.0-snapshot.27 • Published 6 months ago

lockf v0.0.0-snapshot.27

Weekly downloads
-
License
MIT
Repository
github
Last release
6 months ago

@antongolub/lockfile

Read and write lockfiles with reasonable losses

Motivation

Each package manager brings its own philosophy of how to describe, store and control project dependencies. It seems acceptable for developers, but literally becomes a pain in * * headache for isec, devops and release engineers. This lib is a naive attempt to build a pm-independent, generic, extensible and reliable deps representation.

The package.json manifest contains its own deps requirements, the lockfile holds the deps resolution snapshot*, so both of them are required to build a dependency graph. We can try to convert this data into a normalized representation for further analysis and processing (for example, to fix vulnerabilities). And then, if necessary, try convert it back to the original/another format.

Status

Proof of concept. The API may change significantly ⚠️

Getting started

Install

yarn add @antongolub/lockfile@snapshot

Usage

tl;dr

import fs from 'fs/promises'
import {parse, analyze} from '@antongolub/lockfile'

const lf = await fs.readFile('yarn.lock', 'utf-8')
const pkg = await fs.readFile('package.json', 'utf-8')

const snapshot = parse(lf, pkg) // Holds JSON-friendly TEntries[]
const idx = analyze(snapshot)   // An index to represent repo dep graphs

// idx.entries
// idx.prod
// idx.edges

API

JS/TS

import { parse, format, analyze } from '@antongolub/lockfile'

const snapshot = parse('yarn.lock <raw contents>', 'package.json <raw contents>', './packages/foo/package.json <raw contents>')

const lf = format(snapshot)
const lf2 = format(snapshot, 'npm-1')         // Throws err: npm v1 meta does not support workspaces

const meta = await readMeta()                 // reads local package.jsons data to gather required data like `engines`, `license`, `bins`, etc
const meta2 = await fetchMeta(snapshot)       // does the same, but from the remote registry
const lf3 = format(snapshot, 'npm-3', {meta}) // format with options

const idx = analyze(snapshot)
idx.edges
// [
//  [ '', '@antongolub/npm-test@4.0.1' ],
//  [ '@antongolub/npm-test@4.0.1', '@antongolub/npm-test@3.0.1' ],
//  [ '@antongolub/npm-test@3.0.1', '@antongolub/npm-test@2.0.1' ],
//  [ '@antongolub/npm-test@2.0.1', '@antongolub/npm-test@1.0.0' ]
// ]

CLI

npx @antongolub/lockfile@snapshot <cmd> [options]

npx @antongolub/lockfile@snapshot lockfile parse --input=yarn.lock,package.json --output=snapshot.json
npx @antongolub/lockfile@snapshot lockfile format --input=snapshot.json --output=yarn.lock
Command / OptionDescription
parseParses lockfiles and package manifests into a snapshot
formatFormats a snapshot into a lockfile
--inputA comma-separated list of files to parse: snapshot.json or yarn.lock,package.json
--outputA file to write the result to: snapshot.json or yarn.lock
--formatA lockfile format: npm-1, npm-2, npm-3, yarn-berry, yarn-classic

Terms

nmtree — fs projection of deps, directories structure
deptree — bounds full dep paths with their resolved packages
depgraph — describes how resolved pkgs are related with each other

Lockfiles types

Package managerMeta formatReadWrite
npm <71
npm >=72
npm >=93
yarn 1 (classic)1
yarn 2, 3, 4 (berry)5, 6, 7

Dependency protocols

TypeSupportedExampleDescription
semver^1.2.3Resolves from the default registry
taglatestResolves from the default registry
npmnpm:name@...Resolves from the npm registry
gitgit@github.com:foo/bar.gitDownloads a public package from a Git repository
githubgithub:foo/barDownloads a public package from GitHub
githubfoo/barAlias for the github: protocol
filefile:./my-packageCopies the target location into the cache
linklink:./my-folderCreates a link to the ./my-folder folder (ignore dependencies)
patchlimitedpatch:left-pad@1.0.0#./my-patch.patchCreates a patched copy of the original package
portalportal:./my-folderCreates a link to the ./my-folder folder (follow dependencies)
workspacelimitedworkspace:*Creates a link to a package in another workspace

https://v3.yarnpkg.com/features/protocols
https://yarnpkg.com/protocols
https://docs.npmjs.com/cli/v10/configuring-npm/package-json#dependencies

TSnapshot

export type TSnapshot = Record<string, TEntry>

export type TEntry = {
  name:       string
  version:    string
  ranges:     string[]
  hashes:     {
    sha512?:  string
    sha256?:  string
    sha1?:    string
    checksum?: string
    md5?:     string
  }
  source:     {
    type:     TSourceType // npm, workspace, gh, patch, etc
    id:       string
    registry?: string
  }
  // optional pm-specific lockfile meta
  manifest?:              TManifest
  conditions?:            string
  dependencies?:          TDependencies
  dependenciesMeta?:      TDependenciesMeta
  devDependencies?:       TDependencies
  optionalDependencies?:  TDependencies
  peerDependencies?:      TDependencies
  peerDependenciesMeta?:  TDependenciesMeta
  bin?:                   Record<string, string>
  engines?:               Record<string, string>
  funding?:               Record<string, string>
}

TSnapshotIndex

export interface TSnapshotIndex {
  snapshot: TSnapshot
  entries:  TEntry[]
  roots:    TEntry[]
  edges:    [string, string][]
  tree:       Record<string, {
    key:      string
    chunks:   string[]
    parents:  TEntry[]
    id:       string
    name:     string
    version:  string
    entry:    TEntry
  }>
  prod: Set<TEntry>
  getEntryId ({name, version}: TEntry): string
  getEntry (name: string, version?: string): TEntry | undefined,
  getEntryByRange (name: string, range: string): TEntry | undefined
  getEntryDeps(entry: TEntry): TEntry[]
}

Caveats

  • There is an infinite number of nmtrees that corresponds to the specified deptree, but among them there is a finite set of effective (sufficient) for the target criterion — for example, nesting, size, homogeneity of versions
  • npm1: optional: true label is not supported yet
  • yarn berry: no idea how to resolve and inject PnP patches https://github.com/yarnpkg/berry/tree/master/packages/plugin-compat
  • npm2 and npm3 requires engines and funding data, while yarn* or npm1 does not contain it
  • many nmtree projections may correspond to the specified depgraph
  • pkg.json resolutions and overrides directives are completely ignored for now
  • pkg aliases are not fully supported yet #2

Inspired by

Refs

License

MIT