check method
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));
}