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
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export 'src/copy_workspace_pubspec_lock.dart';
export 'src/create_bundle.dart';
export 'src/create_external_packages_folder.dart';
export 'src/dart_pub_get.dart';
Expand All @@ -6,3 +7,6 @@ export 'src/exit_overrides.dart';
export 'src/get_internal_path_dependencies.dart';
export 'src/get_pubspec_lock.dart';
export 'src/uses_workspace_resolution.dart';

/// A void callback function (e.g. `void Function()`).
typedef VoidCallback = void Function();
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
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 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'));
if (!pubspecLockFile.existsSync()) return () {};

try {
pubspecLockFile.copySync(path.join(projectDirectory, 'pubspec.lock'));
return () {
File(path.join(projectDirectory, 'pubspec.lock')).delete().ignore();
};
} on Exception catch (error) {
context.logger.err('$error');
exit(1);
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
@@ -1,12 +1,10 @@
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';
import 'package:yaml_edit/yaml_edit.dart';

/// A void callback function (e.g. `void Function()`).
typedef VoidCallback = void Function();

/// Opts out of dart workspaces until we can generate per package lockfiles.
/// https://github.com/dart-lang/pub/issues/4594
VoidCallback disableWorkspaceResolution(
Expand Down
17 changes: 13 additions & 4 deletions bricks/dart_frog_prod_server/hooks/pre_gen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ Future<void> preGen(
);

VoidCallback? restoreWorkspaceResolution;
VoidCallback? revertPubspecLock;

if (usesWorkspaces) {
// Disable workspace resolution until we can generate per-package lockfiles.
Expand All @@ -39,6 +40,13 @@ Future<void> preGen(
projectDirectory: projectDirectory.path,
exit: exit,
);
// Copy the pubspec.lock from the workspace root to ensure the same versions
// of dependencies are used in the production build.
revertPubspecLock = copyWorkspacePubspecLock(
context,
projectDirectory: projectDirectory.path,
exit: exit,
);
}

// We need to make sure that the pubspec.lock file is up to date
Expand Down Expand Up @@ -94,10 +102,6 @@ Future<void> preGen(
onViolationEnd: () => exit(1),
);

final customDockerFile = io.File(
path.join(projectDirectory.path, 'Dockerfile'),
);

final internalPathDependencies = await getInternalPathDependencies(
projectDirectory,
);
Expand All @@ -108,6 +112,11 @@ Future<void> preGen(
copyPath: copyPath,
);

revertPubspecLock?.call();

final customDockerFile = io.File(
path.join(projectDirectory.path, 'Dockerfile'),
);
final addDockerfile = !customDockerFile.existsSync();

context.vars = {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
import 'dart:io';

import 'package:dart_frog_prod_server_hooks/dart_frog_prod_server_hooks.dart';
import 'package:mason/mason.dart';
import 'package:mocktail/mocktail.dart';
import 'package:path/path.dart' as path;
import 'package:test/test.dart';

class _MockHookContext extends Mock implements HookContext {}

class _MockLogger extends Mock implements Logger {}

void main() {
group('copyWorkspacePubspecLock', () {
late List<int> exitCalls;
late HookContext context;
late Logger logger;
late Directory projectDirectory;
late Directory rootDirectory;

setUp(() {
exitCalls = [];
context = _MockHookContext();
logger = _MockLogger();
rootDirectory = Directory.systemTemp.createTempSync('root');
projectDirectory = Directory(
path.join(rootDirectory.path, 'packages', 'project'),
)..createSync(recursive: true);

when(() => context.logger).thenReturn(logger);

addTearDown(() {
projectDirectory.delete().ignore();
rootDirectory.delete().ignore();
});
});

test('exits with error when unable to determine the workspace root', () {
copyWorkspacePubspecLock(
context,
projectDirectory: projectDirectory.path,
exit: exitCalls.add,
);
expect(exitCalls, equals([1]));
verify(
() => logger.err(
'Unable to determine workspace root for ${projectDirectory.path}',
),
);
});

test('exits with error when unable to parse pubspec.yaml', () {
File(path.join(rootDirectory.path, 'pubspec.yaml'))
.writeAsStringSync('invalid pubspec.yaml');
copyWorkspacePubspecLock(
context,
projectDirectory: projectDirectory.path,
exit: exitCalls.add,
);
expect(exitCalls, equals([1]));
verify(
() => logger.err(
'Unable to determine workspace root for ${projectDirectory.path}',
),
);
});

test('does nothing when pubspec.lock does not exist in workspace root', () {
File(path.join(rootDirectory.path, 'pubspec.yaml')).writeAsStringSync('''
name: _
version: 0.0.0
environment:
sdk: ^3.8.0
workspace:
- packages/hello_world
''');
copyWorkspacePubspecLock(
context,
projectDirectory: projectDirectory.path,
exit: exitCalls.add,
);
expect(exitCalls, isEmpty);
verifyNever(() => logger.err(any()));
expect(projectDirectory.listSync(), isEmpty);
});

test('exits with error when unable to copy lockfile', () {
const pubspecLockContents = '''
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
''';
File(path.join(rootDirectory.path, 'pubspec.yaml')).writeAsStringSync('''
name: _
version: 0.0.0
environment:
sdk: ^3.8.0
workspace:
- packages/hello_world
''');
final file = File(path.join(rootDirectory.path, 'pubspec.lock'))
..writeAsStringSync(pubspecLockContents);
Process.runSync('chmod', ['000', file.path]);
copyWorkspacePubspecLock(
context,
projectDirectory: projectDirectory.path,
exit: exitCalls.add,
);
expect(exitCalls, equals([1]));
verify(
() => logger.err(any(that: contains('Permission denied'))),
);
});

test('copies pubspec.lock to project directory when found', () {
const pubspecLockContents = '''
# Generated by pub
# See https://dart.dev/tools/pub/glossary#lockfile
packages:
''';
File(path.join(rootDirectory.path, 'pubspec.yaml')).writeAsStringSync('''
name: _
version: 0.0.0
environment:
sdk: ^3.8.0
workspace:
- packages/hello_world
''');
File(path.join(rootDirectory.path, 'pubspec.lock'))
.writeAsStringSync(pubspecLockContents);
copyWorkspacePubspecLock(
context,
projectDirectory: projectDirectory.path,
exit: exitCalls.add,
);
expect(exitCalls, isEmpty);
verifyNever(() => logger.err(any()));
final projectDirectoryContents = projectDirectory.listSync();
expect(projectDirectoryContents, hasLength(1));
expect(
projectDirectoryContents.first,
isA<File>().having(
(f) => path.basename(f.path),
'name',
'pubspec.lock',
),
);
expect(
(projectDirectoryContents.first as File).readAsStringSync(),
equals(pubspecLockContents),
);
});
});
}
Loading
Loading