Skip to content

Commit 85f0a60

Browse files
authored
fix: Limit how many projects are considered for version check validation on generate (serverpod#3273)
1 parent 1f8f153 commit 85f0a60

File tree

15 files changed

+598
-367
lines changed

15 files changed

+598
-367
lines changed

templates/pubspecs/tools/serverpod_cli/pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ dependencies:
4141

4242
dev_dependencies:
4343
serverpod_lints: SERVERPOD_VERSION
44+
term_glyph: ^1.2.2
4445
test: ^1.24.2
4546

4647
dependency_overrides:

tools/serverpod_cli/lib/src/commands/generate.dart

+17-3
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ import 'dart:io';
33
import 'package:cli_tools/cli_tools.dart';
44
import 'package:path/path.dart' as path;
55
import 'package:pub_semver/pub_semver.dart';
6-
76
import 'package:serverpod_cli/analyzer.dart';
87
import 'package:serverpod_cli/src/analyzer/models/stateful_analyzer.dart';
98
import 'package:serverpod_cli/src/generated/version.dart';
@@ -12,6 +11,7 @@ import 'package:serverpod_cli/src/generator/generator_continuous.dart';
1211
import 'package:serverpod_cli/src/runner/serverpod_command.dart';
1312
import 'package:serverpod_cli/src/serverpod_packages_version_check/serverpod_packages_version_check.dart';
1413
import 'package:serverpod_cli/src/util/model_helper.dart';
14+
import 'package:serverpod_cli/src/util/pubspec_plus.dart';
1515
import 'package:serverpod_cli/src/util/serverpod_cli_logger.dart';
1616

1717
class GenerateCommand extends ServerpodCommand {
@@ -44,9 +44,23 @@ class GenerateCommand extends ServerpodCommand {
4444
throw ExitException(ServerpodCommand.commandInvokedCannotExecute);
4545
}
4646

47+
// Directory.current is the server directory
48+
var serverPubspecFile = File('pubspec.yaml');
49+
var clientPubspecFile = File(path.joinAll([
50+
...config.clientPackagePathParts,
51+
'pubspec.yaml',
52+
]));
53+
var pubspecsToCheck = [
54+
serverPubspecFile,
55+
if (await clientPubspecFile.exists()) clientPubspecFile,
56+
].map(PubspecPlus.fromFile);
57+
4758
// Validate cli version is compatible with serverpod packages
48-
var warnings = performServerpodPackagesAndCliVersionCheck(
49-
Version.parse(templateVersion), Directory.current.parent);
59+
var cliVersion = Version.parse(templateVersion);
60+
var warnings = [
61+
for (var p in pubspecsToCheck)
62+
...validateServerpodPackagesVersion(cliVersion, p)
63+
];
5064
if (warnings.isNotEmpty) {
5165
log.warning(
5266
'The version of the CLI may be incompatible with the Serverpod '

tools/serverpod_cli/lib/src/config/config.dart

+5-7
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import 'package:serverpod_cli/src/util/directory.dart';
99
import 'package:serverpod_cli/src/util/locate_modules.dart';
1010
import 'package:serverpod_cli/src/util/pubspec_helpers.dart';
1111
import 'package:serverpod_cli/src/util/serverpod_cli_logger.dart';
12+
import 'package:serverpod_cli/src/util/yaml_util.dart';
1213
import 'package:source_span/source_span.dart';
1314
import 'package:yaml/yaml.dart';
1415

@@ -259,13 +260,10 @@ class GeneratorConfig implements ModelLoadConfig {
259260
var name = _stripPackage(serverPackage);
260261

261262
var file = File(p.join(serverRootDir, 'config', 'generator.yaml'));
262-
Map generatorConfig = {};
263-
try {
264-
var yamlStr = file.readAsStringSync();
265-
generatorConfig = loadYaml(yamlStr);
266-
} catch (_) {}
267-
268-
PackageType type = getPackageType(generatorConfig);
263+
YamlMap generatorConfig = await file.exists()
264+
? loadYamlMap(await file.readAsString(), sourceUrl: file.uri)
265+
: YamlMap();
266+
var type = getPackageType(generatorConfig);
269267

270268
var relativeDartClientPackagePathParts = ['..', '${name}_client'];
271269

tools/serverpod_cli/lib/src/serverpod_packages_version_check/serverpod_packages_version_check.dart

+25-99
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-
import 'dart:io';
2-
31
import 'package:pub_semver/pub_semver.dart';
4-
import 'package:pubspec_parse/pubspec_parse.dart';
52
import 'package:serverpod_cli/src/analyzer/code_analysis_collector.dart';
6-
import 'package:serverpod_cli/src/util/pubspec_helpers.dart';
7-
import 'package:yaml/yaml.dart';
3+
import 'package:serverpod_cli/src/util/pubspec_plus.dart';
84

5+
/// Warnings for when the package version does not match the version of the
6+
/// Serverpod's CLI.
97
class ServerpodPackagesVersionCheckWarnings {
108
static const incompatibleVersion =
119
'The package version does not match the version of the Serverpod\'s '
@@ -17,112 +15,40 @@ class ServerpodPackagesVersionCheckWarnings {
1715
'the Serverpod packages.';
1816
}
1917

20-
List<SourceSpanSeverityException> performServerpodPackagesAndCliVersionCheck(
21-
Version cliVersion,
22-
Directory directory,
23-
) {
24-
List<SourceSpanSeverityException> accumulatedWarnings = [];
25-
26-
var pubspecFiles = findPubspecsFiles(
27-
directory,
28-
ignorePaths: ['vendor', 'serverpod'],
29-
);
30-
if (pubspecFiles.isEmpty) {
31-
return accumulatedWarnings;
32-
}
33-
34-
for (var pubspecFile in pubspecFiles) {
35-
try {
36-
var file = pubspecFile.readAsStringSync();
37-
var pubspec = Pubspec.parse(file);
38-
var yaml = loadYaml(file, sourceUrl: pubspecFile.uri) as YamlMap;
39-
var warnings =
40-
_validateServerpodPackagesVersion(cliVersion, pubspec, yaml);
41-
42-
accumulatedWarnings.addAll(warnings);
43-
} catch (e) {
44-
// TODO: Log to debug output
45-
// Failed to open or parse pubspec file
46-
}
47-
}
48-
49-
return accumulatedWarnings;
18+
extension on PubspecPlus {
19+
Iterable<HostedDep> get serverpodDeps =>
20+
deps.whereType<HostedDep>().where((d) => d.name.startsWith('serverpod'));
5021
}
5122

52-
List<SourceSpanSeverityException> _validateServerpodPackagesVersion(
23+
List<SourceSpanSeverityException> validateServerpodPackagesVersion(
5324
Version version,
54-
Pubspec pubspec,
55-
YamlMap yamlPubspec,
25+
PubspecPlus pubspecPlus,
5626
) {
57-
List<SourceSpanSeverityException> warnings = [];
58-
59-
var serverpodDependencies =
60-
_getHostedServerpodDependencies(pubspec.dependencies);
61-
if (serverpodDependencies.isNotEmpty) {
62-
warnings.addAll(_validatePackageCompatibilities(
63-
serverpodDependencies, version, yamlPubspec['dependencies']));
64-
}
65-
66-
var serverpodDevDependencies =
67-
_getHostedServerpodDependencies(pubspec.devDependencies);
68-
if (serverpodDevDependencies.isNotEmpty) {
69-
warnings.addAll(_validatePackageCompatibilities(
70-
serverpodDependencies, version, yamlPubspec['devDependencies']));
71-
}
72-
73-
return warnings;
74-
}
75-
76-
List<MapEntry<String, HostedDependency>> _getHostedServerpodDependencies(
77-
Map<String, Dependency> dependencies,
78-
) {
79-
return dependencies.entries.fold([], (hostedDependencies, dependency) {
80-
if (!dependency.key.startsWith('serverpod') ||
81-
dependency.value is! HostedDependency) {
82-
return hostedDependencies;
83-
}
84-
85-
// Cast dependencies to HostedDependency type
86-
return [
87-
...hostedDependencies,
88-
MapEntry(dependency.key, dependency.value as HostedDependency)
89-
];
90-
});
27+
return [
28+
for (var dep in pubspecPlus.serverpodDeps)
29+
..._validatePackageCompatibilities(dep, version)
30+
];
9131
}
9232

9333
List<SourceSpanSeverityException> _validatePackageCompatibilities(
94-
List<MapEntry<String, HostedDependency>> serverpodPackages,
34+
HostedDep serverpodDep,
9535
Version cliVersion,
96-
YamlMap packagesYaml,
9736
) {
9837
List<SourceSpanSeverityException> packageWarnings = [];
9938

100-
for (var element in serverpodPackages) {
101-
var packageName = element.key;
102-
var package = element.value;
103-
var packageYamlNode = packagesYaml.nodes[packageName];
104-
105-
if (packageYamlNode == null) {
106-
throw SourceSpanSeverityException(
107-
'Could not find package "$packageName" in pubspec file.',
108-
packagesYaml.span,
109-
severity: SourceSpanSeverity.warning);
110-
}
111-
112-
var packageVersion = package.version;
113-
if (!packageVersion.allowsAny(cliVersion)) {
114-
packageWarnings.add(SourceSpanSeverityException(
115-
ServerpodPackagesVersionCheckWarnings.incompatibleVersion,
116-
packageYamlNode.span,
117-
severity: SourceSpanSeverity.warning));
118-
}
39+
var span = serverpodDep.span;
40+
var version = serverpodDep.dependency.version;
41+
if (!version.allowsAny(cliVersion)) {
42+
packageWarnings.add(SourceSpanSeverityException(
43+
ServerpodPackagesVersionCheckWarnings.incompatibleVersion, span,
44+
severity: SourceSpanSeverity.warning));
45+
}
11946

120-
if (packageVersion is! Version) {
121-
packageWarnings.add(SourceSpanSeverityException(
122-
ServerpodPackagesVersionCheckWarnings.approximateVersion(cliVersion),
123-
packageYamlNode.span,
124-
severity: SourceSpanSeverity.warning));
125-
}
47+
if (version is! Version) {
48+
packageWarnings.add(SourceSpanSeverityException(
49+
ServerpodPackagesVersionCheckWarnings.approximateVersion(cliVersion),
50+
span,
51+
severity: SourceSpanSeverity.warning));
12652
}
12753

12854
return packageWarnings;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,91 @@
1+
import 'dart:io';
2+
import 'package:pubspec_parse/pubspec_parse.dart';
3+
4+
import 'package:source_span/source_span.dart';
5+
import 'package:yaml/yaml.dart';
6+
7+
import 'yaml_util.dart';
8+
9+
/// A pubspec plus the yaml map it was read from, and a list of all augmented
10+
/// dependencies regardless of kind.
11+
class PubspecPlus {
12+
final Pubspec pubspec;
13+
final YamlMap yaml;
14+
15+
PubspecPlus.fromFile(File file)
16+
: this.parse(file.readAsStringSync(), sourceUrl: file.uri);
17+
18+
PubspecPlus.parse(String yamlString, {Uri? sourceUrl})
19+
: pubspec = Pubspec.parse(yamlString, sourceUrl: sourceUrl),
20+
yaml = loadYamlMap(yamlString, sourceUrl: sourceUrl);
21+
22+
/// All dependencies in the pubspec, regardless of type. Augmented with name,
23+
/// kind, and span.
24+
// Only calculate once, but no need to calculate unless needed
25+
late List<Dep> deps = _iteratedDeps().toList();
26+
27+
Iterable<Dep> _iteratedDeps() sync* {
28+
for (var x in [
29+
(
30+
deps: pubspec.dependencies,
31+
type: DepKind.normal,
32+
yaml: yaml['dependencies'] as YamlMap?
33+
),
34+
(
35+
deps: pubspec.devDependencies,
36+
type: DepKind.dev,
37+
yaml: yaml['dev_dependencies'] as YamlMap?
38+
),
39+
(
40+
deps: pubspec.dependencyOverrides,
41+
type: DepKind.override,
42+
yaml: yaml['dependency_overrides'] as YamlMap?
43+
),
44+
]) {
45+
for (var e in x.deps.entries) {
46+
var span = x.yaml?.nodes[e.key]?.span ??
47+
(throw StateError('Missing span for dependency ${e.key}'));
48+
yield Dep(e.key, e.value, x.type, span);
49+
}
50+
}
51+
}
52+
}
53+
54+
/// A dependency can be either normal dependency, a dev dependency, or a
55+
/// dependency override.
56+
enum DepKind { normal, dev, override }
57+
58+
/// A container for a dependency that augments the dependency with its own name,
59+
/// kind, and span in the associated pubspec.
60+
class Dep<T extends Dependency> {
61+
final String name;
62+
final T dependency;
63+
final DepKind kind;
64+
final SourceSpan span;
65+
66+
const Dep._(this.name, this.dependency, this.kind, this.span);
67+
68+
factory Dep(
69+
String name,
70+
Dependency dependency,
71+
DepKind kind,
72+
SourceSpan span,
73+
) {
74+
// trick to get the correct wrapped static type runtime
75+
return switch (dependency) {
76+
(HostedDependency dep) => Dep<HostedDependency>._(name, dep, kind, span),
77+
(PathDependency dep) => Dep<PathDependency>._(name, dep, kind, span),
78+
(GitDependency dep) => Dep<GitDependency>._(name, dep, kind, span),
79+
(SdkDependency dep) => Dep<SdkDependency>._(name, dep, kind, span),
80+
// Prior to pubspec_parse 1.4.0 the Dependency class was not sealed.
81+
// ignore: unreachable_switch_case
82+
_ => throw StateError('Unknown dependency type: $dependency'),
83+
} as Dep<T>;
84+
}
85+
}
86+
87+
// for convenience
88+
typedef HostedDep = Dep<HostedDependency>;
89+
typedef PathDep = Dep<PathDependency>;
90+
typedef GitDep = Dep<GitDependency>;
91+
typedef SdkDep = Dep<SdkDependency>;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
import 'package:source_span/source_span.dart';
2+
import 'package:yaml/yaml.dart';
3+
4+
YamlMap loadYamlMap(String yamlString, {Uri? sourceUrl}) {
5+
var node = loadYamlNode(yamlString, sourceUrl: sourceUrl);
6+
if (node is YamlMap) return node;
7+
if (node is YamlScalar) {
8+
var value = node.value;
9+
// A empty or whitespace only string will parse as YamlScalar,
10+
// but is also a valid map
11+
if (value == null || (value is String && value.trim().isEmpty)) {
12+
return YamlMap();
13+
}
14+
}
15+
throw SourceSpanException('Expected a map', node.span);
16+
}

tools/serverpod_cli/pubspec.yaml

+1
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ dependencies:
4242

4343
dev_dependencies:
4444
serverpod_lints: 2.4.0
45+
term_glyph: ^1.2.2
4546
test: ^1.24.2
4647

4748
dependency_overrides:

0 commit comments

Comments
 (0)