prepare method

Future<Directory> prepare()

Create inspectable package directories and fixed tarballs in a new output. No registry writes occur. Existing preparations are never replaced.

Implementation

Future<Directory> prepare() async {
  for (final field in ['name', 'version']) {
    if (metadata.containsKey(field)) {
      throw ArgumentError('Supply "$field" through PackageRelease.$field.');
    }
  }
  final supplied = packageMetadata({
    ...metadata,
    'name': name,
    'version': version,
  });
  final destination = output.absolute;
  await checkOutputInputs(destination, [
    for (final module in modules) module.directory,
    ...assets.values,
  ]);
  if (await FileSystemEntity.type(destination.path, followLinks: false) !=
      FileSystemEntityType.notFound) {
    throw ArgumentError('Release output already exists: ${destination.path}');
  }
  await destination.parent.create(recursive: true);
  final staging = await destination.parent.createTemp('.napi-release-');
  var committed = false;
  try {
    final packages =
        modules.any((module) => module.target == BuildTarget.native)
        ? await assemblePackageSet(
            modules: modules,
            output: Directory('${staging.path}/packages'),
            metadata: supplied,
            target: target,
            wasmFallback: wasmFallback,
            assets: assets,
          )
        : [
            await assemblePackage(
              modules: modules,
              output: Directory('${staging.path}/packages/package'),
              metadata: supplied,
              target: target,
              wasmFallback: wasmFallback,
              assets: assets,
            ),
          ];
    final tarballs = await Directory('${staging.path}/tarballs').create();
    final prepared = <Map<String, Object?>>[];
    for (final directory in packages) {
      final result = await _npm([
        'pack',
        '--ignore-scripts',
        '--json',
        '--offline',
        '--pack-destination',
        tarballs.path,
      ], cwd: directory.path);
      final rows = jsonDecode(result.stdout as String);
      if (rows is! List || rows.length != 1 || rows.single is! Map) {
        throw const FormatException('npm pack did not return one package.');
      }
      final packed = rows.single as Map;
      final filename = packed['filename'];
      if (filename is! String ||
          path.basename(filename) != filename ||
          !filename.endsWith('.tgz')) {
        throw const FormatException(
          'npm pack returned an invalid tarball path.',
        );
      }
      final package = jsonDecode(
        await File('${directory.path}/package.json').readAsString(),
      ) as Map;
      final tarball = File('${tarballs.path}/$filename');
      final integrity = await _integrity(tarball);
      if (packed['name'] != package['name'] ||
          packed['version'] != version ||
          packed['integrity'] != integrity) {
        throw StateError(
          'npm pack output does not match ${package['name']}@$version.',
        );
      }
      prepared.add({
        'name': package['name'],
        'version': version,
        'directory': path.relative(directory.path, from: staging.path),
        'tarball': 'tarballs/$filename',
        'integrity': integrity,
      });
    }
    await File('${staging.path}/.napi-release.json').writeAsString(
      '${const JsonEncoder.withIndent('  ').convert({'format': 1, 'name': name, 'version': version, 'publishConfig': supplied['publishConfig'], 'packages': prepared})}\n',
    );
    publishDirectory(staging, destination);
    committed = true;
    return destination;
  } finally {
    if (!committed) await cleanBuildStaging(staging, committed: false);
  }
}