Skip to content

Commit 8543fdb

Browse files
authored
feat(dart_frog_cli): add dart_frog_new_brick (#623)
1 parent fbb8ca0 commit 8543fdb

25 files changed

+1315
-0
lines changed

.github/workflows/dart_frog_new.yaml

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
name: dart_frog_new
2+
3+
on:
4+
pull_request:
5+
paths:
6+
- ".github/workflows/dart_frog_new.yaml"
7+
- "bricks/dart_frog_new/hooks/**"
8+
push:
9+
branches:
10+
- main
11+
paths:
12+
- ".github/workflows/dart_frog_new.yaml"
13+
- "bricks/dart_frog_new/hooks/**"
14+
15+
jobs:
16+
build:
17+
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1
18+
with:
19+
runs_on: macos-latest
20+
working_directory: bricks/dart_frog_new/hooks
21+
analyze_directories: .

bricks/dart_frog_new/LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2023 Very Good Ventures
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

bricks/dart_frog_new/README.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
# dart_frog_new
2+
3+
[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason)
4+
5+
A dart frog brick to create routes and middleware.
6+
7+
_Generated by [mason][1] 🧱_
8+
9+
[1]: https://github.com/felangel/mason
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import 'package:dart_frog/dart_frog.dart';
2+
3+
{{> route.dart}}
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
{{^params}}Response onRequest(RequestContext context) {
2+
{{/params}}{{#params.0}}Response onRequest(
3+
RequestContext context,{{#params}}
4+
String {{.}},{{/params}}
5+
) {
6+
{{/params.0}} return Response(body: 'Welcome to Dart Frog!');
7+
}

bricks/dart_frog_new/brick.yaml

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
name: dart_frog_new
2+
description: A dart frog brick to create routes and middleware
3+
version: 0.1.0+1
4+
5+
environment:
6+
mason: ">=0.1.0-dev <0.1.0"
7+
8+
vars:
9+
route_path:
10+
type: string
11+
description: The path for the desired route
12+
prompt: What is the path for the desired route?
13+
type:
14+
type: enum
15+
description: Whether to create a route or a middleware
16+
prompt: Want to create a route or a middleware?
17+
default: route
18+
values:
19+
- route
20+
- middleware

bricks/dart_frog_new/hooks/.gitignore

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
.dart_tool
2+
.packages
3+
pubspec.lock
4+
build
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
include: package:very_good_analysis/analysis_options.4.0.0.yaml
2+
3+
linter:
4+
rules:
5+
public_member_api_docs: false
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
import 'dart:io' as io;
2+
3+
import 'package:dart_frog_new_hooks/src/exit_overrides.dart';
4+
import 'package:mason/mason.dart';
5+
import 'package:path/path.dart' as path;
6+
7+
void _defaultExit(int code) => ExitOverrides.current?.exit ?? io.exit;
8+
9+
Future<void> postGen(
10+
HookContext context, {
11+
io.Directory? directory,
12+
void Function(int exitCode) exit = _defaultExit,
13+
}) async {
14+
final dirPath = context.vars['dir_path'] as String;
15+
final currentDirectory = directory ?? io.Directory.current;
16+
17+
final containingDirectoryPath = path.relative(
18+
io.Directory(path.join(currentDirectory.path, dirPath)).path,
19+
);
20+
final filename = context.vars['filename'] as String;
21+
try {
22+
io.Directory(containingDirectoryPath).createSync(recursive: true);
23+
io.File(
24+
path.join(currentDirectory.path, filename),
25+
).renameSync('$containingDirectoryPath/$filename');
26+
} catch (error) {
27+
context.logger.err('$error');
28+
return exit(1);
29+
}
30+
}
Lines changed: 131 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,131 @@
1+
import 'dart:io' as io;
2+
3+
import 'package:dart_frog_gen/dart_frog_gen.dart';
4+
import 'package:dart_frog_new_hooks/src/exit_overrides.dart';
5+
import 'package:dart_frog_new_hooks/src/normalize_route_path.dart';
6+
import 'package:dart_frog_new_hooks/src/parameter_syntax.dart';
7+
import 'package:dart_frog_new_hooks/src/route_configuration_utils.dart';
8+
import 'package:dart_frog_new_hooks/src/route_to_path.dart';
9+
10+
import 'package:mason/mason.dart';
11+
import 'package:path/path.dart' as path;
12+
13+
typedef RouteConfigurationBuilder = RouteConfiguration Function(
14+
io.Directory directory,
15+
);
16+
17+
void _defaultExit(int code) => ExitOverrides.current?.exit ?? io.exit;
18+
19+
void preGen(
20+
HookContext context, {
21+
io.Directory? directory,
22+
RouteConfigurationBuilder buildConfiguration = buildRouteConfiguration,
23+
void Function(int exitCode) exit = _defaultExit,
24+
}) {
25+
// The dart frog server project directory
26+
final projectDirectory = directory ?? io.Directory.current;
27+
28+
// Build the route configuration
29+
final RouteConfiguration routeConfiguration;
30+
try {
31+
routeConfiguration = buildConfiguration(projectDirectory);
32+
} catch (error) {
33+
context.logger.err('$error');
34+
return exit(1);
35+
}
36+
37+
// Get the desired type of creation
38+
final type = context.vars['type'] as String;
39+
40+
// Verify if current route configuration have conflicts and bail out if
41+
// any are found
42+
try {
43+
routeConfiguration.validate();
44+
} on FormatException catch (exception) {
45+
context.logger.err('Failed to create $type: ${exception.message}');
46+
return exit(1);
47+
}
48+
49+
// The path in which the route will be created
50+
final routePath = normalizeRoutePath(context.vars['route_path'] as String);
51+
52+
return _preGenRoute(
53+
context,
54+
routePath: routePath,
55+
routeConfiguration: routeConfiguration,
56+
projectDirectory: projectDirectory,
57+
exit: exit,
58+
);
59+
}
60+
61+
void _preGenRoute(
62+
HookContext context, {
63+
required String routePath,
64+
required RouteConfiguration routeConfiguration,
65+
required io.Directory projectDirectory,
66+
required void Function(int exitCode) exit,
67+
}) {
68+
final routesDirectoryPath = path.relative(
69+
io.Directory(path.join(projectDirectory.path, 'routes')).path,
70+
);
71+
72+
// Verify if the endpoint does already exist.
73+
final endpointExists = routeConfiguration.endpoints.containsKey(routePath);
74+
if (endpointExists) {
75+
context.logger.err('Failed to create route: $routePath already exists.');
76+
return exit(1);
77+
}
78+
79+
// Verify if the given route already exists as directory.
80+
final existsAsDirectory = io.Directory(
81+
path.withoutExtension(
82+
routeToPath(
83+
routePath,
84+
preamble: routesDirectoryPath,
85+
).toBracketParameterSyntax,
86+
),
87+
).existsSync();
88+
89+
// If the route does not exist as directory, we must check if any of its
90+
// ancestor routes exists as file routes to avoid rogue routes.
91+
if (!existsAsDirectory) {
92+
final fileRoute = routeConfiguration.containingFileRoute(routePath);
93+
94+
if (fileRoute != null) {
95+
final filepath = path.normalize(
96+
path.join(
97+
routesDirectoryPath,
98+
fileRoute.path,
99+
),
100+
);
101+
102+
io.Directory(path.withoutExtension(filepath)).createSync();
103+
104+
final newFilepath = filepath.replaceFirst('.dart', '/index.dart');
105+
io.File(filepath).renameSync(newFilepath);
106+
context.logger.detail(
107+
'Renamed $filepath to $newFilepath to avoid rogue routes',
108+
);
109+
}
110+
}
111+
112+
final routeFileName = routeToPath(
113+
routePath,
114+
preferIndex: existsAsDirectory,
115+
preamble: routesDirectoryPath,
116+
).toBracketParameterSyntax;
117+
118+
context.logger.detail('Creating route file: $routeFileName');
119+
120+
final List<String> parameterNames;
121+
try {
122+
parameterNames = routeFileName.getParameterNames();
123+
} on FormatException catch (exception) {
124+
context.logger.err('Failed to create route: ${exception.message}');
125+
return exit(1);
126+
}
127+
128+
context.vars['dir_path'] = path.dirname(routeFileName);
129+
context.vars['filename'] = path.basename(routeFileName);
130+
context.vars['params'] = parameterNames;
131+
}

0 commit comments

Comments
 (0)