check method

Future<ContractResult> check(
  1. ContractCase contractCase
)

Runs the stages 3 to 5 of the pipeline and missingTemplateTags for contractCase, then, if render is on and they found no error, the stages 7 and 8 and checkRendered.

The stage 5 gets the options of the case and roleOptions, which smf create gets for the app of the case, so an option of a role that the app lacks is a warning of the result, as in smf create.

As the pipeline does, it renders the app with the contributions that the choices of stage 7 decided (see Collection.decide), and the result has them, with the ContractResult.hook of that app; the stages before take a contribution with conditions on what the roles chose as one that applies.

Before the stage 7, it reports a value that the case or roleOptions give an option that does not take it (see RoleOption.allowed), as the command line would refuse it, and makes no choice with it.

Between the stages 7 and 8, it holds the choice of each role of the app with a mode option to what the option states (see RoleOption.mode). For that it makes the choices of the app a second time, without a terminal, as smf create --no-input makes them when the matrices generate the app. The value of a choice is what RoleTemplate.optionsOf gives the option for it:

  • with a value of the option, from the case or from roleOptions, the choice has that value in both runs, so a template that does not read the option, or not without a terminal, is an error;
  • without one, the choice has the first value of the option in both runs, also when the role asks and the harness answers as a user who presses Enter;
  • no role fails or asks in the run without a terminal, which gets no value for a mode option that the case leaves out;
  • the two choices of the role are equal, so that the run without a terminal generates the app that the harness renders.

The issues of the choices that the app is rendered with, those of the run in a terminal, are issues of the result too: the warnings that the hooks report with RoleChoiceContext.warn (see ContractResult.warnings), and the errors of such warnings with a code of another role. A hook that warns through the logger, in any of the runs, is an error of the role's template, since its warning has no code.

An app with such an error is not rendered.

Implementation

Future<ContractResult> check(ContractCase contractCase) async {
  final environment = PipelineEnvironment(
    _silentHost,
    interactive: false,
    skipExternalSetup: true,
  );
  final ResolverResult resolved;
  try {
    resolved = await resolve(
      requested: contractCase.requested,
      registry: registry,
      environment: environment,
      answers: {...contractCase.picks},
    );
  } on SmfUsageException catch (error) {
    return ContractResult(contractCase, [SmfIssue(error.message)]);
  }
  final resolution = resolved.resolution;
  if (resolution == null) {
    return ContractResult(contractCase, resolved.issues);
  }
  final collection = collect(resolution, context);
  final options = {...roleOptions, ...contractCase.roleOptions};
  final validation = validate(
    registry: registry,
    resolution: resolution,
    collection: collection,
    context: context,
    // The options that `smf create` gets for the app of the case.
    optionValues: options,
    interactive: environment.interactive,
    skipExternalSetup: environment.skipExternalSetup,
    setupLink: setupLink,
  );
  final checked = ContractResult(
    contractCase,
    [
      ...resolved.issues,
      ...validation.issues,
      ...missingTemplateTags(
        registry: registry,
        resolution: resolution,
        collection: collection,
      ),
      ..._copiedTemplateIssues(collection),
    ],
    resolution: resolution,
    collection: collection,
    validation: validation,
  );
  if (!render || checked.errors.isNotEmpty) return checked;

  final refused = _refusedValues(options);
  if (refused.isNotEmpty) return checked._with(refused);
  final Map<Role, Object?> choices;
  // The issues of the choices that the app is rendered with.
  final List<SmfIssue> chosen;
  final Collection decided;
  final RenderedApp app;
  final answering = _EnterPrompter();
  final logged = _LoggedWarnings();
  // The choices of the roles that asked, which the harness answered.
  final answered = <Role, Object?>{};
  final Map<String, String> answers;
  try {
    (:choices, issues: chosen) = await chooseRoles(
      resolution: resolution,
      collection: collection,
      optionValues: options,
      environment: PipelineEnvironment(
        _answeringHost(answering, logged),
        interactive: true,
        skipExternalSetup: true,
      ),
      context: context,
      onChoice: (role, choice) {
        logged.chose(role);
        if (!answering.asked) return;
        answering.asked = false;
        answered[role] = choice;
      },
    );
    final (options: given, :problems) = await _answersOf(
      answered,
      chosen: chosen,
      resolution: resolution,
      collection: collection,
      options: options,
      logged: logged,
    );
    if (problems.isNotEmpty) {
      return checked._with(
        [...chosen, ...problems, ...logged.problems],
        choices: choices,
      );
    }
    final replayed = await _replayProblems(
      choices,
      chosen: chosen,
      asked: {...answered.keys},
      resolution: resolution,
      collection: collection,
      options: options,
      answers: given,
      logged: logged,
    );
    if (replayed.isNotEmpty) {
      return checked._with(
        [...chosen, ...replayed, ...logged.problems],
        choices: choices,
      );
    }
    final ofChoices = [...chosen, ...logged.problems];
    if (ofChoices.any((issue) => issue.isError)) {
      return checked._with(ofChoices, choices: choices);
    }
    answers = given;
    decided = collection.decide(choices, resolution.presentRoles);
    app = renderApp(
      registry: registry,
      resolution: resolution,
      collection: decided,
      context: context,
      choices: choices,
      pubspec: validation.pubspec,
      manualSteps: validation.manualSteps,
      setupLink: validation.setupLink,
    );
  } on SmfUsageException catch (error) {
    return checked._with([SmfIssue(error.message)]);
  } on GenerationFailedException catch (error) {
    return checked._with([
      if (error.issues.isEmpty) SmfIssue(error.message),
      ...error.issues,
    ]);
  }
  final rendered = checked._with(
    chosen,
    collection: decided,
    choices: choices,
    answers: answers,
    app: app,
    hook: hookRequest(
      registry: registry,
      resolution: resolution,
      collection: decided,
      context: context,
      choices: choices,
      manualSteps: validation.manualSteps,
      setupLink: validation.setupLink,
    ),
  );
  return rendered._with(checkRendered(rendered, app));
}