@_nazmiforreal/flutter-ota 0.1.1 → 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 (68) hide show
  1. package/bin/flutter-patcher.js +11 -0
  2. package/dart-src/packages/cli-tools/.dart_tool/package_graph.json +380 -380
  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/test/backends_test.dart +207 -0
  18. package/dart-src/packages/cli-tools/test/build_pack_test.dart +101 -0
  19. package/dart-src/packages/cli-tools/test/cli_test.dart +442 -0
  20. package/dart-src/packages/cli-tools/test/mocks/mock_aws_s3_client.dart +83 -0
  21. package/dart-src/packages/cli-tools/test/mocks/mock_d1_client.dart +256 -0
  22. package/dart-src/packages/cli-tools/test/mocks/mock_postgres_client.dart +347 -0
  23. package/dart-src/packages/cli-tools/test/standalone_backend_test.dart +315 -0
  24. package/dart-src/packages/core/test/rollout_test.dart +128 -0
  25. package/dart-src/packages/core/test/semver_test.dart +52 -0
  26. package/dart-src/packages/core/test/semver_vectors.json +1 -0
  27. package/dart-src/packages/core/test/types_test.dart +129 -0
  28. package/dart-src/packages/core/test/uuid_test.dart +38 -0
  29. package/dart-src/plugins/aws/test/aws_database_test.dart +167 -0
  30. package/dart-src/plugins/aws/test/aws_lambda_edge_storage_test.dart +190 -0
  31. package/dart-src/plugins/aws/test/aws_storage_test.dart +73 -0
  32. package/dart-src/plugins/aws/test/mock_aws_s3_client.dart +83 -0
  33. package/dart-src/plugins/cloudflare/test/cloudflare_worker_database_test.dart +122 -0
  34. package/dart-src/plugins/cloudflare/test/d1_bundle_mapper_test.dart +106 -0
  35. package/dart-src/plugins/cloudflare/test/d1_database_test.dart +190 -0
  36. package/dart-src/plugins/cloudflare/test/mock_d1_client.dart +256 -0
  37. package/dart-src/plugins/cloudflare/test/mock_r2_client.dart +83 -0
  38. package/dart-src/plugins/cloudflare/test/r2_storage_test.dart +77 -0
  39. package/dart-src/plugins/plugin-core/test/asset_storage_layout_test.dart +166 -0
  40. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_store_test.dart +74 -0
  41. package/dart-src/plugins/plugin-core/test/bundle_unit_of_work_test.dart +332 -0
  42. package/dart-src/plugins/plugin-core/test/create_blob_database_plugin_test.dart +711 -0
  43. package/dart-src/plugins/plugin-core/test/create_database_plugin_test.dart +294 -0
  44. package/dart-src/plugins/plugin-core/test/create_storage_plugin_test.dart +192 -0
  45. package/dart-src/plugins/plugin-core/test/generate_min_bundle_id_test.dart +51 -0
  46. package/dart-src/plugins/plugin-core/test/get_update_info_test.dart +1600 -0
  47. package/dart-src/plugins/plugin-core/test/helpers_test.dart +259 -0
  48. package/dart-src/plugins/plugin-core/test/query_bundles_test.dart +139 -0
  49. package/dart-src/plugins/plugin-core/test/request_update_bundle_state_test.dart +86 -0
  50. package/dart-src/plugins/plugin-core/test/uuidv7_test.dart +77 -0
  51. package/dart-src/plugins/postgres/test/mock_postgres_client.dart +297 -0
  52. package/dart-src/plugins/postgres/test/postgres_bundle_mapper_test.dart +140 -0
  53. package/dart-src/plugins/postgres/test/postgres_database_test.dart +181 -0
  54. package/dart-src/plugins/standalone/test/standalone_test.dart +264 -0
  55. package/dart-src/plugins/supabase/test/mock_supabase_client.dart +654 -0
  56. package/dart-src/plugins/supabase/test/supabase_bundle_mapper_test.dart +112 -0
  57. package/dart-src/plugins/supabase/test/supabase_database_test.dart +207 -0
  58. package/dart-src/plugins/supabase/test/supabase_storage_test.dart +175 -0
  59. package/package.json +1 -1
  60. package/scripts/postinstall.js +27 -8
  61. package/scripts/vendor.sh +29 -0
  62. package/dart-src/packages/core/pubspec.lock +0 -381
  63. package/dart-src/plugins/aws/pubspec.lock +0 -435
  64. package/dart-src/plugins/cloudflare/pubspec.lock +0 -411
  65. package/dart-src/plugins/plugin-core/pubspec.lock +0 -396
  66. package/dart-src/plugins/postgres/pubspec.lock +0 -427
  67. package/dart-src/plugins/standalone/pubspec.lock +0 -411
  68. package/dart-src/plugins/supabase/pubspec.lock +0 -515
@@ -0,0 +1,112 @@
1
+ import 'package:flutter_patcher_core/flutter_patcher_core.dart';
2
+ import 'package:flutter_patcher_supabase/src/supabase_bundle_mapper.dart';
3
+ import 'package:flutter_patcher_supabase/src/types.dart';
4
+ import 'package:test/test.dart';
5
+
6
+ Bundle _makeBundle({List<BundlePatchArtifact> patches = const []}) => Bundle(
7
+ id: '018f0000-0000-7000-8000-000000000001',
8
+ platform: Platform.android,
9
+ shouldForceUpdate: false,
10
+ enabled: true,
11
+ fileHash: 'file-hash-1',
12
+ gitCommitHash: 'git-1',
13
+ message: 'hello',
14
+ channel: 'production',
15
+ storageUri: 'supabase-storage://updates/bundles/b1.zip',
16
+ targetAppVersion: '1.0.0',
17
+ fingerprintHash: 'fp-1',
18
+ metadata: const BundleMetadata(appVersion: '1.2.3'),
19
+ manifestStorageUri: 'supabase-storage://updates/manifests/m1.json',
20
+ manifestFileHash: 'mf-1',
21
+ assetBaseStorageUri: 'supabase-storage://updates/assets',
22
+ rolloutCohortCount: 500,
23
+ targetCohorts: ['team-a'],
24
+ patches: patches,
25
+ patchBaseBundleId: patches.isNotEmpty ? patches.first.baseBundleId : null,
26
+ patchBaseFileHash:
27
+ patches.isNotEmpty ? patches.first.baseFileHash : null,
28
+ patchFileHash: patches.isNotEmpty ? patches.first.patchFileHash : null,
29
+ patchStorageUri:
30
+ patches.isNotEmpty ? patches.first.patchStorageUri : null,
31
+ );
32
+
33
+ void main() {
34
+ group('bundleToRow / mapRowToBundle round-trip', () {
35
+ test('preserves scalar fields and stripBundleArtifactMetadata', () {
36
+ final bundle = _makeBundle();
37
+ final row = bundleToRow(bundle);
38
+
39
+ expect(row.id, bundle.id);
40
+ expect(row.channel, 'production');
41
+ expect(row.enabled, true);
42
+ expect(row.shouldForceUpdate, false);
43
+ expect(row.fileHash, 'file-hash-1');
44
+ expect(row.gitCommitHash, 'git-1');
45
+ expect(row.message, 'hello');
46
+ expect(row.platform, 'android');
47
+ expect(row.targetAppVersion, '1.0.0');
48
+ expect(row.fingerprintHash, 'fp-1');
49
+ expect(row.storageUri, 'supabase-storage://updates/bundles/b1.zip');
50
+ expect(row.manifestStorageUri, bundle.manifestStorageUri);
51
+ expect(row.manifestFileHash, 'mf-1');
52
+ expect(row.assetBaseStorageUri, bundle.assetBaseStorageUri);
53
+ expect(row.rolloutCohortCount, 500);
54
+ expect(row.targetCohorts, ['team-a']);
55
+ expect(row.metadata, {'app_version': '1.2.3'});
56
+ });
57
+
58
+ test('mapRowToBundle rebuilds derived patch fields', () {
59
+ final baseBundleId = '018f0000-0000-7000-8000-000000000000';
60
+ final bundle = _makeBundle(
61
+ patches: [
62
+ BundlePatchArtifact(
63
+ baseBundleId: baseBundleId,
64
+ baseFileHash: 'base-hash',
65
+ patchFileHash: 'patch-hash',
66
+ patchStorageUri: 'supabase-storage://updates/patches/p1.patch',
67
+ ),
68
+ ],
69
+ );
70
+
71
+ final row = bundleToRow(bundle);
72
+ final patchRows = bundleToPatchRows(bundle);
73
+ expect(patchRows, hasLength(1));
74
+ expect(patchRows.first.id, '${bundle.id}:$baseBundleId');
75
+ expect(patchRows.first.bundleId, bundle.id);
76
+ expect(patchRows.first.baseBundleId, baseBundleId);
77
+ expect(patchRows.first.orderIndex, 0);
78
+
79
+ final back = mapRowToBundle(
80
+ SupabaseBundleRow.fromJson(row.toJson()),
81
+ patchRows.map((p) => SupabaseBundlePatchRow.fromJson(p.toJson())).toList(),
82
+ );
83
+
84
+ expect(back.id, bundle.id);
85
+ expect(back.patches, hasLength(1));
86
+ expect(back.patchBaseBundleId, baseBundleId);
87
+ expect(back.patchBaseFileHash, 'base-hash');
88
+ expect(back.patches!.first.patchFileHash, 'patch-hash');
89
+ expect(back.patchStorageUri, 'supabase-storage://updates/patches/p1.patch');
90
+ expect(back.metadata?.appVersion, '1.2.3');
91
+ });
92
+
93
+ test('defaults when row fields are missing', () {
94
+ final row = SupabaseBundleRow.fromJson({
95
+ 'id': 'id-1',
96
+ 'channel': 'production',
97
+ 'enabled': true,
98
+ 'platform': 'android',
99
+ 'should_force_update': false,
100
+ 'file_hash': 'fh',
101
+ 'storage_uri': 's',
102
+ 'target_app_version': '1.0.0',
103
+ 'metadata': {'app_version': '9.9.9'},
104
+ });
105
+ final bundle = mapRowToBundle(row);
106
+ expect(bundle.rolloutCohortCount, 1000);
107
+ expect(bundle.targetCohorts, isNull);
108
+ expect(bundle.metadata?.appVersion, '9.9.9');
109
+ expect(bundle.manifestStorageUri, isNull);
110
+ });
111
+ });
112
+ }
@@ -0,0 +1,207 @@
1
+ import 'package:flutter_patcher_core/flutter_patcher_core.dart';
2
+ import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart';
3
+ import 'package:flutter_patcher_supabase/flutter_patcher_supabase.dart';
4
+ import 'package:test/test.dart';
5
+
6
+ import 'mock_supabase_client.dart';
7
+
8
+ late Store store;
9
+ late FakeStorageBucket bucket;
10
+ late SupabaseClientLike client;
11
+
12
+ DatabasePlugin newPlugin() => supabaseDatabase(
13
+ SupabaseServiceRoleConfig(
14
+ supabaseUrl: 'https://test.supabase.invalid',
15
+ supabaseAnonKey: 'test-anon-key',
16
+ clientFactory: (_, __) => client,
17
+ ),
18
+ )();
19
+
20
+ Bundle _bundle(
21
+ String id, {
22
+ bool enabled = true,
23
+ String? fp,
24
+ String? message,
25
+ String channel = 'production',
26
+ }) =>
27
+ Bundle(
28
+ id: id,
29
+ platform: Platform.android,
30
+ shouldForceUpdate: false,
31
+ enabled: enabled,
32
+ fileHash: 'file-$id',
33
+ gitCommitHash: 'git-$id',
34
+ message: message ?? 'msg-$id',
35
+ channel: channel,
36
+ storageUri: 'supabase-storage://updates/bundles/$id.zip',
37
+ targetAppVersion: '1.0.0',
38
+ fingerprintHash: fp,
39
+ metadata: const BundleMetadata(),
40
+ manifestStorageUri: 'supabase-storage://updates/manifests/$id.json',
41
+ manifestFileHash: 'mf-$id',
42
+ assetBaseStorageUri: 'supabase-storage://updates/assets',
43
+ );
44
+
45
+ void main() {
46
+ setUp(() {
47
+ store = Store();
48
+ bucket = FakeStorageBucket();
49
+ client = createMockSupabaseClient(store: store, bucket: bucket);
50
+ });
51
+
52
+ group('supabaseDatabase bundle methods', () {
53
+ test('insert + commit + getBundleById round-trips', () async {
54
+ final plugin = newPlugin();
55
+ await plugin.appendBundle(_bundle('b1'));
56
+ await plugin.commitBundle();
57
+ final got = await plugin.getBundleById('b1');
58
+ expect(got, isNotNull);
59
+ expect(got!.id, 'b1');
60
+ expect(got.fileHash, 'file-b1');
61
+ expect(got.targetAppVersion, '1.0.0');
62
+ expect(got.rolloutCohortCount, 1000);
63
+ });
64
+
65
+ test('getBundles filters by where + pagination', () async {
66
+ final plugin = newPlugin();
67
+ for (var i = 0; i < 5; i++) {
68
+ await plugin.appendBundle(_bundle('b$i'));
69
+ }
70
+ await plugin.commitBundle();
71
+
72
+ final res = await plugin.getBundles(
73
+ DatabaseBundleQueryOptions(
74
+ where: DatabaseBundleQueryWhere(
75
+ channel: 'production',
76
+ platform: Platform.android,
77
+ ),
78
+ limit: 3,
79
+ ),
80
+ );
81
+ expect(res.data, hasLength(3));
82
+ expect(res.pagination.total, 5);
83
+ });
84
+
85
+ test('getChannels returns distinct channels', () async {
86
+ final plugin = newPlugin();
87
+ await plugin.appendBundle(_bundle('b1'));
88
+ await plugin.appendBundle(
89
+ _bundle('b2', channel: 'staging'),
90
+ );
91
+ await plugin.commitBundle();
92
+ final channels = await plugin.getChannels();
93
+ expect(channels, containsAll(['production', 'staging']));
94
+ expect(channels, hasLength(2));
95
+ });
96
+
97
+ test('updateBundle persists changes', () async {
98
+ final plugin = newPlugin();
99
+ await plugin.appendBundle(_bundle('b1'));
100
+ await plugin.commitBundle();
101
+ final updated = _bundle('b1', message: 'updated');
102
+ await plugin.updateBundle('b1', updated.toJson());
103
+ await plugin.commitBundle();
104
+ final got = await plugin.getBundleById('b1');
105
+ expect(got!.message, 'updated');
106
+ });
107
+
108
+ test('deleteBundle removes the row', () async {
109
+ final plugin = newPlugin();
110
+ await plugin.appendBundle(_bundle('b1'));
111
+ await plugin.commitBundle();
112
+ final got = await plugin.getBundleById('b1');
113
+ await plugin.deleteBundle(got!);
114
+ await plugin.commitBundle();
115
+ expect(await plugin.getBundleById('b1'), isNull);
116
+ });
117
+ });
118
+
119
+ group('supabaseDatabase getUpdateInfo via RPC', () {
120
+ test('appVersion strategy returns newer enabled bundle', () async {
121
+ final plugin = newPlugin();
122
+ await plugin.appendBundle(
123
+ _bundle('018f0000-0000-7000-8000-000000000001'),
124
+ );
125
+ await plugin.appendBundle(
126
+ _bundle('018f0000-0000-7000-8000-000000000002'),
127
+ );
128
+ await plugin.commitBundle();
129
+
130
+ final info = await plugin.getUpdateInfo(
131
+ AppVersionGetBundlesArgs(
132
+ appVersion: '1.0.0',
133
+ bundleId: '018f0000-0000-7000-8000-000000000001',
134
+ channel: 'production',
135
+ minBundleId: nilUuid,
136
+ platform: Platform.android,
137
+ ),
138
+ );
139
+ expect(info, isNotNull);
140
+ expect(info!.id, '018f0000-0000-7000-8000-000000000002');
141
+ expect(info.status, UpdateStatus.update);
142
+ });
143
+
144
+ test('fingerprint strategy returns matching bundle', () async {
145
+ final plugin = newPlugin();
146
+ await plugin.appendBundle(
147
+ _bundle(
148
+ '018f0000-0000-7000-8000-000000000001',
149
+ fp: 'fp-current',
150
+ ),
151
+ );
152
+ await plugin.appendBundle(
153
+ _bundle(
154
+ '018f0000-0000-7000-8000-000000000002',
155
+ fp: 'fp-target',
156
+ ),
157
+ );
158
+ await plugin.commitBundle();
159
+
160
+ final info = await plugin.getUpdateInfo(
161
+ FingerprintGetBundlesArgs(
162
+ fingerprintHash: 'fp-target',
163
+ bundleId: '018f0000-0000-7000-8000-000000000001',
164
+ channel: 'production',
165
+ minBundleId: nilUuid,
166
+ platform: Platform.android,
167
+ ),
168
+ );
169
+ expect(info, isNotNull);
170
+ expect(info!.id, '018f0000-0000-7000-8000-000000000002');
171
+ });
172
+
173
+ test('returns rollback when no compatible newer bundle exists', () async {
174
+ final plugin = newPlugin();
175
+ await plugin.appendBundle(
176
+ _bundle('018f0000-0000-7000-8000-000000000001'),
177
+ );
178
+ await plugin.commitBundle();
179
+
180
+ final info = await plugin.getUpdateInfo(
181
+ AppVersionGetBundlesArgs(
182
+ appVersion: '9.9.9',
183
+ bundleId: '018f0000-0000-7000-8000-000000000001',
184
+ channel: 'production',
185
+ minBundleId: nilUuid,
186
+ platform: Platform.android,
187
+ ),
188
+ );
189
+ expect(info, isNotNull);
190
+ expect(info!.status, UpdateStatus.rollback);
191
+ });
192
+
193
+ test('returns null for an init bundle with no candidates', () async {
194
+ final plugin = newPlugin();
195
+ final info = await plugin.getUpdateInfo(
196
+ AppVersionGetBundlesArgs(
197
+ appVersion: '9.9.9',
198
+ bundleId: nilUuid,
199
+ channel: 'production',
200
+ minBundleId: nilUuid,
201
+ platform: Platform.android,
202
+ ),
203
+ );
204
+ expect(info, isNull);
205
+ });
206
+ });
207
+ }
@@ -0,0 +1,175 @@
1
+ import 'dart:io' as io;
2
+
3
+ import 'package:flutter_patcher_plugin_core/flutter_patcher_plugin_core.dart';
4
+ import 'package:flutter_patcher_supabase/flutter_patcher_supabase.dart';
5
+ import 'package:test/test.dart';
6
+
7
+ import 'mock_supabase_client.dart';
8
+
9
+ late Store store;
10
+ late FakeStorageBucket bucket;
11
+ late SupabaseClientLike client;
12
+ late StoragePlugin storage;
13
+ late NodeStorageProfile node;
14
+ late RuntimeStorageProfile runtime;
15
+
16
+ void main() {
17
+ setUp(() {
18
+ store = Store();
19
+ bucket = FakeStorageBucket();
20
+ client = createMockSupabaseClient(store: store, bucket: bucket);
21
+ storage = supabaseStorage(
22
+ SupabaseStorageConfig(
23
+ bucketName: 'updates',
24
+ supabaseUrl: 'https://example.supabase.co',
25
+ supabaseAnonKey: 'anon-key',
26
+ clientFactory: (_, __) => client,
27
+ ),
28
+ );
29
+ node = storage.profiles.node!;
30
+ runtime = storage.profiles.runtime!;
31
+ });
32
+
33
+ group('supabaseStorage node.exists', () {
34
+ test('returns true for existing, signable objects', () async {
35
+ bucket.existsData = true;
36
+ bucket.signedUrlData = 'https://example.supabase.co/signed-url';
37
+ final result = await node.exists(
38
+ 'supabase-storage://updates/assets/sha256/fi/file-hash.png',
39
+ );
40
+ expect(result, isTrue);
41
+ expect(bucket.existsCalls, ['assets/sha256/fi/file-hash.png']);
42
+ expect(bucket.createSignedUrlCalls, ['assets/sha256/fi/file-hash.png']);
43
+ });
44
+
45
+ test('returns false when object is missing', () async {
46
+ bucket.existsData = false;
47
+ bucket.existsError = 'Object not found';
48
+ final result = await node.exists(
49
+ 'supabase-storage://updates/assets/sha256/fi/file-hash.png',
50
+ );
51
+ expect(result, isFalse);
52
+ expect(bucket.createSignedUrlCalls, isEmpty);
53
+ });
54
+
55
+ test('rejects existing objects that are not signable', () async {
56
+ bucket.existsData = true;
57
+ bucket.signedUrlError = 'Object not found';
58
+ await expectLater(
59
+ node.exists(
60
+ 'supabase-storage://updates/assets/sha256/fi/file-hash.png',
61
+ ),
62
+ throwsA(
63
+ isA<StateError>().having(
64
+ (e) => e.message,
65
+ 'message',
66
+ contains(
67
+ 'Failed to generate download URL for '
68
+ '"assets/sha256/fi/file-hash.png": Object not found',
69
+ ),
70
+ ),
71
+ ),
72
+ );
73
+ expect(bucket.createSignedUrlCalls, hasLength(1));
74
+ });
75
+
76
+ test('rejects a different bucket', () async {
77
+ await expectLater(
78
+ node.exists(
79
+ 'supabase-storage://other/assets/sha256/fi/file-hash.png',
80
+ ),
81
+ throwsA(
82
+ isA<StateError>().having(
83
+ (e) => e.message,
84
+ 'message',
85
+ contains('Bucket name mismatch: expected "updates"'),
86
+ ),
87
+ ),
88
+ );
89
+ expect(bucket.existsCalls, isEmpty);
90
+ });
91
+ });
92
+
93
+ group('supabaseStorage runtime.getDownloadUrl', () {
94
+ test('batches concurrent signed URL requests', () async {
95
+ final paths = List.generate(
96
+ 20,
97
+ (i) => 'assets/sha256/file-$i.png',
98
+ );
99
+ final results = await Future.wait(
100
+ paths.map((p) => runtime.getDownloadUrl('supabase-storage://updates/$p')),
101
+ );
102
+ expect(
103
+ results,
104
+ paths
105
+ .map((p) => {'fileUrl': 'https://example.supabase.co/3600/$p'})
106
+ .toList(),
107
+ );
108
+ expect(bucket.createSignedUrlsCalls, hasLength(1));
109
+ expect(bucket.createSignedUrlsCalls.first, paths);
110
+ });
111
+
112
+ test('surfaces non-missing signed URL errors', () async {
113
+ bucket.signedUrlError = 'Storage API failed';
114
+ await expectLater(
115
+ runtime.getDownloadUrl(
116
+ 'supabase-storage://updates/assets/sha256/fi/file-hash.png',
117
+ ),
118
+ throwsA(
119
+ isA<StateError>().having(
120
+ (e) => e.message,
121
+ 'message',
122
+ contains(
123
+ 'Failed to generate download URL for '
124
+ '"assets/sha256/fi/file-hash.png": Storage API failed',
125
+ ),
126
+ ),
127
+ ),
128
+ );
129
+ expect(bucket.createSignedUrlsCalls, hasLength(1));
130
+ });
131
+
132
+ test('decodes percent-encoded object keys before signing and removing',
133
+ () async {
134
+ const uri = 'supabase-storage://updates/'
135
+ 'assets/bootsplash/logo-ios%402x.png';
136
+ final url = await runtime.getDownloadUrl(uri);
137
+ expect(
138
+ url,
139
+ {
140
+ 'fileUrl': 'https://example.supabase.co/3600/'
141
+ 'assets/bootsplash/logo-ios@2x.png'
142
+ },
143
+ );
144
+ expect(
145
+ bucket.createSignedUrlsCalls.first,
146
+ ['assets/bootsplash/logo-ios@2x.png'],
147
+ );
148
+
149
+ await node.delete(uri);
150
+ expect(
151
+ bucket.removeCalls.first,
152
+ ['assets/bootsplash/logo-ios@2x.png'],
153
+ );
154
+ });
155
+ });
156
+
157
+ group('supabaseStorage node.upload', () {
158
+ test('returns storageUri for a signable upload', () async {
159
+ final dir = await io.Directory.systemTemp.createTemp('hu-supabase-');
160
+ final uploadPath = '${dir.path}/bundle.zip';
161
+ await io.File(uploadPath).writeAsString('bundle');
162
+ bucket.uploadData = {'fullPath': 'updates/bundles/bundle.zip'};
163
+
164
+ final result = await node.upload('bundles', uploadPath);
165
+ expect(
166
+ result,
167
+ {'storageUri': 'supabase-storage://updates/bundles/bundle.zip'},
168
+ );
169
+ expect(bucket.uploadCalls, ['bundles/bundle.zip']);
170
+ expect(bucket.createSignedUrlCalls, ['bundles/bundle.zip']);
171
+
172
+ await dir.delete(recursive: true);
173
+ });
174
+ });
175
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@_nazmiforreal/flutter-ota",
3
- "version": "0.1.1",
3
+ "version": "0.1.3",
4
4
  "description": "Command-line interface for flutter_patcher — an OTA code-push tool for Flutter, hot-updater compatible.",
5
5
  "bin": {
6
6
  "flutter-patcher": "./bin/flutter-patcher.js"
@@ -6,6 +6,11 @@
6
6
  // `flutter-patcher-<os>-<arch>` binary ships in `bin/`, use it. Otherwise, when
7
7
  // the Dart SDK is present, compile one from the bundled Dart source so the
8
8
  // package works on architectures we did not prebuild (e.g. arm64).
9
+ //
10
+ // We explicitly run `dart pub get` first so dependency resolution is reliable
11
+ // (a stale/locked tree would otherwise fail to fetch packages). If compilation
12
+ // cannot be performed we warn (and exit 0) rather than failing the whole npm
13
+ // install — the launcher prints a clear "how to build" message at runtime.
9
14
 
10
15
  const { spawnSync } = require('child_process');
11
16
  const fs = require('fs');
@@ -27,6 +32,7 @@ const ext = process.platform === 'win32' ? '.exe' : '';
27
32
  const pkgDir = path.join(__dirname, '..');
28
33
  const binDir = path.join(pkgDir, 'bin');
29
34
  const target = path.join(binDir, `flutter-patcher-${platformName}-${archName}${ext}`);
35
+ const cliDir = path.join(pkgDir, 'dart-src', 'packages', 'cli-tools');
30
36
 
31
37
  if (fs.existsSync(target)) {
32
38
  console.log(`flutter-patcher: prebuilt binary present (${platformName}-${archName}), skipping build.`);
@@ -43,20 +49,33 @@ if (dartCheck.status !== 0) {
43
49
  process.exit(0);
44
50
  }
45
51
 
46
- const sourceEntry = path.join(
47
- pkgDir, 'dart-src', 'packages', 'cli-tools', 'bin', 'flutter_patcher.dart',
48
- );
49
- if (!fs.existsSync(sourceEntry)) {
52
+ if (!fs.existsSync(path.join(cliDir, 'pubspec.yaml'))) {
50
53
  console.warn('flutter-patcher: bundled Dart source not found; skipping build.');
51
54
  process.exit(0);
52
55
  }
53
56
 
54
- // Compile from within the cli-tools package dir so its pubspec (and local path
55
- // dependencies) resolve correctly.
57
+ console.log('flutter-patcher: resolving Dart dependencies (dart pub get)...');
58
+ const getRes = spawnSync('dart', ['pub', 'get'], { stdio: 'inherit', cwd: cliDir });
59
+ if (getRes.status !== 0) {
60
+ console.warn(
61
+ 'flutter-patcher: `dart pub get` failed (check your network / Dart version). ' +
62
+ `To build manually:\n cd ${cliDir} && dart compile exe bin/flutter_patcher.dart ` +
63
+ `-o ${target}`,
64
+ );
65
+ process.exit(0);
66
+ }
67
+
56
68
  console.log('flutter-patcher: building native binary with `dart compile exe`...');
57
69
  const res = spawnSync(
58
70
  'dart',
59
71
  ['compile', 'exe', 'bin/flutter_patcher.dart', '-o', target],
60
- { stdio: 'inherit', cwd: path.dirname(path.dirname(sourceEntry)) },
72
+ { stdio: 'inherit', cwd: cliDir },
61
73
  );
62
- process.exit(res.status == null ? 1 : res.status);
74
+ if (res.status !== 0) {
75
+ console.warn(
76
+ 'flutter-patcher: build failed. To build manually:\n cd ' +
77
+ `${cliDir} && dart compile exe bin/flutter_patcher.dart -o ${target}`,
78
+ );
79
+ process.exit(0);
80
+ }
81
+ console.log(`flutter-patcher: built ${target}`);
@@ -0,0 +1,29 @@
1
+ #!/usr/bin/env bash
2
+ # Regenerate the vendored Dart source under dart-src/ from the monorepo so the
3
+ # npm package can `dart compile exe` on any architecture (e.g. arm64).
4
+ set -euo pipefail
5
+
6
+ ROOT="$(cd "$(dirname "$0")/../../.." && pwd)"
7
+ DST="$(cd "$(dirname "$0")/.." && pwd)/dart-src"
8
+ SRC="$ROOT"
9
+
10
+ echo "Vendoring Dart source from $SRC into $DST"
11
+
12
+ rm -rf "$DST"
13
+ mkdir -p "$DST/packages" "$DST/plugins"
14
+
15
+ for p in packages/core packages/cli-tools; do
16
+ cp -R "$SRC/$p" "$DST/$p"
17
+ done
18
+ for p in plugins/cloudflare plugins/plugin-core plugins/postgres plugins/aws plugins/standalone plugins/supabase; do
19
+ cp -R "$SRC/$p" "$DST/$p"
20
+ done
21
+
22
+ # Drop tooling/lock artifacts so `dart pub get` resolves fresh on install.
23
+ find "$DST" -name pubspec.lock -delete
24
+ find "$DST" -name .dart_tool -type d -prune -exec rm -rf {} +
25
+ find "$DST" -name build -type d -prune -exec rm -rf {} +
26
+ find "$DST" -name .git -type d -prune -exec rm -rf {} +
27
+ find "$DST" -name coverage -type d -prune -exec rm -rf {} +
28
+
29
+ echo "Done. dart-src size: $(du -sh "$DST" | cut -f1)"