fix(contract): deterministic model collection order

This commit is contained in:
2026-10-24 11:37:34 +03:00
parent 7308e3e972
commit 4aadc8a9d8
2 changed files with 30 additions and 10 deletions

View File

@@ -1654,11 +1654,12 @@
"Ingredient": {
"required": [
"name",
"unit",
"portion",
"product_nutritional_id"
],
"optional": [],
"optional": [
"unit"
],
"types": {
"name": "String",
"unit": "String",
@@ -3538,18 +3539,18 @@
}
},
"Step": {
"required": [],
"optional": [
"required": [
"image",
"description"
"description_md"
],
"optional": [],
"types": {
"image": "String",
"description": "String"
"description_md": "String"
},
"kinds": {
"image": "string",
"description": "string"
"description_md": "string"
}
},
"StepEntity": {

View File

@@ -31,7 +31,13 @@ void main(List<String> args) {
final sources = <File, String>{};
final enumNames = <String>{};
for (final file in root.listSync(recursive: true).whereType<File>()) {
final files = root.listSync(recursive: true).whereType<File>().toList();
// ДЕТЕРМИНИЗМ: сортируем по пути — порядок Directory.listSync зависит от файловой системы,
// а при коллизии имён моделей побеждает последняя обработанная. Без сортировки локально
// и на CI получаются разные манифесты (баг, из-за которого CI падал на чистом PR).
files.sort((a, b) => a.path.compareTo(b.path));
for (final file in files) {
if (!file.path.endsWith('.dart') || file.path.endsWith('.g.dart') || file.path.endsWith('.freezed.dart')) {
continue;
}
@@ -45,8 +51,21 @@ void main(List<String> args) {
}
final contract = <String, Map<String, dynamic>>{};
for (final source in sources.values) {
contract.addAll(extractModels(source));
// modelOrigin: имя модели -> файл, из которого она взята (для предупреждений о коллизиях).
final modelOrigin = <String, String>{};
for (final entry in sources.entries) {
final extracted = extractModels(entry.value);
for (final modelName in extracted.keys) {
if (contract.containsKey(modelName)) {
stderr.writeln(
'WARNING: model "$modelName" defined in both "${modelOrigin[modelName]}" '
'and "${entry.key.path}" — keeping the one from "${entry.key.path}" (sorted-order last wins). '
'Consider renaming the Dart classes to make the contract unambiguous.',
);
}
modelOrigin[modelName] = entry.key.path;
contract[modelName] = extracted[modelName]!;
}
}
// kinds считаются второй проходкой: нужны все имена моделей (contract.keys) и enum'ов.