Skip to content

Commit de65c69

Browse files
authored
feat(dart_frog_prod): detect external path dependencies (#341)
1 parent bfe9675 commit de65c69

File tree

2 files changed

+147
-1
lines changed

2 files changed

+147
-1
lines changed

bricks/dart_frog_prod_server/hooks/pre_gen.dart

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,15 @@ Future<void> preGen(
2626

2727
final RouteConfiguration configuration;
2828
try {
29-
configuration = buildConfiguration(io.Directory.current);
29+
configuration = buildConfiguration(projectDirectory);
3030
} catch (error) {
3131
context.logger.err('$error');
3232
return _exit(1);
3333
}
3434

3535
reportRouteConflicts(context, configuration, _exit);
3636
reportRogueRoutes(context, configuration, _exit);
37+
await reportExternalPathDependencies(context, projectDirectory, _exit);
3738

3839
context.vars = {
3940
'directories': configuration.directories
@@ -144,6 +145,40 @@ void reportRogueRoutes(
144145
}
145146
}
146147

148+
Future<void> reportExternalPathDependencies(
149+
HookContext context,
150+
io.Directory directory,
151+
void Function(int exitCode) exit,
152+
) async {
153+
final pubspec = Pubspec.parse(
154+
await io.File(path.join(directory.path, 'pubspec.yaml')).readAsString(),
155+
);
156+
157+
final dependencies = pubspec.dependencies;
158+
final devDependencies = pubspec.devDependencies;
159+
final pathDependencies = [...dependencies.entries, ...devDependencies.entries]
160+
.where((entry) => entry.value is PathDependency)
161+
.map((entry) {
162+
final value = entry.value as PathDependency;
163+
return [entry.key, value.path];
164+
}).toList();
165+
final externalDependencies = pathDependencies.where(
166+
(dep) => !path.isWithin(directory.path, dep.last),
167+
);
168+
169+
if (externalDependencies.isNotEmpty) {
170+
context.logger
171+
..err('All path dependencies must be within the project.')
172+
..err('External path dependencies detected:');
173+
for (final dependency in externalDependencies) {
174+
final dependencyName = dependency.first;
175+
final dependencyPath = path.normalize(dependency.last);
176+
context.logger.err(' \u{2022} $dependencyName from $dependencyPath');
177+
}
178+
exit(1);
179+
}
180+
}
181+
147182
const _asyncRunZoned = runZoned;
148183

149184
abstract class ExitOverrides {

bricks/dart_frog_prod_server/hooks/test/pre_gen_test.dart

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -399,6 +399,117 @@ dev_dependencies:
399399
});
400400
});
401401

402+
group('reportExternalPathDependencies', () {
403+
late HookContext context;
404+
late Logger logger;
405+
406+
setUp(() {
407+
context = _MockHookContext();
408+
logger = _MockLogger();
409+
410+
when(() => context.logger).thenReturn(logger);
411+
});
412+
413+
test('reports nothing when there are no external path dependencies',
414+
() async {
415+
final exitCalls = <int>[];
416+
final directory = Directory.systemTemp.createTempSync();
417+
File(path.join(directory.path, 'pubspec.yaml')).writeAsStringSync(
418+
'''
419+
name: example
420+
version: 0.1.0
421+
environment:
422+
sdk: ^2.17.0
423+
dependencies:
424+
mason: any
425+
dev_dependencies:
426+
test: any
427+
''',
428+
);
429+
await expectLater(
430+
reportExternalPathDependencies(context, directory, exitCalls.add),
431+
completes,
432+
);
433+
verifyNever(() => logger.err(any()));
434+
expect(exitCalls, isEmpty);
435+
directory.delete(recursive: true).ignore();
436+
});
437+
438+
test('reports when there is a single external path dependency', () async {
439+
final exitCalls = <int>[];
440+
final directory = Directory.systemTemp.createTempSync();
441+
File(path.join(directory.path, 'pubspec.yaml')).writeAsStringSync(
442+
'''
443+
name: example
444+
version: 0.1.0
445+
environment:
446+
sdk: ^2.17.0
447+
dependencies:
448+
mason: any
449+
foo:
450+
path: ../../foo
451+
dev_dependencies:
452+
test: any
453+
''',
454+
);
455+
await expectLater(
456+
reportExternalPathDependencies(context, directory, exitCalls.add),
457+
completes,
458+
);
459+
expect(exitCalls, equals([1]));
460+
verify(
461+
() => logger.err('All path dependencies must be within the project.'),
462+
).called(1);
463+
verify(
464+
() => logger.err('External path dependencies detected:'),
465+
).called(1);
466+
verify(
467+
() => logger.err(' \u{2022} foo from ../../foo'),
468+
).called(1);
469+
directory.delete(recursive: true).ignore();
470+
});
471+
472+
test('reports when there are multiple external path dependencies',
473+
() async {
474+
final exitCalls = <int>[];
475+
final directory = Directory.systemTemp.createTempSync();
476+
File(path.join(directory.path, 'pubspec.yaml')).writeAsStringSync(
477+
'''
478+
name: example
479+
version: 0.1.0
480+
environment:
481+
sdk: ^2.17.0
482+
dependencies:
483+
mason: any
484+
foo:
485+
path: ../../foo
486+
dev_dependencies:
487+
test: any
488+
bar:
489+
path: ../../bar
490+
''',
491+
);
492+
await expectLater(
493+
reportExternalPathDependencies(context, directory, exitCalls.add),
494+
completes,
495+
);
496+
expect(exitCalls, equals([1]));
497+
verify(
498+
() => logger.err('All path dependencies must be within the project.'),
499+
).called(1);
500+
verify(
501+
() => logger.err('External path dependencies detected:'),
502+
).called(1);
503+
verify(
504+
() => logger.err(' \u{2022} foo from ../../foo'),
505+
).called(1);
506+
verify(
507+
() => logger.err(' \u{2022} bar from ../../bar'),
508+
).called(1);
509+
directory.delete(recursive: true).ignore();
510+
});
511+
});
512+
402513
group('ExitOverrides', () {
403514
group('runZoned', () {
404515
test('uses default exit when not specified', () {

0 commit comments

Comments
 (0)