@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,359 @@
1
+ import fs from "node:fs";
2
+ import os from "node:os";
3
+ import path from "node:path";
4
+
5
+ import { afterEach, expect, jest, test } from "@jest/globals";
6
+ import {
7
+ createCreateInstruction,
8
+ createVerifyInstruction,
9
+ VerificationArgs,
10
+ } from "@metaplex-foundation/mpl-token-metadata";
11
+ import {
12
+ ComputeBudgetProgram,
13
+ Keypair,
14
+ PublicKey,
15
+ SYSVAR_INSTRUCTIONS_PUBKEY,
16
+ SystemProgram,
17
+ Transaction,
18
+ TransactionInstruction,
19
+ } from "@solana/web3.js";
20
+ import { signSerializedTransaction } from "../../../../core/src/portal/signer.js";
21
+
22
+ import { createPublicationSignerFromKeypair, parseKeypair } from "../signer.js";
23
+
24
+ const tempDirs: string[] = [];
25
+ const TOKEN_METADATA_PROGRAM_ID = new PublicKey(
26
+ "metaqbxxUerdq28cj1RbAWkYQm3ybzjb6a8bt518x1s"
27
+ );
28
+ const TOKEN_METADATA_SEED = Buffer.from("metadata");
29
+ const TOKEN_METADATA_EDITION_SEED = Buffer.from("edition");
30
+
31
+ afterEach(() => {
32
+ while (tempDirs.length > 0) {
33
+ fs.rmSync(tempDirs.pop()!, { recursive: true, force: true });
34
+ }
35
+ });
36
+
37
+ function createReleaseMintTransaction(options?: {
38
+ signer?: Keypair;
39
+ mint?: Keypair;
40
+ appMintAddress?: PublicKey;
41
+ addUnexpectedProgram?: boolean;
42
+ tamperAfterMintSignature?: boolean;
43
+ }) {
44
+ const signer = options?.signer ?? Keypair.generate();
45
+ const mint = options?.mint ?? Keypair.generate();
46
+ const appMintAddress =
47
+ options?.appMintAddress ?? Keypair.generate().publicKey;
48
+
49
+ const transaction = new Transaction();
50
+ transaction.feePayer = signer.publicKey;
51
+ transaction.recentBlockhash = Keypair.generate().publicKey.toBase58();
52
+ transaction.add(
53
+ ComputeBudgetProgram.setComputeUnitLimit({
54
+ units: 500_000,
55
+ })
56
+ );
57
+ transaction.add(
58
+ (() => {
59
+ const instruction = createCreateInstruction(
60
+ {
61
+ metadata: Keypair.generate().publicKey,
62
+ masterEdition: Keypair.generate().publicKey,
63
+ mint: mint.publicKey,
64
+ authority: signer.publicKey,
65
+ payer: signer.publicKey,
66
+ updateAuthority: signer.publicKey,
67
+ sysvarInstructions: Keypair.generate().publicKey,
68
+ splTokenProgram: Keypair.generate().publicKey,
69
+ },
70
+ {
71
+ createArgs: {
72
+ __kind: "V1",
73
+ assetData: {
74
+ name: "Example release",
75
+ symbol: "",
76
+ uri: "https://example.com/release.json",
77
+ sellerFeeBasisPoints: 0,
78
+ creators: null,
79
+ primarySaleHappened: false,
80
+ isMutable: false,
81
+ tokenStandard: 0,
82
+ collection: {
83
+ verified: false,
84
+ key: appMintAddress,
85
+ },
86
+ uses: null,
87
+ collectionDetails: null,
88
+ ruleSet: null,
89
+ },
90
+ decimals: null,
91
+ printSupply: null,
92
+ },
93
+ }
94
+ );
95
+
96
+ instruction.keys[2]!.isSigner = true;
97
+ instruction.keys[5]!.isSigner = true;
98
+
99
+ return instruction;
100
+ })()
101
+ );
102
+
103
+ if (options?.addUnexpectedProgram) {
104
+ transaction.add(
105
+ SystemProgram.transfer({
106
+ fromPubkey: signer.publicKey,
107
+ toPubkey: Keypair.generate().publicKey,
108
+ lamports: 1,
109
+ })
110
+ );
111
+ }
112
+
113
+ transaction.partialSign(mint);
114
+
115
+ if (options?.tamperAfterMintSignature) {
116
+ transaction.instructions[1] = new TransactionInstruction({
117
+ programId: TOKEN_METADATA_PROGRAM_ID,
118
+ keys: transaction.instructions[1]!.keys,
119
+ data: Buffer.from([9, 9, 9]),
120
+ });
121
+ }
122
+
123
+ return {
124
+ signer,
125
+ mint,
126
+ appMintAddress,
127
+ blockhash: transaction.recentBlockhash!,
128
+ serialized: transaction
129
+ .serialize({
130
+ requireAllSignatures: false,
131
+ verifySignatures: false,
132
+ })
133
+ .toString("base64"),
134
+ };
135
+ }
136
+
137
+ function getMetadataPda(mintAddress: PublicKey): PublicKey {
138
+ return PublicKey.findProgramAddressSync(
139
+ [
140
+ TOKEN_METADATA_SEED,
141
+ TOKEN_METADATA_PROGRAM_ID.toBuffer(),
142
+ mintAddress.toBuffer(),
143
+ ],
144
+ TOKEN_METADATA_PROGRAM_ID
145
+ )[0];
146
+ }
147
+
148
+ function getMasterEditionPda(mintAddress: PublicKey): PublicKey {
149
+ return PublicKey.findProgramAddressSync(
150
+ [
151
+ TOKEN_METADATA_SEED,
152
+ TOKEN_METADATA_PROGRAM_ID.toBuffer(),
153
+ mintAddress.toBuffer(),
154
+ TOKEN_METADATA_EDITION_SEED,
155
+ ],
156
+ TOKEN_METADATA_PROGRAM_ID
157
+ )[0];
158
+ }
159
+
160
+ function createVerifyCollectionTransaction(options?: {
161
+ signer?: Keypair;
162
+ nftMintAddress?: PublicKey;
163
+ collectionMintAddress?: PublicKey;
164
+ }) {
165
+ const signer = options?.signer ?? Keypair.generate();
166
+ const nftMintAddress =
167
+ options?.nftMintAddress ?? Keypair.generate().publicKey;
168
+ const collectionMintAddress =
169
+ options?.collectionMintAddress ?? Keypair.generate().publicKey;
170
+
171
+ const transaction = new Transaction();
172
+ transaction.feePayer = signer.publicKey;
173
+ transaction.recentBlockhash = Keypair.generate().publicKey.toBase58();
174
+ transaction.add(
175
+ ComputeBudgetProgram.setComputeUnitPrice({
176
+ microLamports: 1_000,
177
+ })
178
+ );
179
+ transaction.add(
180
+ createVerifyInstruction(
181
+ {
182
+ authority: signer.publicKey,
183
+ metadata: getMetadataPda(nftMintAddress),
184
+ collectionMint: collectionMintAddress,
185
+ collectionMetadata: getMetadataPda(collectionMintAddress),
186
+ collectionMasterEdition: getMasterEditionPda(collectionMintAddress),
187
+ systemProgram: SystemProgram.programId,
188
+ sysvarInstructions: SYSVAR_INSTRUCTIONS_PUBKEY,
189
+ },
190
+ {
191
+ verificationArgs: VerificationArgs.CollectionV1,
192
+ }
193
+ )
194
+ );
195
+
196
+ return {
197
+ signer,
198
+ nftMintAddress,
199
+ collectionMintAddress,
200
+ blockhash: transaction.recentBlockhash!,
201
+ serialized: transaction
202
+ .serialize({
203
+ requireAllSignatures: false,
204
+ verifySignatures: false,
205
+ })
206
+ .toString("base64"),
207
+ };
208
+ }
209
+
210
+ test("parseKeypair loads a Solana keypair from a JSON array file", () => {
211
+ const keypair = Keypair.generate();
212
+ const tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "dapp-store-signer-"));
213
+ const keypairPath = path.join(tempDir, "signer.json");
214
+ tempDirs.push(tempDir);
215
+
216
+ fs.writeFileSync(keypairPath, JSON.stringify(Array.from(keypair.secretKey)));
217
+
218
+ const parsedKeypair = parseKeypair(keypairPath);
219
+
220
+ expect(parsedKeypair?.publicKey.toBase58()).toBe(
221
+ keypair.publicKey.toBase58()
222
+ );
223
+ });
224
+
225
+ test("parseKeypair returns undefined and logs an error when the file is missing", () => {
226
+ const logSpy = jest.spyOn(console, "log").mockImplementation(() => {});
227
+
228
+ try {
229
+ expect(parseKeypair("/tmp/does-not-exist.json")).toBeUndefined();
230
+ expect(logSpy).toHaveBeenCalled();
231
+ } finally {
232
+ logSpy.mockRestore();
233
+ }
234
+ });
235
+
236
+ test("signSerializedTransaction signs a validated release mint transaction", async () => {
237
+ const { signer, mint, appMintAddress, blockhash, serialized } =
238
+ createReleaseMintTransaction();
239
+
240
+ const accountAddresses = Transaction.from(
241
+ Buffer.from(serialized, "base64")
242
+ )
243
+ .compileMessage()
244
+ .accountKeys.map((key) => key.toBase58());
245
+ expect(accountAddresses).not.toContain(appMintAddress.toBase58());
246
+
247
+ const signedTransaction = await signSerializedTransaction(
248
+ createPublicationSignerFromKeypair(signer),
249
+ serialized,
250
+ {
251
+ kind: "release-mint",
252
+ expectedBlockhash: blockhash,
253
+ expectedFeePayerAddress: signer.publicKey.toBase58(),
254
+ expectedSignerAddress: signer.publicKey.toBase58(),
255
+ expectedMintAddress: mint.publicKey.toBase58(),
256
+ expectedAppMintAddress: appMintAddress.toBase58(),
257
+ }
258
+ );
259
+
260
+ expect(
261
+ Transaction.from(
262
+ Buffer.from(signedTransaction, "base64")
263
+ ).verifySignatures()
264
+ ).toBe(true);
265
+ });
266
+
267
+ test("signSerializedTransaction rejects release mint transactions with unexpected programs", async () => {
268
+ const { signer, mint, appMintAddress, blockhash, serialized } =
269
+ createReleaseMintTransaction({
270
+ addUnexpectedProgram: true,
271
+ });
272
+
273
+ await expect(
274
+ signSerializedTransaction(
275
+ createPublicationSignerFromKeypair(signer),
276
+ serialized,
277
+ {
278
+ kind: "release-mint",
279
+ expectedBlockhash: blockhash,
280
+ expectedFeePayerAddress: signer.publicKey.toBase58(),
281
+ expectedSignerAddress: signer.publicKey.toBase58(),
282
+ expectedMintAddress: mint.publicKey.toBase58(),
283
+ expectedAppMintAddress: appMintAddress.toBase58(),
284
+ }
285
+ )
286
+ ).rejects.toThrow("unexpected program ids");
287
+ });
288
+
289
+ test("signSerializedTransaction rejects release mint transactions with invalid pre-existing signatures", async () => {
290
+ const { signer, mint, appMintAddress, blockhash, serialized } =
291
+ createReleaseMintTransaction({
292
+ tamperAfterMintSignature: true,
293
+ });
294
+
295
+ await expect(
296
+ signSerializedTransaction(
297
+ createPublicationSignerFromKeypair(signer),
298
+ serialized,
299
+ {
300
+ kind: "release-mint",
301
+ expectedBlockhash: blockhash,
302
+ expectedFeePayerAddress: signer.publicKey.toBase58(),
303
+ expectedSignerAddress: signer.publicKey.toBase58(),
304
+ expectedMintAddress: mint.publicKey.toBase58(),
305
+ expectedAppMintAddress: appMintAddress.toBase58(),
306
+ }
307
+ )
308
+ ).rejects.toThrow("invalid existing signatures");
309
+ });
310
+
311
+ test("signSerializedTransaction rejects release mint transactions with a mismatched collection encoded in metadata", async () => {
312
+ const { signer, mint, blockhash, serialized } = createReleaseMintTransaction();
313
+ const unexpectedAppMintAddress = Keypair.generate().publicKey;
314
+
315
+ await expect(
316
+ signSerializedTransaction(
317
+ createPublicationSignerFromKeypair(signer),
318
+ serialized,
319
+ {
320
+ kind: "release-mint",
321
+ expectedBlockhash: blockhash,
322
+ expectedFeePayerAddress: signer.publicKey.toBase58(),
323
+ expectedSignerAddress: signer.publicKey.toBase58(),
324
+ expectedMintAddress: mint.publicKey.toBase58(),
325
+ expectedAppMintAddress: unexpectedAppMintAddress.toBase58(),
326
+ }
327
+ )
328
+ ).rejects.toThrow("collection mismatch");
329
+ });
330
+
331
+ test("signSerializedTransaction signs a validated collection verification transaction", async () => {
332
+ const {
333
+ signer,
334
+ nftMintAddress,
335
+ collectionMintAddress,
336
+ blockhash,
337
+ serialized,
338
+ } = createVerifyCollectionTransaction();
339
+
340
+ const signedTransaction = await signSerializedTransaction(
341
+ createPublicationSignerFromKeypair(signer),
342
+ serialized,
343
+ {
344
+ kind: "verify-collection",
345
+ expectedBlockhash: blockhash,
346
+ expectedFeePayerAddress: signer.publicKey.toBase58(),
347
+ expectedSignerAddress: signer.publicKey.toBase58(),
348
+ expectedNftMintAddress: nftMintAddress.toBase58(),
349
+ expectedCollectionMintAddress: collectionMintAddress.toBase58(),
350
+ expectedCollectionAuthority: signer.publicKey.toBase58(),
351
+ }
352
+ );
353
+
354
+ expect(
355
+ Transaction.from(
356
+ Buffer.from(signedTransaction, "base64")
357
+ ).verifySignatures()
358
+ ).toBe(true);
359
+ });
@@ -0,0 +1,3 @@
1
+ export class Constants {
2
+ static CLI_VERSION = '1.0.0';
3
+ }
@@ -0,0 +1,27 @@
1
+ import boxen from 'boxen';
2
+
3
+ export const showMessage = (
4
+ titleMessage = '',
5
+ contentMessage = '',
6
+ type: 'standard' | 'error' | 'warning' = 'standard',
7
+ ): string => {
8
+ let color = 'cyan';
9
+ if (type === 'error') {
10
+ color = 'redBright';
11
+ } else if (type === 'warning') {
12
+ color = 'yellow';
13
+ }
14
+
15
+ const message = boxen(contentMessage, {
16
+ title: titleMessage,
17
+ padding: 1,
18
+ margin: 1,
19
+ borderStyle: 'single',
20
+ borderColor: color,
21
+ textAlignment: 'left',
22
+ titleAlignment: 'center',
23
+ });
24
+
25
+ console.log(message);
26
+ return message;
27
+ };
@@ -0,0 +1,62 @@
1
+ import { formatUpdatedCliUsageError } from "../publication/cliValidation.js";
2
+
3
+ type CommanderLikeError = {
4
+ code?: string;
5
+ exitCode?: number;
6
+ message?: string;
7
+ name?: string;
8
+ };
9
+
10
+ const COMMANDER_EXIT_CODES_TO_IGNORE = new Set([
11
+ "commander.help",
12
+ "commander.helpDisplayed",
13
+ "commander.version",
14
+ ]);
15
+
16
+ const UPDATED_DOCS_PARSE_ERROR_CODES = new Set([
17
+ "commander.excessArguments",
18
+ "commander.unknownCommand",
19
+ "commander.unknownOption",
20
+ ]);
21
+
22
+ export function isCommanderLifecycleExit(error: unknown): boolean {
23
+ return (
24
+ isCommanderError(error) &&
25
+ COMMANDER_EXIT_CODES_TO_IGNORE.has(error.code ?? "")
26
+ );
27
+ }
28
+
29
+ export function getCommanderUserFacingError(error: unknown): {
30
+ exitCode: number;
31
+ message: string;
32
+ } | null {
33
+ if (!isCommanderError(error)) {
34
+ return null;
35
+ }
36
+
37
+ const message = error.message ?? "Invalid CLI arguments.";
38
+ return {
39
+ exitCode: error.exitCode ?? 1,
40
+ message: UPDATED_DOCS_PARSE_ERROR_CODES.has(error.code ?? "")
41
+ ? formatUpdatedCliUsageError(message)
42
+ : normalizeCommanderErrorMessage(message),
43
+ };
44
+ }
45
+
46
+ function isCommanderError(error: unknown): error is CommanderLikeError {
47
+ return (
48
+ typeof error === "object" &&
49
+ error !== null &&
50
+ "code" in error &&
51
+ "message" in error
52
+ );
53
+ }
54
+
55
+ function normalizeCommanderErrorMessage(message: string): string {
56
+ const normalized = message.replace(/^error:\s*/i, "").trim();
57
+ if (normalized.length === 0) {
58
+ return "Invalid CLI arguments.";
59
+ }
60
+
61
+ return normalized.endsWith(".") ? normalized : `${normalized}.`;
62
+ }
@@ -0,0 +1,59 @@
1
+ import { readFile } from "node:fs/promises";
2
+
3
+ import semver from "semver";
4
+ import updateNotifier from "update-notifier";
5
+
6
+ import { Constants } from "./constants.js";
7
+
8
+ type CliPackageMetadata = {
9
+ name: string;
10
+ version: string;
11
+ };
12
+
13
+ const FALLBACK_CLI_PACKAGE: CliPackageMetadata = {
14
+ name: "@solana-mobile/dapp-store-cli",
15
+ version: Constants.CLI_VERSION,
16
+ };
17
+
18
+ async function loadCliPackageMetadata(): Promise<CliPackageMetadata> {
19
+ try {
20
+ const packageContents = await readFile(
21
+ new URL("../../package.json", import.meta.url),
22
+ { encoding: "utf8" }
23
+ );
24
+ const parsed: unknown = JSON.parse(packageContents);
25
+
26
+ if (
27
+ typeof parsed === "object" &&
28
+ parsed !== null &&
29
+ typeof (parsed as Record<string, unknown>).name === "string" &&
30
+ typeof (parsed as Record<string, unknown>).version === "string"
31
+ ) {
32
+ return {
33
+ name: String((parsed as Record<string, unknown>).name),
34
+ version: String((parsed as Record<string, unknown>).version),
35
+ };
36
+ }
37
+ } catch {
38
+ return FALLBACK_CLI_PACKAGE;
39
+ }
40
+
41
+ return FALLBACK_CLI_PACKAGE;
42
+ }
43
+
44
+ export const checkForSelfUpdate = async () => {
45
+ const notifier = updateNotifier({ pkg: await loadCliPackageMetadata() });
46
+ const updateInfo = await notifier.fetchInfo();
47
+
48
+ const latestVersion = new semver.SemVer(updateInfo.latest);
49
+ const currentVersion = new semver.SemVer(updateInfo.current);
50
+
51
+ if (
52
+ latestVersion.major > currentVersion.major ||
53
+ latestVersion.minor > currentVersion.minor
54
+ ) {
55
+ throw new Error(
56
+ `Please update to the latest version of the dApp Store CLI before proceeding.\nCurrent version is ${currentVersion.raw}\nLatest version is ${latestVersion.raw}`
57
+ );
58
+ }
59
+ };
@@ -0,0 +1,38 @@
1
+ import fs from 'node:fs';
2
+
3
+ import { Keypair, Transaction } from '@solana/web3.js';
4
+ import nacl from 'tweetnacl';
5
+ import {
6
+ createPublicationSigner,
7
+ type PublicationSigner,
8
+ } from '@solana-mobile/dapp-store-publishing-tools';
9
+
10
+ import { showMessage } from './messages.js';
11
+
12
+ export const parseKeypair = (pathToKeypairFile: string) => {
13
+ try {
14
+ const keypairFile = fs.readFileSync(pathToKeypairFile, 'utf-8');
15
+ return Keypair.fromSecretKey(Buffer.from(JSON.parse(keypairFile)));
16
+ } catch {
17
+ showMessage(
18
+ 'KeyPair Error',
19
+ 'Something went wrong when attempting to retrieve the keypair at ' +
20
+ pathToKeypairFile,
21
+ 'error',
22
+ );
23
+ }
24
+ };
25
+
26
+ export function createPublicationSignerFromKeypair(
27
+ keypair: Keypair,
28
+ ): PublicationSigner {
29
+ return createPublicationSigner({
30
+ publicKey: keypair.publicKey.toBase58(),
31
+ signTransaction: async (transaction: Transaction) => {
32
+ transaction.partialSign(keypair);
33
+ return transaction;
34
+ },
35
+ signMessage: async (message: Uint8Array) =>
36
+ nacl.sign.detached(message, keypair.secretKey),
37
+ });
38
+ }
package/src/index.ts CHANGED
@@ -1,7 +1,34 @@
1
+ import { showMessage } from "./CliUtils.js";
1
2
  import { mainCli } from "./CliSetup.js";
3
+ import {
4
+ getCommanderUserFacingError,
5
+ isCommanderLifecycleExit,
6
+ } from "./cli/parseErrors.js";
2
7
 
3
- async function main() {
4
- await mainCli.parseAsync(process.argv);
5
- }
8
+ export async function main(argv = process.argv) {
9
+ mainCli.showHelpAfterError(false);
10
+ mainCli.exitOverride();
11
+
12
+ const outputConfig = mainCli.configureOutput();
13
+ mainCli.configureOutput({
14
+ ...outputConfig,
15
+ outputError() {},
16
+ });
17
+
18
+ try {
19
+ await mainCli.parseAsync(argv);
20
+ } catch (error) {
21
+ if (isCommanderLifecycleExit(error)) {
22
+ return;
23
+ }
6
24
 
7
- main();
25
+ const userFacingError = getCommanderUserFacingError(error);
26
+ if (userFacingError) {
27
+ showMessage("Error", userFacingError.message, "error");
28
+ process.exitCode = userFacingError.exitCode;
29
+ return;
30
+ }
31
+
32
+ throw error;
33
+ }
34
+ }