Skip to content

Commit dbce4ac

Browse files
authored
fix(cli): Ensure all dependencies are correctly resolved (#467)
When auth or a database are added to a project for the first time, ensure that the necessary dependencies are added to the pubspec and resolvable by the analyzer before analysis.
1 parent 924f362 commit dbce4ac

File tree

7 files changed

+156
-57
lines changed

7 files changed

+156
-57
lines changed

apps/cli/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
## NEXT
22

33
- fix: Improved reload handling
4+
- fix: Ensure all dependencies are correctly resolved
45

56
## 1.0.14
67

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import 'package:analyzer/dart/analysis/results.dart';
2+
import 'package:analyzer/dart/ast/ast.dart';
3+
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
4+
import 'package:celest_cli/src/codegen/client_code_generator.dart';
5+
6+
final class CelestProjectParser {
7+
CelestProjectParser({
8+
required String projectRoot,
9+
required this.projectDart,
10+
required DriverBasedAnalysisContext analysisContext,
11+
}) : _analysisContext = analysisContext,
12+
_dependencies = CodegenDependencies(rootDir: projectRoot);
13+
14+
final String projectDart;
15+
final DriverBasedAnalysisContext _analysisContext;
16+
final CodegenDependencies _dependencies;
17+
18+
CodegenDependencies parseDependencies() {
19+
final projectDartLibrary = _analysisContext.currentSession.getParsedLibrary(
20+
projectDart,
21+
);
22+
if (projectDartLibrary is! ParsedLibraryResult) {
23+
throw StateError('Could not parse $projectDart');
24+
}
25+
26+
final topLevelVariables =
27+
projectDartLibrary.units
28+
.expand((unit) => unit.unit.declarations)
29+
.whereType<TopLevelVariableDeclaration>()
30+
.expand((declaration) => declaration.variables.variables)
31+
.toList();
32+
33+
if (_findDatabases(topLevelVariables)) {
34+
_dependencies.add('drift_hrana');
35+
}
36+
if (_findAuth(topLevelVariables)) {
37+
_dependencies.add('celest_cloud_auth');
38+
_dependencies.add('drift_hrana');
39+
}
40+
41+
return _dependencies;
42+
}
43+
44+
bool _findAuth(List<VariableDeclaration> topLevelVariables) {
45+
for (final variable in topLevelVariables) {
46+
if (variable case VariableDeclaration(
47+
initializer: MethodInvocation(
48+
methodName: SimpleIdentifier(name: 'Auth'),
49+
),
50+
)) {
51+
return true;
52+
}
53+
}
54+
return false;
55+
}
56+
57+
bool _findDatabases(List<VariableDeclaration> topLevelVariables) {
58+
for (final variable in topLevelVariables) {
59+
if (variable case VariableDeclaration(
60+
initializer: MethodInvocation(
61+
methodName: SimpleIdentifier(name: 'Database'),
62+
),
63+
)) {
64+
return true;
65+
}
66+
}
67+
return false;
68+
}
69+
}

apps/cli/lib/src/codegen/client_code_generator.dart

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,13 @@ import 'package:celest_cli/src/codegen/code_generator.dart';
77
import 'package:celest_cli/src/codegen/code_outputs.dart';
88
import 'package:celest_cli/src/context.dart';
99
import 'package:celest_cli/src/project/celest_project.dart';
10+
import 'package:celest_cli/src/pub/project_dependency.dart';
11+
import 'package:celest_cli/src/pub/pub_action.dart';
12+
import 'package:celest_cli/src/pub/pubspec.dart';
1013
import 'package:collection/collection.dart';
14+
import 'package:logging/logging.dart';
15+
import 'package:pub_semver/pub_semver.dart';
16+
import 'package:pubspec_parse/pubspec_parse.dart';
1117

1218
final class ClientCodeGenerator {
1319
ClientCodeGenerator({
@@ -55,4 +61,45 @@ final class CodegenDependencies extends DelegatingSet<String> {
5561
static CodegenDependencies get current {
5662
return Zone.current[CodegenDependencies] as CodegenDependencies;
5763
}
64+
65+
static final Logger _logger = Logger('CodegenDependencies');
66+
67+
Future<bool> save() async {
68+
var (pubspec, pubspecYaml) =
69+
p.equals(rootDir, projectPaths.projectRoot)
70+
? (celestProject.pubspec, celestProject.pubspecYaml)
71+
: (celestProject.clientPubspec, celestProject.clientPubspecYaml);
72+
final pubspecFile = fileSystem.directory(rootDir).childFile('pubspec.yaml');
73+
final currentDependencies = Set.of(pubspec.dependencies.keys);
74+
final missingDependencies = difference(currentDependencies);
75+
final needsUpdate = missingDependencies.isNotEmpty;
76+
if (needsUpdate) {
77+
_logger.fine(
78+
'Adding dependencies to ${pubspecFile.path}: '
79+
'${missingDependencies.toList()}',
80+
);
81+
pubspec = pubspec.addDeps(
82+
dependencies: {
83+
for (final dependency in this)
84+
dependency:
85+
ProjectDependency.all[dependency]?.pubDependency ??
86+
HostedDependency(version: VersionConstraint.any),
87+
},
88+
);
89+
if (pubspecFile.existsSync()) {
90+
await pubspecFile.writeAsString(pubspec.toYaml(source: pubspecYaml));
91+
await runPub(
92+
action: PubAction.get,
93+
workingDirectory: pubspecFile.parent.path,
94+
);
95+
} else {
96+
_logger.fine(
97+
'Skipping dependency update',
98+
'Pubspec not found: ${pubspecFile.path}',
99+
StackTrace.current,
100+
);
101+
}
102+
}
103+
return needsUpdate;
104+
}
58105
}

apps/cli/lib/src/codegen/code_outputs.dart

Lines changed: 1 addition & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,6 @@
11
import 'package:celest_cli/src/codegen/client_code_generator.dart';
22
import 'package:celest_cli/src/context.dart';
3-
import 'package:celest_cli/src/pub/project_dependency.dart';
4-
import 'package:celest_cli/src/pub/pub_action.dart';
5-
import 'package:celest_cli/src/pub/pubspec.dart';
63
import 'package:collection/collection.dart';
7-
import 'package:logging/logging.dart';
8-
import 'package:pub_semver/pub_semver.dart';
9-
import 'package:pubspec_parse/pubspec_parse.dart';
104

115
final class CodeOutputs extends DelegatingMap<String, String> {
126
const CodeOutputs(super.base, {required this.codegenDependencies});
@@ -17,8 +11,6 @@ final class CodeOutputs extends DelegatingMap<String, String> {
1711
/// to ensure referenced types are available.
1812
final CodegenDependencies codegenDependencies;
1913

20-
static final Logger _logger = Logger('CodeOutputs');
21-
2214
Future<void> write() async {
2315
final outputFutures = <Future<void>>[];
2416
forEach((path, library) {
@@ -32,49 +24,7 @@ final class CodeOutputs extends DelegatingMap<String, String> {
3224
);
3325
});
3426
if (codegenDependencies.isNotEmpty) {
35-
var (pubspec, pubspecYaml) =
36-
p.equals(codegenDependencies.rootDir, projectPaths.projectRoot)
37-
? (celestProject.pubspec, celestProject.pubspecYaml)
38-
: (celestProject.clientPubspec, celestProject.clientPubspecYaml);
39-
final pubspecFile = fileSystem
40-
.directory(codegenDependencies.rootDir)
41-
.childFile('pubspec.yaml');
42-
final currentDependencies = Set.of(pubspec.dependencies.keys);
43-
final missingDependencies = codegenDependencies.difference(
44-
currentDependencies,
45-
);
46-
if (missingDependencies.isNotEmpty) {
47-
_logger.fine(
48-
'Adding dependencies to ${pubspecFile.path}: '
49-
'${missingDependencies.toList()}',
50-
);
51-
pubspec = pubspec.addDeps(
52-
dependencies: {
53-
for (final dependency in codegenDependencies)
54-
dependency:
55-
ProjectDependency.all[dependency]?.pubDependency ??
56-
HostedDependency(version: VersionConstraint.any),
57-
},
58-
);
59-
if (pubspecFile.existsSync()) {
60-
outputFutures.add(
61-
pubspecFile.writeAsString(pubspec.toYaml(source: pubspecYaml)).then(
62-
(_) {
63-
return runPub(
64-
action: PubAction.get,
65-
workingDirectory: pubspecFile.parent.path,
66-
);
67-
},
68-
),
69-
);
70-
} else {
71-
_logger.fine(
72-
'Skipping dependency update',
73-
'Pubspec not found: ${pubspecFile.path}',
74-
StackTrace.current,
75-
);
76-
}
77-
}
27+
outputFutures.add(codegenDependencies.save());
7828
}
7929
await Future.wait(outputFutures);
8030
}

apps/cli/lib/src/frontend/celest_frontend.dart

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import 'package:celest_ast/celest_ast.dart' hide Sdk;
1111
import 'package:celest_cli/src/analyzer/analysis_error.dart';
1212
import 'package:celest_cli/src/analyzer/analysis_result.dart';
1313
import 'package:celest_cli/src/analyzer/celest_analyzer.dart';
14+
import 'package:celest_cli/src/analyzer/project_parser.dart';
1415
import 'package:celest_cli/src/ast/project_diff.dart';
1516
import 'package:celest_cli/src/cli/stop_signal.dart';
1617
import 'package:celest_cli/src/codegen/api/dockerfile_generator.dart';
@@ -334,6 +335,8 @@ final class CelestFrontend with CloudRepository {
334335
_logErrors(errors);
335336
}
336337

338+
await _updateDependencies();
339+
337340
final analysisResult = await _analyzeProject(
338341
migrateProject: migrateProject,
339342
);
@@ -757,6 +760,24 @@ final class CelestFrontend with CloudRepository {
757760
}
758761
}
759762

763+
/// Parses the project to look for any required analysis dependencies.
764+
Future<void> _updateDependencies() =>
765+
performance.trace('CelestFrontend', 'updateDependencies', () async {
766+
logger.fine('Parsing project...');
767+
final parser = CelestProjectParser(
768+
projectRoot: projectPaths.projectRoot,
769+
projectDart: projectPaths.projectDart,
770+
analysisContext: celestProject.analysisContext,
771+
);
772+
final dependencies = parser.parseDependencies();
773+
if (dependencies.isNotEmpty) {
774+
if (await dependencies.save()) {
775+
celestProject.invalidatePubspec();
776+
}
777+
}
778+
stopSignal.check();
779+
});
780+
760781
/// Analyzes the project and reports if there are any errors.
761782
Future<CelestAnalysisResult> _analyzeProject({
762783
required bool migrateProject,

apps/cli/lib/src/project/celest_project.dart

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ final class CelestProject {
137137
AnalysisOptions get analysisOptions => _analysisOptions;
138138

139139
final ByteStore _byteStore;
140-
late final _analysisContextCollection = AnalysisContextCollectionImpl(
140+
late var _analysisContextCollection = AnalysisContextCollectionImpl(
141141
includedPaths: [projectPaths.projectRoot],
142142
sdkPath: Sdk.current.sdkPath,
143143
// Needed for collecting subtypes.
@@ -170,8 +170,8 @@ final class CelestProject {
170170
final ParentProject? parentProject;
171171

172172
/// The [AnalysisContext] for the current project.
173-
late final DriverBasedAnalysisContext analysisContext =
174-
_analysisContextCollection.contextFor(projectPaths.projectDart);
173+
late DriverBasedAnalysisContext analysisContext = _analysisContextCollection
174+
.contextFor(projectPaths.projectDart);
175175

176176
/// The [CelestConfig] for the current project.
177177
final CelestConfig config;
@@ -221,6 +221,19 @@ final class CelestProject {
221221
return changedFiles.toSet();
222222
}
223223

224+
void invalidatePubspec() {
225+
_analysisContextCollection = AnalysisContextCollectionImpl(
226+
includedPaths: [projectPaths.projectRoot],
227+
sdkPath: Sdk.current.sdkPath,
228+
// Needed for collecting subtypes.
229+
enableIndex: true,
230+
byteStore: _byteStore,
231+
);
232+
analysisContext = _analysisContextCollection.contextFor(
233+
projectPaths.projectDart,
234+
);
235+
}
236+
224237
/// The name of the project as declared in the `project.dart` file.
225238
///
226239
/// We parse the `project.dart` file to retrieve it since it is cheap and

apps/cli/lib/src/pub/project_dependency.dart

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -107,9 +107,7 @@ final class ProjectDependency {
107107
'celest_cloud_auth',
108108
DependencyType.dependency,
109109
HostedDependency(
110-
version: VersionConstraint.compatibleWith(
111-
Version.parse('0.3.0').firstPreRelease,
112-
),
110+
version: VersionConstraint.compatibleWith(Version.parse('0.3.0')),
113111
),
114112
);
115113

0 commit comments

Comments
 (0)