@solana-mobile/dapp-store-cli 0.16.0 → 1.0.0

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 (113) 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 -458
  82. package/lib/upload/TurboStorageDriver.js +0 -718
  83. package/lib/upload/__tests__/CachedStorageDriver.test.js +0 -437
  84. package/lib/upload/__tests__/TurboStorageDriver.test.js +0 -17
  85. package/lib/upload/__tests__/contentGateway.test.js +0 -17
  86. package/lib/upload/contentGateway.js +0 -23
  87. package/lib/upload/index.js +0 -2
  88. package/src/commands/ValidateCommand.ts +0 -82
  89. package/src/commands/create/CreateCliApp.ts +0 -93
  90. package/src/commands/create/CreateCliRelease.ts +0 -149
  91. package/src/commands/create/index.ts +0 -47
  92. package/src/commands/index.ts +0 -3
  93. package/src/commands/publish/PublishCliRemove.ts +0 -66
  94. package/src/commands/publish/PublishCliSubmit.ts +0 -93
  95. package/src/commands/publish/PublishCliSupport.ts +0 -66
  96. package/src/commands/publish/PublishCliUpdate.ts +0 -101
  97. package/src/commands/publish/index.ts +0 -29
  98. package/src/commands/scaffolding/ScaffoldInit.ts +0 -20
  99. package/src/commands/scaffolding/index.ts +0 -1
  100. package/src/commands/utils.ts +0 -33
  101. package/src/config/EnvVariables.ts +0 -39
  102. package/src/config/PublishDetails.ts +0 -456
  103. package/src/config/S3StorageManager.ts +0 -47
  104. package/src/config/index.ts +0 -2
  105. package/src/prebuild_schema/publishing_source.yaml +0 -64
  106. package/src/prebuild_schema/schemagen.js +0 -31
  107. package/src/upload/CachedStorageDriver.ts +0 -179
  108. package/src/upload/TurboStorageDriver.ts +0 -283
  109. package/src/upload/__tests__/CachedStorageDriver.test.ts +0 -246
  110. package/src/upload/__tests__/TurboStorageDriver.test.ts +0 -15
  111. package/src/upload/__tests__/contentGateway.test.ts +0 -31
  112. package/src/upload/contentGateway.ts +0 -37
  113. package/src/upload/index.ts +0 -2
@@ -0,0 +1,750 @@
1
+ import type {
2
+ PublicationBundle,
3
+ PublicationIngestionSession,
4
+ PublicationSession,
5
+ PublicationSource,
6
+ } from '@solana-mobile/dapp-store-publishing-tools';
7
+
8
+ import {
9
+ ensureHttpsUrl,
10
+ inferFileNameFromUrl,
11
+ inferMimeType,
12
+ } from './files.js';
13
+ import { firstString, isRecord } from './records.js';
14
+ import type { PortalSourceKind } from './types.js';
15
+
16
+ function asString(value: unknown): string {
17
+ return String(value || '');
18
+ }
19
+
20
+ function optionalString(value: unknown): string | null {
21
+ return typeof value === 'string' ? value : null;
22
+ }
23
+
24
+ function stringArray(value: unknown): string[] {
25
+ return Array.isArray(value) ? (value as string[]) : [];
26
+ }
27
+
28
+ function numberOrDefault(value: unknown, fallback: number): number {
29
+ return typeof value === 'number' ? value : fallback;
30
+ }
31
+
32
+ function buildInstallFileDetails(
33
+ release: Record<string, unknown>,
34
+ installFile: Record<string, unknown>
35
+ ) {
36
+ const url = asString(installFile.uri || release.releaseFileUrl || '');
37
+ const fileName =
38
+ (typeof release.releaseFileName === 'string' && release.releaseFileName) ||
39
+ inferFileNameFromUrl(url);
40
+
41
+ return {
42
+ url,
43
+ fileName,
44
+ mimeType:
45
+ typeof installFile.mimeType === 'string'
46
+ ? installFile.mimeType
47
+ : inferMimeType(fileName),
48
+ size: numberOrDefault(installFile.size, 0),
49
+ sha256: typeof installFile.sha256 === 'string' ? installFile.sha256 : null,
50
+ canonicalUrl:
51
+ typeof installFile.canonicalUrl === 'string'
52
+ ? installFile.canonicalUrl
53
+ : url,
54
+ };
55
+ }
56
+
57
+ function normalizePublicationCheckpoint(session: {
58
+ stage?: string;
59
+ mintTransactionSignature?: string | null;
60
+ verificationTransactionSignature?: string | null;
61
+ attestationRequestUniqueId?: string | null;
62
+ hubspotTicketId?: string | null;
63
+ expectedMintAddress?: string | null;
64
+ metadataUri?: string | null;
65
+ }): PublicationSession['checkpoint'] {
66
+ switch (session.stage) {
67
+ case 'Submitted':
68
+ return 'submitted';
69
+ case 'Attested':
70
+ return 'verified';
71
+ case 'Verified':
72
+ case 'VerificationSubmitted':
73
+ return 'verified';
74
+ case 'MintSaved':
75
+ return 'mint-saved';
76
+ case 'MintSubmitted':
77
+ return 'mint-submitted';
78
+ case 'PreparedForMint':
79
+ return 'bundle-ready';
80
+ case 'Failed':
81
+ break;
82
+ default:
83
+ break;
84
+ }
85
+
86
+ if (session.hubspotTicketId) {
87
+ return 'submitted';
88
+ }
89
+
90
+ if (session.attestationRequestUniqueId) {
91
+ return 'verified';
92
+ }
93
+
94
+ if (session.verificationTransactionSignature) {
95
+ return 'verified';
96
+ }
97
+
98
+ if (session.mintTransactionSignature) {
99
+ return 'mint-submitted';
100
+ }
101
+
102
+ if (session.expectedMintAddress || session.metadataUri) {
103
+ return 'bundle-ready';
104
+ }
105
+
106
+ return 'created';
107
+ }
108
+
109
+ function normalizePublicationStatus(session: {
110
+ stage?: string;
111
+ hubspotTicketId?: string | null;
112
+ }) {
113
+ if (session.stage === 'Failed') {
114
+ return 'failed' as const;
115
+ }
116
+
117
+ if (session.stage === 'Submitted' || session.hubspotTicketId) {
118
+ return 'completed' as const;
119
+ }
120
+
121
+ return 'running' as const;
122
+ }
123
+
124
+ export function inferPublicationSourceKind(
125
+ sourceKind?: string
126
+ ): PortalSourceKind {
127
+ if (sourceKind === 'externalUrl') {
128
+ return 'external';
129
+ }
130
+
131
+ return 'portal';
132
+ }
133
+
134
+ export function buildReleaseMetadataDocument(
135
+ bundle: PublicationBundle,
136
+ sourceKind: PortalSourceKind
137
+ ): Record<string, unknown> {
138
+ const releaseName =
139
+ bundle.release.localizedName ||
140
+ bundle.release.releaseName ||
141
+ bundle.metadata.localizedName ||
142
+ bundle.dapp.dappName;
143
+ const shortDescription =
144
+ bundle.metadata.shortDescription || bundle.release.newInVersion || '';
145
+ const longDescription =
146
+ bundle.metadata.longDescription ||
147
+ shortDescription ||
148
+ bundle.dapp.description ||
149
+ '';
150
+ const newInVersion =
151
+ bundle.metadata.newInVersion || bundle.release.newInVersion || '';
152
+ const installFile = bundle.metadata.installFile;
153
+ const image =
154
+ bundle.dapp.dappIconUrl ||
155
+ bundle.dapp.featureGraphicUrl ||
156
+ bundle.dapp.bannerUrl;
157
+
158
+ if (!image) {
159
+ throw new Error(
160
+ 'Publication bundle did not include a public app image URL.'
161
+ );
162
+ }
163
+
164
+ const publisherWebsite =
165
+ bundle.metadata.publisherWebsite ||
166
+ bundle.publisher.website ||
167
+ bundle.dapp.appWebsite ||
168
+ '';
169
+ const licenseUrl =
170
+ bundle.metadata.legal.licenseUrl || bundle.dapp.licenseUrl || '';
171
+ const copyrightUrl =
172
+ bundle.metadata.legal.copyrightUrl || bundle.dapp.copyrightUrl || '';
173
+ const privacyPolicyUrl =
174
+ bundle.metadata.legal.privacyPolicyUrl ||
175
+ bundle.dapp.privacyPolicyUrl ||
176
+ '';
177
+
178
+ return {
179
+ schema_version: '0.4.0',
180
+ name: releaseName.slice(0, 32),
181
+ description: shortDescription || longDescription || releaseName,
182
+ image,
183
+ properties: {
184
+ category: 'dApp',
185
+ creators: [
186
+ {
187
+ address:
188
+ bundle.signerAuthority.dappWalletAddress ||
189
+ bundle.signerAuthority.collectionAuthority,
190
+ share: 100,
191
+ },
192
+ ],
193
+ },
194
+ extensions: {
195
+ solana_dapp_store: {
196
+ publisher_details: {
197
+ name: bundle.publisher.name,
198
+ ...(publisherWebsite
199
+ ? { website: ensureHttpsUrl(publisherWebsite) }
200
+ : {}),
201
+ contact: bundle.publisher.email,
202
+ support_email:
203
+ bundle.publisher.supportEmail ||
204
+ bundle.metadata.supportEmail ||
205
+ bundle.publisher.email,
206
+ },
207
+ release_details: {
208
+ updated_on: new Date().toISOString(),
209
+ ...(licenseUrl ? { license_url: ensureHttpsUrl(licenseUrl) } : {}),
210
+ ...(copyrightUrl ? { copyright_url: copyrightUrl } : {}),
211
+ ...(privacyPolicyUrl
212
+ ? { privacy_policy_url: ensureHttpsUrl(privacyPolicyUrl) }
213
+ : {}),
214
+ localized_resources: {
215
+ short_description: '5',
216
+ long_description: '1',
217
+ new_in_version: '2',
218
+ name: '4',
219
+ },
220
+ },
221
+ media: bundle.metadata.media ?? [],
222
+ files: [
223
+ {
224
+ mime: installFile.mimeType || inferMimeType(installFile.fileName),
225
+ purpose: 'install',
226
+ size: installFile.size ?? 0,
227
+ sha256: installFile.sha256 || '',
228
+ uri: installFile.url,
229
+ },
230
+ ],
231
+ android_details: {
232
+ android_package: bundle.release.androidPackage,
233
+ version_code: bundle.release.versionCode,
234
+ version: bundle.release.versionName,
235
+ min_sdk: bundle.release.minSdkVersion ?? 1,
236
+ cert_fingerprint: bundle.release.certificateFingerprint || '',
237
+ permissions: bundle.release.permissions ?? [],
238
+ locales: bundle.release.locales ?? bundle.dapp.languages ?? ['en-US'],
239
+ },
240
+ },
241
+ i18n: {
242
+ 'en-US': {
243
+ '1': longDescription,
244
+ '2': newInVersion,
245
+ '4': releaseName,
246
+ '5': shortDescription.slice(0, 50),
247
+ },
248
+ },
249
+ },
250
+ __origin: sourceKind,
251
+ };
252
+ }
253
+
254
+ export function mapBackendBundleToPublicationBundle(
255
+ backendBundle: Record<string, unknown>,
256
+ releaseMetadataUri: string,
257
+ sourceKind: PortalSourceKind
258
+ ): PublicationBundle {
259
+ const release = isRecord(backendBundle.release) ? backendBundle.release : {};
260
+ const dapp = isRecord(backendBundle.dapp) ? backendBundle.dapp : {};
261
+ const publisher = isRecord(backendBundle.publisher)
262
+ ? backendBundle.publisher
263
+ : {};
264
+ const installFile = isRecord(backendBundle.installFile)
265
+ ? backendBundle.installFile
266
+ : {};
267
+ const signerAuthority = isRecord(backendBundle.signerAuthority)
268
+ ? backendBundle.signerAuthority
269
+ : {};
270
+ const installFileDetails = buildInstallFileDetails(release, installFile);
271
+
272
+ const releaseName =
273
+ (typeof release.localizedName === 'string' && release.localizedName) ||
274
+ (typeof dapp.dappName === 'string' && dapp.dappName) ||
275
+ 'Release update';
276
+ const shortDescription =
277
+ (typeof release.shortDescription === 'string' &&
278
+ release.shortDescription) ||
279
+ (typeof dapp.subtitle === 'string' && dapp.subtitle) ||
280
+ asString(dapp.description || '').slice(0, 50);
281
+ const localizedShortDescription =
282
+ (typeof release.shortDescription === 'string' &&
283
+ release.shortDescription) ||
284
+ asString(dapp.description || '').slice(0, 50);
285
+ const longDescription =
286
+ (typeof release.longDescription === 'string' && release.longDescription) ||
287
+ asString(dapp.description || '');
288
+ const newInVersion =
289
+ (typeof release.newInVersion === 'string' && release.newInVersion) || '';
290
+
291
+ return {
292
+ ingestionSessionId: asString(backendBundle.ingestionSessionId || ''),
293
+ publicationSessionId: asString(backendBundle.publicationSessionId || ''),
294
+ releaseId: asString(backendBundle.releaseId || release.id || ''),
295
+ dapp: {
296
+ id: asString(dapp.id || ''),
297
+ dappName: asString(dapp.dappName || releaseName),
298
+ subtitle: optionalString(dapp.subtitle),
299
+ description: asString(dapp.description || ''),
300
+ androidPackage: asString(
301
+ dapp.androidPackage || release.androidPackage || ''
302
+ ),
303
+ dappIconUrl: optionalString(dapp.dappIconUrl),
304
+ dappPreviewUrls: stringArray(dapp.dappPreviewUrls),
305
+ bannerUrl: optionalString(dapp.bannerUrl),
306
+ featureGraphicUrl: optionalString(dapp.featureGraphicUrl),
307
+ editorsChoiceGraphicUrl: optionalString(dapp.editorsChoiceGraphicUrl),
308
+ appWebsite: optionalString(dapp.appWebsite),
309
+ contactEmail: optionalString(dapp.contactEmail),
310
+ supportEmail: asString(dapp.supportEmail || publisher.supportEmail || ''),
311
+ languages: stringArray(dapp.languages),
312
+ licenseUrl: optionalString(dapp.licenseUrl),
313
+ copyrightUrl: optionalString(dapp.copyrightUrl),
314
+ privacyPolicyUrl: optionalString(dapp.privacyPolicyUrl),
315
+ walletAddress: asString(dapp.walletAddress || ''),
316
+ nftMintAddress: asString(dapp.nftMintAddress || ''),
317
+ lastApprovedReleaseId: optionalString(dapp.lastApprovedReleaseId),
318
+ website: optionalString(dapp.website || dapp.appWebsite),
319
+ },
320
+ publisher: {
321
+ id: asString(publisher.id || ''),
322
+ type:
323
+ publisher.type === 'organization' || publisher.type === 'individual'
324
+ ? publisher.type
325
+ : 'organization',
326
+ name: asString(publisher.name || ''),
327
+ website: asString(publisher.website || ''),
328
+ email: asString(publisher.email || ''),
329
+ supportEmail: asString(
330
+ publisher.supportEmail || dapp.supportEmail || publisher.email || ''
331
+ ),
332
+ },
333
+ installFile: {
334
+ uri: installFileDetails.url,
335
+ mimeType: installFileDetails.mimeType,
336
+ size: installFileDetails.size,
337
+ sha256: installFileDetails.sha256,
338
+ fileName: installFileDetails.fileName,
339
+ canonicalUrl: installFileDetails.canonicalUrl,
340
+ url: installFileDetails.url,
341
+ origin: sourceKind,
342
+ },
343
+ metadata: {
344
+ localizedName: releaseName,
345
+ shortDescription,
346
+ longDescription,
347
+ newInVersion,
348
+ publisherWebsite: optionalString(publisher.website),
349
+ supportEmail: optionalString(publisher.supportEmail),
350
+ website: optionalString(dapp.appWebsite),
351
+ locales: stringArray(dapp.languages),
352
+ legal: {
353
+ licenseUrl: optionalString(dapp.licenseUrl),
354
+ copyrightUrl: optionalString(dapp.copyrightUrl),
355
+ privacyPolicyUrl: optionalString(dapp.privacyPolicyUrl),
356
+ },
357
+ media: [],
358
+ installFile: {
359
+ url: installFileDetails.url,
360
+ fileName: installFileDetails.fileName,
361
+ mimeType: installFileDetails.mimeType,
362
+ size: installFileDetails.size,
363
+ sha256: installFileDetails.sha256,
364
+ canonicalUrl: installFileDetails.canonicalUrl,
365
+ origin: sourceKind,
366
+ },
367
+ localizedStrings: [
368
+ {
369
+ locale: 'en-US',
370
+ name: releaseName,
371
+ shortDescription: localizedShortDescription,
372
+ longDescription,
373
+ newInVersion,
374
+ },
375
+ ],
376
+ releaseMetadataUri:
377
+ releaseMetadataUri ||
378
+ (typeof release.nftMetadataUri === 'string'
379
+ ? release.nftMetadataUri
380
+ : null),
381
+ },
382
+ signerAuthority: {
383
+ dappWalletAddress: asString(
384
+ signerAuthority.dappWalletAddress ||
385
+ signerAuthority.requiredSigner ||
386
+ dapp.walletAddress ||
387
+ ''
388
+ ),
389
+ collectionAuthority: asString(
390
+ signerAuthority.collectionAuthority ||
391
+ signerAuthority.dappWalletAddress ||
392
+ dapp.walletAddress ||
393
+ ''
394
+ ),
395
+ appMintAddress: asString(
396
+ signerAuthority.appMintAddress || dapp.nftMintAddress || ''
397
+ ),
398
+ sameSignerRequired:
399
+ typeof signerAuthority.sameSignerRequired === 'boolean'
400
+ ? signerAuthority.sameSignerRequired
401
+ : true,
402
+ acceptedSignerRoles: Array.isArray(signerAuthority.acceptedSignerRoles)
403
+ ? (signerAuthority.acceptedSignerRoles as Array<'publisher' | 'payer'>)
404
+ : ['publisher', 'payer'],
405
+ dappId: asString(dapp.id || ''),
406
+ requiredSigner:
407
+ typeof signerAuthority.requiredSigner === 'string'
408
+ ? signerAuthority.requiredSigner
409
+ : asString(
410
+ signerAuthority.dappWalletAddress ||
411
+ signerAuthority.collectionAuthority ||
412
+ dapp.walletAddress ||
413
+ ''
414
+ ),
415
+ mintSigner:
416
+ typeof signerAuthority.mintSigner === 'string'
417
+ ? signerAuthority.mintSigner
418
+ : asString(
419
+ signerAuthority.dappWalletAddress ||
420
+ signerAuthority.collectionAuthority ||
421
+ dapp.walletAddress ||
422
+ ''
423
+ ),
424
+ feePayer: optionalString(signerAuthority.feePayer),
425
+ },
426
+ release: {
427
+ id: asString(release.id || backendBundle.releaseId || ''),
428
+ dappId: asString(release.dappId || dapp.id || ''),
429
+ releaseFileUrl: optionalString(
430
+ typeof release.releaseFileUrl === 'string'
431
+ ? release.releaseFileUrl
432
+ : installFileDetails.url
433
+ ),
434
+ releaseFileName: asString(
435
+ typeof release.releaseFileName === 'string'
436
+ ? release.releaseFileName
437
+ : installFileDetails.fileName
438
+ ),
439
+ releaseFileSize: numberOrDefault(
440
+ release.releaseFileSize || installFileDetails.size,
441
+ installFileDetails.size
442
+ ),
443
+ releaseFileHash:
444
+ typeof release.releaseFileHash === 'string'
445
+ ? release.releaseFileHash
446
+ : installFileDetails.sha256,
447
+ releaseName,
448
+ versionName:
449
+ (typeof release.versionName === 'string' && release.versionName) ||
450
+ asString(release.versionCode || '1'),
451
+ versionCode: numberOrDefault(release.versionCode, 1),
452
+ androidPackage: asString(
453
+ release.androidPackage || dapp.androidPackage || ''
454
+ ),
455
+ minSdkVersion:
456
+ typeof release.minSdkVersion === 'number'
457
+ ? release.minSdkVersion
458
+ : null,
459
+ targetSdkVersion:
460
+ typeof release.targetSdkVersion === 'number'
461
+ ? release.targetSdkVersion
462
+ : null,
463
+ permissions: stringArray(release.permissions),
464
+ locales: stringArray(release.locales),
465
+ certificateFingerprint:
466
+ typeof release.certificateFingerprint === 'string'
467
+ ? release.certificateFingerprint
468
+ : null,
469
+ shortDescription:
470
+ typeof release.shortDescription === 'string'
471
+ ? release.shortDescription
472
+ : null,
473
+ longDescription:
474
+ typeof release.longDescription === 'string'
475
+ ? release.longDescription
476
+ : null,
477
+ localizedName:
478
+ (typeof release.localizedName === 'string' && release.localizedName) ||
479
+ releaseName,
480
+ newInVersion,
481
+ sagaFeatures:
482
+ typeof release.sagaFeatures === 'string' ? release.sagaFeatures : null,
483
+ status: typeof release.status === 'string' ? release.status : undefined,
484
+ processingError: optionalString(release.processingError),
485
+ processedAt: optionalString(release.processedAt),
486
+ releaseMintAddress: optionalString(release.nftMintAddress),
487
+ releaseMetadataUri:
488
+ releaseMetadataUri ||
489
+ (typeof release.nftMetadataUri === 'string'
490
+ ? release.nftMetadataUri
491
+ : null),
492
+ nftMintAddress: optionalString(release.nftMintAddress),
493
+ nftTransactionSignature: optionalString(release.nftTransactionSignature),
494
+ nftMetadataUri: optionalString(release.nftMetadataUri),
495
+ nftCluster: optionalString(release.nftCluster),
496
+ isCollectionVerified:
497
+ typeof release.isCollectionVerified === 'boolean'
498
+ ? release.isCollectionVerified
499
+ : undefined,
500
+ uploadProvider:
501
+ release.uploadProvider === 'Arweave' ||
502
+ release.uploadProvider === 'S3' ||
503
+ release.uploadProvider === 'R2' ||
504
+ release.uploadProvider === 'IPFS'
505
+ ? release.uploadProvider
506
+ : null,
507
+ uploadProviderId: optionalString(release.uploadProviderId),
508
+ publishedAt: optionalString(release.publishedAt),
509
+ rejectedAt: optionalString(release.rejectedAt),
510
+ rejectionReason: optionalString(release.rejectionReason),
511
+ submissionStatus:
512
+ typeof release.submissionStatus === 'string'
513
+ ? release.submissionStatus
514
+ : undefined,
515
+ hubspotTicketId: optionalString(release.hubspotTicketId),
516
+ submittedAt: optionalString(release.submittedAt),
517
+ reviewStartedAt: optionalString(release.reviewStartedAt),
518
+ reviewCompletedAt: optionalString(release.reviewCompletedAt),
519
+ source:
520
+ release.source === 'Portal' || release.source === 'Hubspot'
521
+ ? release.source
522
+ : undefined,
523
+ created: optionalString(release.created) || undefined,
524
+ updated: optionalString(release.updated) || undefined,
525
+ isLive: typeof release.isLive === 'boolean' ? release.isLive : undefined,
526
+ liveVersionComparison:
527
+ typeof release.liveVersionComparison === 'string'
528
+ ? release.liveVersionComparison
529
+ : undefined,
530
+ },
531
+ };
532
+ }
533
+
534
+ export function translateBackendPublicationSession(
535
+ backendSession: Record<string, unknown>
536
+ ): PublicationSession {
537
+ const stage =
538
+ typeof backendSession.stage === 'string'
539
+ ? backendSession.stage
540
+ : 'PreparedForMint';
541
+
542
+ return {
543
+ id: asString(backendSession.id || ''),
544
+ ingestionSessionId: asString(backendSession.ingestionSessionId || ''),
545
+ releaseId: asString(backendSession.releaseId || ''),
546
+ status: normalizePublicationStatus({
547
+ stage,
548
+ hubspotTicketId: optionalString(backendSession.hubspotTicketId),
549
+ }),
550
+ checkpoint: normalizePublicationCheckpoint({
551
+ stage,
552
+ mintTransactionSignature: optionalString(
553
+ backendSession.mintTransactionSignature
554
+ ),
555
+ verificationTransactionSignature: optionalString(
556
+ backendSession.verificationTransactionSignature
557
+ ),
558
+ attestationRequestUniqueId: optionalString(
559
+ backendSession.attestationRequestUniqueId
560
+ ),
561
+ hubspotTicketId: optionalString(backendSession.hubspotTicketId),
562
+ expectedMintAddress: optionalString(backendSession.expectedMintAddress),
563
+ metadataUri: optionalString(backendSession.metadataUri),
564
+ }),
565
+ metadataUri: optionalString(backendSession.metadataUri),
566
+ releaseMintAddress: optionalString(backendSession.expectedMintAddress),
567
+ collectionMintAddress: null,
568
+ mintTransactionSignature: optionalString(
569
+ backendSession.mintTransactionSignature
570
+ ),
571
+ verifyTransactionSignature: optionalString(
572
+ backendSession.verificationTransactionSignature
573
+ ),
574
+ attestationRequestUniqueId: optionalString(
575
+ backendSession.attestationRequestUniqueId
576
+ ),
577
+ attestationPayload: null,
578
+ hubspotTicketId: optionalString(backendSession.hubspotTicketId),
579
+ error: optionalString(backendSession.lastError),
580
+ created:
581
+ typeof backendSession.created === 'string'
582
+ ? backendSession.created
583
+ : new Date().toISOString(),
584
+ updated:
585
+ typeof backendSession.updated === 'string'
586
+ ? backendSession.updated
587
+ : new Date().toISOString(),
588
+ createdAt:
589
+ typeof backendSession.created === 'string'
590
+ ? backendSession.created
591
+ : undefined,
592
+ updatedAt:
593
+ typeof backendSession.updated === 'string'
594
+ ? backendSession.updated
595
+ : undefined,
596
+ };
597
+ }
598
+
599
+ function translateIngestionSource(
600
+ backendSession: Record<string, unknown>
601
+ ): PublicationSource {
602
+ const sourceKind =
603
+ typeof backendSession.sourceKind === 'string'
604
+ ? backendSession.sourceKind
605
+ : 'portalUpload';
606
+ const sourceUrl =
607
+ typeof backendSession.sourceUrl === 'string'
608
+ ? backendSession.sourceUrl
609
+ : '';
610
+
611
+ return {
612
+ kind:
613
+ sourceKind === 'externalUrl'
614
+ ? ('apk-url' as const)
615
+ : sourceKind === 'existingRelease'
616
+ ? ('existingRelease' as const)
617
+ : ('apk-file' as const),
618
+ filePath:
619
+ sourceKind === 'existingRelease'
620
+ ? asString(backendSession.releaseId || '')
621
+ : sourceUrl,
622
+ fileName:
623
+ typeof backendSession.releaseFileName === 'string'
624
+ ? backendSession.releaseFileName
625
+ : inferFileNameFromUrl(sourceUrl),
626
+ url: sourceUrl,
627
+ mimeType: inferMimeType(
628
+ typeof backendSession.releaseFileName === 'string'
629
+ ? backendSession.releaseFileName
630
+ : inferFileNameFromUrl(sourceUrl)
631
+ ),
632
+ size:
633
+ typeof backendSession.releaseFileSize === 'number'
634
+ ? backendSession.releaseFileSize
635
+ : undefined,
636
+ sha256:
637
+ typeof backendSession.releaseFileHash === 'string'
638
+ ? backendSession.releaseFileHash
639
+ : undefined,
640
+ canonicalUrl:
641
+ typeof backendSession.canonicalSourceUrl === 'string'
642
+ ? backendSession.canonicalSourceUrl
643
+ : undefined,
644
+ sourceReleaseId: asString(backendSession.releaseId || ''),
645
+ } as
646
+ | {
647
+ kind: 'apk-file';
648
+ fileName: string;
649
+ filePath: string;
650
+ mimeType?: string;
651
+ size?: number;
652
+ sha256?: string;
653
+ }
654
+ | {
655
+ kind: 'apk-url';
656
+ url: string;
657
+ fileName?: string;
658
+ mimeType?: string;
659
+ size?: number;
660
+ sha256?: string;
661
+ canonicalUrl?: string;
662
+ }
663
+ | {
664
+ kind: 'existingRelease';
665
+ sourceReleaseId: string;
666
+ };
667
+ }
668
+
669
+ export function translateBackendIngestionSession(
670
+ backendSession: Record<string, unknown>,
671
+ bundle?: Record<string, unknown>,
672
+ publicationSession?: Record<string, unknown>
673
+ ): PublicationIngestionSession & {
674
+ bundle?: PublicationBundle;
675
+ publicationSession?: PublicationSession;
676
+ } {
677
+ const sourceKind =
678
+ typeof backendSession.sourceKind === 'string'
679
+ ? backendSession.sourceKind
680
+ : 'portalUpload';
681
+ const translatedPublicationSession = publicationSession
682
+ ? translateBackendPublicationSession(publicationSession)
683
+ : undefined;
684
+ const translatedBundle = bundle
685
+ ? {
686
+ ...mapBackendBundleToPublicationBundle(
687
+ bundle,
688
+ firstString(publicationSession, ['metadataUri']) ||
689
+ firstString(bundle, ['release.nftMetadataUri']) ||
690
+ '',
691
+ inferPublicationSourceKind(sourceKind)
692
+ ),
693
+ ingestionSessionId: asString(backendSession.id || ''),
694
+ publicationSessionId:
695
+ typeof backendSession.publicationSessionId === 'string'
696
+ ? backendSession.publicationSessionId
697
+ : translatedPublicationSession?.id || '',
698
+ releaseId:
699
+ typeof backendSession.releaseId === 'string'
700
+ ? backendSession.releaseId
701
+ : firstString(bundle, ['release.id']) || '',
702
+ }
703
+ : undefined;
704
+
705
+ return {
706
+ id: asString(backendSession.id || ''),
707
+ dappId: asString(backendSession.dappId || ''),
708
+ idempotencyKey: asString(backendSession.idempotencyKey || ''),
709
+ source: translateIngestionSource(backendSession),
710
+ whatsNew: asString(backendSession.whatsNew || ''),
711
+ status:
712
+ backendSession.status === 'Failed'
713
+ ? 'failed'
714
+ : backendSession.status === 'Ready'
715
+ ? 'ready'
716
+ : backendSession.status === 'Processing'
717
+ ? 'processing'
718
+ : 'created',
719
+ publicationSessionId:
720
+ typeof backendSession.publicationSessionId === 'string'
721
+ ? backendSession.publicationSessionId
722
+ : translatedPublicationSession?.id ?? null,
723
+ releaseId:
724
+ typeof backendSession.releaseId === 'string'
725
+ ? backendSession.releaseId
726
+ : translatedBundle?.releaseId ??
727
+ translatedPublicationSession?.releaseId ??
728
+ null,
729
+ processingError: optionalString(backendSession.processingError),
730
+ processingProgress:
731
+ typeof backendSession.processingProgress === 'number'
732
+ ? backendSession.processingProgress
733
+ : null,
734
+ processingStage: optionalString(backendSession.processingStage),
735
+ processingDetail: optionalString(backendSession.processingDetail),
736
+ error: optionalString(backendSession.processingError),
737
+ createdAt:
738
+ typeof backendSession.created === 'string'
739
+ ? backendSession.created
740
+ : undefined,
741
+ updatedAt:
742
+ typeof backendSession.updated === 'string'
743
+ ? backendSession.updated
744
+ : undefined,
745
+ ...(translatedBundle ? { bundle: translatedBundle } : {}),
746
+ ...(translatedPublicationSession
747
+ ? { publicationSession: translatedPublicationSession }
748
+ : {}),
749
+ };
750
+ }