Skip to content

Commit dfe97a5

Browse files
committed
fix(dart_frog_cli): use existing pubspec.lock when building with workspaces
1 parent 698ac77 commit dfe97a5

File tree

5 files changed

+264
-5
lines changed

5 files changed

+264
-5
lines changed

bricks/dart_frog_prod_server/hooks/lib/dart_frog_prod_server_hooks.dart

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
export 'src/copy_workspace_pubspec_lock.dart';
12
export 'src/create_bundle.dart';
23
export 'src/create_external_packages_folder.dart';
34
export 'src/dart_pub_get.dart';
Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
import 'dart:io';
2+
import 'package:mason/mason.dart';
3+
import 'package:path/path.dart' as path;
4+
import 'package:yaml/yaml.dart';
5+
6+
/// Copies the pubspec.lock from the workspace root into the project directory
7+
/// in order to ensure the production build uses the exact same versions of all
8+
/// dependencies.
9+
void copyWorkspacePubspecLock(
10+
HookContext context, {
11+
required String projectDirectory,
12+
required void Function(int exitCode) exit,
13+
}) {
14+
final workspaceRoot = _getWorkspaceRoot(projectDirectory);
15+
if (workspaceRoot == null) {
16+
context.logger.err(
17+
'Unable to determine workspace root for $projectDirectory',
18+
);
19+
return exit(1);
20+
}
21+
22+
final pubspecLockFile = File(path.join(workspaceRoot.path, 'pubspec.lock'));
23+
if (!pubspecLockFile.existsSync()) return;
24+
25+
try {
26+
pubspecLockFile.copySync(path.join(projectDirectory, 'pubspec.lock'));
27+
} on Exception catch (error) {
28+
context.logger.err('$error');
29+
return exit(1);
30+
}
31+
}
32+
33+
/// Returns the root directory of the nearest Dart workspace.
34+
Directory? _getWorkspaceRoot(String workingDirectory) {
35+
final file = _findNearestAncestor(
36+
where: (path) => _getWorkspaceRootPubspecYaml(cwd: Directory(path)),
37+
cwd: Directory(workingDirectory),
38+
);
39+
if (file == null || !file.existsSync()) return null;
40+
return Directory(path.dirname(file.path));
41+
}
42+
43+
/// The workspace root `pubspec.yaml` file for this project.
44+
File? _getWorkspaceRootPubspecYaml({required Directory cwd}) {
45+
try {
46+
final pubspecYamlFile = File(path.join(cwd.path, 'pubspec.yaml'));
47+
if (!pubspecYamlFile.existsSync()) return null;
48+
final pubspec = loadYaml(pubspecYamlFile.readAsStringSync());
49+
if (pubspec is! YamlMap) return null;
50+
final workspace = pubspec['workspace'] as List?;
51+
if (workspace?.isEmpty ?? true) return null;
52+
return pubspecYamlFile;
53+
} on Exception {
54+
return null;
55+
}
56+
}
57+
58+
/// Finds nearest ancestor file
59+
/// relative to the [cwd] that satisfies [where].
60+
File? _findNearestAncestor({
61+
required File? Function(String path) where,
62+
required Directory cwd,
63+
}) {
64+
Directory? prev;
65+
var dir = cwd;
66+
while (prev?.path != dir.path) {
67+
final file = where(dir.path);
68+
if (file?.existsSync() ?? false) return file;
69+
prev = dir;
70+
dir = dir.parent;
71+
}
72+
return null;
73+
}

bricks/dart_frog_prod_server/hooks/pre_gen.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,13 @@ Future<void> preGen(
3939
projectDirectory: projectDirectory.path,
4040
exit: exit,
4141
);
42+
// Copy the pubspec.lock from the workspace root to ensure the same versions
43+
// of dependencies are used in the production build.
44+
copyWorkspacePubspecLock(
45+
context,
46+
projectDirectory: projectDirectory.path,
47+
exit: exit,
48+
);
4249
}
4350

4451
// We need to make sure that the pubspec.lock file is up to date
Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
import 'dart:io';
2+
3+
import 'package:dart_frog_prod_server_hooks/dart_frog_prod_server_hooks.dart';
4+
import 'package:mason/mason.dart';
5+
import 'package:mocktail/mocktail.dart';
6+
import 'package:path/path.dart' as path;
7+
import 'package:test/test.dart';
8+
9+
class _MockHookContext extends Mock implements HookContext {}
10+
11+
class _MockLogger extends Mock implements Logger {}
12+
13+
void main() {
14+
group('copyWorkspacePubspecLock', () {
15+
late List<int> exitCalls;
16+
late HookContext context;
17+
late Logger logger;
18+
late Directory projectDirectory;
19+
late Directory rootDirectory;
20+
21+
setUp(() {
22+
exitCalls = [];
23+
context = _MockHookContext();
24+
logger = _MockLogger();
25+
rootDirectory = Directory.systemTemp.createTempSync('root');
26+
projectDirectory = Directory(
27+
path.join(rootDirectory.path, 'packages', 'project'),
28+
)..createSync(recursive: true);
29+
30+
when(() => context.logger).thenReturn(logger);
31+
32+
addTearDown(() {
33+
projectDirectory.delete().ignore();
34+
rootDirectory.delete().ignore();
35+
});
36+
});
37+
38+
test('exits with error when unable to determine the workspace root', () {
39+
copyWorkspacePubspecLock(
40+
context,
41+
projectDirectory: projectDirectory.path,
42+
exit: exitCalls.add,
43+
);
44+
expect(exitCalls, equals([1]));
45+
verify(
46+
() => logger.err(
47+
'Unable to determine workspace root for ${projectDirectory.path}',
48+
),
49+
);
50+
});
51+
52+
test('exits with error when unable to parse pubspec.yaml', () {
53+
File(path.join(rootDirectory.path, 'pubspec.yaml'))
54+
.writeAsStringSync('invalid pubspec.yaml');
55+
copyWorkspacePubspecLock(
56+
context,
57+
projectDirectory: projectDirectory.path,
58+
exit: exitCalls.add,
59+
);
60+
expect(exitCalls, equals([1]));
61+
verify(
62+
() => logger.err(
63+
'Unable to determine workspace root for ${projectDirectory.path}',
64+
),
65+
);
66+
});
67+
68+
test('does nothing when pubspec.lock does not exist in workspace root', () {
69+
File(path.join(rootDirectory.path, 'pubspec.yaml')).writeAsStringSync('''
70+
name: _
71+
version: 0.0.0
72+
environment:
73+
sdk: ^3.8.0
74+
workspace:
75+
- packages/hello_world
76+
''');
77+
copyWorkspacePubspecLock(
78+
context,
79+
projectDirectory: projectDirectory.path,
80+
exit: exitCalls.add,
81+
);
82+
expect(exitCalls, isEmpty);
83+
verifyNever(() => logger.err(any()));
84+
expect(projectDirectory.listSync(), isEmpty);
85+
});
86+
87+
test('exits with error when unable to copy lockfile', () {
88+
const pubspecLockContents = '''
89+
# Generated by pub
90+
# See https://dart.dev/tools/pub/glossary#lockfile
91+
packages:
92+
''';
93+
File(path.join(rootDirectory.path, 'pubspec.yaml')).writeAsStringSync('''
94+
name: _
95+
version: 0.0.0
96+
environment:
97+
sdk: ^3.8.0
98+
workspace:
99+
- packages/hello_world
100+
''');
101+
final file = File(path.join(rootDirectory.path, 'pubspec.lock'))
102+
..writeAsStringSync(pubspecLockContents);
103+
Process.runSync('chmod', ['000', file.path]);
104+
copyWorkspacePubspecLock(
105+
context,
106+
projectDirectory: projectDirectory.path,
107+
exit: exitCalls.add,
108+
);
109+
expect(exitCalls, equals([1]));
110+
verify(
111+
() => logger.err(any(that: contains('Permission denied'))),
112+
);
113+
});
114+
115+
test('copies pubspec.lock to project directory when found', () {
116+
const pubspecLockContents = '''
117+
# Generated by pub
118+
# See https://dart.dev/tools/pub/glossary#lockfile
119+
packages:
120+
''';
121+
File(path.join(rootDirectory.path, 'pubspec.yaml')).writeAsStringSync('''
122+
name: _
123+
version: 0.0.0
124+
environment:
125+
sdk: ^3.8.0
126+
workspace:
127+
- packages/hello_world
128+
''');
129+
File(path.join(rootDirectory.path, 'pubspec.lock'))
130+
.writeAsStringSync(pubspecLockContents);
131+
copyWorkspacePubspecLock(
132+
context,
133+
projectDirectory: projectDirectory.path,
134+
exit: exitCalls.add,
135+
);
136+
expect(exitCalls, isEmpty);
137+
verifyNever(() => logger.err(any()));
138+
final projectDirectoryContents = projectDirectory.listSync();
139+
expect(projectDirectoryContents, hasLength(1));
140+
expect(
141+
projectDirectoryContents.first,
142+
isA<File>().having(
143+
(f) => path.basename(f.path),
144+
'name',
145+
'pubspec.lock',
146+
),
147+
);
148+
expect(
149+
(projectDirectoryContents.first as File).readAsStringSync(),
150+
equals(pubspecLockContents),
151+
);
152+
});
153+
});
154+
}

0 commit comments

Comments
 (0)