https://github.com/indexzero/flatlock
the Matlock of lockfile parsers
https://github.com/indexzero/flatlock
Last synced: 2 months ago
JSON representation
the Matlock of lockfile parsers
- Host: GitHub
- URL: https://github.com/indexzero/flatlock
- Owner: indexzero
- License: apache-2.0
- Created: 2025-12-10T20:03:40.000Z (6 months ago)
- Default Branch: main
- Last Pushed: 2026-04-14T15:43:16.000Z (2 months ago)
- Last Synced: 2026-04-14T16:14:57.784Z (2 months ago)
- Language: JavaScript
- Homepage:
- Size: 2.43 MB
- Stars: 0
- Watchers: 0
- Forks: 1
- Open Issues: 0
-
Metadata Files:
- Readme: README.md
- License: LICENSE
Awesome Lists containing this project
README
# `flatlock`
The Matlock of lockfile parsers - cuts through the complexity to get just the facts. Flat lockfile parser that extracts packages without building dependency graphs.
## What makes `flatlock` different?

Most lockfile parsers (like `@npmcli/arborist` or `snyk-nodejs-lockfile-parser`) build the full dependency graph with edges representing relationships between packages. This is necessary for dependency resolution but overkill for many use cases.
**flatlock** takes a different approach: it extracts a flat stream of packages from any lockfile format. No trees, no graphs, no edges - just packages.
```javascript
import * as flatlock from 'flatlock';
// Stream packages from any lockfile
for await (const pkg of flatlock.fromPath('./package-lock.json')) {
console.log(pkg.name, pkg.version, pkg.integrity);
}
```
## When to use flatlock
| Use Case | Needs Graph? | Use flatlock? |
|------------------------|--------------|------------------|
| SBOM generation | No | Yes |
| Vulnerability scanning | No | Yes |
| License compliance | No | Yes |
| Integrity verification | No | Yes |
| Package enumeration | No | Yes |
| Dependency resolution | Yes | No, use Arborist |
| "Why is X installed?" | Yes | No, use Arborist |
## Supported Formats
- **npm**: package-lock.json (v1, v2, v3)
- **pnpm**: pnpm-lock.yaml (v5.4, v6, v9)
- **yarn classic**: yarn.lock v1
- **yarn berry**: yarn.lock v2+
## CLI Tools
Three command-line tools are included for common workflows:
```bash
# Extract dependencies from any lockfile
npx flatlock package-lock.json --specs --json
# Verify parser accuracy against official tools
npx flatlock-cmp --dir ./fixtures --glob "**/*lock*"
# Check registry availability of all dependencies
npx flatcover package-lock.json --summary
```
| Command | Purpose |
|---------|---------|
| `flatlock` | Extract dependencies as plain text, JSON, or NDJSON |
| `flatlock-cmp` | Compare output against @npmcli/arborist, @yarnpkg/lockfile, @pnpm/lockfile.fs |
| `flatcover` | Verify packages exist on registry (useful for private registry migrations) |
Run any command with `--help` for full options.
## API
```javascript
import * as flatlock from 'flatlock';
// Auto-detect format from file
for await (const pkg of flatlock.fromPath('./pnpm-lock.yaml')) { }
// Parse string content (sync generator)
for (const pkg of flatlock.fromString(content, { path: 'yarn.lock' })) { }
// Format-specific parsers
for (const pkg of flatlock.fromPackageLock(content)) { }
for (const pkg of flatlock.fromPnpmLock(content)) { }
for (const pkg of flatlock.fromYarnLock(content)) { } // auto-detects v1 vs v2+
for (const pkg of flatlock.fromYarnClassicLock(content)) { }
for (const pkg of flatlock.fromYarnBerryLock(content)) { }
// Error handling with Result type
const result = flatlock.tryFromPath('./package-lock.json');
if (result.ok) {
for await (const pkg of result.value) { }
}
// Collect all packages into array
const packages = await flatlock.collect('./package-lock.json');
// Type detection
const type = flatlock.detectType({ path: 'yarn.lock', content });
console.log(type); // 'yarn-classic' or 'yarn-berry'
// Content-only detection (path is optional)
flatlock.detectType({ content }); // auto-detect from content alone
// Type constants
console.log(flatlock.Type.NPM); // 'npm'
```
## Output Format
Each yielded package has:
```typescript
{
name: string; // Package name (e.g., "@babel/core")
version: string; // Resolved version (e.g., "7.23.0")
integrity?: string; // Integrity hash (sha512, sha384, sha256, sha1)
resolved?: string; // Download URL (registry or private)
link?: boolean; // True if this is a workspace symlink
}
```
## FlatlockSet
For more advanced use cases, `FlatlockSet` provides Set-like operations on lockfile dependencies:
```javascript
import { FlatlockSet } from 'flatlock';
// Create from lockfile
const set = await FlatlockSet.fromPath('./package-lock.json');
console.log(set.size); // 1234
console.log(set.has('lodash@4.17.21')); // true
// Set operations (immutable - return new sets)
const other = await FlatlockSet.fromPath('./other-lock.json');
const common = set.intersection(other); // packages in both
const added = other.difference(set); // packages only in other
const all = set.union(other); // packages in either
// Predicates
set.isSubsetOf(other); // true if all packages in set are in other
set.isSupersetOf(other); // true if set contains all packages in other
set.isDisjointFrom(other); // true if no packages in common
// Iterate like a Set
for (const dep of set) {
console.log(dep.name, dep.version);
}
```
### Workspace-Specific SBOMs
For monorepos, use `dependenciesOf()` to get only the dependencies of a specific workspace:
```javascript
import { readFile } from 'node:fs/promises';
import { FlatlockSet } from 'flatlock';
const lockfile = await FlatlockSet.fromPath('./package-lock.json');
const pkg = JSON.parse(await readFile('./packages/api/package.json', 'utf8'));
// Get only dependencies reachable from this workspace
const subset = await lockfile.dependenciesOf(pkg, {
workspacePath: 'packages/api', // for correct resolution in monorepos
repoDir: '.', // reads workspace package.json files for accurate traversal
dev: false, // exclude devDependencies
optional: true, // include optionalDependencies
peer: false // exclude peerDependencies
});
console.log(`${pkg.name} has ${subset.size} production dependencies`);
```
**Note:** Sets created via `union()`, `intersection()`, or `difference()` cannot use `dependenciesOf()` because they lack the raw lockfile data needed for traversal. Check `set.canTraverse` before calling.
## Compare API
Verify flatlock output against official package manager parsers:
```javascript
// Both import styles work - use whichever you prefer
import { compare } from 'flatlock';
import { compare } from 'flatlock/compare';
const result = await compare('./package-lock.json');
console.log(result.equinumerous); // true if counts match
console.log(result.flatlockCount); // packages found by flatlock
console.log(result.comparisonCount); // packages found by official parser
```
The dedicated `flatlock/compare` entry point exists for tools that want explicit imports, but the main export works identically.
## License
Apache-2.0