@_nazmiforreal/flutter-ota 0.1.2 → 0.1.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (61) hide show
  1. package/dart-src/packages/cli-tools/.dart_tool/package_config.json +496 -0
  2. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +743 -0
  3. package/dart-src/packages/cli-tools/lib/src/cli_base.dart +7 -6
  4. package/dart-src/packages/cli-tools/lib/src/commands/build.dart +46 -49
  5. package/dart-src/packages/cli-tools/lib/src/commands/bundle.dart +44 -23
  6. package/dart-src/packages/cli-tools/lib/src/commands/channel.dart +27 -17
  7. package/dart-src/packages/cli-tools/lib/src/commands/console.dart +11 -6
  8. package/dart-src/packages/cli-tools/lib/src/commands/deploy.dart +39 -22
  9. package/dart-src/packages/cli-tools/lib/src/commands/doctor.dart +22 -13
  10. package/dart-src/packages/cli-tools/lib/src/commands/init.dart +6 -5
  11. package/dart-src/packages/cli-tools/lib/src/commands/keys.dart +15 -9
  12. package/dart-src/packages/cli-tools/lib/src/commands/migrate.dart +14 -8
  13. package/dart-src/packages/cli-tools/lib/src/commands/rollback.dart +37 -12
  14. package/dart-src/packages/cli-tools/lib/src/operations.dart +45 -0
  15. package/dart-src/packages/cli-tools/lib/src/pack.dart +9 -0
  16. package/dart-src/packages/cli-tools/lib/src/ui/ui.dart +234 -0
  17. package/dart-src/packages/cli-tools/pubspec.lock +638 -0
  18. package/dart-src/packages/cli-tools/test/backends_test.dart +207 -0
  19. package/dart-src/packages/cli-tools/test/build_pack_test.dart +101 -0
  20. package/dart-src/packages/cli-tools/test/cli_test.dart +442 -0
  21. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +83 -0
  22. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +256 -0
  23. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +347 -0
  24. package/dart-src/packages/cli-tools/test/standalone_backend_test.dart +315 -0
  25. package/dart-src/packages/core/test/rollout_test.dart +128 -0
  26. package/dart-src/packages/core/test/semver_test.dart +52 -0
  27. package/dart-src/packages/core/test/semver_vectors.json +1 -0
  28. package/dart-src/packages/core/test/types_test.dart +129 -0
  29. package/dart-src/packages/core/test/uuid_test.dart +38 -0
  30. package/dart-src/plugins/aws/test/aws_database_test.dart +167 -0
  31. package/dart-src/plugins/aws/test/aws_lambda_edge_storage_test.dart +190 -0
  32. package/dart-src/plugins/aws/test/aws_storage_test.dart +73 -0
  33. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +83 -0
  34. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +122 -0
  35. package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +106 -0
  36. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +190 -0
  37. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +256 -0
  38. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +83 -0
  39. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +77 -0
  40. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +166 -0
  41. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +74 -0
  42. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +332 -0
  43. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +711 -0
  44. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +294 -0
  45. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +192 -0
  46. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +51 -0
  47. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +1600 -0
  48. package/dart-src/plugins/plugin-core/test/helpers_test.dart +259 -0
  49. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +139 -0
  50. package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +86 -0
  51. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +77 -0
  52. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +297 -0
  53. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +140 -0
  54. package/dart-src/plugins/postgres/test/postgres_database_test.dart +181 -0
  55. package/dart-src/plugins/standalone/test/standalone_test.dart +264 -0
  56. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +654 -0
  57. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +112 -0
  58. package/dart-src/plugins/supabase/test/supabase_database_test.dart +207 -0
  59. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +175 -0
  60. package/package.json +1 -1
  61. package/scripts/vendor.sh +29 -0
@@ -0,0 +1,315 @@
1
+ import 'dart:convert' show jsonDecode, jsonEncode, utf8;
2
+ import 'dart:io' show Directory, File;
3
+ import 'dart:typed_data' show Uint8List;
4
+
5
+ import 'package:archive/archive.dart';
6
+ import 'package:flutter_patcher_cli/flutter_patcher_cli.dart';
7
+ import 'package:http/http.dart' as http;
8
+ import 'package:http/testing.dart' show MockClient;
9
+ import 'package:test/test.dart';
10
+
11
+ // Stateful in-memory fake of the standalone (self-hosted) REST server.
12
+ class _FakeServer {
13
+ final Map<String, Map<String, dynamic>> bundles = {};
14
+ final Map<String, List<int>> storage = {};
15
+
16
+ Future<http.Response> handle(http.Request request) async {
17
+ final path = request.url.path;
18
+ final method = request.method;
19
+
20
+ if (path == '/api/bundles/channels' && method == 'GET') {
21
+ final channels = bundles.values.map((b) => b['channel'] as String).toSet().toList();
22
+ return http.Response(jsonEncode({'data': {'channels': channels}}), 200);
23
+ }
24
+ if (path == '/api/bundles' && method == 'POST') {
25
+ final list = jsonDecode(request.body) as List;
26
+ for (final item in list) {
27
+ final b = (item as Map).cast<String, dynamic>();
28
+ bundles[b['id'] as String] = {...b};
29
+ }
30
+ return http.Response(jsonEncode({'success': true}), 201);
31
+ }
32
+ if (path == '/api/bundles' && method == 'GET') {
33
+ final channel = request.url.queryParameters['channel'];
34
+ final enabled = request.url.queryParameters['enabled'];
35
+ var items = bundles.values.toList();
36
+ if (channel != null) items = items.where((b) => b['channel'] == channel).toList();
37
+ if (enabled == 'true') items = items.where((b) => b['enabled'] == true).toList();
38
+ // Emulate the DB's default id-desc ordering (latest bundle first).
39
+ items = items.reversed.toList();
40
+ return http.Response(
41
+ jsonEncode({
42
+ 'data': items,
43
+ 'pagination': {
44
+ 'total': items.length,
45
+ 'hasNextPage': false,
46
+ 'hasPreviousPage': false,
47
+ 'currentPage': 1,
48
+ 'totalPages': 1,
49
+ 'nextCursor': null,
50
+ 'previousCursor': null,
51
+ },
52
+ }),
53
+ 200,
54
+ );
55
+ }
56
+ if (path.startsWith('/api/bundles/') && method == 'PATCH') {
57
+ final id = path.substring('/api/bundles/'.length);
58
+ final patch = jsonDecode(request.body) as Map<String, dynamic>;
59
+ final existing = bundles[id];
60
+ if (existing == null) return http.Response('{"error":"not found"}', 404);
61
+ existing.addAll(patch);
62
+ return http.Response(jsonEncode({'success': true}), 200);
63
+ }
64
+ if (path.startsWith('/api/bundles/') && method == 'DELETE') {
65
+ final id = path.substring('/api/bundles/'.length);
66
+ bundles.remove(id);
67
+ return http.Response(jsonEncode({'success': true}), 200);
68
+ }
69
+ if (path.startsWith('/api/bundles/') && method == 'GET') {
70
+ final id = path.substring('/api/bundles/'.length);
71
+ final b = bundles[id];
72
+ if (b == null) return http.Response('{"error":"not found"}', 404);
73
+ return http.Response(jsonEncode(b), 200);
74
+ }
75
+ if (path == '/api/upload' && method == 'POST') {
76
+ final key = request.url.queryParameters['key']!;
77
+ storage[key] = request.bodyBytes;
78
+ return http.Response(jsonEncode({'storageUri': 'mem://$key'}), 201);
79
+ }
80
+ if (path == '/api/getDownloadUrl' && method == 'POST') {
81
+ final uri = (jsonDecode(request.body) as Map)['storageUri'] as String;
82
+ final key = uri.replaceFirst('mem://', '');
83
+ return http.Response(
84
+ jsonEncode({'fileUrl': 'http://localhost/mem/$key'}),
85
+ 200,
86
+ );
87
+ }
88
+ if (path == '/api/delete' && method == 'DELETE') {
89
+ final uri = (jsonDecode(request.body) as Map)['storageUri'] as String;
90
+ storage.remove(uri.replaceFirst('mem://', ''));
91
+ return http.Response(jsonEncode({'success': true}), 200);
92
+ }
93
+ if (request.url.host == 'localhost' && path.startsWith('/mem/')) {
94
+ final key = path.substring('/mem/'.length);
95
+ final bytes = storage[key];
96
+ if (bytes == null) return http.Response('not found', 404);
97
+ return http.Response.bytes(bytes, 200);
98
+ }
99
+ return http.Response(jsonEncode({'error': 'not found'}), 404);
100
+ }
101
+ }
102
+
103
+ void main() {
104
+ group('cli backends', () {
105
+ test('standalone backend (self-hosted REST)', () async {
106
+ final fake = _FakeServer();
107
+ final client = MockClient(fake.handle);
108
+ final config = FlutterPatcherConfig(
109
+ provider: 'standalone',
110
+ supabase: const SupabaseConfigJson(url: 'https://x.supabase.co'),
111
+ standalone: StandaloneConfigJson(baseUrl: 'http://localhost'),
112
+ channel: 'production',
113
+ platform: 'android',
114
+ source: './dist',
115
+ );
116
+ final backend = resolveBackend(
117
+ config,
118
+ standaloneClientFactory: () => client,
119
+ );
120
+
121
+ final source = Directory.systemTemp.createTempSync('fp-src-');
122
+ File('${source.path}/app.so').writeAsStringSync('fake-libapp-bytes');
123
+ try {
124
+ final bundle = await deployBundle(
125
+ backend,
126
+ DeployOptions(
127
+ source: source.path,
128
+ channel: 'production',
129
+ platform: 'android',
130
+ message: 'first',
131
+ targetAppVersion: '>=1.0.0',
132
+ ),
133
+ );
134
+ expect(bundle.id, isNotEmpty);
135
+ expect(bundle.storageUri, isNotEmpty);
136
+ expect(await backend.storage.exists(bundle.storageUri), isTrue);
137
+
138
+ final dl = File('${Directory.systemTemp.path}/fp-dl-${bundle.id}.zip');
139
+ await backend.storage.downloadFile(bundle.storageUri, dl.path);
140
+ expect(dl.existsSync() && await dl.length() > 0, isTrue);
141
+ await dl.delete();
142
+
143
+ final listed = await listBundles(backend, const ListOptions());
144
+ expect(listed.data.where((b) => b.id == bundle.id), hasLength(1));
145
+
146
+ final live = await getChannel(backend, 'production');
147
+ expect(live?.id, bundle.id);
148
+
149
+ final second = await deployBundle(
150
+ backend,
151
+ DeployOptions(
152
+ source: source.path,
153
+ channel: 'production',
154
+ platform: 'android',
155
+ message: 'second',
156
+ targetAppVersion: '>=1.0.0',
157
+ ),
158
+ );
159
+ await promoteBundle(backend, second.id, 'production');
160
+ final disabled = await rollbackChannel(backend, 'production');
161
+ expect(disabled, second.id);
162
+
163
+ await deleteBundle(backend, bundle.id);
164
+ final afterDelete = await listBundles(
165
+ backend,
166
+ const ListOptions(channel: 'production'),
167
+ );
168
+ expect(
169
+ afterDelete.data.where((b) => b.id == bundle.id).isEmpty,
170
+ isTrue,
171
+ );
172
+ } finally {
173
+ source.deleteSync(recursive: true);
174
+ }
175
+ });
176
+
177
+ test('standalone backend signs the deployed bundle (md5 fileHash + '
178
+ 'metadata.signature verifiable)', () async {
179
+ final (privateB64, publicB64) = await generateEd25519KeyPair();
180
+ final fake = _FakeServer();
181
+ final client = MockClient(fake.handle);
182
+ final config = FlutterPatcherConfig(
183
+ provider: 'standalone',
184
+ supabase: const SupabaseConfigJson(url: 'https://x.supabase.co'),
185
+ standalone: StandaloneConfigJson(baseUrl: 'http://localhost'),
186
+ channel: 'production',
187
+ platform: 'android',
188
+ source: './dist',
189
+ );
190
+ final backend = resolveBackend(
191
+ config,
192
+ standaloneClientFactory: () => client,
193
+ );
194
+
195
+ final source = Directory.systemTemp.createTempSync('fp-src-signed-');
196
+ File('${source.path}/app.so').writeAsStringSync('fake-libapp-bytes');
197
+ try {
198
+ final bundle = await deployBundle(
199
+ backend,
200
+ DeployOptions(
201
+ source: source.path,
202
+ channel: 'production',
203
+ platform: 'android',
204
+ message: 'signed',
205
+ targetAppVersion: '>=1.0.0',
206
+ signingKeyBase64: privateB64,
207
+ ),
208
+ );
209
+
210
+ // fileHash must be a plain MD5 hex (device rejects anything else).
211
+ expect(bundle.fileHash, matches(RegExp(r'^[0-9a-f]{32}$')));
212
+ expect(bundle.metadata, isNotNull);
213
+ final signature = bundle.metadata!.signature;
214
+ expect(signature, isNotNull);
215
+ expect(signature, isNotEmpty);
216
+
217
+ // The signature verifies over the MD5-hex UTF-8 bytes with the SPKI
218
+ // public key, exactly as the Android device SDK checks it.
219
+ final verified = await ed25519Verify(
220
+ utf8.encode(bundle.fileHash),
221
+ signature!,
222
+ publicB64,
223
+ );
224
+ expect(verified, isTrue);
225
+ } finally {
226
+ source.deleteSync(recursive: true);
227
+ }
228
+ });
229
+
230
+ test('build + deploy uploads the real patch.zip (no nested-zip mangling)', () async {
231
+ // 1) Build a synthetic multi-ABI APK in memory.
232
+ final apk = Archive();
233
+ for (final abi in const ['arm64-v8a', 'armeabi-v7a', 'x86_64']) {
234
+ final bytes = utf8.encode('LIBAPP_$abi');
235
+ apk.addFile(ArchiveFile('lib/$abi/libapp.so', bytes.length, bytes));
236
+ }
237
+ final apkDir = Directory.systemTemp.createTempSync('fp-apk-');
238
+ final apkFile = File('${apkDir.path}/app.apk')
239
+ ..writeAsBytesSync(Uint8List.fromList(ZipEncoder().encode(apk)));
240
+
241
+ // 2) Pack it.
242
+ final distDir = Directory.systemTemp.createTempSync('fp-dist-');
243
+ await packPatch(
244
+ apkPath: apkFile.path,
245
+ version: '9.9.9',
246
+ targetVersionCode: 42,
247
+ out: distDir.path,
248
+ );
249
+
250
+ // 3) Deploy the packed dist/ with a signing key.
251
+ final (privateB64, publicB64) = await generateEd25519KeyPair();
252
+ final fake = _FakeServer();
253
+ final client = MockClient(fake.handle);
254
+ final config = FlutterPatcherConfig(
255
+ provider: 'standalone',
256
+ supabase: const SupabaseConfigJson(url: 'https://x.supabase.co'),
257
+ standalone: StandaloneConfigJson(baseUrl: 'http://localhost'),
258
+ channel: 'production',
259
+ platform: 'android',
260
+ source: distDir.path,
261
+ );
262
+ final backend = resolveBackend(
263
+ config,
264
+ standaloneClientFactory: () => client,
265
+ );
266
+
267
+ final bundle = await deployBundle(
268
+ backend,
269
+ DeployOptions(
270
+ source: distDir.path,
271
+ channel: 'production',
272
+ platform: 'android',
273
+ message: 'built',
274
+ targetAppVersion: '>=1.0.0',
275
+ signingKeyBase64: privateB64,
276
+ ),
277
+ );
278
+
279
+ // 4) Download what the device would fetch and make sure it is the
280
+ // genuine patch (root manifest.json + per-ABI libapp.so), NOT a
281
+ // zip that nests the real patch under dist/patch.zip (the old bug).
282
+ final dl = File('${distDir.path}/downloaded.zip');
283
+ await backend.storage.downloadFile(bundle.storageUri, dl.path);
284
+ final downloaded = ZipDecoder().decodeBytes(dl.readAsBytesSync());
285
+
286
+ final names = downloaded.files.map((f) => f.name).toList();
287
+ expect(names, contains('manifest.json'));
288
+ expect(names, contains('lib/arm64-v8a/libapp.so'));
289
+ expect(names, isNot(contains('dist/patch.zip')),
290
+ reason: 'deploy must upload the real patch, not re-zip the dir');
291
+
292
+ final manifest = Map<String, dynamic>.from(
293
+ jsonDecode(utf8.decode(
294
+ downloaded.files.firstWhere((f) => f.name == 'manifest.json').content
295
+ as List<int>,
296
+ )) as Map,
297
+ );
298
+ expect(manifest['schemaVersion'], 2);
299
+ expect((manifest['lib'] as Map).keys, hasLength(3));
300
+
301
+ // 5) signature chain still holds on the deployed bundle.
302
+ expect(
303
+ await ed25519Verify(
304
+ utf8.encode(bundle.fileHash),
305
+ bundle.metadata!.signature!,
306
+ publicB64,
307
+ ),
308
+ isTrue,
309
+ );
310
+
311
+ apkDir.deleteSync(recursive: true);
312
+ distDir.deleteSync(recursive: true);
313
+ });
314
+ });
315
+ }
@@ -0,0 +1,128 @@
1
+ import 'package:flutter_patcher_core/flutter_patcher_core.dart';
2
+ import 'package:test/test.dart';
3
+
4
+ /// Ground-truth values generated by executing hot-updater's actual
5
+ /// rollout.ts logic under node (see scripts/verify_rollout.mjs).
6
+ void main() {
7
+ group('ground-truth vectors (TS-verified)', () {
8
+ test('getDefaultNumericCohort matches TS', () {
9
+ expect(getDefaultNumericCohort('device-abc-123'), '809');
10
+ expect(getDefaultNumericCohort('ANDROID_ID_SAMPLE_42'), '64');
11
+ expect(getDefaultNumericCohort('8f2a1b6c'), '996');
12
+ });
13
+
14
+ test('shuffle parameters match TS', () {
15
+ expect(getNumericCohortRolloutPosition('bundle-a', 1), 309);
16
+ expect(getNumericCohortRolloutPosition('bundle-a', 500), 228);
17
+ expect(getNumericCohortRolloutPosition('bundle-b', 999), 816);
18
+ });
19
+
20
+ test('rolled-out cohort sets match TS', () {
21
+ final a137 = getRolledOutNumericCohorts('bundle-a', 137);
22
+ expect(a137.take(3), [4, 11, 16]);
23
+ expect(a137.skip(a137.length - 3), [980, 985, 992]);
24
+ expect(a137.length, 137);
25
+
26
+ final b100 = getRolledOutNumericCohorts('bundle-b', 100);
27
+ expect(b100.take(3), [1, 14, 22]);
28
+ });
29
+ });
30
+
31
+ group('getRolledOutNumericCohorts (spec cases)', () {
32
+ test('ascending unique set matching eligibility', () {
33
+ final cohorts = getRolledOutNumericCohorts('x-bundle', 137);
34
+ expect(cohorts.length, 137);
35
+ expect(cohorts, [...cohorts]..sort());
36
+ expect(cohorts.toSet().length, 137);
37
+ for (final c in cohorts) {
38
+ expect(isCohortEligibleForUpdate('x-bundle', '$c', 137, null), isTrue);
39
+ }
40
+ final excluded = List.generate(1000, (i) => i + 1)
41
+ .firstWhere((c) => !cohorts.contains(c));
42
+ expect(
43
+ isCohortEligibleForUpdate('x-bundle', '$excluded', 137, null),
44
+ isFalse,
45
+ );
46
+ });
47
+
48
+ test('expanding rollout keeps existing cohorts', () {
49
+ final small = getRolledOutNumericCohorts('y-bundle', 100).toSet();
50
+ final large = getRolledOutNumericCohorts('y-bundle', 400);
51
+ expect(large.length, 400);
52
+ expect(large.toSet().containsAll(small), isTrue);
53
+ });
54
+
55
+ test('boundaries', () {
56
+ expect(getRolledOutNumericCohorts('z-bundle', 0), isEmpty);
57
+ expect(getRolledOutNumericCohorts('z-bundle', 1000).length, 1000);
58
+ expect(getRolledOutNumericCohorts('z-bundle', -5), isEmpty);
59
+ });
60
+ });
61
+
62
+ group('isCohortEligibleForUpdate (spec cases)', () {
63
+ test('numeric rollout stays active with target cohorts configured', () {
64
+ const bundleId = 'd-bundle';
65
+ final cohorts = getRolledOutNumericCohorts(bundleId, 137);
66
+ expect(
67
+ isCohortEligibleForUpdate(
68
+ bundleId, '${cohorts.first}', 137, ['qa-group']),
69
+ isTrue,
70
+ );
71
+ });
72
+
73
+ test('targeted custom cohort included beyond percentage', () {
74
+ expect(
75
+ isCohortEligibleForUpdate('e-bundle', 'qa-group', 137, ['qa-group']),
76
+ isTrue,
77
+ );
78
+ });
79
+
80
+ test('targeted numeric cohort wins even when excluded by rollout', () {
81
+ const bundleId = 'f-bundle';
82
+ final rolledOut = getRolledOutNumericCohorts(bundleId, 10).toSet();
83
+ final excluded = List.generate(1000, (i) => i + 1)
84
+ .firstWhere((c) => !rolledOut.contains(c));
85
+ expect(
86
+ isCohortEligibleForUpdate(bundleId, '$excluded', 10, ['$excluded']),
87
+ isTrue,
88
+ );
89
+ });
90
+
91
+ test('null cohort: eligible only at full rollout', () {
92
+ expect(isCohortEligibleForUpdate('g-bundle', null, 500, null), isFalse);
93
+ expect(isCohortEligibleForUpdate('g-bundle', null, null, null), isTrue);
94
+ expect(isCohortEligibleForUpdate('g-bundle', null, 0, null), isFalse);
95
+ });
96
+
97
+ test('custom cohorts never match percentage rollouts', () {
98
+ expect(isCohortEligibleForUpdate('h-bundle', 'qa-team', 999, null),
99
+ isFalse);
100
+ expect(
101
+ isCohortEligibleForUpdate('h-bundle', 'qa-team', 999, ['qa-team']),
102
+ isTrue,
103
+ );
104
+ });
105
+ });
106
+
107
+ group('cohort validation (spec cases)', () {
108
+ test('rejects empty and out-of-range numeric strings', () {
109
+ expect(isValidCohort(''), isFalse);
110
+ expect(isValidCohort('1001'), isFalse);
111
+ expect(isCustomCohort('1001'), isFalse);
112
+ });
113
+
114
+ test('enforces max custom length', () {
115
+ expect(isValidCohort('a' * maxCohortLength), isTrue);
116
+ expect(isValidCohort('a' * (maxCohortLength + 1)), isFalse);
117
+ });
118
+
119
+ test('message mentions limit', () {
120
+ expect(invalidCohortErrorMessage.contains('$maxCohortLength'), isTrue);
121
+ });
122
+
123
+ test('normalization trims and lowercases', () {
124
+ expect(normalizeCohortValue(' QA-Group '), 'qa-group');
125
+ expect(normalizeCohortValue('007'), '7');
126
+ });
127
+ });
128
+ }
@@ -0,0 +1,52 @@
1
+ import 'dart:convert';
2
+ import 'dart:io';
3
+
4
+ import 'package:flutter_patcher_core/flutter_patcher_core.dart';
5
+ import 'package:test/test.dart';
6
+
7
+ /// Ground truth generated by executing the REAL verkit package
8
+ /// (hot-updater's semver dependency) under node.
9
+ /// Regenerate: see scripts/verify_semver.mjs.
10
+ void main() {
11
+ final raw = File('test/semver_vectors.json').readAsStringSync();
12
+ final vectors = jsonDecode(raw) as Map<String, dynamic>;
13
+
14
+ group('semverCoerce (verkit ground truth)', () {
15
+ vectors['coerce']!.forEach((input, expected) {
16
+ test('coerce($input) → $expected', () {
17
+ final got = semverCoerce(input);
18
+ expect(got?.toString(), expected);
19
+ });
20
+ });
21
+ });
22
+
23
+ group('satisfies (verkit ground truth)', () {
24
+ for (final entry in (vectors['sat'] as List)) {
25
+ final range = entry[0] as String;
26
+ final version = entry[1] as String;
27
+ final expected = entry[2];
28
+ test('$version in "$range" → $expected', () {
29
+ final parsed = SemVer.tryParse(version)!;
30
+ if (expected == 'ERR') {
31
+ // verkit threw on invalid input; we treat as non-match.
32
+ expect(satisfies(parsed, range), isFalse);
33
+ } else {
34
+ expect(satisfies(parsed, range), expected,
35
+ reason: '$version vs range "$range"');
36
+ }
37
+ });
38
+ }
39
+ });
40
+
41
+ group('semverSatisfies facade', () {
42
+ test('mirrors hot-updater call order (coerce then satisfies)', () {
43
+ expect(semverSatisfies('^1.2.0', 'v1.3.0'), isTrue);
44
+ // coerce("1") -> 1.0.0 which does NOT match ^1.2.0 (verkit-verified).
45
+ expect(semverSatisfies('^1.2.0', '1'), isFalse);
46
+ // ...but ^1.0.0 matches coerced 1.0.0.
47
+ expect(semverSatisfies('^1.0.0', '1'), isTrue);
48
+ expect(semverSatisfies('^1.2.0', 'junk'), isFalse);
49
+ expect(semverSatisfies('~2.3.4', 'app version 2.3.6!'), isTrue);
50
+ });
51
+ });
52
+ }
@@ -0,0 +1 @@
1
+ {"coerce":{"0":"0.0.0","1":"1.0.0","12":"12.0.0","1.2.3":"1.2.3","v1.2.3":"1.2.3","=1.2.3":"1.2.3",">1.2.3":"1.2.3","1.2":"1.2.0","10.20.30":"10.20.30","1.2.3-beta.1":"1.2.3","1.2.3+build.5":"1.2.3","1.2.3-beta.1+build":"1.2.3"," v 1.2.3 ":"1.2.3","version 2.3.4":"2.3.4","x1.2.3":"1.2.3","1.2.3.4":"1.2.3","1..3":"1.0.0","abc":null,"1a2b3c":"1.0.0","1.2.x":"1.2.0","1.x.x":"1.0.0","*":null,"":null,"1.2.3-rc.2+meta":"1.2.3","99999999999999999999.1.1":"1.1.0","1.2.3 - 2.3.4":"1.2.3","~1.2.3":"1.2.3","^1.2.3":"1.2.3","1.2.3 || 2.0.0":"1.2.3"},"sat":[["1.2.3","0.0.0",false],["1.2.3","0.0.3",false],["1.2.3","0.2.0",false],["1.2.3","0.2.3",false],["1.2.3","1.0.0",false],["1.2.3","1.2.2",false],["1.2.3","1.2.3",true],["1.2.3","1.2.4",false],["1.2.3","1.2.9",false],["1.2.3","1.3.0",false],["1.2.3","1.5.2",false],["1.2.3","2.0.0",false],["1.2.3","2.3.4",false],["1.2.3","3.0.0",false],["1.2.3","1.2.3-alpha",false],["1.2.3","1.2.3-alpha.2",false],["1.2.3","1.2.3-beta.4",false],["1.2.3","2.0.0-rc.1",false],["1.2.3","1.2.3+build",true],["1.2.3","0.2.4",false],["=1.2.3","0.0.0",false],["=1.2.3","0.0.3",false],["=1.2.3","0.2.0",false],["=1.2.3","0.2.3",false],["=1.2.3","1.0.0",false],["=1.2.3","1.2.2",false],["=1.2.3","1.2.3",true],["=1.2.3","1.2.4",false],["=1.2.3","1.2.9",false],["=1.2.3","1.3.0",false],["=1.2.3","1.5.2",false],["=1.2.3","2.0.0",false],["=1.2.3","2.3.4",false],["=1.2.3","3.0.0",false],["=1.2.3","1.2.3-alpha",false],["=1.2.3","1.2.3-alpha.2",false],["=1.2.3","1.2.3-beta.4",false],["=1.2.3","2.0.0-rc.1",false],["=1.2.3","1.2.3+build",true],["=1.2.3","0.2.4",false],[">1.2.3","0.0.0",false],[">1.2.3","0.0.3",false],[">1.2.3","0.2.0",false],[">1.2.3","0.2.3",false],[">1.2.3","1.0.0",false],[">1.2.3","1.2.2",false],[">1.2.3","1.2.3",false],[">1.2.3","1.2.4",true],[">1.2.3","1.2.9",true],[">1.2.3","1.3.0",true],[">1.2.3","1.5.2",true],[">1.2.3","2.0.0",true],[">1.2.3","2.3.4",true],[">1.2.3","3.0.0",true],[">1.2.3","1.2.3-alpha",false],[">1.2.3","1.2.3-alpha.2",false],[">1.2.3","1.2.3-beta.4",false],[">1.2.3","2.0.0-rc.1",false],[">1.2.3","1.2.3+build",false],[">1.2.3","0.2.4",false],["<1.2.3","0.0.0",true],["<1.2.3","0.0.3",true],["<1.2.3","0.2.0",true],["<1.2.3","0.2.3",true],["<1.2.3","1.0.0",true],["<1.2.3","1.2.2",true],["<1.2.3","1.2.3",false],["<1.2.3","1.2.4",false],["<1.2.3","1.2.9",false],["<1.2.3","1.3.0",false],["<1.2.3","1.5.2",false],["<1.2.3","2.0.0",false],["<1.2.3","2.3.4",false],["<1.2.3","3.0.0",false],["<1.2.3","1.2.3-alpha",false],["<1.2.3","1.2.3-alpha.2",false],["<1.2.3","1.2.3-beta.4",false],["<1.2.3","2.0.0-rc.1",false],["<1.2.3","1.2.3+build",false],["<1.2.3","0.2.4",true],[">=1.2.3","0.0.0",false],[">=1.2.3","0.0.3",false],[">=1.2.3","0.2.0",false],[">=1.2.3","0.2.3",false],[">=1.2.3","1.0.0",false],[">=1.2.3","1.2.2",false],[">=1.2.3","1.2.3",true],[">=1.2.3","1.2.4",true],[">=1.2.3","1.2.9",true],[">=1.2.3","1.3.0",true],[">=1.2.3","1.5.2",true],[">=1.2.3","2.0.0",true],[">=1.2.3","2.3.4",true],[">=1.2.3","3.0.0",true],[">=1.2.3","1.2.3-alpha",false],[">=1.2.3","1.2.3-alpha.2",false],[">=1.2.3","1.2.3-beta.4",false],[">=1.2.3","2.0.0-rc.1",false],[">=1.2.3","1.2.3+build",true],[">=1.2.3","0.2.4",false],["<=1.2.3","0.0.0",true],["<=1.2.3","0.0.3",true],["<=1.2.3","0.2.0",true],["<=1.2.3","0.2.3",true],["<=1.2.3","1.0.0",true],["<=1.2.3","1.2.2",true],["<=1.2.3","1.2.3",true],["<=1.2.3","1.2.4",false],["<=1.2.3","1.2.9",false],["<=1.2.3","1.3.0",false],["<=1.2.3","1.5.2",false],["<=1.2.3","2.0.0",false],["<=1.2.3","2.3.4",false],["<=1.2.3","3.0.0",false],["<=1.2.3","1.2.3-alpha",false],["<=1.2.3","1.2.3-alpha.2",false],["<=1.2.3","1.2.3-beta.4",false],["<=1.2.3","2.0.0-rc.1",false],["<=1.2.3","1.2.3+build",true],["<=1.2.3","0.2.4",true],["1.2.x","0.0.0",false],["1.2.x","0.0.3",false],["1.2.x","0.2.0",false],["1.2.x","0.2.3",false],["1.2.x","1.0.0",false],["1.2.x","1.2.2",true],["1.2.x","1.2.3",true],["1.2.x","1.2.4",true],["1.2.x","1.2.9",true],["1.2.x","1.3.0",false],["1.2.x","1.5.2",false],["1.2.x","2.0.0",false],["1.2.x","2.3.4",false],["1.2.x","3.0.0",false],["1.2.x","1.2.3-alpha",false],["1.2.x","1.2.3-alpha.2",false],["1.2.x","1.2.3-beta.4",false],["1.2.x","2.0.0-rc.1",false],["1.2.x","1.2.3+build",true],["1.2.x","0.2.4",false],["1.X","0.0.0",false],["1.X","0.0.3",false],["1.X","0.2.0",false],["1.X","0.2.3",false],["1.X","1.0.0",true],["1.X","1.2.2",true],["1.X","1.2.3",true],["1.X","1.2.4",true],["1.X","1.2.9",true],["1.X","1.3.0",true],["1.X","1.5.2",true],["1.X","2.0.0",false],["1.X","2.3.4",false],["1.X","3.0.0",false],["1.X","1.2.3-alpha",false],["1.X","1.2.3-alpha.2",false],["1.X","1.2.3-beta.4",false],["1.X","2.0.0-rc.1",false],["1.X","1.2.3+build",true],["1.X","0.2.4",false],["1.*","0.0.0",false],["1.*","0.0.3",false],["1.*","0.2.0",false],["1.*","0.2.3",false],["1.*","1.0.0",true],["1.*","1.2.2",true],["1.*","1.2.3",true],["1.*","1.2.4",true],["1.*","1.2.9",true],["1.*","1.3.0",true],["1.*","1.5.2",true],["1.*","2.0.0",false],["1.*","2.3.4",false],["1.*","3.0.0",false],["1.*","1.2.3-alpha",false],["1.*","1.2.3-alpha.2",false],["1.*","1.2.3-beta.4",false],["1.*","2.0.0-rc.1",false],["1.*","1.2.3+build",true],["1.*","0.2.4",false],["*","0.0.0",true],["*","0.0.3",true],["*","0.2.0",true],["*","0.2.3",true],["*","1.0.0",true],["*","1.2.2",true],["*","1.2.3",true],["*","1.2.4",true],["*","1.2.9",true],["*","1.3.0",true],["*","1.5.2",true],["*","2.0.0",true],["*","2.3.4",true],["*","3.0.0",true],["*","1.2.3-alpha",false],["*","1.2.3-alpha.2",false],["*","1.2.3-beta.4",false],["*","2.0.0-rc.1",false],["*","1.2.3+build",true],["*","0.2.4",true],["1.x","0.0.0",false],["1.x","0.0.3",false],["1.x","0.2.0",false],["1.x","0.2.3",false],["1.x","1.0.0",true],["1.x","1.2.2",true],["1.x","1.2.3",true],["1.x","1.2.4",true],["1.x","1.2.9",true],["1.x","1.3.0",true],["1.x","1.5.2",true],["1.x","2.0.0",false],["1.x","2.3.4",false],["1.x","3.0.0",false],["1.x","1.2.3-alpha",false],["1.x","1.2.3-alpha.2",false],["1.x","1.2.3-beta.4",false],["1.x","2.0.0-rc.1",false],["1.x","1.2.3+build",true],["1.x","0.2.4",false],["1.2","0.0.0",false],["1.2","0.0.3",false],["1.2","0.2.0",false],["1.2","0.2.3",false],["1.2","1.0.0",false],["1.2","1.2.2",true],["1.2","1.2.3",true],["1.2","1.2.4",true],["1.2","1.2.9",true],["1.2","1.3.0",false],["1.2","1.5.2",false],["1.2","2.0.0",false],["1.2","2.3.4",false],["1.2","3.0.0",false],["1.2","1.2.3-alpha",false],["1.2","1.2.3-alpha.2",false],["1.2","1.2.3-beta.4",false],["1.2","2.0.0-rc.1",false],["1.2","1.2.3+build",true],["1.2","0.2.4",false],["1","0.0.0",false],["1","0.0.3",false],["1","0.2.0",false],["1","0.2.3",false],["1","1.0.0",true],["1","1.2.2",true],["1","1.2.3",true],["1","1.2.4",true],["1","1.2.9",true],["1","1.3.0",true],["1","1.5.2",true],["1","2.0.0",false],["1","2.3.4",false],["1","3.0.0",false],["1","1.2.3-alpha",false],["1","1.2.3-alpha.2",false],["1","1.2.3-beta.4",false],["1","2.0.0-rc.1",false],["1","1.2.3+build",true],["1","0.2.4",false],["~1.2.3","0.0.0",false],["~1.2.3","0.0.3",false],["~1.2.3","0.2.0",false],["~1.2.3","0.2.3",false],["~1.2.3","1.0.0",false],["~1.2.3","1.2.2",false],["~1.2.3","1.2.3",true],["~1.2.3","1.2.4",true],["~1.2.3","1.2.9",true],["~1.2.3","1.3.0",false],["~1.2.3","1.5.2",false],["~1.2.3","2.0.0",false],["~1.2.3","2.3.4",false],["~1.2.3","3.0.0",false],["~1.2.3","1.2.3-alpha",false],["~1.2.3","1.2.3-alpha.2",false],["~1.2.3","1.2.3-beta.4",false],["~1.2.3","2.0.0-rc.1",false],["~1.2.3","1.2.3+build",true],["~1.2.3","0.2.4",false],["~1.2","0.0.0",false],["~1.2","0.0.3",false],["~1.2","0.2.0",false],["~1.2","0.2.3",false],["~1.2","1.0.0",false],["~1.2","1.2.2",true],["~1.2","1.2.3",true],["~1.2","1.2.4",true],["~1.2","1.2.9",true],["~1.2","1.3.0",false],["~1.2","1.5.2",false],["~1.2","2.0.0",false],["~1.2","2.3.4",false],["~1.2","3.0.0",false],["~1.2","1.2.3-alpha",false],["~1.2","1.2.3-alpha.2",false],["~1.2","1.2.3-beta.4",false],["~1.2","2.0.0-rc.1",false],["~1.2","1.2.3+build",true],["~1.2","0.2.4",false],["~1","0.0.0",false],["~1","0.0.3",false],["~1","0.2.0",false],["~1","0.2.3",false],["~1","1.0.0",true],["~1","1.2.2",true],["~1","1.2.3",true],["~1","1.2.4",true],["~1","1.2.9",true],["~1","1.3.0",true],["~1","1.5.2",true],["~1","2.0.0",false],["~1","2.3.4",false],["~1","3.0.0",false],["~1","1.2.3-alpha",false],["~1","1.2.3-alpha.2",false],["~1","1.2.3-beta.4",false],["~1","2.0.0-rc.1",false],["~1","1.2.3+build",true],["~1","0.2.4",false],["~0.2.3","0.0.0",false],["~0.2.3","0.0.3",false],["~0.2.3","0.2.0",false],["~0.2.3","0.2.3",true],["~0.2.3","1.0.0",false],["~0.2.3","1.2.2",false],["~0.2.3","1.2.3",false],["~0.2.3","1.2.4",false],["~0.2.3","1.2.9",false],["~0.2.3","1.3.0",false],["~0.2.3","1.5.2",false],["~0.2.3","2.0.0",false],["~0.2.3","2.3.4",false],["~0.2.3","3.0.0",false],["~0.2.3","1.2.3-alpha",false],["~0.2.3","1.2.3-alpha.2",false],["~0.2.3","1.2.3-beta.4",false],["~0.2.3","2.0.0-rc.1",false],["~0.2.3","1.2.3+build",false],["~0.2.3","0.2.4",true],["~1.2.3-beta.2","0.0.0",false],["~1.2.3-beta.2","0.0.3",false],["~1.2.3-beta.2","0.2.0",false],["~1.2.3-beta.2","0.2.3",false],["~1.2.3-beta.2","1.0.0",false],["~1.2.3-beta.2","1.2.2",false],["~1.2.3-beta.2","1.2.3",true],["~1.2.3-beta.2","1.2.4",true],["~1.2.3-beta.2","1.2.9",true],["~1.2.3-beta.2","1.3.0",false],["~1.2.3-beta.2","1.5.2",false],["~1.2.3-beta.2","2.0.0",false],["~1.2.3-beta.2","2.3.4",false],["~1.2.3-beta.2","3.0.0",false],["~1.2.3-beta.2","1.2.3-alpha",false],["~1.2.3-beta.2","1.2.3-alpha.2",false],["~1.2.3-beta.2","1.2.3-beta.4",true],["~1.2.3-beta.2","2.0.0-rc.1",false],["~1.2.3-beta.2","1.2.3+build",true],["~1.2.3-beta.2","0.2.4",false],["^1.2.3","0.0.0",false],["^1.2.3","0.0.3",false],["^1.2.3","0.2.0",false],["^1.2.3","0.2.3",false],["^1.2.3","1.0.0",false],["^1.2.3","1.2.2",false],["^1.2.3","1.2.3",true],["^1.2.3","1.2.4",true],["^1.2.3","1.2.9",true],["^1.2.3","1.3.0",true],["^1.2.3","1.5.2",true],["^1.2.3","2.0.0",false],["^1.2.3","2.3.4",false],["^1.2.3","3.0.0",false],["^1.2.3","1.2.3-alpha",false],["^1.2.3","1.2.3-alpha.2",false],["^1.2.3","1.2.3-beta.4",false],["^1.2.3","2.0.0-rc.1",false],["^1.2.3","1.2.3+build",true],["^1.2.3","0.2.4",false],["^0.2.3","0.0.0",false],["^0.2.3","0.0.3",false],["^0.2.3","0.2.0",false],["^0.2.3","0.2.3",true],["^0.2.3","1.0.0",false],["^0.2.3","1.2.2",false],["^0.2.3","1.2.3",false],["^0.2.3","1.2.4",false],["^0.2.3","1.2.9",false],["^0.2.3","1.3.0",false],["^0.2.3","1.5.2",false],["^0.2.3","2.0.0",false],["^0.2.3","2.3.4",false],["^0.2.3","3.0.0",false],["^0.2.3","1.2.3-alpha",false],["^0.2.3","1.2.3-alpha.2",false],["^0.2.3","1.2.3-beta.4",false],["^0.2.3","2.0.0-rc.1",false],["^0.2.3","1.2.3+build",false],["^0.2.3","0.2.4",true],["^0.0.3","0.0.0",false],["^0.0.3","0.0.3",true],["^0.0.3","0.2.0",false],["^0.0.3","0.2.3",false],["^0.0.3","1.0.0",false],["^0.0.3","1.2.2",false],["^0.0.3","1.2.3",false],["^0.0.3","1.2.4",false],["^0.0.3","1.2.9",false],["^0.0.3","1.3.0",false],["^0.0.3","1.5.2",false],["^0.0.3","2.0.0",false],["^0.0.3","2.3.4",false],["^0.0.3","3.0.0",false],["^0.0.3","1.2.3-alpha",false],["^0.0.3","1.2.3-alpha.2",false],["^0.0.3","1.2.3-beta.4",false],["^0.0.3","2.0.0-rc.1",false],["^0.0.3","1.2.3+build",false],["^0.0.3","0.2.4",false],["^1.x","0.0.0",false],["^1.x","0.0.3",false],["^1.x","0.2.0",false],["^1.x","0.2.3",false],["^1.x","1.0.0",true],["^1.x","1.2.2",true],["^1.x","1.2.3",true],["^1.x","1.2.4",true],["^1.x","1.2.9",true],["^1.x","1.3.0",true],["^1.x","1.5.2",true],["^1.x","2.0.0",false],["^1.x","2.3.4",false],["^1.x","3.0.0",false],["^1.x","1.2.3-alpha",false],["^1.x","1.2.3-alpha.2",false],["^1.x","1.2.3-beta.4",false],["^1.x","2.0.0-rc.1",false],["^1.x","1.2.3+build",true],["^1.x","0.2.4",false],["^0.0.x","0.0.0",true],["^0.0.x","0.0.3",true],["^0.0.x","0.2.0",false],["^0.0.x","0.2.3",false],["^0.0.x","1.0.0",false],["^0.0.x","1.2.2",false],["^0.0.x","1.2.3",false],["^0.0.x","1.2.4",false],["^0.0.x","1.2.9",false],["^0.0.x","1.3.0",false],["^0.0.x","1.5.2",false],["^0.0.x","2.0.0",false],["^0.0.x","2.3.4",false],["^0.0.x","3.0.0",false],["^0.0.x","1.2.3-alpha",false],["^0.0.x","1.2.3-alpha.2",false],["^0.0.x","1.2.3-beta.4",false],["^0.0.x","2.0.0-rc.1",false],["^0.0.x","1.2.3+build",false],["^0.0.x","0.2.4",false],["^0.0.0","0.0.0",true],["^0.0.0","0.0.3",false],["^0.0.0","0.2.0",false],["^0.0.0","0.2.3",false],["^0.0.0","1.0.0",false],["^0.0.0","1.2.2",false],["^0.0.0","1.2.3",false],["^0.0.0","1.2.4",false],["^0.0.0","1.2.9",false],["^0.0.0","1.3.0",false],["^0.0.0","1.5.2",false],["^0.0.0","2.0.0",false],["^0.0.0","2.3.4",false],["^0.0.0","3.0.0",false],["^0.0.0","1.2.3-alpha",false],["^0.0.0","1.2.3-alpha.2",false],["^0.0.0","1.2.3-beta.4",false],["^0.0.0","2.0.0-rc.1",false],["^0.0.0","1.2.3+build",false],["^0.0.0","0.2.4",false],["^1.2.3-beta.2","0.0.0",false],["^1.2.3-beta.2","0.0.3",false],["^1.2.3-beta.2","0.2.0",false],["^1.2.3-beta.2","0.2.3",false],["^1.2.3-beta.2","1.0.0",false],["^1.2.3-beta.2","1.2.2",false],["^1.2.3-beta.2","1.2.3",true],["^1.2.3-beta.2","1.2.4",true],["^1.2.3-beta.2","1.2.9",true],["^1.2.3-beta.2","1.3.0",true],["^1.2.3-beta.2","1.5.2",true],["^1.2.3-beta.2","2.0.0",false],["^1.2.3-beta.2","2.3.4",false],["^1.2.3-beta.2","3.0.0",false],["^1.2.3-beta.2","1.2.3-alpha",false],["^1.2.3-beta.2","1.2.3-alpha.2",false],["^1.2.3-beta.2","1.2.3-beta.4",true],["^1.2.3-beta.2","2.0.0-rc.1",false],["^1.2.3-beta.2","1.2.3+build",true],["^1.2.3-beta.2","0.2.4",false],[">1.2","0.0.0",false],[">1.2","0.0.3",false],[">1.2","0.2.0",false],[">1.2","0.2.3",false],[">1.2","1.0.0",false],[">1.2","1.2.2",false],[">1.2","1.2.3",false],[">1.2","1.2.4",false],[">1.2","1.2.9",false],[">1.2","1.3.0",true],[">1.2","1.5.2",true],[">1.2","2.0.0",true],[">1.2","2.3.4",true],[">1.2","3.0.0",true],[">1.2","1.2.3-alpha",false],[">1.2","1.2.3-alpha.2",false],[">1.2","1.2.3-beta.4",false],[">1.2","2.0.0-rc.1",false],[">1.2","1.2.3+build",false],[">1.2","0.2.4",false],["<1.2.9 >=1.2.3","0.0.0",false],["<1.2.9 >=1.2.3","0.0.3",false],["<1.2.9 >=1.2.3","0.2.0",false],["<1.2.9 >=1.2.3","0.2.3",false],["<1.2.9 >=1.2.3","1.0.0",false],["<1.2.9 >=1.2.3","1.2.2",false],["<1.2.9 >=1.2.3","1.2.3",true],["<1.2.9 >=1.2.3","1.2.4",true],["<1.2.9 >=1.2.3","1.2.9",false],["<1.2.9 >=1.2.3","1.3.0",false],["<1.2.9 >=1.2.3","1.5.2",false],["<1.2.9 >=1.2.3","2.0.0",false],["<1.2.9 >=1.2.3","2.3.4",false],["<1.2.9 >=1.2.3","3.0.0",false],["<1.2.9 >=1.2.3","1.2.3-alpha",false],["<1.2.9 >=1.2.3","1.2.3-alpha.2",false],["<1.2.9 >=1.2.3","1.2.3-beta.4",false],["<1.2.9 >=1.2.3","2.0.0-rc.1",false],["<1.2.9 >=1.2.3","1.2.3+build",true],["<1.2.9 >=1.2.3","0.2.4",false],[">=0.0.0","0.0.0",true],[">=0.0.0","0.0.3",true],[">=0.0.0","0.2.0",true],[">=0.0.0","0.2.3",true],[">=0.0.0","1.0.0",true],[">=0.0.0","1.2.2",true],[">=0.0.0","1.2.3",true],[">=0.0.0","1.2.4",true],[">=0.0.0","1.2.9",true],[">=0.0.0","1.3.0",true],[">=0.0.0","1.5.2",true],[">=0.0.0","2.0.0",true],[">=0.0.0","2.3.4",true],[">=0.0.0","3.0.0",true],[">=0.0.0","1.2.3-alpha",false],[">=0.0.0","1.2.3-alpha.2",false],[">=0.0.0","1.2.3-beta.4",false],[">=0.0.0","2.0.0-rc.1",false],[">=0.0.0","1.2.3+build",true],[">=0.0.0","0.2.4",true],["<=2.0","0.0.0",true],["<=2.0","0.0.3",true],["<=2.0","0.2.0",true],["<=2.0","0.2.3",true],["<=2.0","1.0.0",true],["<=2.0","1.2.2",true],["<=2.0","1.2.3",true],["<=2.0","1.2.4",true],["<=2.0","1.2.9",true],["<=2.0","1.3.0",true],["<=2.0","1.5.2",true],["<=2.0","2.0.0",true],["<=2.0","2.3.4",false],["<=2.0","3.0.0",false],["<=2.0","1.2.3-alpha",false],["<=2.0","1.2.3-alpha.2",false],["<=2.0","1.2.3-beta.4",false],["<=2.0","2.0.0-rc.1",false],["<=2.0","1.2.3+build",true],["<=2.0","0.2.4",true],["1.2.3 - 2.3.4","0.0.0",false],["1.2.3 - 2.3.4","0.0.3",false],["1.2.3 - 2.3.4","0.2.0",false],["1.2.3 - 2.3.4","0.2.3",false],["1.2.3 - 2.3.4","1.0.0",false],["1.2.3 - 2.3.4","1.2.2",false],["1.2.3 - 2.3.4","1.2.3",true],["1.2.3 - 2.3.4","1.2.4",true],["1.2.3 - 2.3.4","1.2.9",true],["1.2.3 - 2.3.4","1.3.0",true],["1.2.3 - 2.3.4","1.5.2",true],["1.2.3 - 2.3.4","2.0.0",true],["1.2.3 - 2.3.4","2.3.4",true],["1.2.3 - 2.3.4","3.0.0",false],["1.2.3 - 2.3.4","1.2.3-alpha",false],["1.2.3 - 2.3.4","1.2.3-alpha.2",false],["1.2.3 - 2.3.4","1.2.3-beta.4",false],["1.2.3 - 2.3.4","2.0.0-rc.1",false],["1.2.3 - 2.3.4","1.2.3+build",true],["1.2.3 - 2.3.4","0.2.4",false],["1.2 - 2.3.4","0.0.0",false],["1.2 - 2.3.4","0.0.3",false],["1.2 - 2.3.4","0.2.0",false],["1.2 - 2.3.4","0.2.3",false],["1.2 - 2.3.4","1.0.0",false],["1.2 - 2.3.4","1.2.2",true],["1.2 - 2.3.4","1.2.3",true],["1.2 - 2.3.4","1.2.4",true],["1.2 - 2.3.4","1.2.9",true],["1.2 - 2.3.4","1.3.0",true],["1.2 - 2.3.4","1.5.2",true],["1.2 - 2.3.4","2.0.0",true],["1.2 - 2.3.4","2.3.4",true],["1.2 - 2.3.4","3.0.0",false],["1.2 - 2.3.4","1.2.3-alpha",false],["1.2 - 2.3.4","1.2.3-alpha.2",false],["1.2 - 2.3.4","1.2.3-beta.4",false],["1.2 - 2.3.4","2.0.0-rc.1",false],["1.2 - 2.3.4","1.2.3+build",true],["1.2 - 2.3.4","0.2.4",false],["1.2.3 - 2.3","0.0.0",false],["1.2.3 - 2.3","0.0.3",false],["1.2.3 - 2.3","0.2.0",false],["1.2.3 - 2.3","0.2.3",false],["1.2.3 - 2.3","1.0.0",false],["1.2.3 - 2.3","1.2.2",false],["1.2.3 - 2.3","1.2.3",true],["1.2.3 - 2.3","1.2.4",true],["1.2.3 - 2.3","1.2.9",true],["1.2.3 - 2.3","1.3.0",true],["1.2.3 - 2.3","1.5.2",true],["1.2.3 - 2.3","2.0.0",true],["1.2.3 - 2.3","2.3.4",true],["1.2.3 - 2.3","3.0.0",false],["1.2.3 - 2.3","1.2.3-alpha",false],["1.2.3 - 2.3","1.2.3-alpha.2",false],["1.2.3 - 2.3","1.2.3-beta.4",false],["1.2.3 - 2.3","2.0.0-rc.1",false],["1.2.3 - 2.3","1.2.3+build",true],["1.2.3 - 2.3","0.2.4",false],["1.2.3-beta - 2","0.0.0",false],["1.2.3-beta - 2","0.0.3",false],["1.2.3-beta - 2","0.2.0",false],["1.2.3-beta - 2","0.2.3",false],["1.2.3-beta - 2","1.0.0",false],["1.2.3-beta - 2","1.2.2",false],["1.2.3-beta - 2","1.2.3",true],["1.2.3-beta - 2","1.2.4",true],["1.2.3-beta - 2","1.2.9",true],["1.2.3-beta - 2","1.3.0",true],["1.2.3-beta - 2","1.5.2",true],["1.2.3-beta - 2","2.0.0",true],["1.2.3-beta - 2","2.3.4",true],["1.2.3-beta - 2","3.0.0",false],["1.2.3-beta - 2","1.2.3-alpha",false],["1.2.3-beta - 2","1.2.3-alpha.2",false],["1.2.3-beta - 2","1.2.3-beta.4",true],["1.2.3-beta - 2","2.0.0-rc.1",false],["1.2.3-beta - 2","1.2.3+build",true],["1.2.3-beta - 2","0.2.4",false],["1.2.7 || >=1.2.9 <2.0.0","0.0.0",false],["1.2.7 || >=1.2.9 <2.0.0","0.0.3",false],["1.2.7 || >=1.2.9 <2.0.0","0.2.0",false],["1.2.7 || >=1.2.9 <2.0.0","0.2.3",false],["1.2.7 || >=1.2.9 <2.0.0","1.0.0",false],["1.2.7 || >=1.2.9 <2.0.0","1.2.2",false],["1.2.7 || >=1.2.9 <2.0.0","1.2.3",false],["1.2.7 || >=1.2.9 <2.0.0","1.2.4",false],["1.2.7 || >=1.2.9 <2.0.0","1.2.9",true],["1.2.7 || >=1.2.9 <2.0.0","1.3.0",true],["1.2.7 || >=1.2.9 <2.0.0","1.5.2",true],["1.2.7 || >=1.2.9 <2.0.0","2.0.0",false],["1.2.7 || >=1.2.9 <2.0.0","2.3.4",false],["1.2.7 || >=1.2.9 <2.0.0","3.0.0",false],["1.2.7 || >=1.2.9 <2.0.0","1.2.3-alpha",false],["1.2.7 || >=1.2.9 <2.0.0","1.2.3-alpha.2",false],["1.2.7 || >=1.2.9 <2.0.0","1.2.3-beta.4",false],["1.2.7 || >=1.2.9 <2.0.0","2.0.0-rc.1",false],["1.2.7 || >=1.2.9 <2.0.0","1.2.3+build",false],["1.2.7 || >=1.2.9 <2.0.0","0.2.4",false],["^1.2.3 ^0.2.3","0.0.0",false],["^1.2.3 ^0.2.3","0.0.3",false],["^1.2.3 ^0.2.3","0.2.0",false],["^1.2.3 ^0.2.3","0.2.3",false],["^1.2.3 ^0.2.3","1.0.0",false],["^1.2.3 ^0.2.3","1.2.2",false],["^1.2.3 ^0.2.3","1.2.3",false],["^1.2.3 ^0.2.3","1.2.4",false],["^1.2.3 ^0.2.3","1.2.9",false],["^1.2.3 ^0.2.3","1.3.0",false],["^1.2.3 ^0.2.3","1.5.2",false],["^1.2.3 ^0.2.3","2.0.0",false],["^1.2.3 ^0.2.3","2.3.4",false],["^1.2.3 ^0.2.3","3.0.0",false],["^1.2.3 ^0.2.3","1.2.3-alpha",false],["^1.2.3 ^0.2.3","1.2.3-alpha.2",false],["^1.2.3 ^0.2.3","1.2.3-beta.4",false],["^1.2.3 ^0.2.3","2.0.0-rc.1",false],["^1.2.3 ^0.2.3","1.2.3+build",false],["^1.2.3 ^0.2.3","0.2.4",false],["1.2.3 2.0.0","0.0.0",false],["1.2.3 2.0.0","0.0.3",false],["1.2.3 2.0.0","0.2.0",false],["1.2.3 2.0.0","0.2.3",false],["1.2.3 2.0.0","1.0.0",false],["1.2.3 2.0.0","1.2.2",false],["1.2.3 2.0.0","1.2.3",false],["1.2.3 2.0.0","1.2.4",false],["1.2.3 2.0.0","1.2.9",false],["1.2.3 2.0.0","1.3.0",false],["1.2.3 2.0.0","1.5.2",false],["1.2.3 2.0.0","2.0.0",false],["1.2.3 2.0.0","2.3.4",false],["1.2.3 2.0.0","3.0.0",false],["1.2.3 2.0.0","1.2.3-alpha",false],["1.2.3 2.0.0","1.2.3-alpha.2",false],["1.2.3 2.0.0","1.2.3-beta.4",false],["1.2.3 2.0.0","2.0.0-rc.1",false],["1.2.3 2.0.0","1.2.3+build",false],["1.2.3 2.0.0","0.2.4",false],[">=1.0.0-0 <2.0.0","0.0.0",false],[">=1.0.0-0 <2.0.0","0.0.3",false],[">=1.0.0-0 <2.0.0","0.2.0",false],[">=1.0.0-0 <2.0.0","0.2.3",false],[">=1.0.0-0 <2.0.0","1.0.0",true],[">=1.0.0-0 <2.0.0","1.2.2",true],[">=1.0.0-0 <2.0.0","1.2.3",true],[">=1.0.0-0 <2.0.0","1.2.4",true],[">=1.0.0-0 <2.0.0","1.2.9",true],[">=1.0.0-0 <2.0.0","1.3.0",true],[">=1.0.0-0 <2.0.0","1.5.2",true],[">=1.0.0-0 <2.0.0","2.0.0",false],[">=1.0.0-0 <2.0.0","2.3.4",false],[">=1.0.0-0 <2.0.0","3.0.0",false],[">=1.0.0-0 <2.0.0","1.2.3-alpha",false],[">=1.0.0-0 <2.0.0","1.2.3-alpha.2",false],[">=1.0.0-0 <2.0.0","1.2.3-beta.4",false],[">=1.0.0-0 <2.0.0","2.0.0-rc.1",false],[">=1.0.0-0 <2.0.0","1.2.3+build",true],[">=1.0.0-0 <2.0.0","0.2.4",false],["1.5.x || >2.a","0.0.0",false],["1.5.x || >2.a","0.0.3",false],["1.5.x || >2.a","0.2.0",false],["1.5.x || >2.a","0.2.3",false],["1.5.x || >2.a","1.0.0",false],["1.5.x || >2.a","1.2.2",false],["1.5.x || >2.a","1.2.3",false],["1.5.x || >2.a","1.2.4",false],["1.5.x || >2.a","1.2.9",false],["1.5.x || >2.a","1.3.0",false],["1.5.x || >2.a","1.5.2",false],["1.5.x || >2.a","2.0.0",false],["1.5.x || >2.a","2.3.4",false],["1.5.x || >2.a","3.0.0",false],["1.5.x || >2.a","1.2.3-alpha",false],["1.5.x || >2.a","1.2.3-alpha.2",false],["1.5.x || >2.a","1.2.3-beta.4",false],["1.5.x || >2.a","2.0.0-rc.1",false],["1.5.x || >2.a","1.2.3+build",false],["1.5.x || >2.a","0.2.4",false]]}