@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
package/lib/CliSetup.js CHANGED
@@ -27,6 +27,41 @@ function _async_to_generator(fn) {
27
27
  });
28
28
  };
29
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 _instanceof(left, right) {
44
+ if (right != null && typeof Symbol !== "undefined" && right[Symbol.hasInstance]) {
45
+ return !!right[Symbol.hasInstance](left);
46
+ } else {
47
+ return left instanceof right;
48
+ }
49
+ }
50
+ function _object_spread(target) {
51
+ for(var i = 1; i < arguments.length; i++){
52
+ var source = arguments[i] != null ? arguments[i] : {};
53
+ var ownKeys = Object.keys(source);
54
+ if (typeof Object.getOwnPropertySymbols === "function") {
55
+ ownKeys = ownKeys.concat(Object.getOwnPropertySymbols(source).filter(function(sym) {
56
+ return Object.getOwnPropertyDescriptor(source, sym).enumerable;
57
+ }));
58
+ }
59
+ ownKeys.forEach(function(key) {
60
+ _define_property(target, key, source[key]);
61
+ });
62
+ }
63
+ return target;
64
+ }
30
65
  function _ts_generator(thisArg, body) {
31
66
  var f, y, t, _ = {
32
67
  label: 0,
@@ -118,101 +153,46 @@ function _ts_generator(thisArg, body) {
118
153
  };
119
154
  }
120
155
  }
121
- import { Command } from "commander";
122
- import { validateCommand } from "./commands/index.js";
123
- import { createAppCommand, createReleaseCommand } from "./commands/create/index.js";
124
- import { publishRemoveCommand, publishSubmitCommand, publishSupportCommand, publishUpdateCommand } from "./commands/publish/index.js";
125
- import { checkForSelfUpdate, checkSubmissionNetwork, Constants, alphaAppSubmissionMessage, dryRunSuccessMessage, generateNetworkSuffix, parseKeypair, showMessage, showNetworkWarningIfApplicable } from "./CliUtils.js";
126
- import * as dotenv from "dotenv";
127
- import { initScaffold } from "./commands/scaffolding/index.js";
128
- import { loadPublishDetails, loadPublishDetailsWithChecks } from "./config/PublishDetails.js";
156
+ import { randomUUID } from 'node:crypto';
157
+ import path from 'node:path';
158
+ import * as dotenv from 'dotenv';
159
+ import { Command, Option } from 'commander';
160
+ import { Constants, createPortalAttestationClient, createPortalWorkflowClient, createPublicationSignerFromKeypair, parseKeypair, showMessage } from './CliUtils.js';
161
+ import { DEFAULT_API_KEY_ENV, DEFAULT_PRODUCTION_PORTAL_URL, resolveApiKey, resolvePortalTargets, validateNewVersionArgs, validateResumeArgs } from './publication/cliValidation.js';
162
+ import { createPublicationProgressReporter } from './publication/PublicationProgressReporter.js';
163
+ import { extractPublicationSummaryLines } from './publication/publicationSummary.js';
164
+ import { runPublicationWorkflow } from './publication/runPublicationWorkflow.js';
165
+ import { ensurePublicationSignerBalance } from './publication/fundingPreflight.js';
129
166
  dotenv.config();
130
- var hasAddressInConfig = function(param) {
131
- var address = param.address;
132
- return !!address;
133
- };
134
167
  export var mainCli = new Command();
135
- function resolveBuildToolsPath(buildToolsPath) {
136
- // If a path was specified on the command line, use that
137
- if (buildToolsPath !== undefined) {
138
- return buildToolsPath;
139
- }
140
- // If a path is specified in a .env file, use that
141
- if (process.env.ANDROID_TOOLS_DIR !== undefined) {
142
- return process.env.ANDROID_TOOLS_DIR;
143
- }
144
- // No path was specified
145
- return;
146
- }
147
- /**
148
- * This method should be updated with each new release of the CLI, and just do nothing when there isn't anything to report
149
- */ function latestReleaseMessage() {
150
- var messages = [
151
- "- Banner Graphic image of size 1200x600px is now manadatory for publishing updates.",
152
- "- Feature Graphic image of size 1200x1200px is required to be featured in Editor's choice carousel. (optional)",
153
- "- Release metadata now publishes publisher.support_email when provided; otherwise we reuse publisher.email for end-user support."
154
- ].join('\n\n');
155
- showMessage("Publishing Tools Version ".concat(Constants.CLI_VERSION), messages, "warning");
156
- }
157
- function tryWithErrorMessage(block) {
168
+ mainCli.name('dapp-store').version(Constants.CLI_VERSION).description('Portal-backed CLI for Solana Mobile dApp version publishing').showHelpAfterError();
169
+ mainCli.option('--apk-file <path>', 'Path to the APK file to publish').option('--apk-url <url>', 'HTTPS URL for an externally hosted APK').option('--whats-new <text>', 'What changed in this version').option('--portal-url <url>', 'Publishing portal base URL').option('--api-key-env <name>', 'Environment variable that contains the portal API key', DEFAULT_API_KEY_ENV).option('--api-key-stdin', 'Read the portal API key from stdin instead of an env var').option('--keypair <path>', 'Path to the Solana signer keypair').addOption(new Option('--rpc-url <url>', 'Solana RPC URL').hideHelp()).option('--local-dev', 'Allow localhost portal endpoints and skip gating').option('--skip-self-update', 'Bypass the self-update check when working against a local portal').option('--idempotency-key <key>', 'Optional idempotency key for safe retries').option('--verbose', 'Print detailed publication identifiers as they are emitted').action(function() {
158
170
  return _async_to_generator(function() {
159
- var e, _e_message, errorMsg;
160
171
  return _ts_generator(this, function(_state) {
161
172
  switch(_state.label){
162
173
  case 0:
163
- _state.trys.push([
164
- 0,
165
- 2,
166
- ,
167
- 3
168
- ]);
169
174
  return [
170
175
  4,
171
- block()
176
+ runRootAction()
172
177
  ];
173
178
  case 1:
174
179
  _state.sent();
175
- return [
176
- 3,
177
- 3
178
- ];
179
- case 2:
180
- e = _state.sent();
181
- errorMsg = (_e_message = e === null || e === void 0 ? void 0 : e.message) !== null && _e_message !== void 0 ? _e_message : "";
182
- showMessage("Error", errorMsg, "error");
183
- process.exit(-1);
184
- return [
185
- 3,
186
- 3
187
- ];
188
- case 3:
189
180
  return [
190
181
  2
191
182
  ];
192
183
  }
193
184
  });
194
185
  })();
195
- }
196
- mainCli.name("dapp-store").version(Constants.CLI_VERSION).description("CLI to assist with publishing to the Saga Dapp Store");
197
- export var initCliCmd = mainCli.command("init").description("First-time initialization of tooling configuration").action(function() {
186
+ });
187
+ var resumeCommand = mainCli.command('resume');
188
+ resumeCommand.description('Resume a partially completed publication session').option('--release-id <id>', 'Publication release identifier').option('--resume-release <id>', 'Alias for --release-id').option('--session-id <id>', 'Publication session identifier').option('--resume-session <id>', 'Alias for --session-id').option('--portal-url <url>', 'Publishing portal base URL').option('--api-key-env <name>', 'Environment variable that contains the portal API key', DEFAULT_API_KEY_ENV).option('--api-key-stdin', 'Read the portal API key from stdin instead of an env var').option('--keypair <path>', 'Path to the Solana signer keypair').addOption(new Option('--rpc-url <url>', 'Solana RPC URL').hideHelp()).option('--local-dev', 'Allow localhost portal endpoints and skip gating').option('--skip-self-update', 'Bypass the self-update check when working against a local portal').option('--verbose', 'Print detailed publication identifiers as they are emitted').action(function(options) {
198
189
  return _async_to_generator(function() {
199
190
  return _ts_generator(this, function(_state) {
200
191
  switch(_state.label){
201
192
  case 0:
202
193
  return [
203
194
  4,
204
- tryWithErrorMessage(function() {
205
- return _async_to_generator(function() {
206
- var msg;
207
- return _ts_generator(this, function(_state) {
208
- msg = initScaffold();
209
- showMessage("Initialized", msg);
210
- return [
211
- 2
212
- ];
213
- });
214
- })();
215
- })
195
+ runResumeAction(options)
216
196
  ];
217
197
  case 1:
218
198
  _state.sent();
@@ -223,200 +203,114 @@ export var initCliCmd = mainCli.command("init").description("First-time initiali
223
203
  });
224
204
  })();
225
205
  });
226
- export var createCliCmd = mainCli.command("create").description("Create a `app`, or `release`");
227
- createCliCmd.addHelpText("after", [
228
- "",
229
- "Release metadata notes:",
230
- " We include publisher.support_email when provided; if omitted we fall back to publisher.email."
231
- ].join("\n"));
232
- export var createAppCliCmd = createCliCmd.command("app").description("Create a app").requiredOption("-k, --keypair <path-to-keypair-file>", "Path to keypair file").option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET).option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT").option("-s, --storage-config <storage-config>", "Provide alternative storage configuration details").option("-p, --priority-fee-lamports <priority-fee-lamports>", "Priority Fee lamports").action(function(param) {
233
- var keypair = param.keypair, url = param.url, dryRun = param.dryRun, storageConfig = param.storageConfig, priorityFeeLamports = param.priorityFeeLamports;
206
+ mainCli.addHelpText('after', [
207
+ '',
208
+ 'Usage:',
209
+ ' dapp-store --apk-file ./app.apk --whats-new "Bug fixes"',
210
+ ' dapp-store --apk-url https://example.com/app.apk --whats-new "Bug fixes"',
211
+ ' dapp-store resume --release-id <release-id> [--session-id <session-id>]',
212
+ '',
213
+ 'Portal:',
214
+ ' Set DAPP_STORE_PORTAL_URL to the portal origin (for example https://staging.publish.solanamobile.com).',
215
+ ' The CLI derives the /api endpoint from that URL for the active publication workflow.',
216
+ " If unset, it defaults to ".concat(DEFAULT_PRODUCTION_PORTAL_URL, "."),
217
+ ' The target app must already exist in the portal and already have its App NFT.',
218
+ ' The portal decides whether the submission is the first release for an existing app or a later update.',
219
+ '',
220
+ 'Secrets:',
221
+ " Portal API key defaults to ".concat(DEFAULT_API_KEY_ENV, " or the name passed via --api-key-env."),
222
+ ' Use --api-key-stdin to read the portal API key from stdin.',
223
+ '',
224
+ 'Local development:',
225
+ ' Pass --local-dev to allow localhost portal endpoints and to skip self-update gating.',
226
+ ' Local-dev mode rejects non-local portal URLs.'
227
+ ].join('\n'));
228
+ function runRootAction() {
234
229
  return _async_to_generator(function() {
235
230
  return _ts_generator(this, function(_state) {
236
231
  switch(_state.label){
237
232
  case 0:
238
233
  return [
239
234
  4,
240
- tryWithErrorMessage(function() {
235
+ runWithUserFacingErrors(function() {
241
236
  return _async_to_generator(function() {
242
- var signer, result, displayUrl, transactionUrl, resultText;
237
+ var options, targets, apiKey, signerKeypair, balanceWarning, signer, clients, progress, result, error;
243
238
  return _ts_generator(this, function(_state) {
244
239
  switch(_state.label){
245
240
  case 0:
246
- showNetworkWarningIfApplicable(url);
247
- latestReleaseMessage();
241
+ options = mainCli.opts();
242
+ if (!hasPublicationInputs(options)) {
243
+ mainCli.outputHelp();
244
+ return [
245
+ 2
246
+ ];
247
+ }
248
+ validateNewVersionArgs(options);
249
+ enforceSelfUpdatePolicy(options);
250
+ targets = resolvePortalTargets(options);
248
251
  return [
249
252
  4,
250
- checkForSelfUpdate()
253
+ resolveApiKey(options)
251
254
  ];
252
255
  case 1:
253
- _state.sent();
254
- signer = parseKeypair(keypair);
255
- if (!signer) return [
256
- 3,
257
- 3
258
- ];
256
+ apiKey = _state.sent();
257
+ signerKeypair = loadSignerKeypair(options.keypair);
259
258
  return [
260
259
  4,
261
- createAppCommand({
262
- signer: signer,
263
- url: url,
264
- dryRun: dryRun,
265
- storageParams: storageConfig,
266
- priorityFeeLamports: priorityFeeLamports
260
+ ensurePublicationSignerBalance({
261
+ publicKey: signerKeypair.publicKey.toBase58(),
262
+ rpcUrl: options.rpcUrl,
263
+ localDev: options.localDev
267
264
  })
268
265
  ];
269
266
  case 2:
270
- result = _state.sent();
271
- if (dryRun) {
272
- dryRunSuccessMessage();
273
- } else {
274
- displayUrl = "https://explorer.solana.com/address/".concat(result.appAddress).concat(generateNetworkSuffix(url));
275
- transactionUrl = "https://explorer.solana.com/tx/".concat(result.transactionSignature).concat(generateNetworkSuffix(url));
276
- resultText = "App NFT successfully minted:\n".concat(displayUrl, "\n").concat(transactionUrl);
277
- showMessage("Success", resultText);
267
+ balanceWarning = _state.sent();
268
+ if (balanceWarning) {
269
+ showMessage('Warning', balanceWarning, 'warning');
278
270
  }
271
+ signer = createPublicationSignerFromKeypair(signerKeypair);
272
+ clients = createPortalClients(targets, apiKey);
273
+ progress = createPublicationProgressReporter({
274
+ title: 'Publishing version',
275
+ mode: 'new-version',
276
+ verbose: options.verbose
277
+ });
278
+ progress.start({
279
+ message: 'Connecting to publishing portal',
280
+ metadata: buildNewVersionProgressMetadata(options)
281
+ });
279
282
  _state.label = 3;
280
283
  case 3:
281
- return [
282
- 2
283
- ];
284
- }
285
- });
286
- })();
287
- })
288
- ];
289
- case 1:
290
- _state.sent();
291
- return [
292
- 2
293
- ];
294
- }
295
- });
296
- })();
297
- });
298
- export var createReleaseCliCmd = createCliCmd.command("release").description("Create a release").requiredOption("-k, --keypair <path-to-keypair-file>", "Path to keypair file").option("-a, --app-mint-address <app-mint-address>", "The mint address of the app NFT").option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET).option("-d, --dry-run", "Flag for dry run. Doesn't mint an NFT").option("-b, --build-tools-path <build-tools-path>", "Path to Android build tools which contains AAPT2").option("-s, --storage-config <storage-config>", "Provide alternative storage configuration details").option("-p, --priority-fee-lamports <priority-fee-lamports>", "Priority Fee lamports").action(function(param) {
299
- var appMintAddress = param.appMintAddress, keypair = param.keypair, url = param.url, dryRun = param.dryRun, buildToolsPath = param.buildToolsPath, storageConfig = param.storageConfig, priorityFeeLamports = param.priorityFeeLamports;
300
- return _async_to_generator(function() {
301
- return _ts_generator(this, function(_state) {
302
- switch(_state.label){
303
- case 0:
304
- return [
305
- 4,
306
- tryWithErrorMessage(function() {
307
- return _async_to_generator(function() {
308
- var resolvedBuildToolsPath, config, signer, result, displayUrl, transactionUrl, resultText;
309
- return _ts_generator(this, function(_state) {
310
- switch(_state.label){
311
- case 0:
312
- showNetworkWarningIfApplicable(url);
313
- latestReleaseMessage();
314
- return [
315
- 4,
316
- checkForSelfUpdate()
317
- ];
318
- case 1:
319
- _state.sent();
320
- resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
321
- if (resolvedBuildToolsPath === undefined) {
322
- throw new Error("Please specify an Android build tools directory in the .env file or via the command line argument.");
323
- }
324
- return [
325
- 4,
326
- loadPublishDetailsWithChecks()
327
- ];
328
- case 2:
329
- config = _state.sent();
330
- if (!hasAddressInConfig(config.app) && !appMintAddress) {
331
- throw new Error("Either specify an app mint address in the config file or specify as a CLI argument to this command");
332
- }
333
- signer = parseKeypair(keypair);
334
- if (!signer) return [
284
+ _state.trys.push([
335
285
  3,
336
- 4
337
- ];
286
+ 5,
287
+ ,
288
+ 6
289
+ ]);
338
290
  return [
339
291
  4,
340
- createReleaseCommand({
341
- appMintAddress: appMintAddress,
342
- buildToolsPath: resolvedBuildToolsPath,
343
- signer: signer,
344
- url: url,
345
- dryRun: dryRun,
346
- storageParams: storageConfig,
347
- priorityFeeLamports: priorityFeeLamports
292
+ runPublicationWorkflow({
293
+ mode: 'new-version',
294
+ client: clients.workflowClient,
295
+ input: buildNewVersionWorkflowInput(options, signer, clients.attestationClient),
296
+ options: {
297
+ logger: progress.logger
298
+ }
348
299
  })
349
300
  ];
350
- case 3:
351
- result = _state.sent();
352
- if (dryRun) {
353
- dryRunSuccessMessage();
354
- } else {
355
- displayUrl = "https://explorer.solana.com/address/".concat(result === null || result === void 0 ? void 0 : result.releaseAddress).concat(generateNetworkSuffix(url));
356
- transactionUrl = "https://explorer.solana.com/tx/".concat(result.transactionSignature).concat(generateNetworkSuffix(url));
357
- resultText = "Release NFT successfully minted:\n".concat(displayUrl, "\n").concat(transactionUrl);
358
- showMessage("Success", resultText);
359
- }
360
- _state.label = 4;
361
301
  case 4:
302
+ result = _state.sent();
303
+ progress.complete(result);
304
+ showPublicationSummary('Version publication completed', result);
362
305
  return [
363
- 2
364
- ];
365
- }
366
- });
367
- })();
368
- })
369
- ];
370
- case 1:
371
- _state.sent();
372
- return [
373
- 2
374
- ];
375
- }
376
- });
377
- })();
378
- });
379
- mainCli.command("validate").description("Validates details prior to publishing").requiredOption("-k, --keypair <path-to-keypair-file>", "Path to keypair file").option("-b, --build-tools-path <build-tools-path>", "Path to Android build tools which contains AAPT2").action(function(param) {
380
- var keypair = param.keypair, buildToolsPath = param.buildToolsPath;
381
- return _async_to_generator(function() {
382
- return _ts_generator(this, function(_state) {
383
- switch(_state.label){
384
- case 0:
385
- return [
386
- 4,
387
- tryWithErrorMessage(function() {
388
- return _async_to_generator(function() {
389
- var resolvedBuildToolsPath, signer;
390
- return _ts_generator(this, function(_state) {
391
- switch(_state.label){
392
- case 0:
393
- latestReleaseMessage();
394
- return [
395
- 4,
396
- checkForSelfUpdate()
397
- ];
398
- case 1:
399
- _state.sent();
400
- resolvedBuildToolsPath = resolveBuildToolsPath(buildToolsPath);
401
- if (resolvedBuildToolsPath === undefined) {
402
- throw new Error("Please specify an Android build tools directory in the .env file or via the command line argument.");
403
- }
404
- signer = parseKeypair(keypair);
405
- if (!signer) return [
406
306
  3,
407
- 3
408
- ];
409
- return [
410
- 4,
411
- validateCommand({
412
- signer: signer,
413
- buildToolsPath: resolvedBuildToolsPath
414
- })
307
+ 6
415
308
  ];
416
- case 2:
417
- _state.sent();
418
- _state.label = 3;
419
- case 3:
309
+ case 5:
310
+ error = _state.sent();
311
+ progress.fail(error);
312
+ throw error;
313
+ case 6:
420
314
  return [
421
315
  2
422
316
  ];
@@ -433,173 +327,72 @@ mainCli.command("validate").description("Validates details prior to publishing")
433
327
  }
434
328
  });
435
329
  })();
436
- });
437
- var publishCommand = mainCli.command("publish").description("Submit a publishing request (`submit`, `update`, `remove`, or `support`) to the Solana Mobile dApp publisher portal");
438
- publishCommand.command("submit").description("Submit a new app to the Solana Mobile dApp publisher portal").requiredOption("-k, --keypair <path-to-keypair-file>", "Path to keypair file").requiredOption("--complies-with-solana-dapp-store-policies", "An attestation that the app complies with the Solana dApp Store policies").requiredOption("--requestor-is-authorized", "An attestation that the party making this Solana dApp publisher portal request is authorized to do so").option("-a, --app-mint-address <app-mint-address>", "The mint address of the app NFT. If not specified, the value from your config file will be used.").option("-r, --release-mint-address <release-mint-address>", "The mint address of the release NFT. If not specified, the value from your config file will be used.").option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET).option("-d, --dry-run", "Flag for dry run. Doesn't submit the request to the publisher portal.").option("-l, --alpha", "Flag to mark the submission as alpha test.").action(function(param) {
439
- var appMintAddress = param.appMintAddress, releaseMintAddress = param.releaseMintAddress, keypair = param.keypair, url = param.url, compliesWithSolanaDappStorePolicies = param.compliesWithSolanaDappStorePolicies, requestorIsAuthorized = param.requestorIsAuthorized, dryRun = param.dryRun, alpha = param.alpha;
330
+ }
331
+ function runResumeAction(options) {
440
332
  return _async_to_generator(function() {
441
333
  return _ts_generator(this, function(_state) {
442
334
  switch(_state.label){
443
335
  case 0:
444
336
  return [
445
337
  4,
446
- tryWithErrorMessage(function() {
338
+ runWithUserFacingErrors(function() {
447
339
  return _async_to_generator(function() {
448
- var config, signer;
340
+ var targets, apiKey, signer, clients, progress, result, error;
449
341
  return _ts_generator(this, function(_state) {
450
342
  switch(_state.label){
451
343
  case 0:
344
+ validateResumeArgs(options);
345
+ enforceSelfUpdatePolicy(options);
346
+ targets = resolvePortalTargets(options);
452
347
  return [
453
348
  4,
454
- checkForSelfUpdate()
349
+ resolveApiKey(options)
455
350
  ];
456
351
  case 1:
457
- _state.sent();
458
- checkSubmissionNetwork(url);
459
- return [
460
- 4,
461
- loadPublishDetails(Constants.getConfigFilePath())
462
- ];
352
+ apiKey = _state.sent();
353
+ signer = createPublicationSignerFromKeypair(loadSignerKeypair(options.keypair));
354
+ clients = createPortalClients(targets, apiKey);
355
+ progress = createPublicationProgressReporter({
356
+ title: 'Resuming publication',
357
+ mode: 'resume',
358
+ verbose: options.verbose
359
+ });
360
+ progress.start({
361
+ message: 'Connecting to publishing portal',
362
+ metadata: buildResumeProgressMetadata(options)
363
+ });
364
+ _state.label = 2;
463
365
  case 2:
464
- config = _state.sent();
465
- if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
466
- throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.");
467
- }
468
- if (alpha) {
469
- alphaAppSubmissionMessage();
470
- }
471
- signer = parseKeypair(keypair);
472
- if (!signer) return [
473
- 3,
474
- 7
475
- ];
476
- if (!(config.lastUpdatedVersionOnStore != null && config.lastSubmittedVersionOnChain.address != null)) return [
477
- 3,
478
- 4
479
- ];
366
+ _state.trys.push([
367
+ 2,
368
+ 4,
369
+ ,
370
+ 5
371
+ ]);
480
372
  return [
481
373
  4,
482
- publishUpdateCommand({
483
- appMintAddress: appMintAddress,
484
- releaseMintAddress: releaseMintAddress,
485
- signer: signer,
486
- url: url,
487
- dryRun: dryRun,
488
- compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
489
- requestorIsAuthorized: requestorIsAuthorized,
490
- critical: false,
491
- alphaTest: alpha
374
+ runPublicationWorkflow({
375
+ mode: 'resume',
376
+ client: clients.workflowClient,
377
+ input: buildResumeWorkflowInput(options, signer, clients.attestationClient),
378
+ options: {
379
+ logger: progress.logger
380
+ }
492
381
  })
493
382
  ];
494
383
  case 3:
495
- _state.sent();
384
+ result = _state.sent();
385
+ progress.complete(result);
386
+ showPublicationSummary('Publication resume completed', result);
496
387
  return [
497
388
  3,
498
- 6
389
+ 5
499
390
  ];
500
391
  case 4:
501
- return [
502
- 4,
503
- publishSubmitCommand({
504
- appMintAddress: appMintAddress,
505
- releaseMintAddress: releaseMintAddress,
506
- signer: signer,
507
- url: url,
508
- dryRun: dryRun,
509
- compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
510
- requestorIsAuthorized: requestorIsAuthorized,
511
- alphaTest: alpha
512
- })
513
- ];
392
+ error = _state.sent();
393
+ progress.fail(error);
394
+ throw error;
514
395
  case 5:
515
- _state.sent();
516
- _state.label = 6;
517
- case 6:
518
- if (dryRun) {
519
- dryRunSuccessMessage();
520
- } else {
521
- showMessage("Success", "Successfully submitted to the Solana Mobile dApp publisher portal");
522
- }
523
- _state.label = 7;
524
- case 7:
525
- return [
526
- 2
527
- ];
528
- }
529
- });
530
- })();
531
- })
532
- ];
533
- case 1:
534
- _state.sent();
535
- return [
536
- 2
537
- ];
538
- }
539
- });
540
- })();
541
- });
542
- publishCommand.command("update").description("Update an existing app on the Solana Mobile dApp publisher portal").requiredOption("-k, --keypair <path-to-keypair-file>", "Path to keypair file").requiredOption("--complies-with-solana-dapp-store-policies", "An attestation that the app complies with the Solana dApp Store policies").requiredOption("--requestor-is-authorized", "An attestation that the party making this Solana dApp publisher portal request is authorized to do so").option("-a, --app-mint-address <app-mint-address>", "The mint address of the app NFT. If not specified, the value from your config file will be used.").option("-r, --release-mint-address <release-mint-address>", "The mint address of the release NFT. If not specified, the value from your config file will be used.").option("-c, --critical", "Flag for a critical app update request").option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET).option("-d, --dry-run", "Flag for dry run. Doesn't submit the request to the publisher portal.").option("-l, --alpha", "Flag to mark the submission as alpha test.").action(function(param) {
543
- var appMintAddress = param.appMintAddress, releaseMintAddress = param.releaseMintAddress, keypair = param.keypair, url = param.url, compliesWithSolanaDappStorePolicies = param.compliesWithSolanaDappStorePolicies, requestorIsAuthorized = param.requestorIsAuthorized, critical = param.critical, dryRun = param.dryRun, alpha = param.alpha;
544
- return _async_to_generator(function() {
545
- return _ts_generator(this, function(_state) {
546
- switch(_state.label){
547
- case 0:
548
- return [
549
- 4,
550
- tryWithErrorMessage(function() {
551
- return _async_to_generator(function() {
552
- var config, signer;
553
- return _ts_generator(this, function(_state) {
554
- switch(_state.label){
555
- case 0:
556
- return [
557
- 4,
558
- checkForSelfUpdate()
559
- ];
560
- case 1:
561
- _state.sent();
562
- checkSubmissionNetwork(url);
563
- return [
564
- 4,
565
- loadPublishDetails(Constants.getConfigFilePath())
566
- ];
567
- case 2:
568
- config = _state.sent();
569
- if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
570
- throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.");
571
- }
572
- if (alpha) {
573
- alphaAppSubmissionMessage();
574
- }
575
- signer = parseKeypair(keypair);
576
- if (!signer) return [
577
- 3,
578
- 4
579
- ];
580
- return [
581
- 4,
582
- publishUpdateCommand({
583
- appMintAddress: appMintAddress,
584
- releaseMintAddress: releaseMintAddress,
585
- signer: signer,
586
- url: url,
587
- dryRun: dryRun,
588
- compliesWithSolanaDappStorePolicies: compliesWithSolanaDappStorePolicies,
589
- requestorIsAuthorized: requestorIsAuthorized,
590
- critical: critical,
591
- alphaTest: alpha
592
- })
593
- ];
594
- case 3:
595
- _state.sent();
596
- if (dryRun) {
597
- dryRunSuccessMessage();
598
- } else {
599
- showMessage("Success", "dApp successfully updated on the publisher portal");
600
- }
601
- _state.label = 4;
602
- case 4:
603
396
  return [
604
397
  2
605
398
  ];
@@ -616,150 +409,156 @@ publishCommand.command("update").description("Update an existing app on the Sola
616
409
  }
617
410
  });
618
411
  })();
619
- });
620
- publishCommand.command("remove").description("Remove an existing app from the Solana Mobile dApp publisher portal").requiredOption("-k, --keypair <path-to-keypair-file>", "Path to keypair file").requiredOption("--requestor-is-authorized", "An attestation that the party making this Solana dApp publisher portal request is authorized to do so").option("-a, --app-mint-address <app-mint-address>", "The mint address of the app NFT. If not specified, the value from your config file will be used.").option("-r, --release-mint-address <release-mint-address>", "The mint address of the release NFT. If not specified, the value from your config file will be used.").option("-c, --critical", "Flag for a critical app removal request").option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET).option("-d, --dry-run", "Flag for dry run. Doesn't submit the request to the publisher portal.").action(function(param) {
621
- var appMintAddress = param.appMintAddress, releaseMintAddress = param.releaseMintAddress, keypair = param.keypair, url = param.url, requestorIsAuthorized = param.requestorIsAuthorized, critical = param.critical, dryRun = param.dryRun;
412
+ }
413
+ function createPortalClients(targets, apiKey) {
414
+ return {
415
+ workflowClient: createPortalWorkflowClient({
416
+ apiBaseUrl: targets.apiBaseUrl,
417
+ apiKey: apiKey
418
+ }),
419
+ attestationClient: createPortalAttestationClient({
420
+ apiBaseUrl: targets.apiBaseUrl,
421
+ apiKey: apiKey
422
+ })
423
+ };
424
+ }
425
+ function buildNewVersionWorkflowInput(options, signer, attestationClient) {
426
+ var _options_whatsNew, _options_idempotencyKey;
427
+ return {
428
+ source: buildPublicationSource(options),
429
+ whatsNew: (_options_whatsNew = options.whatsNew) !== null && _options_whatsNew !== void 0 ? _options_whatsNew : '',
430
+ idempotencyKey: (_options_idempotencyKey = options.idempotencyKey) !== null && _options_idempotencyKey !== void 0 ? _options_idempotencyKey : randomUUID(),
431
+ signer: signer,
432
+ attestationClient: attestationClient
433
+ };
434
+ }
435
+ function buildResumeWorkflowInput(options, signer, attestationClient) {
436
+ return {
437
+ publicationSessionId: resolveResumeSessionId(options),
438
+ releaseId: resolveResumeReleaseId(options),
439
+ signer: signer,
440
+ attestationClient: attestationClient
441
+ };
442
+ }
443
+ function buildPublicationSource(options) {
444
+ if (options.apkFile) {
445
+ return {
446
+ kind: 'apk-file',
447
+ filePath: options.apkFile,
448
+ fileName: path.basename(options.apkFile)
449
+ };
450
+ }
451
+ if (!options.apkUrl) {
452
+ throw new Error('`--apk-file` or `--apk-url` is required.');
453
+ }
454
+ return {
455
+ kind: 'apk-url',
456
+ url: options.apkUrl,
457
+ fileName: inferFileNameFromUrl(options.apkUrl)
458
+ };
459
+ }
460
+ function buildNewVersionProgressMetadata(options) {
461
+ if (options.apkFile) {
462
+ return {
463
+ sourceKind: 'apk-file',
464
+ fileName: path.basename(options.apkFile)
465
+ };
466
+ }
467
+ if (options.apkUrl) {
468
+ var fileName = inferFileNameFromUrl(options.apkUrl);
469
+ return _object_spread({
470
+ sourceKind: 'apk-url',
471
+ apkUrl: options.apkUrl
472
+ }, fileName ? {
473
+ fileName: fileName
474
+ } : {});
475
+ }
476
+ return {};
477
+ }
478
+ function buildResumeProgressMetadata(options) {
479
+ var publicationSessionId = resolveResumeSessionId(options);
480
+ var releaseId = resolveResumeReleaseId(options);
481
+ return _object_spread({}, publicationSessionId ? {
482
+ publicationSessionId: publicationSessionId
483
+ } : {}, releaseId ? {
484
+ releaseId: releaseId
485
+ } : {});
486
+ }
487
+ function inferFileNameFromUrl(url) {
488
+ try {
489
+ var pathname = new URL(url).pathname;
490
+ var fileName = pathname.split('/').filter(Boolean).pop();
491
+ return fileName || undefined;
492
+ } catch (e) {
493
+ return undefined;
494
+ }
495
+ }
496
+ function enforceSelfUpdatePolicy(options) {
497
+ if (options.skipSelfUpdate && !options.localDev) {
498
+ throw new Error('`--skip-self-update` is only allowed together with `--local-dev`.');
499
+ }
500
+ }
501
+ function resolveResumeReleaseId(options) {
502
+ var _options_releaseId;
503
+ return (_options_releaseId = options.releaseId) !== null && _options_releaseId !== void 0 ? _options_releaseId : options.resumeRelease;
504
+ }
505
+ function resolveResumeSessionId(options) {
506
+ var _options_sessionId;
507
+ return (_options_sessionId = options.sessionId) !== null && _options_sessionId !== void 0 ? _options_sessionId : options.resumeSession;
508
+ }
509
+ function loadSignerKeypair(keypairPath) {
510
+ if (!keypairPath) {
511
+ throw new Error('`--keypair` is required.');
512
+ }
513
+ var keypair = parseKeypair(keypairPath);
514
+ if (!keypair) {
515
+ throw new Error('Failed to load the signer keypair.');
516
+ }
517
+ return keypair;
518
+ }
519
+ function hasPublicationInputs(options) {
520
+ return Boolean(options.apkFile || options.apkUrl || options.whatsNew || options.portalUrl || options.keypair || options.rpcUrl || options.idempotencyKey || options.dappId || options.verbose);
521
+ }
522
+ function showPublicationSummary(title, result) {
523
+ var summaryLines = extractPublicationSummaryLines(result);
524
+ showMessage(title, summaryLines.join('\n'), 'standard');
525
+ }
526
+ function runWithUserFacingErrors(block) {
622
527
  return _async_to_generator(function() {
528
+ var error, message;
623
529
  return _ts_generator(this, function(_state) {
624
530
  switch(_state.label){
625
531
  case 0:
532
+ _state.trys.push([
533
+ 0,
534
+ 2,
535
+ ,
536
+ 3
537
+ ]);
626
538
  return [
627
539
  4,
628
- tryWithErrorMessage(function() {
629
- return _async_to_generator(function() {
630
- var config, signer;
631
- return _ts_generator(this, function(_state) {
632
- switch(_state.label){
633
- case 0:
634
- return [
635
- 4,
636
- checkForSelfUpdate()
637
- ];
638
- case 1:
639
- _state.sent();
640
- checkSubmissionNetwork(url);
641
- return [
642
- 4,
643
- loadPublishDetails(Constants.getConfigFilePath())
644
- ];
645
- case 2:
646
- config = _state.sent();
647
- if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
648
- throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.");
649
- }
650
- signer = parseKeypair(keypair);
651
- if (!signer) return [
652
- 3,
653
- 4
654
- ];
655
- return [
656
- 4,
657
- publishRemoveCommand({
658
- appMintAddress: appMintAddress,
659
- releaseMintAddress: releaseMintAddress,
660
- signer: signer,
661
- url: url,
662
- dryRun: dryRun,
663
- requestorIsAuthorized: requestorIsAuthorized,
664
- critical: critical
665
- })
666
- ];
667
- case 3:
668
- _state.sent();
669
- if (dryRun) {
670
- dryRunSuccessMessage();
671
- } else {
672
- showMessage("Success", "dApp successfully removed from the publisher portal");
673
- }
674
- _state.label = 4;
675
- case 4:
676
- return [
677
- 2
678
- ];
679
- }
680
- });
681
- })();
682
- })
540
+ block()
683
541
  ];
684
542
  case 1:
685
543
  _state.sent();
686
544
  return [
687
- 2
545
+ 3,
546
+ 3
688
547
  ];
689
- }
690
- });
691
- })();
692
- });
693
- publishCommand.command("support <request_details>").description("Submit a support request for an existing app on the Solana Mobile dApp publisher portal").requiredOption("-k, --keypair <path-to-keypair-file>", "Path to keypair file").requiredOption("--requestor-is-authorized", "An attestation that the party making this Solana dApp publisher portal request is authorized to do so").option("-a, --app-mint-address <app-mint-address>", "The mint address of the app NFT. If not specified, the value from your config file will be used.").option("-r, --release-mint-address <release-mint-address>", "The mint address of the release NFT. If not specified, the value from your config file will be used.").option("-u, --url <url>", "RPC URL", Constants.DEFAULT_RPC_DEVNET).option("-d, --dry-run", "Flag for dry run. Doesn't submit the request to the publisher portal.").action(function(requestDetails, param) {
694
- var appMintAddress = param.appMintAddress, releaseMintAddress = param.releaseMintAddress, keypair = param.keypair, url = param.url, requestorIsAuthorized = param.requestorIsAuthorized, dryRun = param.dryRun;
695
- return _async_to_generator(function() {
696
- return _ts_generator(this, function(_state) {
697
- switch(_state.label){
698
- case 0:
548
+ case 2:
549
+ error = _state.sent();
550
+ message = _instanceof(error, Error) ? error.message : 'An unexpected error occurred';
551
+ showMessage('Error', message, 'error');
552
+ process.exitCode = 1;
699
553
  return [
700
- 4,
701
- tryWithErrorMessage(function() {
702
- return _async_to_generator(function() {
703
- var config, signer;
704
- return _ts_generator(this, function(_state) {
705
- switch(_state.label){
706
- case 0:
707
- return [
708
- 4,
709
- checkForSelfUpdate()
710
- ];
711
- case 1:
712
- _state.sent();
713
- checkSubmissionNetwork(url);
714
- return [
715
- 4,
716
- loadPublishDetails(Constants.getConfigFilePath())
717
- ];
718
- case 2:
719
- config = _state.sent();
720
- if (!hasAddressInConfig(config.release) && !releaseMintAddress) {
721
- throw new Error("Either specify a release mint address in the config file or specify as a CLI argument to this command.");
722
- }
723
- signer = parseKeypair(keypair);
724
- if (!signer) return [
725
- 3,
726
- 4
727
- ];
728
- return [
729
- 4,
730
- publishSupportCommand({
731
- appMintAddress: appMintAddress,
732
- releaseMintAddress: releaseMintAddress,
733
- signer: signer,
734
- url: url,
735
- dryRun: dryRun,
736
- requestorIsAuthorized: requestorIsAuthorized,
737
- requestDetails: requestDetails
738
- })
739
- ];
740
- case 3:
741
- _state.sent();
742
- if (dryRun) {
743
- dryRunSuccessMessage();
744
- } else {
745
- showMessage("Success", "Support request sent successfully");
746
- }
747
- _state.label = 4;
748
- case 4:
749
- return [
750
- 2
751
- ];
752
- }
753
- });
754
- })();
755
- })
554
+ 3,
555
+ 3
756
556
  ];
757
- case 1:
758
- _state.sent();
557
+ case 3:
759
558
  return [
760
559
  2
761
560
  ];
762
561
  }
763
562
  });
764
563
  })();
765
- });
564
+ }