Skip to content

Commit fccf805

Browse files
committed
feat(groups): allow many deps to be treated as one
Closes #204
1 parent 4cf7da5 commit fccf805

File tree

14 files changed

+192
-94
lines changed

14 files changed

+192
-94
lines changed

fixtures/fluid-framework/.syncpackrc.json

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,12 @@
44
"engines": { "path": "engines", "strategy": "versionsByName" },
55
"packageManager": { "path": "packageManager", "strategy": "name@version" }
66
},
7+
"dependencyGroups": [
8+
{
9+
"label": "prosemirror-*",
10+
"dependencies": ["prosemirror-*"]
11+
}
12+
],
713
"semverGroups": [
814
{
915
"label": "Version compatibility workarounds should be used, or removed from syncpack.config.cjs if no longer needed.",

npm/syncpack.ts

Lines changed: 54 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ export interface RcFile {
33
$schema?: string;
44
/** @see https://jamiemason.github.io/syncpack/config/custom-types */
55
customTypes?: Record<string, CustomType.Any>;
6+
/** @see https://jamiemason.github.io/syncpack/config/dependency-groups */
7+
dependencyGroups?: DependencyGroup[];
68
/** @see https://jamiemason.github.io/syncpack/config/format-bugs */
79
formatBugs?: boolean;
810
/** @see https://jamiemason.github.io/syncpack/config/format-repository */
@@ -38,7 +40,7 @@ export interface RcFile {
3840
specifierTypes?: never;
3941
}
4042

41-
export interface DependencyGroup {
43+
export interface GroupSelector {
4244
/** @see https://jamiemason.github.io/syncpack/config/version-groups/standard/#dependencies */
4345
dependencies?: string[];
4446
/** @see https://jamiemason.github.io/syncpack/config/version-groups/standard/#dependencytypes */
@@ -51,42 +53,55 @@ export interface DependencyGroup {
5153
specifierTypes?: SpecifierType[];
5254
}
5355

56+
export interface DependencyGroup {
57+
/** @see https://jamiemason.github.io/syncpack/config/dependency-groups/#aliasname */
58+
aliasName: string;
59+
/** @see https://jamiemason.github.io/syncpack/config/dependency-groups/#dependencies */
60+
dependencies?: string[];
61+
/** @see https://jamiemason.github.io/syncpack/config/dependency-groups/#dependencytypes */
62+
dependencyTypes?: DependencyType[];
63+
/** @see https://jamiemason.github.io/syncpack/config/dependency-groups/#packages */
64+
packages?: string[];
65+
/** @see https://jamiemason.github.io/syncpack/config/dependency-groups/#specifiertypes */
66+
specifierTypes?: SpecifierType[];
67+
}
68+
5469
namespace SemverGroup {
55-
export interface Ignored extends DependencyGroup {
70+
export interface Ignored extends GroupSelector {
5671
/** @see https://jamiemason.github.io/syncpack/config/semver-groups/ignored/#isignored */
5772
isIgnored: true;
5873
}
59-
export interface WithRange extends DependencyGroup {
74+
export interface WithRange extends GroupSelector {
6075
/** @see https://jamiemason.github.io/syncpack/config/semver-groups/with-range/#range */
6176
range: SemverRange;
6277
}
6378
export type Any = Ignored | WithRange;
6479
}
6580

6681
namespace VersionGroup {
67-
export interface Banned extends DependencyGroup {
82+
export interface Banned extends GroupSelector {
6883
/** @see https://jamiemason.github.io/syncpack/config/version-groups/banned/#isbanned */
6984
isBanned: true;
7085
}
71-
export interface Ignored extends DependencyGroup {
86+
export interface Ignored extends GroupSelector {
7287
/** @see https://jamiemason.github.io/syncpack/config/version-groups/ignored/#isignored */
7388
isIgnored: true;
7489
}
75-
export interface Pinned extends DependencyGroup {
90+
export interface Pinned extends GroupSelector {
7691
/** @see https://jamiemason.github.io/syncpack/config/version-groups/pinned/#pinversion */
7792
pinVersion: string;
7893
}
79-
export interface SnappedTo extends DependencyGroup {
94+
export interface SnappedTo extends GroupSelector {
8095
/** @see https://jamiemason.github.io/syncpack/config/version-groups/snapped-to/#snapto */
8196
snapTo: string[];
8297
}
83-
export interface SameRange extends DependencyGroup {
98+
export interface SameRange extends GroupSelector {
8499
/** @see https://jamiemason.github.io/syncpack/config/version-groups/same-range/#policy */
85-
policy: "sameRange";
100+
policy: 'sameRange';
86101
}
87-
export interface Standard extends DependencyGroup {
102+
export interface Standard extends GroupSelector {
88103
/** @see https://jamiemason.github.io/syncpack/config/version-groups/lowest-version/#preferversion */
89-
preferVersion?: "highestSemver" | "lowestSemver";
104+
preferVersion?: 'highestSemver' | 'lowestSemver';
90105
}
91106
export type Any =
92107
| Banned
@@ -104,25 +119,25 @@ namespace CustomType {
104119
/** @see https://jamiemason.github.io/syncpack/config/custom-types/#name */
105120
path: string;
106121
/** @see https://jamiemason.github.io/syncpack/config/custom-types/#namestrategy */
107-
strategy: "name~version";
122+
strategy: 'name~version';
108123
}
109124
export interface NamedVersionString {
110125
/** @see https://jamiemason.github.io/syncpack/config/custom-types/#name */
111126
path: string;
112127
/** @see https://jamiemason.github.io/syncpack/config/custom-types/#namestrategy */
113-
strategy: "name@version";
128+
strategy: 'name@version';
114129
}
115130
export interface UnnamedVersionString {
116131
/** @see https://jamiemason.github.io/syncpack/config/custom-types/#name */
117132
path: string;
118133
/** @see https://jamiemason.github.io/syncpack/config/custom-types/#namestrategy */
119-
strategy: "version";
134+
strategy: 'version';
120135
}
121136
export interface VersionsByName {
122137
/** @see https://jamiemason.github.io/syncpack/config/custom-types/#name */
123138
path: string;
124139
/** @see https://jamiemason.github.io/syncpack/config/custom-types/#namestrategy */
125-
strategy: "versionsByName";
140+
strategy: 'versionsByName';
126141
}
127142
export type Any =
128143
| NameAndVersionProps
@@ -131,35 +146,35 @@ namespace CustomType {
131146
| VersionsByName;
132147
}
133148

134-
type SemverRange = "" | "*" | ">" | ">=" | ".x" | "<" | "<=" | "^" | "~";
149+
type SemverRange = '' | '*' | '>' | '>=' | '.x' | '<' | '<=' | '^' | '~';
135150

136151
type DependencyType =
137-
| "dev"
138-
| "local"
139-
| "overrides"
140-
| "peer"
141-
| "pnpmOverrides"
142-
| "prod"
143-
| "resolutions"
152+
| 'dev'
153+
| 'local'
154+
| 'overrides'
155+
| 'peer'
156+
| 'pnpmOverrides'
157+
| 'prod'
158+
| 'resolutions'
144159
| AnyString;
145160

146161
type SpecifierType =
147-
| "alias"
148-
| "exact"
149-
| "file"
150-
| "git"
151-
| "latest"
152-
| "major"
153-
| "minor"
154-
| "missing"
155-
| "range"
156-
| "range-complex"
157-
| "range-major"
158-
| "range-minor"
159-
| "tag"
160-
| "unsupported"
161-
| "url"
162-
| "workspace-protocol"
162+
| 'alias'
163+
| 'exact'
164+
| 'file'
165+
| 'git'
166+
| 'latest'
167+
| 'major'
168+
| 'minor'
169+
| 'missing'
170+
| 'range'
171+
| 'range-complex'
172+
| 'range-major'
173+
| 'range-minor'
174+
| 'tag'
175+
| 'unsupported'
176+
| 'url'
177+
| 'workspace-protocol'
163178
| AnyString;
164179

165180
type AnyString = string & {};

src/cli.rs

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use {
2-
crate::group_selector::GroupSelector,
2+
crate::{group_selector::GroupSelector, packages::Packages},
33
clap::{builder::ValueParser, crate_description, crate_name, crate_version, Arg, ArgMatches, Command},
44
color_print::cformat,
55
itertools::Itertools,
@@ -305,12 +305,20 @@ fn get_filters(matches: &ArgMatches) -> Option<GroupSelector> {
305305
let dependencies = get_patterns(matches, "dependencies");
306306
let dependency_types = get_patterns(matches, "dependency-types");
307307
let label = "CLI filters".to_string();
308+
let all_packages = &Packages::new();
308309
let packages = get_patterns(matches, "packages");
309310
let specifier_types = get_patterns(matches, "specifier-types");
310311
if dependencies.is_empty() && dependency_types.is_empty() && packages.is_empty() && specifier_types.is_empty() {
311312
None
312313
} else {
313-
Some(GroupSelector::new(dependencies, dependency_types, label, packages, specifier_types))
314+
Some(GroupSelector::new(
315+
all_packages,
316+
dependencies,
317+
dependency_types,
318+
label,
319+
packages,
320+
specifier_types,
321+
))
314322
}
315323
}
316324

src/context.rs

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,12 +28,16 @@ pub struct Context {
2828
impl Context {
2929
pub fn create(config: Config, packages: Packages) -> Self {
3030
let mut instances = vec![];
31-
let semver_groups = config.rcfile.get_semver_groups();
31+
let dependency_groups = config.rcfile.get_dependency_groups(&packages);
32+
let semver_groups = config.rcfile.get_semver_groups(&packages);
3233
let version_groups = config.rcfile.get_version_groups(&packages);
3334

3435
packages.get_all_instances(&config, |instance| {
3536
let instance = Rc::new(instance);
3637
instances.push(Rc::clone(&instance));
38+
if let Some(dependency_group) = dependency_groups.iter().find(|alias| alias.can_add(&instance)) {
39+
instance.set_internal_name(&dependency_group.label);
40+
}
3741
if let Some(semver_group) = semver_groups.iter().find(|semver_group| semver_group.selector.can_add(&instance)) {
3842
instance.set_semver_group(semver_group);
3943
}

src/dependency.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ pub struct Dependency {
2828
/// Does every instance match the filter options provided via the CLI?
2929
pub matches_cli_filter: RefCell<bool>,
3030
/// The name of the dependency
31-
pub name: String,
31+
pub name_internal: String,
3232
/// The version to pin all instances to when variant is `Pinned`
3333
pub pinned_specifier: Option<Specifier>,
3434
/// package.json files developed in the monorepo when variant is `SnappedTo`
@@ -39,7 +39,7 @@ pub struct Dependency {
3939

4040
impl Dependency {
4141
pub fn new(
42-
name: String,
42+
name_internal: String,
4343
variant: VersionGroupVariant,
4444
pinned_specifier: Option<Specifier>,
4545
snapped_to_packages: Option<Vec<Rc<RefCell<PackageJson>>>>,
@@ -49,7 +49,7 @@ impl Dependency {
4949
instances: RefCell::new(vec![]),
5050
local_instance: RefCell::new(None),
5151
matches_cli_filter: RefCell::new(false),
52-
name,
52+
name_internal,
5353
pinned_specifier,
5454
snapped_to_packages,
5555
variant,
@@ -175,7 +175,7 @@ impl Dependency {
175175
pub fn get_snapped_to_specifier(&self, every_instance_in_the_project: &[Rc<Instance>]) -> Option<Specifier> {
176176
if let Some(snapped_to_packages) = &self.snapped_to_packages {
177177
for instance in every_instance_in_the_project {
178-
if instance.name == *self.name {
178+
if *instance.name_internal.borrow() == *self.name_internal {
179179
for snapped_to_package in snapped_to_packages {
180180
if instance.package.borrow().get_name_unsafe() == snapped_to_package.borrow().get_name_unsafe() {
181181
return Some(instance.actual_specifier.clone());

src/effects/fix.rs

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ pub fn run(ctx: Context) -> Context {
2424
let mut suspect = 0;
2525

2626
ctx.instances.iter().for_each(|instance| {
27-
let name = &instance.name;
27+
let name_internal = &instance.name_internal.borrow();
2828

2929
if has_cli_filter && !*instance.matches_cli_filter.borrow() {
3030
return;
@@ -50,14 +50,14 @@ pub fn run(ctx: Context) -> Context {
5050
let actual = instance.actual_specifier.unwrap().red();
5151
let arrow = ui.dim_right_arrow();
5252
let expected = instance.expected_specifier.borrow().as_ref().unwrap().unwrap().green();
53-
info!("{name} {actual} {arrow} {expected} {location} {state_link}");
53+
info!("{name_internal} {actual} {arrow} {expected} {location} {state_link}");
5454
instance.package.borrow().copy_expected_specifier(instance);
5555
}
5656
}
5757
}
5858
InvalidInstance::Conflict(_) | InvalidInstance::Unfixable(_) => {
5959
unfixable += 1;
60-
warn!("Unfixable: {name} {location} {state_link}");
60+
warn!("Unfixable: {name_internal} {location} {state_link}");
6161
}
6262
},
6363
InstanceState::Suspect(variant) => match variant {
@@ -66,7 +66,7 @@ pub fn run(ctx: Context) -> Context {
6666
| SuspectInstance::RefuseToSnapLocal
6767
| SuspectInstance::InvalidLocalVersion => {
6868
suspect += 1;
69-
warn!("Suspect: {name} {location} {state_link}");
69+
warn!("Suspect: {name_internal} {location} {state_link}");
7070
}
7171
},
7272
}

src/effects/ui.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ impl Ui<'_> {
6868
};
6969
let instances_len = dependency.instances.borrow().len();
7070
let count = self.count_column(instances_len);
71-
let name = &dependency.name;
71+
let name = &dependency.name_internal;
7272
let name = if self.ctx.config.cli.show_hints && dependency.local_instance.borrow().is_some() {
7373
let local_hint = "(local)".blue();
7474
format!("{name} {local_hint}").normal()

src/group_selector.rs

Lines changed: 33 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use {
2-
crate::instance::Instance,
2+
crate::{instance::Instance, packages::Packages},
33
globset::{Glob, GlobMatcher},
44
};
55

@@ -49,12 +49,14 @@ pub struct GroupSelector {
4949

5050
impl GroupSelector {
5151
pub fn new(
52+
all_packages: &Packages,
5253
dependencies: Vec<String>,
5354
dependency_types: Vec<String>,
5455
label: String,
5556
packages: Vec<String>,
5657
specifier_types: Vec<String>,
5758
) -> GroupSelector {
59+
let dependencies = with_resolved_keywords(&dependencies, all_packages);
5860
GroupSelector {
5961
include_dependencies: create_globs(true, &dependencies),
6062
exclude_dependencies: create_globs(false, &dependencies),
@@ -92,7 +94,11 @@ impl GroupSelector {
9294
}
9395

9496
pub fn matches_dependencies(&self, instance: &Instance) -> bool {
95-
matches_globs(&instance.name, &self.include_dependencies, &self.exclude_dependencies)
97+
matches_globs(
98+
&instance.name_internal.borrow(),
99+
&self.include_dependencies,
100+
&self.exclude_dependencies,
101+
)
96102
}
97103

98104
pub fn matches_specifier_types(&self, instance: &Instance) -> bool {
@@ -144,3 +150,28 @@ fn matches_identifiers(name: &str, includes: &[String], excludes: &[String]) ->
144150
fn matches_any_identifier(value: &str, identifiers: &[String]) -> bool {
145151
identifiers.contains(&value.to_string())
146152
}
153+
154+
/// Resolve keywords such as `$LOCAL` and `!$LOCAL` to their actual values.
155+
fn with_resolved_keywords(dependency_names: &[String], packages: &Packages) -> Vec<String> {
156+
let mut resolved_dependencies: Vec<String> = vec![];
157+
for dependency_name in dependency_names.iter() {
158+
match dependency_name.as_str() {
159+
"$LOCAL" => {
160+
for package in packages.all.iter() {
161+
let package_name = package.borrow().get_name_unsafe();
162+
resolved_dependencies.push(package_name);
163+
}
164+
}
165+
"!$LOCAL" => {
166+
for package in packages.all.iter() {
167+
let package_name = package.borrow().get_name_unsafe();
168+
resolved_dependencies.push(format!("!{}", package_name));
169+
}
170+
}
171+
_ => {
172+
resolved_dependencies.push(dependency_name.clone());
173+
}
174+
}
175+
}
176+
resolved_dependencies
177+
}

0 commit comments

Comments
 (0)