Skip to content

Commit b1303e1

Browse files
committed
feat(groups): allow many deps to be treated as one
Closes #204
1 parent 99dee8c commit b1303e1

File tree

14 files changed

+176
-85
lines changed

14 files changed

+176
-85
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+
"aliases": [
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: 45 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
export interface RcFile {
22
/** @see https://jamiemason.github.io/syncpack/integrations/json-schema */
33
$schema?: string;
4+
/** @see https://jamiemason.github.io/syncpack/config/aliases */
5+
aliases?: AliasGroup[];
46
/** @see https://jamiemason.github.io/syncpack/config/custom-types */
57
customTypes?: Record<string, CustomType.Any>;
68
/** @see https://jamiemason.github.io/syncpack/config/format-bugs */
@@ -38,6 +40,19 @@ export interface RcFile {
3840
specifierTypes?: never;
3941
}
4042

43+
export interface AliasGroup {
44+
/** @see https://jamiemason.github.io/syncpack/config/aliases/#dependencies */
45+
dependencies?: string[];
46+
/** @see https://jamiemason.github.io/syncpack/config/aliases/#dependencytypes */
47+
dependencyTypes?: DependencyType[];
48+
/** @see https://jamiemason.github.io/syncpack/config/aliases/#label */
49+
label?: string;
50+
/** @see https://jamiemason.github.io/syncpack/config/aliases/#packages */
51+
packages?: string[];
52+
/** @see https://jamiemason.github.io/syncpack/config/aliases/#specifiertypes */
53+
specifierTypes?: SpecifierType[];
54+
}
55+
4156
export interface DependencyGroup {
4257
/** @see https://jamiemason.github.io/syncpack/config/version-groups/standard/#dependencies */
4358
dependencies?: string[];
@@ -82,11 +97,11 @@ namespace VersionGroup {
8297
}
8398
export interface SameRange extends DependencyGroup {
8499
/** @see https://jamiemason.github.io/syncpack/config/version-groups/same-range/#policy */
85-
policy: "sameRange";
100+
policy: 'sameRange';
86101
}
87102
export interface Standard extends DependencyGroup {
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 aliases = config.rcfile.get_aliases(&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(alias) = aliases.iter().find(|alias| alias.can_add(&instance)) {
39+
instance.set_alias(&alias.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+
}

src/instance.rs

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ pub struct Instance {
3636
pub matches_cli_filter: RefCell<bool>,
3737
/// The dependency name eg. "react", "react-dom"
3838
pub name: String,
39+
/// When an alias is used, that is used here in place of the actual name, eg.
40+
/// "@aws-sdk/core" -> "all-aws-sdk-packages". Otherwise the actual name is
41+
/// used, this aliased name is only used when allocating `Instance`s to
42+
/// `Dependency`s, the original name is otherwise preserved.
43+
pub name_internal: RefCell<String>,
3944
/// The `.name` of the package.json this file is in
4045
pub package: Rc<RefCell<PackageJson>>,
4146
/// If this instance belongs to a `WithRange` semver group, this is the range.
@@ -65,7 +70,8 @@ impl Instance {
6570
id: format!("{} in {} of {}", name, &dependency_type.path, package_name),
6671
is_local: dependency_type.path == "/version",
6772
matches_cli_filter: RefCell::new(false),
68-
name,
73+
name: name.clone(),
74+
name_internal: RefCell::new(name.clone()),
6975
package: Rc::clone(&package),
7076
preferred_semver_range: RefCell::new(None),
7177
state: RefCell::new(InstanceState::Unknown),
@@ -109,6 +115,11 @@ impl Instance {
109115
self.set_state(InstanceState::Invalid(InvalidInstance::Unfixable(state)), &self.actual_specifier)
110116
}
111117

118+
/// If this instance should use an alias, store it
119+
pub fn set_alias(&self, alias: &str) {
120+
*self.name_internal.borrow_mut() = alias.to_string();
121+
}
122+
112123
/// If this instance should use a preferred semver range, store it
113124
pub fn set_semver_group(&self, group: &SemverGroup) {
114125
if let Some(range) = &group.range {

0 commit comments

Comments
 (0)