@antongolub/lockfile 0.0.0-snapshot.1 → 0.0.0-snapshot.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,105 +1,157 @@
1
- # lockfile
2
- Read, write, and convert npm (v1, v2) and yarn (classic and berry) lockfiles in any directions with reasonable losses.
1
+ # @antongolub/lockfile
2
+ > Read and write lockfiles with reasonable losses
3
3
 
4
- ### Motivation
5
- Every package manager brings its own philosophy of how to describe, store and control projects dependencies.
6
- This is awesome for developers, but literally becomes a ~~pain in *** ***~~ headache for isec, devops and release engineers.
4
+ ## Motivation
5
+ Each package manager brings its own philosophy of how to describe, store and control project dependencies.
6
+ It _seems_ acceptable for developers, but literally becomes a ~~pain in *** ***~~ headache for isec, devops and release engineers.
7
7
  This lib is a naive attempt to build a pm-independent, generic, extensible and reliable deps representation.
8
8
 
9
- The package manifest contains its own deps requirements, the lockfile defines the deps resolution snapshot<sup>*</sup>,
10
- so both of them are required to build a dependency graph. We can convert this data into a normalized representation for further analysis and processing (for example, to fix vulnerabilities).
11
- And then, if necessary, convert back to the original format.
9
+ The `package.json` manifest contains its own deps requirements, the `lockfile` holds the deps resolution snapshot<sup>*</sup>,
10
+ 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).
11
+ And then, if necessary, try convert it back to the original/other format.
12
12
 
13
- ### Status
13
+ ## Status
14
14
  ⚠️ Initial draft. Alpha-version
15
15
 
16
- ### Getting started
17
- #### Install
16
+ ## Getting started
17
+ ### Install
18
18
  ```shell
19
19
  yarn add @antongolub/lockfile
20
20
  ```
21
21
 
22
- #### Usage
23
- ```js
24
- import { parse, format } from '@antongolub/lockfile'
25
-
26
- const parsed = parse({
27
- lockfile: './yarn.lock',
28
- workspaces: {'': './package.json', 'foo': './packages/foo/package.json'},
29
- })
30
-
31
- // output
32
- {
33
- entries: {
34
- '@babel/code-frame@7.10.4': {
35
- name: '@babel/code-frame',
36
- version: '7.10.4',
37
- scope: 'prod/dev/peer/opt',
38
- integrities: {
39
- sha512: 'hashsum',
40
- sha256: '...',
41
- sha1: '...',
42
- md5: '...'
43
- },
44
- reference: {
45
- sourceType: 'npm/git/file/workspace'
46
- source: 'uri://remote/address',
47
- linkType: 'hard/soft',
48
- link: '<root>path/to/package'
49
- },
50
- dependencies: {
51
- '@babel/highlight': '^7.10.4'
52
- }
53
- },
54
- ...
55
- },
56
- meta: {
57
- lockfile: {
58
- type: 'yarn',
59
- version: '5', // metadata format version
60
- },
61
- packageJson: {...},
62
- workspaces: {
63
- patterns: ['./packages/*'],
64
- packages: {
65
- '@qiwi/pijma-core': '<root>/packages/core/package.json'
66
- }
67
- }
68
- },
69
- }
22
+ ## Usage
23
+ tl;dr
24
+ ```ts
25
+ import fs from 'fs/promises'
26
+ import {parse, analyze} from '@antongolub/lockfile'
27
+
28
+ const lf = await fs.readFile('yarn.lock', 'utf-8')
29
+ const pkg = await fs.readFile('package.json', 'utf-8')
30
+
31
+ const snapshot = parse(lf, pkg)
32
+ const idx = analyze(snapshot)
33
+
34
+ // idx.entries
35
+ // idx.prod
36
+ // idx.edges
37
+ ```
38
+
39
+ ## API
40
+ ```ts
41
+ import { parse, format, analyze } from '@antongolub/lockfile'
42
+
43
+ const snapshot = parse('yarn.lock <raw contents>', 'package.json <raw contents>', './packages/foo/package.json <raw contents>')
44
+
45
+ const lf = format(snapshot)
46
+ const lf2 = format(snapshot, 'npm-1') // Throws err: npm v1 meta does not support workspaces
47
+
48
+ const meta = await readMeta() // reads local package.jsons data to gather required data like `engines`, `license`, `bins`, etc
49
+ const meta2 = await fetchMeta(snapshot) // does the same, but from the remote registry
50
+ const lf3 = format(snapshot, 'npm-3', {meta}) // format with options
70
51
 
71
- const data = format({
72
- ...parsed,
73
- lockfileType: 'yarn-2'
74
- })
75
- // output
76
- `
77
- # This file is generated by running "yarn install" inside your project.
78
- # Manual changes might be lost - proceed with caution!
79
-
80
- __metadata:
81
- version: 5
82
- cacheKey: 8
83
-
84
- "@babel/code-frame@npm:7.10.4":
85
- version: 7.10.4
86
- resolution: "@babel/code-frame@npm:7.10.4"
87
- ...
88
- `
52
+ const idx = analyze(snapshot)
53
+ idx.edges
54
+ // [
55
+ // [ '', '@antongolub/npm-test@4.0.1' ],
56
+ // [ '@antongolub/npm-test@4.0.1', '@antongolub/npm-test@3.0.1' ],
57
+ // [ '@antongolub/npm-test@3.0.1', '@antongolub/npm-test@2.0.1' ],
58
+ // [ '@antongolub/npm-test@2.0.1', '@antongolub/npm-test@1.0.0' ]
59
+ // ]
89
60
  ```
90
61
 
91
- ### Lockfile (meta) versions
92
- | Package manager | Meta format | Supported |
93
- |------------------|-------------|-----------|
94
- | npm <7 | 1 | x |
95
- | npm >=7 | 2 | |
96
- | yarn 1 (classic) | 1 | x |
97
- | yarn 3 | 5, 6 | x |
98
- | yarn 4 | 6, 7 | |
62
+ ### Terms
63
+ * `nmtree` fs projection of deps, directories structure
64
+ * `deptree` — bounds full dep paths with their resolved packages
65
+ * `depgraph` describes how resolved pkgs are related with each other
66
+
67
+ ### Lockfiles types
68
+ | Package manager | Meta format | Read | Write |
69
+ |----------------------|-------------|------|-------|
70
+ | npm <7 | 1 | ✓ | ✓ |
71
+ | npm >=7 | 2 | ✓ | |
72
+ | npm >=9 | 3 | ✓ | |
73
+ | yarn 1 (classic) | 1 | ✓ | ✓ |
74
+ | yarn 2, 3, 4 (berry) | 5, 6, 7 | ✓ | ✓ |
75
+
76
+ ### Reference protocols
77
+ | Type | Supported |
78
+ |-----------|-----------|
79
+ | semver | ✓ |
80
+ | npm | ✓ |
81
+ | workspace | _limited_ |
82
+ | patch | _limited_ |
83
+ | file | |
84
+ | github | |
85
+ | tag | |
86
+
87
+ https://yarnpkg.com/features/protocols
88
+
89
+ ### `TSnapshot`
90
+ ```ts
91
+ export type TSnapshot = Record<string, TEntry>
92
+
93
+ export type TEntry = {
94
+ name: string
95
+ version: string
96
+ ranges: string[]
97
+ hashes: {
98
+ sha512?: string
99
+ sha256?: string
100
+ sha1?: string
101
+ checksum?: string
102
+ md5?: string
103
+ }
104
+ source: {
105
+ type: TSourceType // npm, workspace, gh, patch, etc
106
+ id: string
107
+ registry?: string
108
+ }
109
+ // optional pm-specific lockfile meta
110
+ manifest?: TManifest
111
+ conditions?: string
112
+ dependencies?: TDependencies
113
+ dependenciesMeta?: TDependenciesMeta
114
+ devDependencies?: TDependencies
115
+ optionalDependencies?: TDependencies
116
+ peerDependencies?: TDependencies
117
+ peerDependenciesMeta?: TDependenciesMeta
118
+ bin?: Record<string, string>
119
+ engines?: Record<string, string>
120
+ funding?: Record<string, string>
121
+ }>
122
+ ```
123
+
124
+ ### `TSnapshotIndex`
125
+ ```ts
126
+ export interface TSnapshotIndex {
127
+ snapshot: TSnapshot
128
+ entries: TEntry[]
129
+ roots: TEntry[]
130
+ edges: [string, string][]
131
+ tree: Record<string, {
132
+ key: string
133
+ chunks: string[]
134
+ parents: TEntry[]
135
+ id: string
136
+ name: string
137
+ version: string
138
+ entry: TEntry
139
+ }>
140
+ prod: Set<TEntry>
141
+ getEntryId ({name, version}: TEntry): string
142
+ getEntry (name: string, version?: string): TEntry | undefined,
143
+ getEntryByRange (name: string, range: string): TEntry | undefined
144
+ getEntryDeps(entry: TEntry): TEntry[]
145
+ }
146
+ ```
99
147
 
100
148
  ### Caveats
101
- * Only `npm` links are supported for now
102
- * npm1: `optional: true` label is not supported by lockfile formatter
149
+ * 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
150
+ * npm1: `optional: true` label is not supported yet
151
+ * yarn berry: no idea how to resolve and inject PnP patches https://github.com/yarnpkg/berry/tree/master/packages/plugin-compat
152
+ * npm2 and npm3 requires `engines` and `funding` data, while yarn* or npm1 does not contain it
153
+ * many `nmtree` projections may correspond to the specified `depgraph`
154
+ * no idea what todo with yarn `patch`
103
155
 
104
156
  ### Inspired by
105
157
  * [synp](https://github.com/imsnif/synp)
package/package.json CHANGED
@@ -1,23 +1,26 @@
1
1
  {
2
2
  "name": "@antongolub/lockfile",
3
- "version": "0.0.0-snapshot.1",
3
+ "version": "0.0.0-snapshot.11",
4
4
  "publishConfig": {
5
5
  "access": "public"
6
6
  },
7
- "description": "Read, write, and convert npm (v1, v2) and yarn (classic and berry) lockfiles in any directions with reasonable losses.",
8
- "main": "./target/es6/index.mjs",
7
+ "description": "Read and write lockfiles with reasonable losses",
8
+ "main": "./target/es6/index.js",
9
9
  "type": "module",
10
- "exports": "./target/es6/index.mjs",
10
+ "exports": "./target/es6/index.js",
11
11
  "dependencies": {
12
- "js-yaml": "^4.1.0"
12
+ "@semrel-extra/topo": "^1.13.1",
13
+ "js-yaml": "^4.1.0",
14
+ "semver": "^7.5.4"
13
15
  },
14
16
  "devDependencies": {
15
17
  "@types/js-yaml": "^4.0.5",
16
- "esbuild": "^0.15.12",
17
- "esbuild-node-externals": "^1.5.0",
18
- "tsc-esm-fix": "^2.20.5",
19
- "tsm": "^2.2.2",
20
- "typescript": "^4.8.4",
18
+ "@types/node": "^20.5.0",
19
+ "@types/semver": "^7.5.0",
20
+ "esbuild": "^0.19.2",
21
+ "esbuild-node-externals": "^1.8.0",
22
+ "tsm": "^2.3.0",
23
+ "typescript": "^5.1.6",
21
24
  "uvu": "^0.5.6"
22
25
  },
23
26
  "scripts": {
@@ -25,7 +28,8 @@
25
28
  "build:es6": "node ./build.js",
26
29
  "build:libdef": "tsc --emitDeclarationOnly --outDir target/es6",
27
30
  "build:esmfix": "tsc-esm-fix --target=target/es6 --ext=.mjs",
28
- "test": "uvu -r tsm -i helpers 'src/test/ts/'",
31
+ "test": "yarn test:unit",
32
+ "test:unit": "DEBUG=true uvu -r tsm -i helpers 'src/test/ts/'",
29
33
  "publish:snapshot": "npm publish --no-git-tag-version --tag snapshot"
30
34
  },
31
35
  "files": [
@@ -0,0 +1,7 @@
1
+ import { TEntry, TSnapshot, TSnapshotIndex } from './interface';
2
+ export declare const getDeps: {
3
+ (entry: TEntry): Record<string, string>;
4
+ cache: WeakMap<TEntry, Record<string, string>>;
5
+ };
6
+ export declare const getId: (name?: string, version?: string) => string;
7
+ export declare const analyze: (snapshot: TSnapshot) => TSnapshotIndex;
@@ -1,3 +1,26 @@
1
- import { TSnapshot } from './interface';
2
- export declare const normalizeOptions: () => void;
1
+ import { THashes, TManifest, TSnapshot } from './interface';
2
+ export declare const formatTarballUrl: (name: string, version: string, registry?: string) => string;
3
3
  export declare const getSources: (snapshot: TSnapshot) => string[];
4
+ export declare const isProd: (manifest: TManifest, name: string) => boolean;
5
+ export declare const isDev: (manifest: TManifest, name: string) => boolean;
6
+ export declare const isPeer: (manifest: TManifest, name: string) => boolean;
7
+ export declare const isOptional: (manifest: TManifest, name: string) => boolean;
8
+ export declare const parseIntegrity: (integrity?: string) => THashes;
9
+ export declare const formatIntegrity: (hashes: THashes) => string;
10
+ export type IReferenceDeclaration = {
11
+ protocol: string;
12
+ raw: string;
13
+ version: string | null;
14
+ [extra: string]: any;
15
+ };
16
+ export declare const parseReference: (raw?: any) => IReferenceDeclaration;
17
+ export declare const mapReference: (current: string, targetProtocol: string, strategy?: string) => string;
18
+ /**
19
+ * Replaces local monorepo cross-refs with the target protocol: workspace or semver
20
+ */
21
+ export declare const switchMonorefs: ({ cwd, strategy, protocol, dryrun }: {
22
+ cwd?: string | undefined;
23
+ strategy?: string | undefined;
24
+ protocol?: string | undefined;
25
+ dryrun?: boolean | undefined;
26
+ }) => Promise<Record<string, import("@semrel-extra/topo").IPackageEntry>>;
@@ -0,0 +1,2 @@
1
+ import { IFormatOpts, TSnapshot } from './interface';
2
+ export declare const format: (snapshot: TSnapshot, version: string, opts?: IFormatOpts) => string;
@@ -0,0 +1,22 @@
1
+ import { ICheck, IFormat, IParse, IPreformat } from '../interface';
2
+ export declare const version = "npm-1";
3
+ export type TNpm1LockfileEntry = {
4
+ version: string;
5
+ resolved: string;
6
+ integrity: string;
7
+ dev?: boolean;
8
+ requires?: Record<string, string>;
9
+ dependencies?: TNpm1LockfileDeps;
10
+ };
11
+ export type TNpm1LockfileDeps = Record<string, TNpm1LockfileEntry>;
12
+ export type TNpm1Lockfile = {
13
+ lockfileVersion: 1;
14
+ name: string;
15
+ version: string;
16
+ requires?: true;
17
+ dependencies: TNpm1LockfileDeps;
18
+ };
19
+ export declare const check: ICheck;
20
+ export declare const parse: IParse;
21
+ export declare const preformat: IPreformat<TNpm1Lockfile>;
22
+ export declare const format: IFormat;
@@ -0,0 +1,16 @@
1
+ import { ICheck, IFormat, IPreformat } from '../interface';
2
+ import { TNpm1LockfileDeps } from './npm-1';
3
+ import { TNpm3LockfileDeps } from './npm-3';
4
+ export type TNpm2Lockfile = {
5
+ lockfileVersion: 2;
6
+ name: string;
7
+ version: string;
8
+ requires?: true;
9
+ packages: TNpm3LockfileDeps;
10
+ dependencies: TNpm1LockfileDeps;
11
+ };
12
+ export declare const version = "npm-2";
13
+ export { parse } from './npm-3';
14
+ export declare const check: ICheck;
15
+ export declare const preformat: IPreformat<TNpm2Lockfile>;
16
+ export declare const format: IFormat;
@@ -0,0 +1,30 @@
1
+ import { ICheck, IFormat, IPreformat, TSnapshot } from '../interface';
2
+ export type TNpm3LockfileEntry = {
3
+ name?: string;
4
+ version?: string;
5
+ resolved?: string;
6
+ integrity?: string;
7
+ dev?: boolean;
8
+ link?: boolean;
9
+ dependencies?: Record<string, string>;
10
+ engines?: Record<string, string>;
11
+ funding?: Record<string, string>;
12
+ peerDependencies?: Record<string, string>;
13
+ devDependencies?: Record<string, string>;
14
+ optionalDependencies?: Record<string, string>;
15
+ bin?: any;
16
+ license?: string;
17
+ };
18
+ export type TNpm3LockfileDeps = Record<string, TNpm3LockfileEntry>;
19
+ export type TNpm3Lockfile = {
20
+ lockfileVersion: 3;
21
+ name: string;
22
+ version: string;
23
+ requires?: true;
24
+ packages: TNpm3LockfileDeps;
25
+ };
26
+ export declare const version = "npm-3";
27
+ export declare const check: ICheck;
28
+ export declare const parse: (lockfile: string) => TSnapshot;
29
+ export declare const preformat: IPreformat<TNpm3Lockfile>;
30
+ export declare const format: IFormat;
@@ -0,0 +1,14 @@
1
+ import { TDependencies, ICheck, IFormat, IParse, IPreformat } from '../interface';
2
+ export type TYarn1Lockfile = Record<string, {
3
+ version: string;
4
+ resolved: string;
5
+ integrity: string;
6
+ dependencies?: TDependencies;
7
+ optionalDependencies?: TDependencies;
8
+ }>;
9
+ export declare const version = "yarn-1";
10
+ export declare const check: ICheck;
11
+ export declare const preparse: (value: string) => TYarn1Lockfile;
12
+ export declare const parse: IParse;
13
+ export declare const preformat: IPreformat<TYarn1Lockfile>;
14
+ export declare const format: IFormat;
@@ -0,0 +1,20 @@
1
+ import { ICheck, IFormat, IParse, IPreformat, TDependencies, TDependenciesMeta } from '../interface';
2
+ export type TYarn5Lockfile = Record<string, {
3
+ version: string;
4
+ resolution: string;
5
+ conditions?: string;
6
+ checksum: string;
7
+ languageName: string;
8
+ linkType: string;
9
+ dependencies?: TDependencies;
10
+ dependenciesMeta?: TDependenciesMeta;
11
+ optionalDependencies?: TDependencies;
12
+ peerDependencies?: TDependencies;
13
+ peerDependenciesMeta?: TDependenciesMeta;
14
+ bin?: Record<string, string>;
15
+ }>;
16
+ export declare const version = "yarn-5";
17
+ export declare const check: ICheck;
18
+ export declare const parse: IParse;
19
+ export declare const preformat: IPreformat<TYarn5Lockfile>;
20
+ export declare const format: IFormat;
File without changes
@@ -0,0 +1,20 @@
1
+ import { ICheck, IFormat, IParse, IPreformat, TDependencies, TDependenciesMeta } from '../interface';
2
+ export type TYarn5Lockfile = Record<string, {
3
+ version: string;
4
+ resolution: string;
5
+ conditions?: string;
6
+ checksum: string;
7
+ languageName: string;
8
+ linkType: string;
9
+ dependencies?: TDependencies;
10
+ dependenciesMeta?: TDependenciesMeta;
11
+ optionalDependencies?: TDependencies;
12
+ peerDependencies?: TDependencies;
13
+ peerDependenciesMeta?: TDependenciesMeta;
14
+ bin?: Record<string, string>;
15
+ }>;
16
+ export declare const version = "yarn-berry";
17
+ export declare const check: ICheck;
18
+ export declare const parse: IParse;
19
+ export declare const preformat: IPreformat<TYarn5Lockfile>;
20
+ export declare const format: IFormat;
@@ -0,0 +1,14 @@
1
+ import { TDependencies, ICheck, IFormat, IParse, IPreformat } from '../interface';
2
+ export type TYarn1Lockfile = Record<string, {
3
+ version: string;
4
+ resolved: string;
5
+ integrity: string;
6
+ dependencies?: TDependencies;
7
+ optionalDependencies?: TDependencies;
8
+ }>;
9
+ export declare const version = "yarn-1";
10
+ export declare const check: ICheck;
11
+ export declare const preparse: (value: string) => TYarn1Lockfile;
12
+ export declare const parse: IParse;
13
+ export declare const preformat: IPreformat<TYarn1Lockfile>;
14
+ export declare const format: IFormat;
@@ -1,8 +1,5 @@
1
- import { parse as parseNpm1, format as formatNpm1 } from './npm-1';
2
- import { parse as parseYarn1, format as formatYarn1 } from './yarn-1';
3
- import { parse as parseYarn5, format as formatYarn5 } from './yarn-5';
4
- import { TSnapshot } from './interface';
5
- export declare const foo = "bar";
1
+ export { TSnapshot } from './interface';
6
2
  export { getSources } from './common';
7
- export { parseNpm1, formatNpm1, parseYarn1, formatYarn1, parseYarn5, formatYarn5 };
8
- export declare const parse: (lockfile: string, pkg: string) => Promise<TSnapshot>;
3
+ export { analyze } from './analyze';
4
+ export { parse } from './parse';
5
+ export { format } from './format';