generate function
Generates framework entry files from a scanned route tree.
Implementation
Future<List<GeneratedFile>> generate(RouteTree tree, BuildConfig config) async {
final outputDir = p.join(config.rootDir, config.outputDir);
// Generated Dart source files live in a dedicated src/ subdirectory so that
// compiled output (JS, native binaries) can sit alongside them in separate
// per-target subdirectories without mixing concerns.
final genDir = p.join(outputDir, 'src');
final sourceFiles = <String>{
for (final entry in tree.routes) entry.filePath,
for (final entry in tree.globalMiddleware) entry.filePath,
for (final entry in tree.scopedMiddleware) entry.filePath,
for (final entry in tree.scopedErrors) entry.filePath,
?tree.fallback?.filePath,
?tree.hooks?.filePath,
}.toList()..sort();
final aliases = <String, String>{};
final imports = <String>[];
for (var i = 0; i < sourceFiles.length; i++) {
final filePath = sourceFiles[i];
final relative = p.relative(filePath, from: genDir).replaceAll('\\', '/');
final alias = '\$i$i';
aliases[filePath] = alias;
imports.add("import '$relative' as $alias;");
}
final routeGroups = <String, List<RouteEntry>>{};
for (final route in tree.routes) {
routeGroups.putIfAbsent(route.path, () => <RouteEntry>[]).add(route);
}
final openapi = config.openapi;
final hasDocsUi =
openapi != null && openapi.ui != null && openapi.output.type == 'route';
final app = StringBuffer()
..writeln("// Generated by spry - do not edit.")
..writeln(
"import 'package:spry/spry.dart' show Spry, ErrorRoute, MiddlewareRoute;",
);
final usesHttpMethod =
hasDocsUi ||
routeGroups.values.any(
(entries) => entries.any((it) => it.method != null),
) ||
tree.globalMiddleware.any((it) => it.method != null) ||
tree.scopedMiddleware.any((it) => it.method != null) ||
tree.scopedErrors.any((it) => it.method != null);
if (usesHttpMethod) {
app.writeln("import 'package:spry/spry.dart' show HttpMethod;");
}
for (final line in imports) {
app.writeln(line);
}
if (hasDocsUi) {
app.writeln("import '_openapi_docs.dart' as \$docs;");
}
app
..writeln()
..writeln('final app = Spry(')
..writeln(' caseSensitive: ${config.caseSensitive},')
..write(
config.handlerCacheCapacity == null
? ''
: ' handlerCacheCapacity: ${config.handlerCacheCapacity},\n',
)
..writeln(' routes: {');
final sortedPaths = routeGroups.keys.toList()..sort();
for (final path in sortedPaths) {
final entries = routeGroups[path]!..sort(_compareRouteEntries);
app.writeln(" '${_escape(path)}': {");
for (final entry in entries) {
final key = entry.method == null ? 'null' : _methodLiteral(entry.method!);
final alias = aliases[entry.filePath]!;
app.writeln(' $key: ${_handlerLiteral(alias)},');
}
app.writeln(' },');
}
if (hasDocsUi) {
final docsRoute = _escape(openapi!.ui!.route);
app.writeln(" '$docsRoute': {");
app.writeln(' HttpMethod.get: \$docs.handler,');
app.writeln(' },');
}
app
..writeln(' },')
..writeln(' middleware: [');
for (final entry in [...tree.globalMiddleware, ...tree.scopedMiddleware]) {
final alias = aliases[entry.filePath]!;
final methodPart = entry.method == null
? ''
: ', method: ${_methodLiteral(entry.method!)}';
app.writeln(
" MiddlewareRoute(path: '${_escape(entry.path)}'$methodPart, handler: $alias.middleware),",
);
}
app
..writeln(' ],')
..writeln(' errors: [');
for (final entry in tree.scopedErrors) {
final alias = aliases[entry.filePath]!;
final methodPart = entry.method == null
? ''
: ', method: ${_methodLiteral(entry.method!)}';
app.writeln(
" ErrorRoute(path: '${_escape(entry.path)}'$methodPart, handler: $alias.onError),",
);
}
app.writeln(' ],');
if (tree.fallback case final fallback?) {
final alias = aliases[fallback.filePath]!;
app
..writeln(' fallback: {')
..writeln(' null: ${_handlerLiteral(alias)},')
..writeln(' },');
}
if (config.target != BuildTarget.cloudflare) {
app.writeln(" publicDir: '${_escape(config.publicDir)}',");
}
app.writeln(');');
final hooksBuffer = StringBuffer()
..writeln("// Generated by spry - do not edit.");
if (tree.hooks case final hooks?) {
hooksBuffer
..writeln(
"import '${_relativeImport(hooks.filePath, from: genDir)}' as \$source;",
)
..writeln()
..writeln(
hooks.hasOnStart
? 'final onStart = \$source.onStart;'
: 'final onStart = null;',
)
..writeln(
hooks.hasOnStop
? 'final onStop = \$source.onStop;'
: 'final onStop = null;',
)
..writeln(
hooks.hasOnError
? 'final onError = \$source.onError;'
: 'final onError = null;',
);
} else {
hooksBuffer
..writeln()
..writeln('final onStart = null;')
..writeln('final onStop = null;')
..writeln('final onError = null;');
}
final spec = buildTargetSpec(config);
final main = _generateMain(spec);
final files = <GeneratedFile>[
GeneratedFile(path: 'src/app.dart', content: app.toString()),
GeneratedFile(path: 'src/hooks.dart', content: hooksBuffer.toString()),
GeneratedFile(path: 'src/main.dart', content: main),
];
final openApiFile = generateOpenApiDocument(tree, config);
if (openApiFile != null) {
files.add(openApiFile);
}
if (hasDocsUi) {
files.add(_generateDocsFile(openapi!));
}
files.addAll(spec.extraFiles);
return files;
}