@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,174 @@
1
+ import { expect, jest, test } from '@jest/globals';
2
+ import { Writable } from 'node:stream';
3
+ import { createPublicationProgressReporter } from '../PublicationProgressReporter';
4
+ function createTestStream() {
5
+ var output = [];
6
+ var stream = new Writable({
7
+ write: function write(chunk, _encoding, callback) {
8
+ output.push(String(chunk));
9
+ callback();
10
+ }
11
+ });
12
+ stream.isTTY = false;
13
+ stream.columns = 120;
14
+ stream.output = output;
15
+ return stream;
16
+ }
17
+ test('local file upload progress materially advances the bar', function() {
18
+ var logSpy = jest.spyOn(console, 'log').mockImplementation(function() {});
19
+ var stream = createTestStream();
20
+ try {
21
+ var reporter = createPublicationProgressReporter({
22
+ title: 'Publishing version',
23
+ mode: 'new-version',
24
+ stream: stream
25
+ });
26
+ reporter.start({
27
+ metadata: {
28
+ sourceKind: 'apk-file',
29
+ fileName: 'app-release.apk'
30
+ }
31
+ });
32
+ reporter.logger.debug('Uploading APK to portal storage', {
33
+ step: 'source.upload',
34
+ status: 'running',
35
+ fileName: 'app-release.apk',
36
+ fileSize: 100,
37
+ bytesUploaded: 50,
38
+ bytesTotal: 100,
39
+ stepProgress: 0.5
40
+ });
41
+ var lines = reporter.buildLines();
42
+ expect(reporter.getProgressPercent()).toBeGreaterThan(0.2);
43
+ expect(lines[1]).toContain('23%');
44
+ expect(lines).toContain('Upload: 50 B / 100 B (50%)');
45
+ } finally{
46
+ logSpy.mockRestore();
47
+ }
48
+ });
49
+ test('ingestion status updates continue advancing overall progress', function() {
50
+ var logSpy = jest.spyOn(console, 'log').mockImplementation(function() {});
51
+ var stream = createTestStream();
52
+ try {
53
+ var reporter = createPublicationProgressReporter({
54
+ title: 'Publishing version',
55
+ mode: 'new-version',
56
+ stream: stream
57
+ });
58
+ reporter.start({
59
+ metadata: {
60
+ sourceKind: 'apk-file',
61
+ fileName: 'app-release.apk'
62
+ }
63
+ });
64
+ reporter.logger.info('APK uploaded to portal storage', {
65
+ step: 'source.upload',
66
+ status: 'complete',
67
+ fileName: 'app-release.apk'
68
+ });
69
+ reporter.logger.info('Extracting APK metadata', {
70
+ step: 'ingestion.wait',
71
+ status: 'running',
72
+ ingestionStatus: 'processing',
73
+ ingestionStage: 'ExtractingApk',
74
+ ingestionDetail: 'Extracting APK metadata',
75
+ ingestionProgress: 45,
76
+ stepProgress: 0.7
77
+ });
78
+ var lines = reporter.buildLines();
79
+ expect(reporter.getProgressPercent()).toBeGreaterThan(0.55);
80
+ expect(lines[1]).toContain('57%');
81
+ expect(lines).toContain('Ingestion: Extracting APK metadata (45%)');
82
+ } finally{
83
+ logSpy.mockRestore();
84
+ }
85
+ });
86
+ test('ingestion detail and percentage are rendered while polling', function() {
87
+ var logSpy = jest.spyOn(console, 'log').mockImplementation(function() {});
88
+ var stream = createTestStream();
89
+ try {
90
+ var reporter = createPublicationProgressReporter({
91
+ title: 'Publishing version',
92
+ mode: 'new-version',
93
+ stream: stream
94
+ });
95
+ reporter.start({
96
+ metadata: {
97
+ sourceKind: 'apk-file',
98
+ fileName: 'app-release.apk'
99
+ }
100
+ });
101
+ reporter.logger.info('Extracting APK metadata', {
102
+ step: 'ingestion.wait',
103
+ status: 'running',
104
+ ingestionStatus: 'processing',
105
+ ingestionProgress: 45,
106
+ ingestionStage: 'ExtractingApk',
107
+ ingestionDetail: 'Extracting APK metadata',
108
+ stepProgress: 0.45
109
+ });
110
+ var lines = reporter.buildLines();
111
+ expect(lines).toContain('Ingestion: Extracting APK metadata (45%)');
112
+ } finally{
113
+ logSpy.mockRestore();
114
+ }
115
+ });
116
+ test('verbose mode prints detailed identifiers as they arrive', function() {
117
+ var logSpy = jest.spyOn(console, 'log').mockImplementation(function() {});
118
+ var stream = createTestStream();
119
+ try {
120
+ var reporter = createPublicationProgressReporter({
121
+ title: 'Publishing version',
122
+ mode: 'new-version',
123
+ verbose: true,
124
+ stream: stream
125
+ });
126
+ reporter.start({
127
+ metadata: {
128
+ sourceKind: 'apk-file',
129
+ fileName: 'app-release.apk'
130
+ }
131
+ });
132
+ reporter.logger.info('Ingestion session created', {
133
+ step: 'ingestion.create',
134
+ status: 'complete',
135
+ releaseId: 'release-123',
136
+ ingestionSessionId: 'ingestion-456'
137
+ });
138
+ reporter.logger.info('Publication session loaded', {
139
+ step: 'session.load',
140
+ status: 'complete',
141
+ publicationSessionId: 'session-789'
142
+ });
143
+ reporter.logger.info('Release NFT transaction submitted', {
144
+ step: 'mint.submit',
145
+ status: 'complete',
146
+ transactionSignature: 'release-tx-sig'
147
+ });
148
+ reporter.logger.info('Release collection verified', {
149
+ step: 'verify.submit',
150
+ status: 'complete',
151
+ transactionSignature: 'collection-tx-sig'
152
+ });
153
+ reporter.logger.info('Attestation payload created', {
154
+ step: 'attestation.create',
155
+ status: 'complete',
156
+ requestUniqueId: 'attest-111'
157
+ });
158
+ reporter.logger.info('Release submitted to store', {
159
+ step: 'submit.store',
160
+ status: 'complete',
161
+ hubspotTicketId: 'ticket-222'
162
+ });
163
+ var output = stream.output.join('');
164
+ expect(output).toContain('Verbose: Release ID: release-123');
165
+ expect(output).toContain('Verbose: Ingestion session ID: ingestion-456');
166
+ expect(output).toContain('Verbose: Publication session ID: session-789');
167
+ expect(output).toContain('Verbose: Release transaction signature: release-tx-sig');
168
+ expect(output).toContain('Verbose: Collection transaction signature: collection-tx-sig');
169
+ expect(output).toContain('Verbose: Attestation request ID: attest-111');
170
+ expect(output).toContain('Verbose: Ticket ID: ticket-222');
171
+ } finally{
172
+ logSpy.mockRestore();
173
+ }
174
+ });
@@ -118,78 +118,102 @@ function _ts_generator(thisArg, body) {
118
118
  };
119
119
  }
120
120
  }
121
- import { createAppJson, createReleaseJson, validateApp, validateRelease, metaplexFileReplacer } from "@solana-mobile/dapp-store-publishing-tools";
122
- import { debug, showMessage } from "../CliUtils.js";
123
- import { loadPublishDetailsWithChecks } from "../config/PublishDetails.js";
124
- export var validateCommand = function(param) {
125
- var signer = param.signer, buildToolsPath = param.buildToolsPath;
126
- return _async_to_generator(function() {
127
- var _ref, publisherDetails, appDetails, releaseDetails, appJson, _appJson_image, _e_message, errorMsg, releaseJson, objStringified, _e_message1, errorMsg1;
128
- return _ts_generator(this, function(_state) {
129
- switch(_state.label){
130
- case 0:
131
- return [
132
- 4,
133
- loadPublishDetailsWithChecks(buildToolsPath)
134
- ];
135
- case 1:
136
- _ref = _state.sent(), publisherDetails = _ref.publisher, appDetails = _ref.app, releaseDetails = _ref.release;
137
- debug({
138
- publisherDetails: publisherDetails,
139
- appDetails: appDetails,
140
- releaseDetails: releaseDetails
141
- });
142
- appJson = createAppJson(appDetails, signer.publicKey);
143
- if (typeof appJson.image !== "string") {
144
- ;
145
- appJson.image = (_appJson_image = appJson.image) === null || _appJson_image === void 0 ? void 0 : _appJson_image.fileName;
146
- }
147
- debug("appJson=", JSON.stringify({
148
- appJson: appJson
149
- }, metaplexFileReplacer, 2));
150
- try {
151
- validateApp(appJson);
152
- } catch (e) {
153
- ;
154
- errorMsg = (_e_message = e === null || e === void 0 ? void 0 : e.message) !== null && _e_message !== void 0 ? _e_message : "";
155
- showMessage("App JSON invalid", errorMsg, "error");
121
+ import { describe, expect, it, jest } from "@jest/globals";
122
+ import { PublicKey } from "@solana/web3.js";
123
+ import { ensurePublicationSignerBalance, MIN_PUBLICATION_SIGNER_BALANCE_LAMPORTS, resolveFundingPreflightRpcUrl } from "../fundingPreflight.js";
124
+ describe("resolveFundingPreflightRpcUrl", function() {
125
+ it("defaults to mainnet when local-dev is not enabled", function() {
126
+ expect(resolveFundingPreflightRpcUrl({})).toBe("https://api.mainnet-beta.solana.com");
127
+ });
128
+ it("skips the default RPC in local-dev mode", function() {
129
+ expect(resolveFundingPreflightRpcUrl({
130
+ localDev: true
131
+ })).toBeUndefined();
132
+ });
133
+ it("honors an explicit RPC URL", function() {
134
+ expect(resolveFundingPreflightRpcUrl({
135
+ localDev: true,
136
+ rpcUrl: "https://rpc.example.com"
137
+ })).toBe("https://rpc.example.com");
138
+ });
139
+ });
140
+ describe("ensurePublicationSignerBalance", function() {
141
+ var publicKey = new PublicKey(new Uint8Array(32).fill(7)).toBase58();
142
+ it("throws before publication when the signer balance is too low", function() {
143
+ return _async_to_generator(function() {
144
+ return _ts_generator(this, function(_state) {
145
+ switch(_state.label){
146
+ case 0:
147
+ return [
148
+ 4,
149
+ expect(ensurePublicationSignerBalance({
150
+ publicKey: publicKey
151
+ }, function() {
152
+ return {
153
+ getBalance: jest.fn().mockResolvedValue(MIN_PUBLICATION_SIGNER_BALANCE_LAMPORTS - 1)
154
+ };
155
+ })).rejects.toThrow("publishing needs at least")
156
+ ];
157
+ case 1:
158
+ _state.sent();
156
159
  return [
157
160
  2
158
161
  ];
159
- }
160
- return [
161
- 4,
162
- createReleaseJson({
163
- releaseDetails: releaseDetails,
164
- appDetails: appDetails,
165
- publisherDetails: publisherDetails
166
- }, signer.publicKey)
167
- ];
168
- case 2:
169
- releaseJson = _state.sent();
170
- if (appDetails.android_package != releaseDetails.android_details.android_package) {
171
- showMessage("App package name and release package name do not match", "App release specifies " + appDetails.android_package + " while release specifies " + releaseDetails.android_details.android_package, "error");
162
+ }
163
+ });
164
+ })();
165
+ });
166
+ it("returns without warning when the signer balance is sufficient", function() {
167
+ return _async_to_generator(function() {
168
+ var result;
169
+ return _ts_generator(this, function(_state) {
170
+ switch(_state.label){
171
+ case 0:
172
+ return [
173
+ 4,
174
+ ensurePublicationSignerBalance({
175
+ publicKey: publicKey
176
+ }, function() {
177
+ return {
178
+ getBalance: jest.fn().mockResolvedValue(MIN_PUBLICATION_SIGNER_BALANCE_LAMPORTS + 1)
179
+ };
180
+ })
181
+ ];
182
+ case 1:
183
+ result = _state.sent();
184
+ expect(result).toBeUndefined();
172
185
  return [
173
186
  2
174
187
  ];
175
- }
176
- objStringified = JSON.stringify(releaseJson, metaplexFileReplacer, 2);
177
- debug("releaseJson=", objStringified);
178
- try {
179
- validateRelease(JSON.parse(objStringified));
180
- } catch (e) {
181
- ;
182
- errorMsg1 = (_e_message1 = e === null || e === void 0 ? void 0 : e.message) !== null && _e_message1 !== void 0 ? _e_message1 : "";
183
- showMessage("Release JSON invalid", errorMsg1, "error");
188
+ }
189
+ });
190
+ })();
191
+ });
192
+ it("returns a warning when the RPC balance check fails", function() {
193
+ return _async_to_generator(function() {
194
+ var result;
195
+ return _ts_generator(this, function(_state) {
196
+ switch(_state.label){
197
+ case 0:
198
+ return [
199
+ 4,
200
+ ensurePublicationSignerBalance({
201
+ publicKey: publicKey
202
+ }, function() {
203
+ return {
204
+ getBalance: jest.fn().mockRejectedValue(new Error("RPC timeout"))
205
+ };
206
+ })
207
+ ];
208
+ case 1:
209
+ result = _state.sent();
210
+ expect(result).toContain("Continuing without a SOL preflight check");
211
+ expect(result).toContain("RPC timeout");
184
212
  return [
185
213
  2
186
214
  ];
187
- }
188
- showMessage("Json is Valid", "Input data is valid", "standard");
189
- return [
190
- 2
191
- ];
192
- }
193
- });
194
- })();
195
- };
215
+ }
216
+ });
217
+ })();
218
+ });
219
+ });
@@ -0,0 +1,26 @@
1
+ import { expect, test } from '@jest/globals';
2
+ import { extractPublicationSummaryLines } from '../publicationSummary';
3
+ test('summary keeps the completion message and only the compact fields', function() {
4
+ var lines = extractPublicationSummaryLines({
5
+ releaseId: 'release-123',
6
+ publicationSessionId: 'session-123',
7
+ ingestionSessionId: 'ingestion-123',
8
+ releaseMintAddress: 'release-mint-abc',
9
+ collectionMintAddress: 'collection-mint-def',
10
+ releaseTransactionSignature: 'release-tx',
11
+ collectionTransactionSignature: 'collection-tx',
12
+ attestationRequestUniqueId: 'attest-123',
13
+ hubspotTicketId: 'ticket-123'
14
+ });
15
+ expect(lines).toEqual([
16
+ 'This app is now in review.',
17
+ 'Release mint address: release-mint-abc',
18
+ 'Collection mint address: collection-mint-def',
19
+ 'Ticket ID: ticket-123'
20
+ ]);
21
+ });
22
+ test('summary still shows the in-review message when no fields are available', function() {
23
+ expect(extractPublicationSummaryLines(undefined)).toEqual([
24
+ 'This app is now in review.'
25
+ ]);
26
+ });