stampBuildDirectory function
Stamps a flutter build web output directory in place.
- Computes a SHA-256 content hash of the build (ignoring
version.jsonand this tool's own edits) and a build ID<hash16>-<UTC timestamp>. - Writes/merges
version.jsonwithbuildId,builtAt,appVersionandcontentHash, preserving keys Flutter already put there. - Cache-busts
main.dart.js,flutter_bootstrap.jsandflutter.jsreferences inindex.htmlandflutter_bootstrap.jswith?v=<id>. - Injects
<meta name="web-update-guard-build-id">intoindex.html.
Re-running on unchanged output is a byte-for-byte no-op unless force is
set, which issues a new timestamp. appVersion overrides the version read
from pubspecPath (default: <buildDir>/../../pubspec.yaml, then
./pubspec.yaml, then Flutter's own version.json fields). now exists
for tests.
Throws a StampException when the directory or index.html is missing
or malformed.
Implementation
StampResult stampBuildDirectory(
String buildDir, {
String? pubspecPath,
String? appVersion,
bool force = false,
DateTime? now,
}) {
final Directory dir = Directory(buildDir);
if (!dir.existsSync()) {
throw StampException('Build directory "$buildDir" does not exist.');
}
final File index = File(p.join(dir.path, 'index.html'));
if (!index.existsSync()) {
throw StampException(
'No index.html in "$buildDir". Run `flutter build web` first.',
);
}
final File bootstrap = File(p.join(dir.path, 'flutter_bootstrap.js'));
final File versionFile = File(p.join(dir.path, 'version.json'));
final String indexText = index.readAsStringSync();
final String? bootstrapText = bootstrap.existsSync()
? bootstrap.readAsStringSync()
: null;
final (String contentHash, int hashedCount) = _hashBuild(
dir,
indexText,
bootstrapText,
);
final Map<String, Object?> existing = _readJsonObject(versionFile);
DateTime builtAt;
bool reused = false;
final Object? previousAt = existing['builtAt'];
final DateTime? parsedPrevious = previousAt is String
? DateTime.tryParse(previousAt)
: null;
if (!force &&
existing['contentHash'] == contentHash &&
parsedPrevious != null) {
builtAt = parsedPrevious.toUtc();
reused = true;
} else {
final DateTime t = (now ?? DateTime.now()).toUtc();
builtAt = DateTime.utc(t.year, t.month, t.day, t.hour, t.minute, t.second);
}
final String buildId = formatBuildId(contentHash, builtAt);
final BuildInfo info = BuildInfo(
buildId: buildId,
builtAt: builtAt,
appVersion:
appVersion ??
_resolveAppVersion(dir, pubspecPath) ??
_flutterVersion(existing),
contentHash: contentHash,
);
final List<String> written = <String>[];
final String stampedIndex;
try {
stampedIndex = stampIndexHtml(indexText, buildId);
} on FormatException catch (e) {
throw StampException('Cannot stamp index.html: ${e.message}');
}
if (stampedIndex != indexText) {
index.writeAsStringSync(stampedIndex);
written.add('index.html');
}
if (bootstrapText != null) {
final String stamped = stampBootstrapJs(bootstrapText, buildId);
if (stamped != bootstrapText) {
bootstrap.writeAsStringSync(stamped);
written.add('flutter_bootstrap.js');
}
}
final Map<String, Object?> merged = <String, Object?>{
...existing,
...info.toJson(),
};
final String versionJson =
'${const JsonEncoder.withIndent(' ').convert(merged)}\n';
if (!versionFile.existsSync() ||
versionFile.readAsStringSync() != versionJson) {
versionFile.writeAsStringSync(versionJson);
written.add('version.json');
}
return StampResult(
info: info,
writtenFiles: written,
reusedBuiltAt: reused,
hashedFileCount: hashedCount,
);
}