Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions apps/cli/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
## NEXT

- fix: Improved reload handling
- fix: Ensure all dependencies are correctly resolved

## 1.0.14

Expand Down
69 changes: 69 additions & 0 deletions apps/cli/lib/src/analyzer/project_parser.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
import 'package:analyzer/dart/analysis/results.dart';
import 'package:analyzer/dart/ast/ast.dart';
import 'package:analyzer/src/dart/analysis/driver_based_analysis_context.dart';
import 'package:celest_cli/src/codegen/client_code_generator.dart';

final class CelestProjectParser {
CelestProjectParser({
required String projectRoot,
required this.projectDart,
required DriverBasedAnalysisContext analysisContext,
}) : _analysisContext = analysisContext,
_dependencies = CodegenDependencies(rootDir: projectRoot);

final String projectDart;
final DriverBasedAnalysisContext _analysisContext;
final CodegenDependencies _dependencies;

CodegenDependencies parseDependencies() {
final projectDartLibrary = _analysisContext.currentSession.getParsedLibrary(
projectDart,
);
if (projectDartLibrary is! ParsedLibraryResult) {
throw StateError('Could not parse $projectDart');
}

final topLevelVariables =
projectDartLibrary.units
.expand((unit) => unit.unit.declarations)
.whereType<TopLevelVariableDeclaration>()
.expand((declaration) => declaration.variables.variables)
.toList();

if (_findDatabases(topLevelVariables)) {
_dependencies.add('drift_hrana');
}
if (_findAuth(topLevelVariables)) {
_dependencies.add('celest_cloud_auth');
_dependencies.add('drift_hrana');
}

return _dependencies;
}

bool _findAuth(List<VariableDeclaration> topLevelVariables) {
for (final variable in topLevelVariables) {
if (variable case VariableDeclaration(
initializer: MethodInvocation(
methodName: SimpleIdentifier(name: 'Auth'),
),
)) {
return true;
}
}
return false;
}

bool _findDatabases(List<VariableDeclaration> topLevelVariables) {
for (final variable in topLevelVariables) {
if (variable case VariableDeclaration(
initializer: MethodInvocation(
methodName: SimpleIdentifier(name: 'Database'),
),
)) {
return true;
}
}
return false;
}
}
47 changes: 47 additions & 0 deletions apps/cli/lib/src/codegen/client_code_generator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,13 @@ import 'package:celest_cli/src/codegen/code_generator.dart';
import 'package:celest_cli/src/codegen/code_outputs.dart';
import 'package:celest_cli/src/context.dart';
import 'package:celest_cli/src/project/celest_project.dart';
import 'package:celest_cli/src/pub/project_dependency.dart';
import 'package:celest_cli/src/pub/pub_action.dart';
import 'package:celest_cli/src/pub/pubspec.dart';
import 'package:collection/collection.dart';
import 'package:logging/logging.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:pubspec_parse/pubspec_parse.dart';

final class ClientCodeGenerator {
ClientCodeGenerator({
Expand Down Expand Up @@ -55,4 +61,45 @@ final class CodegenDependencies extends DelegatingSet<String> {
static CodegenDependencies get current {
return Zone.current[CodegenDependencies] as CodegenDependencies;
}

static final Logger _logger = Logger('CodegenDependencies');

Future<bool> save() async {
var (pubspec, pubspecYaml) =
p.equals(rootDir, projectPaths.projectRoot)
? (celestProject.pubspec, celestProject.pubspecYaml)
: (celestProject.clientPubspec, celestProject.clientPubspecYaml);
final pubspecFile = fileSystem.directory(rootDir).childFile('pubspec.yaml');
final currentDependencies = Set.of(pubspec.dependencies.keys);
final missingDependencies = difference(currentDependencies);
final needsUpdate = missingDependencies.isNotEmpty;
if (needsUpdate) {
_logger.fine(
'Adding dependencies to ${pubspecFile.path}: '
'${missingDependencies.toList()}',
);
pubspec = pubspec.addDeps(
dependencies: {
for (final dependency in this)
dependency:
ProjectDependency.all[dependency]?.pubDependency ??
HostedDependency(version: VersionConstraint.any),
},
);
if (pubspecFile.existsSync()) {
await pubspecFile.writeAsString(pubspec.toYaml(source: pubspecYaml));
await runPub(
action: PubAction.get,
workingDirectory: pubspecFile.parent.path,
);
} else {
_logger.fine(
'Skipping dependency update',
'Pubspec not found: ${pubspecFile.path}',
StackTrace.current,
);
}
}
return needsUpdate;
}
}
52 changes: 1 addition & 51 deletions apps/cli/lib/src/codegen/code_outputs.dart
Original file line number Diff line number Diff line change
@@ -1,12 +1,6 @@
import 'package:celest_cli/src/codegen/client_code_generator.dart';
import 'package:celest_cli/src/context.dart';
import 'package:celest_cli/src/pub/project_dependency.dart';
import 'package:celest_cli/src/pub/pub_action.dart';
import 'package:celest_cli/src/pub/pubspec.dart';
import 'package:collection/collection.dart';
import 'package:logging/logging.dart';
import 'package:pub_semver/pub_semver.dart';
import 'package:pubspec_parse/pubspec_parse.dart';

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

static final Logger _logger = Logger('CodeOutputs');

Future<void> write() async {
final outputFutures = <Future<void>>[];
forEach((path, library) {
Expand All @@ -32,49 +24,7 @@ final class CodeOutputs extends DelegatingMap<String, String> {
);
});
if (codegenDependencies.isNotEmpty) {
var (pubspec, pubspecYaml) =
p.equals(codegenDependencies.rootDir, projectPaths.projectRoot)
? (celestProject.pubspec, celestProject.pubspecYaml)
: (celestProject.clientPubspec, celestProject.clientPubspecYaml);
final pubspecFile = fileSystem
.directory(codegenDependencies.rootDir)
.childFile('pubspec.yaml');
final currentDependencies = Set.of(pubspec.dependencies.keys);
final missingDependencies = codegenDependencies.difference(
currentDependencies,
);
if (missingDependencies.isNotEmpty) {
_logger.fine(
'Adding dependencies to ${pubspecFile.path}: '
'${missingDependencies.toList()}',
);
pubspec = pubspec.addDeps(
dependencies: {
for (final dependency in codegenDependencies)
dependency:
ProjectDependency.all[dependency]?.pubDependency ??
HostedDependency(version: VersionConstraint.any),
},
);
if (pubspecFile.existsSync()) {
outputFutures.add(
pubspecFile.writeAsString(pubspec.toYaml(source: pubspecYaml)).then(
(_) {
return runPub(
action: PubAction.get,
workingDirectory: pubspecFile.parent.path,
);
},
),
);
} else {
_logger.fine(
'Skipping dependency update',
'Pubspec not found: ${pubspecFile.path}',
StackTrace.current,
);
}
}
outputFutures.add(codegenDependencies.save());
}
await Future.wait(outputFutures);
}
Expand Down
21 changes: 21 additions & 0 deletions apps/cli/lib/src/frontend/celest_frontend.dart
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import 'package:celest_ast/celest_ast.dart' hide Sdk;
import 'package:celest_cli/src/analyzer/analysis_error.dart';
import 'package:celest_cli/src/analyzer/analysis_result.dart';
import 'package:celest_cli/src/analyzer/celest_analyzer.dart';
import 'package:celest_cli/src/analyzer/project_parser.dart';
import 'package:celest_cli/src/ast/project_diff.dart';
import 'package:celest_cli/src/cli/stop_signal.dart';
import 'package:celest_cli/src/codegen/api/dockerfile_generator.dart';
Expand Down Expand Up @@ -334,6 +335,8 @@ final class CelestFrontend with CloudRepository {
_logErrors(errors);
}

await _updateDependencies();

final analysisResult = await _analyzeProject(
migrateProject: migrateProject,
);
Expand Down Expand Up @@ -757,6 +760,24 @@ final class CelestFrontend with CloudRepository {
}
}

/// Parses the project to look for any required analysis dependencies.
Future<void> _updateDependencies() =>
performance.trace('CelestFrontend', 'updateDependencies', () async {
logger.fine('Parsing project...');
final parser = CelestProjectParser(
projectRoot: projectPaths.projectRoot,
projectDart: projectPaths.projectDart,
analysisContext: celestProject.analysisContext,
);
final dependencies = parser.parseDependencies();
if (dependencies.isNotEmpty) {
if (await dependencies.save()) {
celestProject.invalidatePubspec();
}
}
stopSignal.check();
});

/// Analyzes the project and reports if there are any errors.
Future<CelestAnalysisResult> _analyzeProject({
required bool migrateProject,
Expand Down
19 changes: 16 additions & 3 deletions apps/cli/lib/src/project/celest_project.dart
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,7 @@ final class CelestProject {
AnalysisOptions get analysisOptions => _analysisOptions;

final ByteStore _byteStore;
late final _analysisContextCollection = AnalysisContextCollectionImpl(
late var _analysisContextCollection = AnalysisContextCollectionImpl(
includedPaths: [projectPaths.projectRoot],
sdkPath: Sdk.current.sdkPath,
// Needed for collecting subtypes.
Expand Down Expand Up @@ -170,8 +170,8 @@ final class CelestProject {
final ParentProject? parentProject;

/// The [AnalysisContext] for the current project.
late final DriverBasedAnalysisContext analysisContext =
_analysisContextCollection.contextFor(projectPaths.projectDart);
late DriverBasedAnalysisContext analysisContext = _analysisContextCollection
.contextFor(projectPaths.projectDart);

/// The [CelestConfig] for the current project.
final CelestConfig config;
Expand Down Expand Up @@ -221,6 +221,19 @@ final class CelestProject {
return changedFiles.toSet();
}

void invalidatePubspec() {
_analysisContextCollection = AnalysisContextCollectionImpl(
includedPaths: [projectPaths.projectRoot],
sdkPath: Sdk.current.sdkPath,
// Needed for collecting subtypes.
enableIndex: true,
byteStore: _byteStore,
);
analysisContext = _analysisContextCollection.contextFor(
projectPaths.projectDart,
);
}

/// The name of the project as declared in the `project.dart` file.
///
/// We parse the `project.dart` file to retrieve it since it is cheap and
Expand Down
4 changes: 1 addition & 3 deletions apps/cli/lib/src/pub/project_dependency.dart
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,7 @@ final class ProjectDependency {
'celest_cloud_auth',
DependencyType.dependency,
HostedDependency(
version: VersionConstraint.compatibleWith(
Version.parse('0.3.0').firstPreRelease,
),
version: VersionConstraint.compatibleWith(Version.parse('0.3.0')),
),
);

Expand Down
Loading