@antongolub/lockfile 0.0.0-snapshot.2 → 0.0.0-snapshot.5

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,110 @@
1
1
  # lockfile
2
- Read, write, and convert npm (v1, v2) and yarn (classic and berry) lockfiles in any directions with reasonable losses.
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
- }
70
-
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
- `
22
+ ## Usage
23
+ ```ts
24
+ import { parse, format, analyze } from '@antongolub/lockfile'
25
+
26
+ const snapshot = parse('yarn.lock <raw contents>', 'package.json <raw contents>', './packages/foo/package.json <raw contents>')
27
+
28
+ const lf = format(snapshot)
29
+ const lf2 = format(snapshot, 'npm-1') // Throws err: npm v1 meta does not support workspaces
30
+
31
+ const meta = await readMeta() // reads local package.jsons data to gather required data like `engines`, `license`, `bins`, etc
32
+ const meta2 = await fetchMeta(snapshot) // does the same, but from the remote registry
33
+ const lf3 = format(snapshot, 'npm-3', {meta}) // format with options
34
+
35
+ const idx = analyze(snapshot)
36
+ idx.edges
37
+ // [
38
+ // [ '', '@antongolub/npm-test@4.0.1' ],
39
+ // [ '@antongolub/npm-test@4.0.1', '@antongolub/npm-test@3.0.1' ],
40
+ // [ '@antongolub/npm-test@3.0.1', '@antongolub/npm-test@2.0.1' ],
41
+ // [ '@antongolub/npm-test@2.0.1', '@antongolub/npm-test@1.0.0' ]
42
+ // ]
89
43
  ```
90
44
 
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 | |
45
+ ### Terms
46
+ * `nmtree` fs projection of deps, directories structure
47
+ * `deptree` — bounds full dep paths with their resolved packages
48
+ * `depgraph` describes how resolved pkgs are related with each other
49
+
50
+ ### Lockfiles formats
51
+ | Package manager | Meta format | Read | Write |
52
+ |------------------|-------------|------|-------|
53
+ | npm <7 | 1 | ✓ | ✓ |
54
+ | npm >=7 | 2 | ✓ | |
55
+ | npm >=9 | 3 | ✓ | |
56
+ | yarn 1 (classic) | 1 | ✓ | ✓ |
57
+ | yarn 2 (berry) | 5, 6, 7 | ✓ | ✓ |
58
+
59
+ ### Reference protocols
60
+ | Type | Supported |
61
+ |-----------|-----------|
62
+ | semver | ✓ |
63
+ | npm | ✓ |
64
+ | workspace | |
65
+ | patch | |
66
+ | file | |
67
+ | github | |
68
+ | tag | |
69
+
70
+ https://yarnpkg.com/features/protocols
71
+
72
+ ### `TSnapshot`
73
+ ```ts
74
+ export type TSnapshot = Record<string, {
75
+ name: string
76
+ version: string
77
+ ranges: string[]
78
+ hashes: {
79
+ sha512?: string
80
+ sha256?: string
81
+ sha1?: string
82
+ checksum?: string
83
+ md5?: string
84
+ }
85
+ source: string
86
+ sourceType: TSourceType
87
+
88
+ // optional pm-specific lockfile meta
89
+ manifest?: TManifest
90
+ conditions?: string
91
+ dependencies?: TDependencies
92
+ dependenciesMeta?: TDependenciesMeta
93
+ optionalDependencies?: TDependencies
94
+ peerDependencies?: TDependencies
95
+ peerDependenciesMeta?: TDependenciesMeta
96
+ bin?: Record<string, string>
97
+ engines?: Record<string, string>
98
+ funding?: Record<string, string>
99
+ }>
100
+ ```
99
101
 
100
102
  ### Caveats
101
- * Only `npm` links are supported for now
102
- * npm1: `optional: true` label is not supported by lockfile formatter
103
+ * 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
104
+ * npm1: `optional: true` label is not supported by `format`
105
+ * yarn berry: no idea how to resolve and inject PnP patches https://github.com/yarnpkg/berry/tree/master/packages/plugin-compat
106
+ * npm2 and npm3 requires `engines` and `funding` data, while yarn* or npm1 does not contain it
107
+ * many `nmtree` projections may correspond the specified `depgraph`
103
108
 
104
109
  ### Inspired by
105
110
  * [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.2",
3
+ "version": "0.0.0-snapshot.5",
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,2 @@
1
+ import { TSnapshot, TSnapshotIndex } from './interface';
2
+ 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,29 @@
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
+ };
17
+ export type TNpm3LockfileDeps = Record<string, TNpm3LockfileEntry>;
18
+ export type TNpm3Lockfile = {
19
+ lockfileVersion: 3;
20
+ name: string;
21
+ version: string;
22
+ requires?: true;
23
+ packages: TNpm3LockfileDeps;
24
+ };
25
+ export declare const version = "npm-3";
26
+ export declare const check: ICheck;
27
+ export declare const parse: (lockfile: string) => TSnapshot;
28
+ export declare const preformat: IPreformat<TNpm3Lockfile>;
29
+ 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';