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
11 changes: 11 additions & 0 deletions bricks/dart_frog_prod_server/hooks/build.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
targets:
$default:
builders:
source_gen|combining_builder:
options:
ignore_for_file:
- type=lint
json_serializable:
options:
create_to_json: false
checked: true
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,11 @@ export 'src/dart_pub_get.dart';
export 'src/disable_workspace_resolution.dart';
export 'src/exit_overrides.dart';
export 'src/get_internal_path_dependencies.dart';
export 'src/get_package_config.dart';
export 'src/get_package_graph.dart';
export 'src/get_pubspec_lock.dart';
export 'src/get_workspace_root.dart';
export 'src/package_graph/package_graph.dart';
export 'src/uses_workspace_resolution.dart';

/// A void callback function (e.g. `void Function()`).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,17 @@ import 'dart:io';
import 'package:dart_frog_prod_server_hooks/dart_frog_prod_server_hooks.dart';
import 'package:mason/mason.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';

/// Copies the pubspec.lock from the workspace root into the project directory
/// in order to ensure the production build uses the exact same versions of all
/// dependencies.
VoidCallback copyWorkspacePubspecLock(
HookContext context, {
required String projectDirectory,
required String workspaceRoot,
required void Function(int exitCode) exit,
}) {
final workspaceRoot = _getWorkspaceRoot(projectDirectory);
if (workspaceRoot == null) {
context.logger.err(
'Unable to determine workspace root for $projectDirectory',
);
exit(1);
return () {};
}

final pubspecLockFile = File(path.join(workspaceRoot.path, 'pubspec.lock'));
final pubspecLockFile = File(path.join(workspaceRoot, 'pubspec.lock'));
if (!pubspecLockFile.existsSync()) return () {};

try {
Expand All @@ -35,45 +26,3 @@ VoidCallback copyWorkspacePubspecLock(
return () {};
}
}

/// Returns the root directory of the nearest Dart workspace.
Directory? _getWorkspaceRoot(String workingDirectory) {
final file = _findNearestAncestor(
where: (path) => _getWorkspaceRootPubspecYaml(cwd: Directory(path)),
cwd: Directory(workingDirectory),
);
if (file == null || !file.existsSync()) return null;
return Directory(path.dirname(file.path));
}

/// The workspace root `pubspec.yaml` file for this project.
File? _getWorkspaceRootPubspecYaml({required Directory cwd}) {
try {
final pubspecYamlFile = File(path.join(cwd.path, 'pubspec.yaml'));
if (!pubspecYamlFile.existsSync()) return null;
final pubspec = loadYaml(pubspecYamlFile.readAsStringSync());
if (pubspec is! YamlMap) return null;
final workspace = pubspec['workspace'] as List?;
if (workspace?.isEmpty ?? true) return null;
return pubspecYamlFile;
} on Exception {
return null;
}
}

/// Finds nearest ancestor file
/// relative to the [cwd] that satisfies [where].
File? _findNearestAncestor({
required File? Function(String path) where,
required Directory cwd,
}) {
Directory? prev;
var dir = cwd;
while (prev?.path != dir.path) {
final file = where(dir.path);
if (file?.existsSync() ?? false) return file;
prev = dir;
dir = dir.parent;
}
return null;
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ Future<void> dartPubGet(
required String workingDirectory,
required ProcessRunner runProcess,
required void Function(int exitCode) exit,
String message = 'Installing dependencies',
}) async {
final progress = context.logger.progress('Installing dependencies');
final progress = context.logger.progress(message);
try {
final result = await runProcess(
'dart',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'dart:io';
import 'package:dart_frog_prod_server_hooks/dart_frog_prod_server_hooks.dart';
import 'package:mason/mason.dart';
import 'package:package_config/package_config.dart';
import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';
import 'package:yaml_edit/yaml_edit.dart';
Expand All @@ -9,18 +10,40 @@ import 'package:yaml_edit/yaml_edit.dart';
/// https://github.com/dart-lang/pub/issues/4594
VoidCallback disableWorkspaceResolution(
HookContext context, {
required PackageConfig packageConfig,
required PackageGraph packageGraph,
required String projectDirectory,
required String workspaceRoot,
required void Function(int exitCode) exit,
}) {
final VoidCallback restoreWorkspaceResolution;
try {
return overrideResolutionInPubspecOverrides(projectDirectory);
restoreWorkspaceResolution = overrideResolutionInPubspecOverrides(
projectDirectory,
);
} on Exception catch (e) {
context.logger.err('$e');
exit(1);
return () {}; // no-op
}

try {
overridePathDependenciesInPubspecOverrides(
projectDirectory: projectDirectory,
packageConfig: packageConfig,
packageGraph: packageGraph,
);
} on Exception catch (e) {
restoreWorkspaceResolution();
context.logger.err('$e');
exit(1);
return () {}; // no-op
}

return restoreWorkspaceResolution;
}

/// Add resolution:null to pubspec_overrides.yaml.
VoidCallback overrideResolutionInPubspecOverrides(String projectDirectory) {
final pubspecOverridesFile = File(
path.join(projectDirectory, 'pubspec_overrides.yaml'),
Expand All @@ -46,3 +69,93 @@ VoidCallback overrideResolutionInPubspecOverrides(String projectDirectory) {

return () => pubspecOverridesFile.writeAsStringSync(contents);
}

/// Add overrides for all path dependencies to `pubspec_overrides.yaml`
void overridePathDependenciesInPubspecOverrides({
required String projectDirectory,
required PackageConfig packageConfig,
required PackageGraph packageGraph,
}) {
final name = getPackageName(projectDirectory: projectDirectory);
if (name == null) {
throw Exception('Failed to parse "name" from pubspec.yaml');
}

final productionDeps = getProductionDependencies(
packageName: name,
packageGraph: packageGraph,
);

final pathDependencies = packageConfig.packages.where(
(package) => package.relativeRoot && productionDeps.contains(package.name),
);

writePathDependencyOverrides(
projectDirectory: projectDirectory,
pathDependencies: pathDependencies,
);
}

void writePathDependencyOverrides({
required String projectDirectory,
required Iterable<Package> pathDependencies,
}) {
final pubspecOverridesFile = File(
path.join(projectDirectory, 'pubspec_overrides.yaml'),
);
final contents = pubspecOverridesFile.readAsStringSync();
final overrides = loadYaml(contents) as YamlMap;
final editor = YamlEditor(contents);
if (!overrides.containsKey('dependency_overrides')) {
editor.update(['dependency_overrides'], {});
}
for (final package in pathDependencies) {
editor.update(
['dependency_overrides', package.name],
{'path': path.relative(package.root.path, from: projectDirectory)},
);
}
pubspecOverridesFile.writeAsStringSync(editor.toString());
}

/// Extract the package name from the pubspec.yaml in [projectDirectory].
String? getPackageName({required String projectDirectory}) {
final pubspecFile = File(path.join(projectDirectory, 'pubspec.yaml'));
final pubspec = loadYaml(pubspecFile.readAsStringSync());
if (pubspec is! YamlMap) return null;

final name = pubspec['name'];
if (name is! String) return null;

return name;
}

/// Build a complete list of dependencies (direct and transitive).
Set<String> getProductionDependencies({
required String packageName,
required PackageGraph packageGraph,
}) {
final dependencies = <String>{};
final root = packageGraph.roots.firstWhere((root) => root == packageName);
final rootPackage = packageGraph.packages.firstWhere((p) => p.name == root);
final dependenciesToVisit = <String>[...rootPackage.dependencies];

do {
final discoveredDependencies = <String>[];
for (final dependencyToVisit in dependenciesToVisit) {
final package = packageGraph.packages.firstWhere(
(p) => p.name == dependencyToVisit,
);
dependencies.add(package.name);
for (final packageDependency in package.dependencies) {
// Avoid infinite loops from dependency cycles (circular dependencies).
if (dependencies.contains(packageDependency)) continue;
discoveredDependencies.add(packageDependency);
}
}
dependenciesToVisit
..clear()
..addAll(discoveredDependencies);
} while (dependenciesToVisit.isNotEmpty);
return dependencies;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'dart:io';

import 'package:package_config/package_config_types.dart';
import 'package:path/path.dart' as path;

PackageConfig? getPackageConfig(
String workspaceRoot, {
path.Context? pathContext,
}) {
final pathResolver = pathContext ?? path.context;
final packageConfigFile = File(
pathResolver.join(workspaceRoot, '.dart_tool/package_config.json'),
);
if (!packageConfigFile.existsSync()) return null;

try {
final content = packageConfigFile.readAsStringSync();
return PackageConfig.parseString(content, packageConfigFile.uri);
} on Exception {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import 'package:dart_frog_prod_server_hooks/dart_frog_prod_server_hooks.dart';

PackageGraph? getPackageGraph(String workspaceRoot) {
try {
return PackageGraph.load(workspaceRoot);
} on Exception {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
import 'dart:io';

import 'package:path/path.dart' as path;
import 'package:yaml/yaml.dart';

/// Returns the root directory of the nearest Dart workspace.
Directory? getWorkspaceRoot(String workingDirectory) {
final file = _findNearestAncestor(
where: (path) => _getWorkspaceRootPubspecYaml(cwd: Directory(path)),
cwd: Directory(workingDirectory),
);
if (file == null || !file.existsSync()) return null;
return Directory(path.dirname(file.path));
}

/// The workspace root `pubspec.yaml` file for this project.
File? _getWorkspaceRootPubspecYaml({required Directory cwd}) {
try {
final pubspecYamlFile = File(path.join(cwd.path, 'pubspec.yaml'));
if (!pubspecYamlFile.existsSync()) return null;
final pubspec = loadYaml(pubspecYamlFile.readAsStringSync());
if (pubspec is! YamlMap) return null;
final workspace = pubspec['workspace'] as List?;
if (workspace?.isEmpty ?? true) return null;
return pubspecYamlFile;
} on Exception {
return null;
}
}

/// Finds nearest ancestor file
/// relative to the [cwd] that satisfies [where].
File? _findNearestAncestor({
required File? Function(String path) where,
required Directory cwd,
}) {
Directory? prev;
var dir = cwd;
while (prev?.path != dir.path) {
final file = where(dir.path);
if (file?.existsSync() ?? false) return file;
prev = dir;
dir = dir.parent;
}
return null;
}
Loading
Loading