uploadRecordFor function

UploadRecordRequest? uploadRecordFor({
  1. required String commandName,
  2. required ArgParser parser,
  3. required ArgResults args,
  4. BuildManifest? manifest,
  5. String? fallbackVersionName()?,
})

Whether command is an invocation that records an upload, and what of.

Three things disqualify a run, and each was learned rather than designed:

  • Not upload. A promote moves a build the store already holds and is not the moment anything was published from this repository.
  • A subcommand that does not declare --commit. A parser that never had the option is skipped rather than interrogated.
  • No artifact in the run. play upload --metadata publishes a listing and hands over no build — documented in play/cli.dart as a first-class case, "listing-only pushes need no artifact". The record's scope is an artifact reached the store, not the upload command ran, and conflating them made a store-listing typo fix demand a commit, a version and a build number for a build that does not exist. Worse, an operator who supplied the previous build's numbers to get past that would be told the upload collided — exit 3, the loudest error here, on a run that uploaded nothing. Takes the three things the decision actually reads rather than a Command, because a Command's argResults is populated by run() and is null after a bare parse() — so a signature taking one can only be exercised by actually running a store command, which is why this was never tested.

Implementation

UploadRecordRequest? uploadRecordFor({
  required String commandName,
  required ArgParser parser,
  required ArgResults args,
  BuildManifest? manifest,
  String? Function()? fallbackVersionName,
}) {
  // **`parser.options` is what was *declared*; `ArgResults.options` is what
  // was *provided or defaulted*.** This asked the second and meant the first,
  // so recording was skipped for every caller that did not type `--commit` —
  // which is every caller using `--manifest`, the flag that exists to supply it.
  // Nothing failed: the guard is the silent kind, and `record-uploads: true`
  // read as working for as long as nobody looked for the tag.
  if (commandName != 'upload' || !parser.options.containsKey('commit')) {
    return null;
  }
  String? opt(String name) =>
      parser.options.containsKey(name) ? args.option(name) : null;

  // `--aab` on Play, `--artifact` on the App Store, or whatever the manifest
  // named. Absent from all three means nothing was handed over.
  final artifact = opt('aab') ?? opt('artifact') ?? manifest?.artifactPath;
  if (artifact == null) {
    return null;
  }

  // **The bytes have to exist before the record claims they were uploaded.**
  // The tag is written before the store is contacted, deliberately — the
  // documented meaning is "an upload was attempted with this artifact at this
  // commit", and the alternative failure is "shipped but unprovable". But it
  // was also written before anything opened the file, so a mistyped path left
  // a permanent, pushed tag for an upload that was never physically possible:
  // no store contacted, no bytes in existence. *Attempted* implies something
  // was tried, and nothing was.
  //
  // A `--manifest` caller was already covered, because `verify()` refuses a
  // missing artifact before this runs. A caller passing `--aab` or
  // `--artifact` directly — the first thing anyone writes — was not, and both
  // known consumers escaped it only by checking the file in their own scripts
  // first, which is a property of those scripts rather than of this tool.
  //
  // Costs one `stat`, no credentials and no network, and forfeits nothing:
  // everything the record protects against happens after this point.
  if (!File(artifact).existsSync()) {
    throw ReleaseException(
      'no artifact at $artifact, so there is nothing to record an upload of. '
      'The record is written before the store is contacted, and it would '
      'otherwise name an upload that could not have happened.',
    );
  }

  return UploadRecordRequest(
    version:
        opt('version-name') ??
        manifest?.versionName ??
        fallbackVersionName?.call(),
    build: opt('build-number') ?? manifest?.buildNumber,
    // The manifest's gitSha is the whole reason --commit exists, so a caller
    // that passed one has already answered the question the flag asks.
    commit: opt('commit') ?? manifest?.gitSha,
    checksum: manifest?.sha256Digest,
    // A dry run must not write a record: it deletes its store edit rather than
    // committing, so nothing is published and there is nothing to record.
    dryRun: args.options.contains('dry-run') && args.flag('dry-run'),
  );
}