@solana-mobile/dapp-store-cli 0.15.0 → 0.16.1

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 (105) hide show
  1. package/bin/dapp-store.js +3 -1
  2. package/lib/CliSetup.js +304 -505
  3. package/lib/CliUtils.js +6 -376
  4. package/lib/__tests__/CliSetupTest.js +484 -74
  5. package/lib/cli/__tests__/parseErrors.test.js +25 -0
  6. package/lib/cli/__tests__/signer.test.js +436 -0
  7. package/lib/cli/constants.js +23 -0
  8. package/lib/cli/messages.js +21 -0
  9. package/lib/cli/parseErrors.js +41 -0
  10. package/lib/{commands/publish/PublishCliSupport.js → cli/selfUpdate.js} +72 -38
  11. package/lib/{commands/publish/PublishCliRemove.js → cli/signer.js} +35 -56
  12. package/lib/index.js +96 -5
  13. package/lib/package.json +5 -24
  14. package/lib/portal/__tests__/releaseMetadata.test.js +647 -0
  15. package/lib/portal/__tests__/translators.test.js +76 -0
  16. package/lib/portal/__tests__/workflowClient.test.js +457 -0
  17. package/lib/portal/attestationClient.js +143 -0
  18. package/lib/portal/files.js +64 -0
  19. package/lib/portal/http.js +364 -0
  20. package/lib/portal/records.js +64 -0
  21. package/lib/portal/releaseMetadata.js +748 -0
  22. package/lib/portal/translators.js +460 -0
  23. package/lib/portal/types.js +1 -0
  24. package/lib/portal/workflowClient.js +704 -0
  25. package/lib/publication/PublicationProgressReporter.js +1051 -0
  26. package/lib/publication/__tests__/PublicationProgressReporter.test.js +174 -0
  27. package/lib/{commands/ValidateCommand.js → publication/__tests__/fundingPreflight.test.js} +90 -66
  28. package/lib/publication/__tests__/publicationSummary.test.js +26 -0
  29. package/lib/publication/cliValidation.js +482 -0
  30. package/lib/publication/fundingPreflight.js +246 -0
  31. package/lib/publication/publicationSummary.js +99 -0
  32. package/lib/{commands/utils.js → publication/runPublicationWorkflow.js} +16 -46
  33. package/package.json +5 -24
  34. package/src/CliSetup.ts +370 -505
  35. package/src/CliUtils.ts +9 -233
  36. package/src/__tests__/CliSetupTest.ts +272 -120
  37. package/src/cli/__tests__/parseErrors.test.ts +34 -0
  38. package/src/cli/__tests__/signer.test.ts +359 -0
  39. package/src/cli/constants.ts +3 -0
  40. package/src/cli/messages.ts +27 -0
  41. package/src/cli/parseErrors.ts +62 -0
  42. package/src/cli/selfUpdate.ts +59 -0
  43. package/src/cli/signer.ts +38 -0
  44. package/src/index.ts +31 -4
  45. package/src/portal/__tests__/releaseMetadata.test.ts +508 -0
  46. package/src/portal/__tests__/translators.test.ts +82 -0
  47. package/src/portal/__tests__/workflowClient.test.ts +278 -0
  48. package/src/portal/attestationClient.ts +19 -0
  49. package/src/portal/files.ts +73 -0
  50. package/src/portal/http.ts +170 -0
  51. package/src/portal/records.ts +38 -0
  52. package/src/portal/releaseMetadata.ts +489 -0
  53. package/src/portal/translators.ts +750 -0
  54. package/src/portal/types.ts +27 -0
  55. package/src/portal/workflowClient.ts +575 -0
  56. package/src/publication/PublicationProgressReporter.ts +1026 -0
  57. package/src/publication/__tests__/PublicationProgressReporter.test.ts +210 -0
  58. package/src/publication/__tests__/fundingPreflight.test.ts +78 -0
  59. package/src/publication/__tests__/publicationSummary.test.ts +30 -0
  60. package/src/publication/cliValidation.ts +264 -0
  61. package/src/publication/fundingPreflight.ts +123 -0
  62. package/src/publication/publicationSummary.ts +26 -0
  63. package/src/publication/runPublicationWorkflow.ts +46 -0
  64. package/lib/commands/create/CreateCliApp.js +0 -223
  65. package/lib/commands/create/CreateCliRelease.js +0 -290
  66. package/lib/commands/create/index.js +0 -40
  67. package/lib/commands/index.js +0 -3
  68. package/lib/commands/publish/PublishCliSubmit.js +0 -208
  69. package/lib/commands/publish/PublishCliUpdate.js +0 -211
  70. package/lib/commands/publish/index.js +0 -22
  71. package/lib/commands/scaffolding/ScaffoldInit.js +0 -15
  72. package/lib/commands/scaffolding/index.js +0 -1
  73. package/lib/config/EnvVariables.js +0 -59
  74. package/lib/config/PublishDetails.js +0 -915
  75. package/lib/config/S3StorageManager.js +0 -93
  76. package/lib/config/index.js +0 -2
  77. package/lib/generated/config_obj.json +0 -1
  78. package/lib/generated/config_schema.json +0 -1
  79. package/lib/prebuild_schema/publishing_source.yaml +0 -64
  80. package/lib/prebuild_schema/schemagen.js +0 -25
  81. package/lib/upload/CachedStorageDriver.js +0 -293
  82. package/lib/upload/TurboStorageDriver.js +0 -718
  83. package/lib/upload/index.js +0 -2
  84. package/src/commands/ValidateCommand.ts +0 -82
  85. package/src/commands/create/CreateCliApp.ts +0 -93
  86. package/src/commands/create/CreateCliRelease.ts +0 -149
  87. package/src/commands/create/index.ts +0 -47
  88. package/src/commands/index.ts +0 -3
  89. package/src/commands/publish/PublishCliRemove.ts +0 -66
  90. package/src/commands/publish/PublishCliSubmit.ts +0 -93
  91. package/src/commands/publish/PublishCliSupport.ts +0 -66
  92. package/src/commands/publish/PublishCliUpdate.ts +0 -101
  93. package/src/commands/publish/index.ts +0 -29
  94. package/src/commands/scaffolding/ScaffoldInit.ts +0 -20
  95. package/src/commands/scaffolding/index.ts +0 -1
  96. package/src/commands/utils.ts +0 -33
  97. package/src/config/EnvVariables.ts +0 -39
  98. package/src/config/PublishDetails.ts +0 -456
  99. package/src/config/S3StorageManager.ts +0 -47
  100. package/src/config/index.ts +0 -2
  101. package/src/prebuild_schema/publishing_source.yaml +0 -64
  102. package/src/prebuild_schema/schemagen.js +0 -31
  103. package/src/upload/CachedStorageDriver.ts +0 -99
  104. package/src/upload/TurboStorageDriver.ts +0 -277
  105. package/src/upload/index.ts +0 -2
@@ -0,0 +1,647 @@
1
+ function asyncGeneratorStep(gen, resolve, reject, _next, _throw, key, arg) {
2
+ try {
3
+ var info = gen[key](arg);
4
+ var value = info.value;
5
+ } catch (error) {
6
+ reject(error);
7
+ return;
8
+ }
9
+ if (info.done) {
10
+ resolve(value);
11
+ } else {
12
+ Promise.resolve(value).then(_next, _throw);
13
+ }
14
+ }
15
+ function _async_to_generator(fn) {
16
+ return function() {
17
+ var self = this, args = arguments;
18
+ return new Promise(function(resolve, reject) {
19
+ var gen = fn.apply(self, args);
20
+ function _next(value) {
21
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "next", value);
22
+ }
23
+ function _throw(err) {
24
+ asyncGeneratorStep(gen, resolve, reject, _next, _throw, "throw", err);
25
+ }
26
+ _next(undefined);
27
+ });
28
+ };
29
+ }
30
+ function _define_property(obj, key, value) {
31
+ if (key in obj) {
32
+ Object.defineProperty(obj, key, {
33
+ value: value,
34
+ enumerable: true,
35
+ configurable: true,
36
+ writable: true
37
+ });
38
+ } else {
39
+ obj[key] = value;
40
+ }
41
+ return obj;
42
+ }
43
+ function _ts_generator(thisArg, body) {
44
+ var f, y, t, _ = {
45
+ label: 0,
46
+ sent: function() {
47
+ if (t[0] & 1) throw t[1];
48
+ return t[1];
49
+ },
50
+ trys: [],
51
+ ops: []
52
+ }, g = Object.create((typeof Iterator === "function" ? Iterator : Object).prototype);
53
+ return g.next = verb(0), g["throw"] = verb(1), g["return"] = verb(2), typeof Symbol === "function" && (g[Symbol.iterator] = function() {
54
+ return this;
55
+ }), g;
56
+ function verb(n) {
57
+ return function(v) {
58
+ return step([
59
+ n,
60
+ v
61
+ ]);
62
+ };
63
+ }
64
+ function step(op) {
65
+ if (f) throw new TypeError("Generator is already executing.");
66
+ while(g && (g = 0, op[0] && (_ = 0)), _)try {
67
+ if (f = 1, y && (t = op[0] & 2 ? y["return"] : op[0] ? y["throw"] || ((t = y["return"]) && t.call(y), 0) : y.next) && !(t = t.call(y, op[1])).done) return t;
68
+ if (y = 0, t) op = [
69
+ op[0] & 2,
70
+ t.value
71
+ ];
72
+ switch(op[0]){
73
+ case 0:
74
+ case 1:
75
+ t = op;
76
+ break;
77
+ case 4:
78
+ _.label++;
79
+ return {
80
+ value: op[1],
81
+ done: false
82
+ };
83
+ case 5:
84
+ _.label++;
85
+ y = op[1];
86
+ op = [
87
+ 0
88
+ ];
89
+ continue;
90
+ case 7:
91
+ op = _.ops.pop();
92
+ _.trys.pop();
93
+ continue;
94
+ default:
95
+ if (!(t = _.trys, t = t.length > 0 && t[t.length - 1]) && (op[0] === 6 || op[0] === 2)) {
96
+ _ = 0;
97
+ continue;
98
+ }
99
+ if (op[0] === 3 && (!t || op[1] > t[0] && op[1] < t[3])) {
100
+ _.label = op[1];
101
+ break;
102
+ }
103
+ if (op[0] === 6 && _.label < t[1]) {
104
+ _.label = t[1];
105
+ t = op;
106
+ break;
107
+ }
108
+ if (t && _.label < t[2]) {
109
+ _.label = t[2];
110
+ _.ops.push(op);
111
+ break;
112
+ }
113
+ if (t[2]) _.ops.pop();
114
+ _.trys.pop();
115
+ continue;
116
+ }
117
+ op = body.call(thisArg, _);
118
+ } catch (e) {
119
+ op = [
120
+ 6,
121
+ e
122
+ ];
123
+ y = 0;
124
+ } finally{
125
+ f = t = 0;
126
+ }
127
+ if (op[0] & 5) throw op[1];
128
+ return {
129
+ value: op[0] ? op[1] : void 0,
130
+ done: true
131
+ };
132
+ }
133
+ }
134
+ import { afterEach, beforeEach, describe, expect, it, jest } from "@jest/globals";
135
+ import { buildReleaseMetadataDocument } from "../releaseMetadata.js";
136
+ function makePortalClient(options) {
137
+ return {
138
+ fetchRemoteFile: jest.fn(function(input) {
139
+ return _async_to_generator(function() {
140
+ var _options_fetchRemoteFile;
141
+ return _ts_generator(this, function(_state) {
142
+ if (!(options === null || options === void 0 ? void 0 : (_options_fetchRemoteFile = options.fetchRemoteFile) === null || _options_fetchRemoteFile === void 0 ? void 0 : _options_fetchRemoteFile[input.url])) {
143
+ throw new Error("Unexpected remote file fetch for ".concat(input.url));
144
+ }
145
+ return [
146
+ 2,
147
+ options.fetchRemoteFile[input.url]
148
+ ];
149
+ });
150
+ })();
151
+ }),
152
+ createUploadTarget: jest.fn()
153
+ };
154
+ }
155
+ describe("buildReleaseMetadataDocument", function() {
156
+ var originalFetch;
157
+ var fetchMock;
158
+ beforeEach(function() {
159
+ originalFetch = global.fetch;
160
+ fetchMock = jest.fn();
161
+ global.fetch = fetchMock;
162
+ });
163
+ afterEach(function() {
164
+ global.fetch = originalFetch;
165
+ jest.restoreAllMocks();
166
+ });
167
+ it("reuses existing R2 media URLs without reuploading them", function() {
168
+ return _async_to_generator(function() {
169
+ var portal, metadata;
170
+ return _ts_generator(this, function(_state) {
171
+ switch(_state.label){
172
+ case 0:
173
+ portal = makePortalClient({
174
+ fetchRemoteFile: {
175
+ "https://r2.solanamobiledappstore.com/a/icon.png": {
176
+ data: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aR3sAAAAASUVORK5CYII=", "base64").toString("base64"),
177
+ fileName: "icon.png",
178
+ mimeType: "image/png"
179
+ },
180
+ "https://r2.solanamobiledappstore.com/a/preview.png": {
181
+ data: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAIAAAADCAIAAAA2iEnWAAAAFElEQVR42mP8z8AARMAgYGJAAwA5WQYB8m8VXwAAAABJRU5ErkJggg==", "base64").toString("base64"),
182
+ fileName: "preview.png",
183
+ mimeType: "image/png"
184
+ }
185
+ }
186
+ });
187
+ return [
188
+ 4,
189
+ buildReleaseMetadataDocument(portal, {
190
+ ingestionSessionId: "ingestion-1",
191
+ publicationSessionId: "publication-1",
192
+ releaseId: "release-1",
193
+ release: {
194
+ id: "release-1",
195
+ androidPackage: "com.example.app",
196
+ versionName: "1.0.1",
197
+ versionCode: 2,
198
+ minSdkVersion: 26,
199
+ targetSdkVersion: 36,
200
+ certificateFingerprint: "fingerprint",
201
+ permissions: [
202
+ "android.permission.INTERNET"
203
+ ],
204
+ locales: [
205
+ "en-US"
206
+ ],
207
+ shortDescription: "Pay with QR, NFC, Bluetooth, and .skr",
208
+ longDescription: "Long description",
209
+ localizedName: "Seeker PAY",
210
+ newInVersion: "UI improvements"
211
+ },
212
+ dapp: {
213
+ id: "dapp-1",
214
+ dappName: "Seeker PAY",
215
+ subtitle: "Pay with QR, NFC, Bluetooth, and .skr",
216
+ description: "Long description",
217
+ androidPackage: "com.example.app",
218
+ dappIconUrl: "https://r2.solanamobiledappstore.com/a/icon.png",
219
+ dappPreviewUrls: [
220
+ "https://r2.solanamobiledappstore.com/a/preview.png"
221
+ ],
222
+ appWebsite: "https://example.com",
223
+ contactEmail: "contact@example.com",
224
+ supportEmail: "support@example.com",
225
+ languages: [
226
+ "en-US"
227
+ ],
228
+ licenseUrl: "https://example.com/license",
229
+ copyrightUrl: "https://example.com/copyright",
230
+ privacyPolicyUrl: "https://example.com/privacy",
231
+ walletAddress: "publisher-wallet",
232
+ nftMintAddress: "app-mint"
233
+ },
234
+ publisher: {
235
+ id: "publisher-1",
236
+ type: "organization",
237
+ name: "Publisher",
238
+ website: "https://example.com",
239
+ email: "contact@example.com",
240
+ supportEmail: "support@example.com"
241
+ },
242
+ installFile: {
243
+ uri: "https://example.com/release.apk",
244
+ mimeType: "application/vnd.android.package-archive",
245
+ size: 123456,
246
+ sha256: "apk-hash"
247
+ },
248
+ signerAuthority: {
249
+ dappWalletAddress: "publisher-wallet",
250
+ collectionAuthority: "publisher-wallet",
251
+ appMintAddress: "app-mint",
252
+ sameSignerRequired: true,
253
+ acceptedSignerRoles: [
254
+ "publisher"
255
+ ]
256
+ }
257
+ }, "portal")
258
+ ];
259
+ case 1:
260
+ metadata = _state.sent();
261
+ expect(metadata.image).toBe("https://r2.solanamobiledappstore.com/a/icon.png");
262
+ expect(metadata.extensions.solana_dapp_store.media).toEqual([
263
+ {
264
+ mime: "image/png",
265
+ purpose: "icon",
266
+ uri: "https://r2.solanamobiledappstore.com/a/icon.png",
267
+ width: 1,
268
+ height: 1,
269
+ sha256: expect.any(String)
270
+ },
271
+ {
272
+ mime: "image/png",
273
+ purpose: "screenshot",
274
+ uri: "https://r2.solanamobiledappstore.com/a/preview.png",
275
+ width: 2,
276
+ height: 3,
277
+ sha256: expect.any(String)
278
+ }
279
+ ]);
280
+ expect(metadata.extensions.solana_dapp_store.android_details.target_sdk).toBe(36);
281
+ expect(metadata.extensions.solana_dapp_store.android_details.cert_fingerprint).toBe("fingerprint");
282
+ expect(portal.fetchRemoteFile).toHaveBeenCalledTimes(2);
283
+ expect(portal.createUploadTarget).not.toHaveBeenCalled();
284
+ expect(fetchMock).not.toHaveBeenCalled();
285
+ return [
286
+ 2
287
+ ];
288
+ }
289
+ });
290
+ })();
291
+ });
292
+ it("mirrors portal-hosted release media to R2 before emitting metadata", function() {
293
+ return _async_to_generator(function() {
294
+ var iconUrl, previewUrl, _obj, portal, metadata;
295
+ return _ts_generator(this, function(_state) {
296
+ switch(_state.label){
297
+ case 0:
298
+ fetchMock.mockResolvedValue(new Response("", {
299
+ status: 200
300
+ }));
301
+ iconUrl = "https://dev-portal-uploads-prod-854862047012.s3.amazonaws.com/logos/icon.png";
302
+ previewUrl = "https://dev-portal-uploads-prod-854862047012.s3.amazonaws.com/previews/preview.png";
303
+ portal = makePortalClient({
304
+ fetchRemoteFile: (_obj = {}, _define_property(_obj, iconUrl, {
305
+ data: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aR3sAAAAASUVORK5CYII=", "base64").toString("base64"),
306
+ fileName: "icon.png",
307
+ mimeType: "image/png"
308
+ }), _define_property(_obj, previewUrl, {
309
+ data: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAIAAAADCAIAAAA2iEnWAAAAFElEQVR42mP8z8AARMAgYGJAAwA5WQYB8m8VXwAAAABJRU5ErkJggg==", "base64").toString("base64"),
310
+ fileName: "preview.png",
311
+ mimeType: "image/png"
312
+ }), _obj)
313
+ });
314
+ portal.createUploadTarget.mockResolvedValueOnce({
315
+ uploadUrl: "https://upload.example.com/icon",
316
+ key: "icon",
317
+ providerId: "provider-1",
318
+ publicUrl: "https://r2.solanamobiledappstore.com/a/r2-icon.png"
319
+ }).mockResolvedValueOnce({
320
+ uploadUrl: "https://upload.example.com/preview",
321
+ key: "preview",
322
+ providerId: "provider-1",
323
+ publicUrl: "https://r2.solanamobiledappstore.com/a/r2-preview.png"
324
+ });
325
+ return [
326
+ 4,
327
+ buildReleaseMetadataDocument(portal, {
328
+ ingestionSessionId: "ingestion-1",
329
+ publicationSessionId: "publication-1",
330
+ releaseId: "release-1",
331
+ release: {
332
+ id: "release-1",
333
+ androidPackage: "com.example.app",
334
+ versionName: "1.0.1",
335
+ versionCode: 2,
336
+ minSdkVersion: 26,
337
+ targetSdkVersion: 36,
338
+ certificateFingerprint: "fingerprint",
339
+ permissions: [
340
+ "android.permission.INTERNET"
341
+ ],
342
+ locales: [
343
+ "en-US"
344
+ ],
345
+ shortDescription: "Pay with QR, NFC, Bluetooth, and .skr",
346
+ longDescription: "Long description",
347
+ localizedName: "Seeker PAY",
348
+ newInVersion: "UI improvements"
349
+ },
350
+ dapp: {
351
+ id: "dapp-1",
352
+ dappName: "Seeker PAY",
353
+ subtitle: "Pay with QR, NFC, Bluetooth, and .skr",
354
+ description: "Long description",
355
+ androidPackage: "com.example.app",
356
+ dappIconUrl: iconUrl,
357
+ dappPreviewUrls: [
358
+ previewUrl
359
+ ],
360
+ appWebsite: "https://example.com",
361
+ contactEmail: "contact@example.com",
362
+ supportEmail: "support@example.com",
363
+ languages: [
364
+ "en-US"
365
+ ],
366
+ licenseUrl: "https://example.com/license",
367
+ copyrightUrl: "https://example.com/copyright",
368
+ privacyPolicyUrl: "https://example.com/privacy",
369
+ walletAddress: "publisher-wallet",
370
+ nftMintAddress: "app-mint"
371
+ },
372
+ publisher: {
373
+ id: "publisher-1",
374
+ type: "organization",
375
+ name: "Publisher",
376
+ website: "https://example.com",
377
+ email: "contact@example.com",
378
+ supportEmail: "support@example.com"
379
+ },
380
+ installFile: {
381
+ uri: "https://example.com/release.apk",
382
+ mimeType: "application/vnd.android.package-archive",
383
+ size: 123456,
384
+ sha256: "apk-hash"
385
+ },
386
+ signerAuthority: {
387
+ dappWalletAddress: "publisher-wallet",
388
+ collectionAuthority: "publisher-wallet",
389
+ appMintAddress: "app-mint",
390
+ sameSignerRequired: true,
391
+ acceptedSignerRoles: [
392
+ "publisher"
393
+ ]
394
+ }
395
+ }, "portal")
396
+ ];
397
+ case 1:
398
+ metadata = _state.sent();
399
+ expect(metadata.image).toBe("https://r2.solanamobiledappstore.com/a/r2-icon.png");
400
+ expect(metadata.extensions.solana_dapp_store.media).toMatchObject([
401
+ {
402
+ mime: "image/png",
403
+ purpose: "icon",
404
+ uri: "https://r2.solanamobiledappstore.com/a/r2-icon.png",
405
+ width: 1,
406
+ height: 1,
407
+ sha256: expect.any(String)
408
+ },
409
+ {
410
+ mime: "image/png",
411
+ purpose: "screenshot",
412
+ uri: "https://r2.solanamobiledappstore.com/a/r2-preview.png",
413
+ width: 2,
414
+ height: 3,
415
+ sha256: expect.any(String)
416
+ }
417
+ ]);
418
+ expect(portal.fetchRemoteFile).toHaveBeenCalledTimes(2);
419
+ expect(portal.createUploadTarget).toHaveBeenCalledTimes(2);
420
+ expect(fetchMock).toHaveBeenCalledTimes(2);
421
+ return [
422
+ 2
423
+ ];
424
+ }
425
+ });
426
+ })();
427
+ });
428
+ it("normalizes schemeless media URLs before fetching them", function() {
429
+ return _async_to_generator(function() {
430
+ var iconUrl, normalizedIconUrl, portal, metadata;
431
+ return _ts_generator(this, function(_state) {
432
+ switch(_state.label){
433
+ case 0:
434
+ iconUrl = "r2.solanamobiledappstore.com/a/icon.png";
435
+ normalizedIconUrl = "https://".concat(iconUrl);
436
+ portal = makePortalClient({
437
+ fetchRemoteFile: _define_property({}, normalizedIconUrl, {
438
+ data: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aR3sAAAAASUVORK5CYII=", "base64").toString("base64"),
439
+ fileName: "icon.png",
440
+ mimeType: "image/png"
441
+ })
442
+ });
443
+ return [
444
+ 4,
445
+ buildReleaseMetadataDocument(portal, {
446
+ ingestionSessionId: "ingestion-1",
447
+ publicationSessionId: "publication-1",
448
+ releaseId: "release-1",
449
+ release: {
450
+ id: "release-1",
451
+ androidPackage: "com.example.app",
452
+ versionName: "1.0.1",
453
+ versionCode: 2,
454
+ minSdkVersion: 26,
455
+ targetSdkVersion: 36,
456
+ certificateFingerprint: "fingerprint",
457
+ permissions: [
458
+ "android.permission.INTERNET"
459
+ ],
460
+ locales: [
461
+ "en-US"
462
+ ],
463
+ shortDescription: "Pay with QR, NFC, Bluetooth, and .skr",
464
+ longDescription: "Long description",
465
+ localizedName: "Seeker PAY",
466
+ newInVersion: "UI improvements"
467
+ },
468
+ dapp: {
469
+ id: "dapp-1",
470
+ dappName: "Seeker PAY",
471
+ subtitle: "Pay with QR, NFC, Bluetooth, and .skr",
472
+ description: "Long description",
473
+ androidPackage: "com.example.app",
474
+ dappIconUrl: iconUrl,
475
+ dappPreviewUrls: [],
476
+ appWebsite: "https://example.com",
477
+ contactEmail: "contact@example.com",
478
+ supportEmail: "support@example.com",
479
+ languages: [
480
+ "en-US"
481
+ ],
482
+ licenseUrl: "https://example.com/license",
483
+ copyrightUrl: "https://example.com/copyright",
484
+ privacyPolicyUrl: "https://example.com/privacy",
485
+ walletAddress: "publisher-wallet",
486
+ nftMintAddress: "app-mint"
487
+ },
488
+ publisher: {
489
+ id: "publisher-1",
490
+ type: "organization",
491
+ name: "Publisher",
492
+ website: "https://example.com",
493
+ email: "contact@example.com",
494
+ supportEmail: "support@example.com"
495
+ },
496
+ installFile: {
497
+ uri: "https://example.com/release.apk",
498
+ mimeType: "application/vnd.android.package-archive",
499
+ size: 123456,
500
+ sha256: "apk-hash"
501
+ },
502
+ signerAuthority: {
503
+ dappWalletAddress: "publisher-wallet",
504
+ collectionAuthority: "publisher-wallet",
505
+ appMintAddress: "app-mint",
506
+ sameSignerRequired: true,
507
+ acceptedSignerRoles: [
508
+ "publisher"
509
+ ]
510
+ }
511
+ }, "portal")
512
+ ];
513
+ case 1:
514
+ metadata = _state.sent();
515
+ expect(metadata.image).toBe(normalizedIconUrl);
516
+ expect(portal.fetchRemoteFile).toHaveBeenCalledWith(expect.objectContaining({
517
+ url: normalizedIconUrl
518
+ }));
519
+ expect(portal.createUploadTarget).not.toHaveBeenCalled();
520
+ expect(fetchMock).not.toHaveBeenCalled();
521
+ return [
522
+ 2
523
+ ];
524
+ }
525
+ });
526
+ })();
527
+ });
528
+ it("does not force feature graphics to image/png when the source is jpeg", function() {
529
+ return _async_to_generator(function() {
530
+ var iconUrl, featureGraphicUrl, _obj, portal, metadata, featureMedia;
531
+ return _ts_generator(this, function(_state) {
532
+ switch(_state.label){
533
+ case 0:
534
+ fetchMock.mockResolvedValue(new Response("", {
535
+ status: 200
536
+ }));
537
+ iconUrl = "https://r2.solanamobiledappstore.com/a/icon.png";
538
+ featureGraphicUrl = "https://dev-portal-uploads-prod-854862047012.s3.amazonaws.com/previews/feature.jpg";
539
+ portal = makePortalClient({
540
+ fetchRemoteFile: (_obj = {}, _define_property(_obj, iconUrl, {
541
+ data: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aR3sAAAAASUVORK5CYII=", "base64").toString("base64"),
542
+ fileName: "icon.png",
543
+ mimeType: "image/png"
544
+ }), _define_property(_obj, featureGraphicUrl, {
545
+ data: Buffer.from("iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAQAAAC1HAwCAAAAC0lEQVR42mP8/x8AAwMCAO+aR3sAAAAASUVORK5CYII=", "base64").toString("base64"),
546
+ fileName: "feature.jpg",
547
+ mimeType: "image/jpeg"
548
+ }), _obj)
549
+ });
550
+ portal.createUploadTarget.mockResolvedValueOnce({
551
+ uploadUrl: "https://upload.example.com/feature",
552
+ key: "feature",
553
+ providerId: "provider-1",
554
+ publicUrl: "https://r2.solanamobiledappstore.com/a/r2-feature.jpg"
555
+ });
556
+ return [
557
+ 4,
558
+ buildReleaseMetadataDocument(portal, {
559
+ ingestionSessionId: "ingestion-1",
560
+ publicationSessionId: "publication-1",
561
+ releaseId: "release-1",
562
+ release: {
563
+ id: "release-1",
564
+ androidPackage: "com.example.app",
565
+ versionName: "1.0.1",
566
+ versionCode: 2,
567
+ minSdkVersion: 26,
568
+ targetSdkVersion: 36,
569
+ certificateFingerprint: "fingerprint",
570
+ permissions: [
571
+ "android.permission.INTERNET"
572
+ ],
573
+ locales: [
574
+ "en-US"
575
+ ],
576
+ shortDescription: "Pay with QR, NFC, Bluetooth, and .skr",
577
+ longDescription: "Long description",
578
+ localizedName: "Seeker PAY",
579
+ newInVersion: "UI improvements"
580
+ },
581
+ dapp: {
582
+ id: "dapp-1",
583
+ dappName: "Seeker PAY",
584
+ subtitle: "Pay with QR, NFC, Bluetooth, and .skr",
585
+ description: "Long description",
586
+ androidPackage: "com.example.app",
587
+ dappIconUrl: iconUrl,
588
+ dappPreviewUrls: [],
589
+ featureGraphicUrl: featureGraphicUrl,
590
+ appWebsite: "https://example.com",
591
+ contactEmail: "contact@example.com",
592
+ supportEmail: "support@example.com",
593
+ languages: [
594
+ "en-US"
595
+ ],
596
+ licenseUrl: "https://example.com/license",
597
+ copyrightUrl: "https://example.com/copyright",
598
+ privacyPolicyUrl: "https://example.com/privacy",
599
+ walletAddress: "publisher-wallet",
600
+ nftMintAddress: "app-mint"
601
+ },
602
+ publisher: {
603
+ id: "publisher-1",
604
+ type: "organization",
605
+ name: "Publisher",
606
+ website: "https://example.com",
607
+ email: "contact@example.com",
608
+ supportEmail: "support@example.com"
609
+ },
610
+ installFile: {
611
+ uri: "https://example.com/release.apk",
612
+ mimeType: "application/vnd.android.package-archive",
613
+ size: 123456,
614
+ sha256: "apk-hash"
615
+ },
616
+ signerAuthority: {
617
+ dappWalletAddress: "publisher-wallet",
618
+ collectionAuthority: "publisher-wallet",
619
+ appMintAddress: "app-mint",
620
+ sameSignerRequired: true,
621
+ acceptedSignerRoles: [
622
+ "publisher"
623
+ ]
624
+ }
625
+ }, "portal")
626
+ ];
627
+ case 1:
628
+ metadata = _state.sent();
629
+ featureMedia = metadata.extensions.solana_dapp_store.media.find(function(item) {
630
+ return item.purpose === "featureGraphic";
631
+ });
632
+ expect(featureMedia).toMatchObject({
633
+ mime: "image/jpeg",
634
+ uri: "https://r2.solanamobiledappstore.com/a/r2-feature.jpg"
635
+ });
636
+ expect(portal.fetchRemoteFile).toHaveBeenCalledWith(expect.objectContaining({
637
+ url: featureGraphicUrl,
638
+ expectedMimeType: undefined
639
+ }));
640
+ return [
641
+ 2
642
+ ];
643
+ }
644
+ });
645
+ })();
646
+ });
647
+ });