entrypointContent method

String entrypointContent(
  1. Iterable<ConstructYaml> constructs, {
  2. required Directory root,
})

Implementation

String entrypointContent(
  Iterable<ConstructYaml> constructs, {
  required Directory root,
}) {
  const revaliConstruct = 'package:revali_construct/revali_construct.dart';
  const revali = 'package:revali/revali.dart';

  final conflicts = <String, List<String>>{};

  for (final yaml in constructs) {
    for (final construct in yaml.constructs) {
      (conflicts[construct.name] ??= []).add(yaml.packageName);
    }
  }

  final constructItems = [
    for (final yaml in constructs)
      for (final construct in yaml.constructs)
        refer('$ConstructMaker', revaliConstruct).newInstance([], {
          'package': literalString(yaml.packageName),
          'isServer': refer('${construct.isServer}'),
          'isBuild': refer('${construct.isBuild}'),
          'optIn': refer('${construct.optIn}'),
          'name': literalString(construct.name),
          'hasNameConflict': literalBool(
            (conflicts[construct.name] ?? []).length > 1,
          ),
          'maker': refer(
            construct.method,
            '${yaml.packageUri}${construct.path}',
          ),
        }),
  ];

  final constructs0 = declareConst('_constructs')
      .assign(
        literalList(
          constructItems,
          refer('$ConstructMaker', revaliConstruct),
        ),
      )
      .statement;

  final path = declareConst(
    '_root',
  ).assign(literalString(root.path)).statement;

  final main = Method(
    (b) => b
      ..name = 'main'
      ..returns = refer('void')
      ..modifier = MethodModifier.async
      ..requiredParameters.add(
        Parameter(
          (b) => b
            ..name = 'args'
            ..type = TypeReference(
              (b) => b
                ..symbol = 'List'
                ..types.add(refer('String')),
            ),
        ),
      )
      ..optionalParameters.add(
        Parameter(
          (b) => b
            ..name = 'sendPort'
            ..type = TypeReference(
              (b) => b
                ..symbol = 'SendPort'
                ..url = 'dart:isolate'
                ..isNullable = true,
            ),
        ),
      )
      ..body = Block.of([
        declareFinal('result')
            .assign(
              refer('runConstruct', revali)
                  .call(
                    [refer('args')],
                    {
                      'constructs': refer('_constructs'),
                      'path': refer('_root'),
                    },
                  )
                  .awaited,
            )
            .statement,
        const Code('\n'),
        refer(
          'sendPort',
        ).nullSafeProperty('send').call([refer('result')]).statement,
        const Code('\n'),
        refer('exitCode', 'dart:io').assign(refer('result')).statement,
      ]),
  );

  final library = Library((b) => b.body.addAll([constructs0, path, main]));

  final emitter = DartEmitter(
    allocator: Allocator.simplePrefixing(),
    useNullSafetySyntax: true,
  );
  try {
    final content = StringBuffer()
      ..writeln('// ignore_for_file: directives_ordering')
      ..writeln(library.accept(emitter));

    final clean = DartFormatter(
      languageVersion: DartFormatter.latestLanguageVersion,
    ).format(content.toString());

    return clean;
  } on FormatterException {
    logger.err(
      'Generated build script could not be parsed.\n'
      'This is likely caused by a misconfigured builder definition.',
    );
    // TODO(mrgnhnt): throw custom exception
    throw Exception('Failed to generate build script');
  }
}