-
Notifications
You must be signed in to change notification settings - Fork 150
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat(dart_frog_cli): add dart_frog_new_brick (#623)
- Loading branch information
1 parent
fbb8ca0
commit 8543fdb
Showing
25 changed files
with
1,315 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
name: dart_frog_new | ||
|
||
on: | ||
pull_request: | ||
paths: | ||
- ".github/workflows/dart_frog_new.yaml" | ||
- "bricks/dart_frog_new/hooks/**" | ||
push: | ||
branches: | ||
- main | ||
paths: | ||
- ".github/workflows/dart_frog_new.yaml" | ||
- "bricks/dart_frog_new/hooks/**" | ||
|
||
jobs: | ||
build: | ||
uses: VeryGoodOpenSource/very_good_workflows/.github/workflows/dart_package.yml@v1 | ||
with: | ||
runs_on: macos-latest | ||
working_directory: bricks/dart_frog_new/hooks | ||
analyze_directories: . |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
MIT License | ||
|
||
Copyright (c) 2023 Very Good Ventures | ||
|
||
Permission is hereby granted, free of charge, to any person obtaining a copy | ||
of this software and associated documentation files (the "Software"), to deal | ||
in the Software without restriction, including without limitation the rights | ||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | ||
copies of the Software, and to permit persons to whom the Software is | ||
furnished to do so, subject to the following conditions: | ||
|
||
The above copyright notice and this permission notice shall be included in all | ||
copies or substantial portions of the Software. | ||
|
||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | ||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | ||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | ||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | ||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | ||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | ||
SOFTWARE. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
# dart_frog_new | ||
|
||
[![Powered by Mason](https://img.shields.io/endpoint?url=https%3A%2F%2Ftinyurl.com%2Fmason-badge)](https://github.com/felangel/mason) | ||
|
||
A dart frog brick to create routes and middleware. | ||
|
||
_Generated by [mason][1] 🧱_ | ||
|
||
[1]: https://github.com/felangel/mason |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
import 'package:dart_frog/dart_frog.dart'; | ||
|
||
{{> route.dart}} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{{^params}}Response onRequest(RequestContext context) { | ||
{{/params}}{{#params.0}}Response onRequest( | ||
RequestContext context,{{#params}} | ||
String {{.}},{{/params}} | ||
) { | ||
{{/params.0}} return Response(body: 'Welcome to Dart Frog!'); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
name: dart_frog_new | ||
description: A dart frog brick to create routes and middleware | ||
version: 0.1.0+1 | ||
|
||
environment: | ||
mason: ">=0.1.0-dev <0.1.0" | ||
|
||
vars: | ||
route_path: | ||
type: string | ||
description: The path for the desired route | ||
prompt: What is the path for the desired route? | ||
type: | ||
type: enum | ||
description: Whether to create a route or a middleware | ||
prompt: Want to create a route or a middleware? | ||
default: route | ||
values: | ||
- route | ||
- middleware |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
.dart_tool | ||
.packages | ||
pubspec.lock | ||
build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,5 @@ | ||
include: package:very_good_analysis/analysis_options.4.0.0.yaml | ||
|
||
linter: | ||
rules: | ||
public_member_api_docs: false |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
import 'dart:io' as io; | ||
|
||
import 'package:dart_frog_new_hooks/src/exit_overrides.dart'; | ||
import 'package:mason/mason.dart'; | ||
import 'package:path/path.dart' as path; | ||
|
||
void _defaultExit(int code) => ExitOverrides.current?.exit ?? io.exit; | ||
|
||
Future<void> postGen( | ||
HookContext context, { | ||
io.Directory? directory, | ||
void Function(int exitCode) exit = _defaultExit, | ||
}) async { | ||
final dirPath = context.vars['dir_path'] as String; | ||
final currentDirectory = directory ?? io.Directory.current; | ||
|
||
final containingDirectoryPath = path.relative( | ||
io.Directory(path.join(currentDirectory.path, dirPath)).path, | ||
); | ||
final filename = context.vars['filename'] as String; | ||
try { | ||
io.Directory(containingDirectoryPath).createSync(recursive: true); | ||
io.File( | ||
path.join(currentDirectory.path, filename), | ||
).renameSync('$containingDirectoryPath/$filename'); | ||
} catch (error) { | ||
context.logger.err('$error'); | ||
return exit(1); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
import 'dart:io' as io; | ||
|
||
import 'package:dart_frog_gen/dart_frog_gen.dart'; | ||
import 'package:dart_frog_new_hooks/src/exit_overrides.dart'; | ||
import 'package:dart_frog_new_hooks/src/normalize_route_path.dart'; | ||
import 'package:dart_frog_new_hooks/src/parameter_syntax.dart'; | ||
import 'package:dart_frog_new_hooks/src/route_configuration_utils.dart'; | ||
import 'package:dart_frog_new_hooks/src/route_to_path.dart'; | ||
|
||
import 'package:mason/mason.dart'; | ||
import 'package:path/path.dart' as path; | ||
|
||
typedef RouteConfigurationBuilder = RouteConfiguration Function( | ||
io.Directory directory, | ||
); | ||
|
||
void _defaultExit(int code) => ExitOverrides.current?.exit ?? io.exit; | ||
|
||
void preGen( | ||
HookContext context, { | ||
io.Directory? directory, | ||
RouteConfigurationBuilder buildConfiguration = buildRouteConfiguration, | ||
void Function(int exitCode) exit = _defaultExit, | ||
}) { | ||
// The dart frog server project directory | ||
final projectDirectory = directory ?? io.Directory.current; | ||
|
||
// Build the route configuration | ||
final RouteConfiguration routeConfiguration; | ||
try { | ||
routeConfiguration = buildConfiguration(projectDirectory); | ||
} catch (error) { | ||
context.logger.err('$error'); | ||
return exit(1); | ||
} | ||
|
||
// Get the desired type of creation | ||
final type = context.vars['type'] as String; | ||
|
||
// Verify if current route configuration have conflicts and bail out if | ||
// any are found | ||
try { | ||
routeConfiguration.validate(); | ||
} on FormatException catch (exception) { | ||
context.logger.err('Failed to create $type: ${exception.message}'); | ||
return exit(1); | ||
} | ||
|
||
// The path in which the route will be created | ||
final routePath = normalizeRoutePath(context.vars['route_path'] as String); | ||
|
||
return _preGenRoute( | ||
context, | ||
routePath: routePath, | ||
routeConfiguration: routeConfiguration, | ||
projectDirectory: projectDirectory, | ||
exit: exit, | ||
); | ||
} | ||
|
||
void _preGenRoute( | ||
HookContext context, { | ||
required String routePath, | ||
required RouteConfiguration routeConfiguration, | ||
required io.Directory projectDirectory, | ||
required void Function(int exitCode) exit, | ||
}) { | ||
final routesDirectoryPath = path.relative( | ||
io.Directory(path.join(projectDirectory.path, 'routes')).path, | ||
); | ||
|
||
// Verify if the endpoint does already exist. | ||
final endpointExists = routeConfiguration.endpoints.containsKey(routePath); | ||
if (endpointExists) { | ||
context.logger.err('Failed to create route: $routePath already exists.'); | ||
return exit(1); | ||
} | ||
|
||
// Verify if the given route already exists as directory. | ||
final existsAsDirectory = io.Directory( | ||
path.withoutExtension( | ||
routeToPath( | ||
routePath, | ||
preamble: routesDirectoryPath, | ||
).toBracketParameterSyntax, | ||
), | ||
).existsSync(); | ||
|
||
// If the route does not exist as directory, we must check if any of its | ||
// ancestor routes exists as file routes to avoid rogue routes. | ||
if (!existsAsDirectory) { | ||
final fileRoute = routeConfiguration.containingFileRoute(routePath); | ||
|
||
if (fileRoute != null) { | ||
final filepath = path.normalize( | ||
path.join( | ||
routesDirectoryPath, | ||
fileRoute.path, | ||
), | ||
); | ||
|
||
io.Directory(path.withoutExtension(filepath)).createSync(); | ||
|
||
final newFilepath = filepath.replaceFirst('.dart', '/index.dart'); | ||
io.File(filepath).renameSync(newFilepath); | ||
context.logger.detail( | ||
'Renamed $filepath to $newFilepath to avoid rogue routes', | ||
); | ||
} | ||
} | ||
|
||
final routeFileName = routeToPath( | ||
routePath, | ||
preferIndex: existsAsDirectory, | ||
preamble: routesDirectoryPath, | ||
).toBracketParameterSyntax; | ||
|
||
context.logger.detail('Creating route file: $routeFileName'); | ||
|
||
final List<String> parameterNames; | ||
try { | ||
parameterNames = routeFileName.getParameterNames(); | ||
} on FormatException catch (exception) { | ||
context.logger.err('Failed to create route: ${exception.message}'); | ||
return exit(1); | ||
} | ||
|
||
context.vars['dir_path'] = path.dirname(routeFileName); | ||
context.vars['filename'] = path.basename(routeFileName); | ||
context.vars['params'] = parameterNames; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import 'dart:async'; | ||
import 'dart:io' as io; | ||
|
||
const _asyncRunZoned = runZoned; | ||
|
||
abstract class ExitOverrides { | ||
static final _token = Object(); | ||
|
||
static ExitOverrides? get current { | ||
return Zone.current[_token] as ExitOverrides?; | ||
} | ||
|
||
static R runZoned<R>(R Function() body, {void Function(int)? exit}) { | ||
final overrides = _ExitOverridesScope(exit); | ||
return _asyncRunZoned(body, zoneValues: {_token: overrides}); | ||
} | ||
|
||
void Function(int exitCode) get exit => io.exit; | ||
} | ||
|
||
class _ExitOverridesScope extends ExitOverrides { | ||
_ExitOverridesScope(this._exit); | ||
|
||
final ExitOverrides? _previous = ExitOverrides.current; | ||
final void Function(int exitCode)? _exit; | ||
|
||
@override | ||
void Function(int exitCode) get exit { | ||
return _exit ?? _previous?.exit ?? super.exit; | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
bricks/dart_frog_new/hooks/lib/src/normalize_route_path.dart
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import 'package:dart_frog_new_hooks/src/parameter_syntax.dart'; | ||
|
||
String normalizeRoutePath(String routePath) { | ||
final replaced = routePath.toDiamondParameterSyntax.replaceAll(r'\', '/'); | ||
|
||
final segments = replaced.split('/'); | ||
|
||
final normalizedSegments = | ||
segments.fold(<String>[], (previousValue, segment) { | ||
if (segment == '..') { | ||
if (previousValue.length > 1) { | ||
previousValue.removeLast(); | ||
} | ||
} else if (segment.isNotEmpty && segment != '.') { | ||
previousValue.add(segment.encodeSegment()); | ||
} | ||
return previousValue; | ||
}); | ||
|
||
return '/${normalizedSegments.join('/')}'; | ||
} | ||
|
||
extension on String { | ||
String encodeSegment() { | ||
final encoded = Uri.encodeComponent(this); | ||
if (hasDiamondParameter) { | ||
return encoded.replaceAll('%3C', '<').replaceAll('%3E', '>'); | ||
} | ||
return encoded; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
extension ParameterSyntax on String { | ||
/// Replaces [] for <> | ||
String get toDiamondParameterSyntax { | ||
return replaceAll('[', '<').replaceAll(']', '>'); | ||
} | ||
|
||
/// Replaces <> for [] | ||
String get toBracketParameterSyntax { | ||
return replaceAll('<', '[').replaceAll('>', ']'); | ||
} | ||
|
||
/// Detect if the given string has a < and a > after it | ||
bool get hasDiamondParameter { | ||
final regexp = RegExp('<.*?>'); | ||
return regexp.hasMatch(this); | ||
} | ||
|
||
/// Get the route parameters from the given string. | ||
List<String> getParameterNames() { | ||
final regexp = RegExp(r'\[(.*?)\]'); | ||
final names = regexp | ||
.allMatches(toBracketParameterSyntax) | ||
.map((m) => m[0]?.replaceAll(RegExp(r'[\[\]]'), '')) | ||
.where((el) => el != null) | ||
.cast<String>(); | ||
|
||
final duplicates = names | ||
.toSet() | ||
.where((element) => names.where((el) => el == element).length > 1); | ||
if (duplicates.isNotEmpty) { | ||
final plural = duplicates.length > 1; | ||
final message = 'Duplicate parameter name${plural ? 's' : ''} found: ' | ||
'${duplicates.join(', ')}'; | ||
throw FormatException(message); | ||
} | ||
|
||
return names.toList(); | ||
} | ||
} |
Oops, something went wrong.