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