Skip to content

Commit 68701be

Browse files
authored
feat(dart_frog_prod_server): report route conflicts (#205)
1 parent c7e82e1 commit 68701be

File tree

7 files changed

+479
-45
lines changed

7 files changed

+479
-45
lines changed
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: dart_frog_prod_server
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- ".github/workflows/dart_frog_prod_server.yaml"
7+
- "bricks/dart_frog_prod_server/hooks/**"
8+
push:
9+
branches:
10+
- main
11+
paths:
12+
- ".github/workflows/dart_frog_prod_server.yaml"
13+
- "bricks/dart_frog_prod_server/hooks/**"
14+
15+
jobs:
16+
build:
17+
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1
18+
with:
19+
working_directory: bricks/dart_frog_prod_server/hooks
20+
analyze_directories: .
21+
report_on: pre_gen.dart
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
11
.dart_tool
22
.packages
33
pubspec.lock
4+
coverage
5+
build
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
include: package:very_good_analysis/analysis_options.3.0.1.yaml

bricks/dart_frog_prod_server/hooks/post_gen.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,6 @@ Future<void> run(HookContext context) async {
2727
..info('Start the production server by running:')
2828
..info('')
2929
..info(
30-
'${lightCyan.wrap('dart ${path.join(relativeBuildPath, 'bin', 'server.dart')}')}',
30+
'''${lightCyan.wrap('dart ${path.join(relativeBuildPath, 'bin', 'server.dart')}')}''',
3131
);
3232
}
Lines changed: 121 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -1,61 +1,39 @@
1-
import 'dart:io';
1+
import 'dart:async';
2+
import 'dart:io' as io;
23

34
import 'package:dart_frog_gen/dart_frog_gen.dart';
4-
import 'package:io/io.dart';
5+
import 'package:io/io.dart' show copyPath;
56
import 'package:mason/mason.dart';
67
import 'package:path/path.dart' as path;
78
import 'package:pubspec_parse/pubspec_parse.dart';
89

9-
Future<void> run(HookContext context) async {
10-
final projectDirectory = Directory.current;
11-
final buildDirectoryPath = path.join(projectDirectory.path, 'build');
12-
final buildDirectory = Directory(buildDirectoryPath);
13-
final dartFrogDirectoryPath = path.join(projectDirectory.path, '.dart_frog');
14-
final dartFrogDirectory = Directory(dartFrogDirectoryPath);
15-
16-
final pubspec = Pubspec.parse(
17-
await File(path.join(projectDirectory.path, 'pubspec.yaml')).readAsString(),
18-
);
19-
20-
final dependencies = pubspec.dependencies;
21-
final devDependencies = pubspec.devDependencies;
22-
final pathDependencies = [
23-
...dependencies.entries,
24-
...devDependencies.entries,
25-
]
26-
.where((entry) => entry.value is PathDependency)
27-
.map((entry) => (entry.value as PathDependency).path)
28-
.toList();
10+
typedef RouteConfigurationBuilder = RouteConfiguration Function(
11+
io.Directory directory,
12+
);
2913

30-
final bundlingProgress = context.logger.progress('Bundling sources');
31-
if (await buildDirectory.exists()) {
32-
await buildDirectory.delete(recursive: true);
33-
}
14+
Future<void> run(HookContext context) => preGen(context);
3415

35-
if (await dartFrogDirectory.exists()) {
36-
await dartFrogDirectory.delete(recursive: true);
37-
}
38-
39-
final tempDirectory = await Directory.systemTemp.createTemp();
40-
try {
41-
await copyPath('.', '${tempDirectory.path}${path.separator}');
42-
bundlingProgress.complete();
43-
} catch (error) {
44-
bundlingProgress.fail();
45-
context.logger.err('$error');
46-
exit(1);
47-
}
16+
Future<void> preGen(
17+
HookContext context, {
18+
io.Directory? directory,
19+
RouteConfigurationBuilder buildConfiguration = buildRouteConfiguration,
20+
void Function(int exitCode)? exit,
21+
}) async {
22+
final _exit = exit ?? ExitOverrides.current?.exit ?? io.exit;
23+
final projectDirectory = directory ?? io.Directory.current;
4824

49-
await tempDirectory.rename(buildDirectoryPath);
25+
await createBundle(context, projectDirectory, _exit);
5026

5127
final RouteConfiguration configuration;
5228
try {
53-
configuration = buildRouteConfiguration(Directory.current);
29+
configuration = buildConfiguration(io.Directory.current);
5430
} catch (error) {
5531
context.logger.err('$error');
56-
exit(1);
32+
return _exit(1);
5733
}
5834

35+
reportRouteConflicts(context, configuration, _exit);
36+
5937
context.vars = {
6038
'directories': configuration.directories
6139
.map((c) => c.toJson())
@@ -67,6 +45,106 @@ Future<void> run(HookContext context) async {
6745
'globalMiddleware': configuration.globalMiddleware != null
6846
? configuration.globalMiddleware!.toJson()
6947
: false,
70-
'pathDependencies': pathDependencies,
48+
'pathDependencies': await getPathDependencies(projectDirectory),
7149
};
7250
}
51+
52+
Future<void> createBundle(
53+
HookContext context,
54+
io.Directory projectDirectory,
55+
void Function(int exitCode) exit,
56+
) async {
57+
final buildDirectoryPath = path.join(projectDirectory.path, 'build');
58+
final buildDirectory = io.Directory(buildDirectoryPath);
59+
final dartFrogDirectoryPath = path.join(projectDirectory.path, '.dart_frog');
60+
final dartFrogDirectory = io.Directory(dartFrogDirectoryPath);
61+
final bundlingProgress = context.logger.progress('Bundling sources');
62+
final tempDirectory = await io.Directory.systemTemp.createTemp();
63+
64+
if (buildDirectory.existsSync()) {
65+
await buildDirectory.delete(recursive: true);
66+
}
67+
68+
if (dartFrogDirectory.existsSync()) {
69+
await dartFrogDirectory.delete(recursive: true);
70+
}
71+
72+
try {
73+
await copyPath(
74+
projectDirectory.path,
75+
'${tempDirectory.path}${path.separator}',
76+
);
77+
bundlingProgress.complete();
78+
} catch (error) {
79+
bundlingProgress.fail();
80+
context.logger.err('$error');
81+
return exit(1);
82+
}
83+
84+
await tempDirectory.rename(buildDirectory.path);
85+
}
86+
87+
Future<List<String>> getPathDependencies(io.Directory directory) async {
88+
final pubspec = Pubspec.parse(
89+
await io.File(path.join(directory.path, 'pubspec.yaml')).readAsString(),
90+
);
91+
92+
final dependencies = pubspec.dependencies;
93+
final devDependencies = pubspec.devDependencies;
94+
return [...dependencies.entries, ...devDependencies.entries]
95+
.where((entry) => entry.value is PathDependency)
96+
.map((entry) => (entry.value as PathDependency).path)
97+
.toList();
98+
}
99+
100+
void reportRouteConflicts(
101+
HookContext context,
102+
RouteConfiguration configuration,
103+
void Function(int exitCode) exit,
104+
) {
105+
final conflictingEndpoints =
106+
configuration.endpoints.entries.where((entry) => entry.value.length > 1);
107+
if (conflictingEndpoints.isNotEmpty) {
108+
for (final conflict in conflictingEndpoints) {
109+
final originalFilePath = path.normalize(
110+
path.join('routes', conflict.value.first.path),
111+
);
112+
final conflictingFilePath = path.normalize(
113+
path.join('routes', conflict.value.last.path),
114+
);
115+
context.logger.err(
116+
'''Route conflict detected. ${lightCyan.wrap(originalFilePath)} and ${lightCyan.wrap(conflictingFilePath)} both resolve to ${lightCyan.wrap(conflict.key)}.''',
117+
);
118+
}
119+
exit(1);
120+
}
121+
}
122+
123+
const _asyncRunZoned = runZoned;
124+
125+
abstract class ExitOverrides {
126+
static final _token = Object();
127+
128+
static ExitOverrides? get current {
129+
return Zone.current[_token] as ExitOverrides?;
130+
}
131+
132+
static R runZoned<R>(R Function() body, {void Function(int)? exit}) {
133+
final overrides = _ExitOverridesScope(exit);
134+
return _asyncRunZoned(body, zoneValues: {_token: overrides});
135+
}
136+
137+
void Function(int exitCode) get exit => io.exit;
138+
}
139+
140+
class _ExitOverridesScope extends ExitOverrides {
141+
_ExitOverridesScope(this._exit);
142+
143+
final ExitOverrides? _previous = ExitOverrides.current;
144+
final void Function(int exitCode)? _exit;
145+
146+
@override
147+
void Function(int exitCode) get exit {
148+
return _exit ?? _previous?.exit ?? super.exit;
149+
}
150+
}

bricks/dart_frog_prod_server/hooks/pubspec.yaml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,13 @@ environment:
55
sdk: ">=2.12.0 <3.0.0"
66

77
dependencies:
8-
dart_frog_gen: ^0.0.2-dev.4
8+
dart_frog_gen: ^0.0.2-dev.5
99
io: ^1.0.3
1010
mason: ^0.1.0-dev.30
1111
path: ^1.8.1
1212
pubspec_parse: ^1.2.0
13+
14+
dev_dependencies:
15+
mocktail: ^0.3.0
16+
test: ^1.19.2
17+
very_good_analysis: ^3.0.1

0 commit comments

Comments
 (0)