Skip to content

Commit 2a80f6a

Browse files
committed
refactor: refactor package dependencies
1 parent 4306fd5 commit 2a80f6a

3 files changed

Lines changed: 226 additions & 58 deletions

File tree

lib/commands/package/link.js

Lines changed: 9 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { readConfig } from "#core/config";
55
import { exists } from "#core/fs";
66
import { glob } from "#core/glob";
77
import Command from "#lib/command";
8+
import Dependencies from "#lib/package/dependencies";
89

910
const IGNORE = [ ".cache", ".external-resources" ];
1011

@@ -169,17 +170,14 @@ export default class extends Command {
169170

170171
// private
171172
#getDependencies ( config, { peerOnly } = {} ) {
172-
return new Set( [
173-
174-
//
175-
...Object.keys( peerOnly
176-
? {}
177-
: config.dependencies || {} ),
178-
...Object.keys( peerOnly
179-
? {}
180-
: config.devDependencies || {} ),
181-
...Object.keys( config.peerDependencies || {} ),
182-
] );
173+
const dependencies = new Dependencies( config );
174+
175+
if ( peerOnly ) {
176+
return dependencies.peerNames;
177+
}
178+
else {
179+
return dependencies.names;
180+
}
183181
}
184182

185183
#processDependencies ( name, packages ) {

lib/package.js

Lines changed: 21 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -14,12 +14,12 @@ import { glob, globSync } from "#core/glob";
1414
import GlobPatterns from "#core/glob/patterns";
1515
import Locale from "#core/locale";
1616
import SemanticVersion from "#core/semantic-version";
17-
import SemanticVersionRange from "#core/semantic-version/range";
1817
import Table from "#core/text/table";
1918
import { confirm, mergeObjects, objectIsEmpty, repeatAction } from "#core/utils";
2019
import yaml from "#core/yaml";
2120
import Git from "#lib/git";
2221
import lintFile from "#lib/lint/file";
22+
import Dependencies from "#lib/package/dependencies";
2323
import Docs from "#lib/package/docs";
2424
import Localization from "#lib/package/localization";
2525
import Wiki from "#lib/package/wiki";
@@ -320,21 +320,12 @@ export default class Package {
320320

321321
get dependencies () {
322322
if ( !this.#dependencies ) {
323-
this.#dependencies = new Map( Object.entries( {
324-
...this.config.dependencies,
325-
...this.config.devDependencies,
326-
...this.config.peerDependencies,
327-
...this.config.optionalDependencies,
328-
} ) );
323+
this.#dependencies = new Dependencies( this.config );
329324
}
330325

331326
return this.#dependencies;
332327
}
333328

334-
get hasDependencies () {
335-
return Boolean( this.dependencies.size );
336-
}
337-
338329
// public
339330
patchVersion ( version ) {
340331
const root = this.root;
@@ -445,41 +436,20 @@ export default class Package {
445436
}
446437

447438
checkPreReleaseDependencies () {
448-
for ( const [ name, version ] of this.dependencies.entries() ) {
449-
450-
// process known tags
451-
if ( version === "latest" ) {
452-
continue;
453-
}
454-
else if ( version === "next" ) {
455-
return result( [ 500, `Package "${ this.name }" has pre-release dependency "${ name }@${ version }"` ] );
456-
}
457-
458-
let dependencyRange;
459-
460-
// detect git url
461-
const match = version.match( /#semver:(.+)$/ );
462-
463-
if ( match ) {
464-
dependencyRange = match[ 1 ];
465-
}
466-
else {
467-
dependencyRange = version;
468-
}
439+
const preReleaseDependencies = this.dependencies.preReleaseNames;
469440

470-
try {
471-
dependencyRange = new SemanticVersionRange( dependencyRange );
472-
473-
if ( dependencyRange.hasPreReleaseDependencies ) {
474-
return result( [ 500, `Package "${ this.name }" has pre-release dependency "${ name }@${ version }"` ] );
475-
}
476-
}
477-
catch {
478-
return result( [ 500, `Unable to parse dependency version "${ name }@ ${ version }"` ] );
479-
}
441+
if ( preReleaseDependencies.size ) {
442+
return result( [
443+
500,
444+
`Package "${ this.name }" has pre-release dependencies: ${ [ ...preReleaseDependencies ]
445+
.sort()
446+
.map( name => `"${ name }"` )
447+
.join( ", " ) }`,
448+
] );
449+
}
450+
else {
451+
return result( 200 );
480452
}
481-
482-
return result( 200 );
483453
}
484454

485455
async release ( { preReleaseTag, yes } = {} ) {
@@ -674,7 +644,7 @@ export default class Package {
674644
}
675645

676646
async getOutdatedDependencies ( { all } = {} ) {
677-
if ( !this.hasDependencies ) return result( 200 );
647+
if ( !this.dependencies.hasDependencies ) return result( 200 );
678648

679649
return new Promise( resolve => {
680650
childProcess.exec(
@@ -700,7 +670,7 @@ export default class Package {
700670
}
701671

702672
async updateDependencies ( { all, outdated, linked, missing, install, reinstall, commit, quiet, confirmInstall, outdatedDependencies, cache = {} } = {} ) {
703-
if ( !this.hasDependencies ) return result( 200 );
673+
if ( !this.dependencies.hasDependencies ) return result( 200 );
704674

705675
var res;
706676

@@ -1290,6 +1260,10 @@ export default class Package {
12901260
delete config.scripts?.test;
12911261
}
12921262

1263+
// dependencies
1264+
const dependencies = new Dependencies( config );
1265+
dependencies.fix();
1266+
12931267
if ( config.scripts && objectIsEmpty( config.scripts ) ) {
12941268
delete config.scripts;
12951269
}
@@ -1497,7 +1471,7 @@ export default class Package {
14971471

14981472
await Promise.all( [
14991473
...new Set( [
1500-
...this.dependencies.keys(),
1474+
...this.dependencies.names,
15011475
...( await glob( [ "*", "@*/*" ], {
15021476
"cwd": this.root + "/node_modules",
15031477
"files": false,

lib/package/dependencies.js

Lines changed: 196 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,196 @@
1+
import SemanticVersionRange from "#core/semantic-version/range";
2+
3+
const TAGS = new Set( [ "latest", "next" ] ),
4+
DEPENDENCIES = {
5+
"dependencies": {
6+
"dev": false,
7+
"peer": false,
8+
"commit": "build(deps)",
9+
},
10+
"devDependencies": {
11+
"dev": true,
12+
"peer": false,
13+
"commit": "chore(deps)",
14+
},
15+
"peerDependencies": {
16+
"dev": false,
17+
"peer": true,
18+
"commit": "build(deps)",
19+
},
20+
"optionalDependencies": {
21+
"dev": false,
22+
"peer": false,
23+
"commit": "build(deps)",
24+
},
25+
};
26+
27+
export default class PackageDependencies {
28+
#config;
29+
#names = new Set();
30+
#peerNames = new Set();
31+
#preReleaseNames = new Set();
32+
#dependencies = [];
33+
34+
constructor ( config ) {
35+
this.#config = config;
36+
37+
this.#parse();
38+
}
39+
40+
// properties
41+
get config () {
42+
return this.#config;
43+
}
44+
45+
get hasDependencies () {
46+
return Boolean( this.#names.size );
47+
}
48+
49+
get names () {
50+
return this.#names;
51+
}
52+
53+
get peerNames () {
54+
return this.#peerNames;
55+
}
56+
57+
get preReleaseNames () {
58+
return this.#preReleaseNames;
59+
}
60+
61+
// public
62+
has ( name ) {
63+
return this.#names.has( name );
64+
}
65+
66+
fix () {
67+
const json = JSON.stringify( this.config );
68+
69+
for ( const dependency of this.#dependencies ) {
70+
if ( !this.config[ dependency.type ]?.[ dependency.name ] ) continue;
71+
72+
if ( !dependency.range ) continue;
73+
74+
if ( dependency.versionType === "git" ) {
75+
this.config[ dependency.type ][ dependency.name ] = this.config[ dependency.type ][ dependency.name ].replace( /#semver:(.+)$/, "#semver:" + dependency.range.range );
76+
}
77+
else {
78+
this.config[ dependency.type ][ dependency.name ] = dependency.range.range;
79+
}
80+
}
81+
82+
return json !== JSON.stringify( this.config );
83+
}
84+
85+
[ Symbol.iterator ] () {
86+
return this.#dependencies.values();
87+
}
88+
89+
// private
90+
#parse () {
91+
for ( const type in DEPENDENCIES ) {
92+
if ( !this.config[ type ] ) continue;
93+
94+
for ( const [ name, version ] of Object.entries( this.config[ type ] ) ) {
95+
this.#names.add( name );
96+
97+
const data = {
98+
name,
99+
version,
100+
type,
101+
"isDevelopment": DEPENDENCIES[ type ].dev,
102+
"isPeer": DEPENDENCIES[ type ].peer,
103+
"commit": DEPENDENCIES[ type ].commit,
104+
"versionType": null, // file, tarball, git, version
105+
"range": null,
106+
"isPreRelease": null,
107+
};
108+
109+
if ( data.isPeer ) this.#peerNames.add( name );
110+
111+
const res = this.#parseVersion( version );
112+
data.versionType = res.versionType;
113+
data.range = res.range;
114+
data.isPreRelease = res.isPreRelease;
115+
116+
this.#dependencies.push( data );
117+
}
118+
}
119+
}
120+
121+
#parseVersion ( version ) {
122+
const data = {
123+
"versionType": null,
124+
"range": null,
125+
"isPreRelease": false,
126+
};
127+
128+
var range;
129+
130+
if ( !version ) {
131+
range = "*";
132+
}
133+
134+
// tag
135+
else if ( TAGS.has( version ) ) {
136+
data.versionType = "tag";
137+
}
138+
139+
// file: url
140+
else if ( version.startsWith( "file:" ) ) {
141+
data.versionType = "file";
142+
}
143+
144+
// "/"
145+
else if ( version.includes( "/" ) ) {
146+
147+
// file path
148+
if ( version.startsWith( "/" ) || version.startsWith( "./" ) || version.startsWith( "../" ) || version.startsWith( "~/" ) ) {
149+
data.versionType = "file";
150+
}
151+
152+
// url
153+
else {
154+
try {
155+
const url = new URL( version, "git+ssh://git@github.com/" );
156+
157+
// git url
158+
if ( url.protocol.startsWith( "git" ) ) {
159+
data.versionType = "git";
160+
161+
// parse #semver=
162+
const match = url.hash.match( /#semver:(.+)$/ );
163+
164+
if ( match ) {
165+
range = match[ 1 ];
166+
}
167+
}
168+
else {
169+
data.versionType = "url";
170+
}
171+
}
172+
catch {}
173+
}
174+
}
175+
else {
176+
range = version;
177+
}
178+
179+
if ( range ) {
180+
try {
181+
data.range = new SemanticVersionRange( range );
182+
}
183+
catch {}
184+
}
185+
186+
// detect pre-release
187+
if ( data.versionType === "tag" && version === "next" ) {
188+
data.isPreRelease = true;
189+
}
190+
else if ( data.range?.hasPreReleaseDependencies ) {
191+
data.isPreRelease = true;
192+
}
193+
194+
return data;
195+
}
196+
}

0 commit comments

Comments
 (0)