@zendfi/sdk 0.1.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.
package/dist/index.mjs ADDED
@@ -0,0 +1,640 @@
1
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
2
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
3
+ }) : x)(function(x) {
4
+ if (typeof require !== "undefined") return require.apply(this, arguments);
5
+ throw Error('Dynamic require of "' + x + '" is not supported');
6
+ });
7
+
8
+ // src/client.ts
9
+ import fetch from "cross-fetch";
10
+ import { createHmac, timingSafeEqual } from "crypto";
11
+
12
+ // src/types.ts
13
+ var ZendFiError = class extends Error {
14
+ constructor(message, statusCode, code, details) {
15
+ super(message);
16
+ this.statusCode = statusCode;
17
+ this.code = code;
18
+ this.details = details;
19
+ this.name = "ZendFiError";
20
+ }
21
+ };
22
+ var AuthenticationError = class extends ZendFiError {
23
+ constructor(message = "Authentication failed") {
24
+ super(message, 401, "AUTHENTICATION_ERROR");
25
+ this.name = "AuthenticationError";
26
+ }
27
+ };
28
+ var ValidationError = class extends ZendFiError {
29
+ constructor(message, details) {
30
+ super(message, 400, "VALIDATION_ERROR", details);
31
+ this.name = "ValidationError";
32
+ }
33
+ };
34
+ var NetworkError = class extends ZendFiError {
35
+ constructor(message) {
36
+ super(message, 0, "NETWORK_ERROR");
37
+ this.name = "NetworkError";
38
+ }
39
+ };
40
+ var RateLimitError = class extends ZendFiError {
41
+ constructor(message = "Rate limit exceeded") {
42
+ super(message, 429, "RATE_LIMIT_ERROR");
43
+ this.name = "RateLimitError";
44
+ }
45
+ };
46
+
47
+ // src/utils.ts
48
+ var ConfigLoader = class {
49
+ /**
50
+ * Load configuration from various sources
51
+ */
52
+ static load(options) {
53
+ const environment = this.detectEnvironment();
54
+ const apiKey = this.loadApiKey(options?.apiKey);
55
+ const baseURL = this.getBaseURL(environment, options?.baseURL);
56
+ return {
57
+ apiKey,
58
+ baseURL,
59
+ environment,
60
+ timeout: options?.timeout ?? 3e4,
61
+ retries: options?.retries ?? 3,
62
+ idempotencyEnabled: options?.idempotencyEnabled ?? true
63
+ };
64
+ }
65
+ /**
66
+ * Detect environment based on various signals
67
+ */
68
+ static detectEnvironment() {
69
+ const envVar = process.env.ZENDFI_ENVIRONMENT || process.env.NEXT_PUBLIC_ZENDFI_ENVIRONMENT;
70
+ if (envVar) {
71
+ return envVar;
72
+ }
73
+ if (typeof process !== "undefined" && process.env) {
74
+ const nodeEnv = process.env.NODE_ENV;
75
+ if (nodeEnv === "production") return "production";
76
+ if (nodeEnv === "staging") return "staging";
77
+ if (nodeEnv === "development" || nodeEnv === "test") return "development";
78
+ }
79
+ if (typeof window !== "undefined") {
80
+ const hostname = window.location.hostname;
81
+ if (hostname === "yourdomain.com" || hostname === "www.yourdomain.com") {
82
+ return "production";
83
+ }
84
+ if (hostname.includes("staging") || hostname.includes(".vercel.app")) {
85
+ return "staging";
86
+ }
87
+ if (hostname === "localhost" || hostname === "127.0.0.1") {
88
+ return "development";
89
+ }
90
+ }
91
+ return "development";
92
+ }
93
+ /**
94
+ * Load API key from various sources
95
+ */
96
+ static loadApiKey(explicitKey) {
97
+ if (explicitKey) return explicitKey;
98
+ if (typeof process !== "undefined" && process.env) {
99
+ const envKey = process.env.ZENDFI_API_KEY || process.env.NEXT_PUBLIC_ZENDFI_API_KEY || process.env.REACT_APP_ZENDFI_API_KEY;
100
+ if (envKey) return envKey;
101
+ }
102
+ try {
103
+ const credentials = this.loadCLICredentials();
104
+ if (credentials?.apiKey) return credentials.apiKey;
105
+ } catch {
106
+ }
107
+ throw new Error(
108
+ "ZendFi API key not found. Set ZENDFI_API_KEY environment variable or pass apiKey in constructor."
109
+ );
110
+ }
111
+ /**
112
+ * Get base URL for API
113
+ */
114
+ static getBaseURL(_environment, explicitURL) {
115
+ if (explicitURL) return explicitURL;
116
+ return process.env.ZENDFI_API_URL || "https://api.zendfi.tech";
117
+ }
118
+ /**
119
+ * Load credentials from CLI config file (~/.zendfi/credentials.json)
120
+ */
121
+ static loadCLICredentials() {
122
+ if (typeof process === "undefined" || !process.env.HOME) {
123
+ return null;
124
+ }
125
+ try {
126
+ const fs = __require("fs");
127
+ const path = __require("path");
128
+ const credentialsPath = path.join(process.env.HOME, ".zendfi", "credentials.json");
129
+ if (fs.existsSync(credentialsPath)) {
130
+ const data = fs.readFileSync(credentialsPath, "utf-8");
131
+ return JSON.parse(data);
132
+ }
133
+ } catch (error) {
134
+ }
135
+ return null;
136
+ }
137
+ /**
138
+ * Validate API key format
139
+ */
140
+ static validateApiKey(apiKey) {
141
+ if (!apiKey.startsWith("zfi_test_") && !apiKey.startsWith("zfi_live_")) {
142
+ throw new Error(
143
+ 'Invalid API key format. ZendFi API keys should start with "zfi_test_" or "zfi_live_"'
144
+ );
145
+ }
146
+ if (apiKey.startsWith("zfi_live_")) {
147
+ const env = this.detectEnvironment();
148
+ if (env === "development") {
149
+ console.warn(
150
+ "\u26A0\uFE0F Warning: Using a live API key (zfi_live_) in development environment. This will create real transactions. Use a test key (zfi_test_) for development."
151
+ );
152
+ }
153
+ }
154
+ }
155
+ };
156
+ function parseError(response, body) {
157
+ const statusCode = response.status;
158
+ const errorMessage = body?.error || body?.message || response.statusText || "Unknown error";
159
+ const errorCode = body?.code;
160
+ const details = body?.details;
161
+ switch (statusCode) {
162
+ case 401:
163
+ case 403:
164
+ return new AuthenticationError(errorMessage);
165
+ case 400:
166
+ return new ValidationError(errorMessage, details);
167
+ case 429:
168
+ return new RateLimitError(errorMessage);
169
+ case 500:
170
+ case 502:
171
+ case 503:
172
+ case 504:
173
+ return new NetworkError(errorMessage);
174
+ default:
175
+ return new ZendFiError(errorMessage, statusCode, errorCode, details);
176
+ }
177
+ }
178
+ function generateIdempotencyKey() {
179
+ const timestamp = Date.now();
180
+ const random = Math.random().toString(36).substring(2, 15);
181
+ return `zfi_idem_${timestamp}_${random}`;
182
+ }
183
+ function sleep(ms) {
184
+ return new Promise((resolve) => setTimeout(resolve, ms));
185
+ }
186
+
187
+ // src/client.ts
188
+ var ZendFiClient = class {
189
+ config;
190
+ constructor(options) {
191
+ this.config = ConfigLoader.load(options);
192
+ ConfigLoader.validateApiKey(this.config.apiKey);
193
+ }
194
+ /**
195
+ * Create a new payment
196
+ */
197
+ async createPayment(request) {
198
+ return this.request("POST", "/api/v1/payments", {
199
+ ...request,
200
+ currency: request.currency || "USD",
201
+ token: request.token || "USDC"
202
+ });
203
+ }
204
+ /**
205
+ * Get payment by ID
206
+ */
207
+ async getPayment(paymentId) {
208
+ return this.request("GET", `/api/v1/payments/${paymentId}`);
209
+ }
210
+ /**
211
+ * List all payments with pagination
212
+ */
213
+ async listPayments(request) {
214
+ const params = new URLSearchParams();
215
+ if (request?.page) params.append("page", request.page.toString());
216
+ if (request?.limit) params.append("limit", request.limit.toString());
217
+ if (request?.status) params.append("status", request.status);
218
+ if (request?.from_date) params.append("from_date", request.from_date);
219
+ if (request?.to_date) params.append("to_date", request.to_date);
220
+ const query = params.toString() ? `?${params.toString()}` : "";
221
+ return this.request("GET", `/api/v1/payments${query}`);
222
+ }
223
+ /**
224
+ * Create a subscription plan
225
+ */
226
+ async createSubscriptionPlan(request) {
227
+ return this.request("POST", "/api/v1/subscriptions/plans", {
228
+ ...request,
229
+ currency: request.currency || "USD",
230
+ interval_count: request.interval_count || 1,
231
+ trial_days: request.trial_days || 0
232
+ });
233
+ }
234
+ /**
235
+ * Get subscription plan by ID
236
+ */
237
+ async getSubscriptionPlan(planId) {
238
+ return this.request("GET", `/api/v1/subscriptions/plans/${planId}`);
239
+ }
240
+ /**
241
+ * Create a subscription
242
+ */
243
+ async createSubscription(request) {
244
+ return this.request("POST", "/api/v1/subscriptions", request);
245
+ }
246
+ /**
247
+ * Get subscription by ID
248
+ */
249
+ async getSubscription(subscriptionId) {
250
+ return this.request("GET", `/api/v1/subscriptions/${subscriptionId}`);
251
+ }
252
+ /**
253
+ * Cancel a subscription
254
+ */
255
+ async cancelSubscription(subscriptionId) {
256
+ return this.request(
257
+ "POST",
258
+ `/api/v1/subscriptions/${subscriptionId}/cancel`
259
+ );
260
+ }
261
+ /**
262
+ * Create a payment link (shareable checkout URL)
263
+ */
264
+ async createPaymentLink(request) {
265
+ const response = await this.request("POST", "/api/v1/payment-links", {
266
+ ...request,
267
+ currency: request.currency || "USD",
268
+ token: request.token || "USDC"
269
+ });
270
+ return {
271
+ ...response,
272
+ url: response.hosted_page_url
273
+ };
274
+ }
275
+ /**
276
+ * Get payment link by link code
277
+ */
278
+ async getPaymentLink(linkCode) {
279
+ const response = await this.request("GET", `/api/v1/payment-links/${linkCode}`);
280
+ return {
281
+ ...response,
282
+ url: response.hosted_page_url
283
+ };
284
+ }
285
+ /**
286
+ * List all payment links
287
+ */
288
+ async listPaymentLinks() {
289
+ return [];
290
+ }
291
+ // ===================================================================
292
+ // INSTALLMENT PLANS - Pay over time
293
+ // ===================================================================
294
+ /**
295
+ * Create an installment plan
296
+ * Split a purchase into multiple scheduled payments
297
+ */
298
+ async createInstallmentPlan(request) {
299
+ const response = await this.request(
300
+ "POST",
301
+ "/api/v1/installment-plans",
302
+ request
303
+ );
304
+ return {
305
+ id: response.plan_id,
306
+ plan_id: response.plan_id,
307
+ status: response.status
308
+ };
309
+ }
310
+ /**
311
+ * Get installment plan by ID
312
+ */
313
+ async getInstallmentPlan(planId) {
314
+ return this.request("GET", `/api/v1/installment-plans/${planId}`);
315
+ }
316
+ /**
317
+ * List all installment plans for merchant
318
+ */
319
+ async listInstallmentPlans(params) {
320
+ const query = new URLSearchParams();
321
+ if (params?.limit) query.append("limit", params.limit.toString());
322
+ if (params?.offset) query.append("offset", params.offset.toString());
323
+ const queryString = query.toString() ? `?${query.toString()}` : "";
324
+ return this.request("GET", `/api/v1/installment-plans${queryString}`);
325
+ }
326
+ /**
327
+ * List installment plans for a specific customer
328
+ */
329
+ async listCustomerInstallmentPlans(customerWallet) {
330
+ return this.request(
331
+ "GET",
332
+ `/api/v1/customers/${customerWallet}/installment-plans`
333
+ );
334
+ }
335
+ /**
336
+ * Cancel an installment plan
337
+ */
338
+ async cancelInstallmentPlan(planId) {
339
+ return this.request(
340
+ "POST",
341
+ `/api/v1/installment-plans/${planId}/cancel`
342
+ );
343
+ }
344
+ // ===================================================================
345
+ // ESCROW - Secure fund holding
346
+ // ===================================================================
347
+ /**
348
+ * Create an escrow transaction
349
+ * Hold funds until conditions are met
350
+ */
351
+ async createEscrow(request) {
352
+ return this.request("POST", "/api/v1/escrows", {
353
+ ...request,
354
+ currency: request.currency || "USD",
355
+ token: request.token || "USDC"
356
+ });
357
+ }
358
+ /**
359
+ * Get escrow by ID
360
+ */
361
+ async getEscrow(escrowId) {
362
+ return this.request("GET", `/api/v1/escrows/${escrowId}`);
363
+ }
364
+ /**
365
+ * List all escrows for merchant
366
+ */
367
+ async listEscrows(params) {
368
+ const query = new URLSearchParams();
369
+ if (params?.limit) query.append("limit", params.limit.toString());
370
+ if (params?.offset) query.append("offset", params.offset.toString());
371
+ const queryString = query.toString() ? `?${query.toString()}` : "";
372
+ return this.request("GET", `/api/v1/escrows${queryString}`);
373
+ }
374
+ /**
375
+ * Approve escrow release to seller
376
+ */
377
+ async approveEscrow(escrowId, request) {
378
+ return this.request(
379
+ "POST",
380
+ `/api/v1/escrows/${escrowId}/approve`,
381
+ request
382
+ );
383
+ }
384
+ /**
385
+ * Refund escrow to buyer
386
+ */
387
+ async refundEscrow(escrowId, request) {
388
+ return this.request("POST", `/api/v1/escrows/${escrowId}/refund`, request);
389
+ }
390
+ /**
391
+ * Raise a dispute for an escrow
392
+ */
393
+ async disputeEscrow(escrowId, request) {
394
+ return this.request("POST", `/api/v1/escrows/${escrowId}/dispute`, request);
395
+ }
396
+ // ===================================================================
397
+ // INVOICES - Professional billing
398
+ // ===================================================================
399
+ /**
400
+ * Create an invoice
401
+ */
402
+ async createInvoice(request) {
403
+ return this.request("POST", "/api/v1/invoices", {
404
+ ...request,
405
+ token: request.token || "USDC"
406
+ });
407
+ }
408
+ /**
409
+ * Get invoice by ID
410
+ */
411
+ async getInvoice(invoiceId) {
412
+ return this.request("GET", `/api/v1/invoices/${invoiceId}`);
413
+ }
414
+ /**
415
+ * List all invoices for merchant
416
+ */
417
+ async listInvoices() {
418
+ return this.request("GET", "/api/v1/invoices");
419
+ }
420
+ /**
421
+ * Send invoice to customer via email
422
+ */
423
+ async sendInvoice(invoiceId) {
424
+ return this.request("POST", `/api/v1/invoices/${invoiceId}/send`);
425
+ }
426
+ /**
427
+ * Verify webhook signature using HMAC-SHA256
428
+ *
429
+ * @param request - Webhook verification request containing payload, signature, and secret
430
+ * @returns true if signature is valid, false otherwise
431
+ *
432
+ * @example
433
+ * ```typescript
434
+ * const isValid = zendfi.verifyWebhook({
435
+ * payload: req.body,
436
+ * signature: req.headers['x-zendfi-signature'],
437
+ * secret: process.env.ZENDFI_WEBHOOK_SECRET
438
+ * });
439
+ *
440
+ * if (!isValid) {
441
+ * return res.status(401).json({ error: 'Invalid signature' });
442
+ * }
443
+ * ```
444
+ */
445
+ verifyWebhook(request) {
446
+ try {
447
+ if (!request.payload || !request.signature || !request.secret) {
448
+ return false;
449
+ }
450
+ const parsedPayload = JSON.parse(request.payload);
451
+ if (!parsedPayload.event || !parsedPayload.merchant_id || !parsedPayload.timestamp) {
452
+ return false;
453
+ }
454
+ const computedSignature = this.computeHmacSignature(request.payload, request.secret);
455
+ return this.timingSafeEqual(request.signature, computedSignature);
456
+ } catch (error) {
457
+ if (this.config.environment === "development") {
458
+ console.error("Webhook verification error:", error);
459
+ }
460
+ return false;
461
+ }
462
+ }
463
+ /**
464
+ * Compute HMAC-SHA256 signature
465
+ * Works in both Node.js and browser environments
466
+ */
467
+ computeHmacSignature(payload, secret) {
468
+ if (typeof process !== "undefined" && process.versions?.node) {
469
+ return createHmac("sha256", secret).update(payload, "utf8").digest("hex");
470
+ }
471
+ throw new Error(
472
+ "Webhook verification in browser is not supported. Use this method in your backend/server environment."
473
+ );
474
+ }
475
+ /**
476
+ * Timing-safe string comparison to prevent timing attacks
477
+ */
478
+ timingSafeEqual(a, b) {
479
+ if (a.length !== b.length) {
480
+ return false;
481
+ }
482
+ if (typeof process !== "undefined" && process.versions?.node) {
483
+ try {
484
+ const bufferA = Buffer.from(a, "utf8");
485
+ const bufferB = Buffer.from(b, "utf8");
486
+ return timingSafeEqual(bufferA, bufferB);
487
+ } catch {
488
+ }
489
+ }
490
+ let result = 0;
491
+ for (let i = 0; i < a.length; i++) {
492
+ result |= a.charCodeAt(i) ^ b.charCodeAt(i);
493
+ }
494
+ return result === 0;
495
+ }
496
+ /**
497
+ * Make an HTTP request with retry logic
498
+ */
499
+ async request(method, endpoint, data, options = {}) {
500
+ const attempt = options.attempt || 1;
501
+ const idempotencyKey = options.idempotencyKey || (this.config.idempotencyEnabled && method !== "GET" ? generateIdempotencyKey() : void 0);
502
+ try {
503
+ const url = `${this.config.baseURL}${endpoint}`;
504
+ const headers = {
505
+ "Content-Type": "application/json",
506
+ Authorization: `Bearer ${this.config.apiKey}`
507
+ };
508
+ if (idempotencyKey) {
509
+ headers["Idempotency-Key"] = idempotencyKey;
510
+ }
511
+ const controller = new AbortController();
512
+ const timeoutId = setTimeout(() => controller.abort(), this.config.timeout);
513
+ const response = await fetch(url, {
514
+ method,
515
+ headers,
516
+ body: data ? JSON.stringify(data) : void 0,
517
+ signal: controller.signal
518
+ });
519
+ clearTimeout(timeoutId);
520
+ let body;
521
+ try {
522
+ body = await response.json();
523
+ } catch {
524
+ body = null;
525
+ }
526
+ if (!response.ok) {
527
+ const error = parseError(response, body);
528
+ if (response.status >= 500 && attempt < this.config.retries) {
529
+ const delay = Math.pow(2, attempt) * 1e3;
530
+ await sleep(delay);
531
+ return this.request(method, endpoint, data, {
532
+ idempotencyKey,
533
+ attempt: attempt + 1
534
+ });
535
+ }
536
+ throw error;
537
+ }
538
+ return body;
539
+ } catch (error) {
540
+ if (error.name === "AbortError") {
541
+ throw new Error(`Request timeout after ${this.config.timeout}ms`);
542
+ }
543
+ if (attempt < this.config.retries && error.message?.includes("fetch")) {
544
+ const delay = Math.pow(2, attempt) * 1e3;
545
+ await sleep(delay);
546
+ return this.request(method, endpoint, data, {
547
+ idempotencyKey,
548
+ attempt: attempt + 1
549
+ });
550
+ }
551
+ throw error;
552
+ }
553
+ }
554
+ };
555
+ var zendfi = (() => {
556
+ try {
557
+ return new ZendFiClient();
558
+ } catch (error) {
559
+ if (process.env.NODE_ENV === "test" || !process.env.ZENDFI_API_KEY) {
560
+ return new Proxy({}, {
561
+ get() {
562
+ throw new Error(
563
+ 'ZendFi singleton not initialized. Set ZENDFI_API_KEY environment variable or create a custom instance: new ZendFiClient({ apiKey: "..." })'
564
+ );
565
+ }
566
+ });
567
+ }
568
+ throw error;
569
+ }
570
+ })();
571
+
572
+ // src/webhooks.ts
573
+ async function verifyNextWebhook(request, secret) {
574
+ try {
575
+ const payload = await request.text();
576
+ const signature = request.headers.get("x-zendfi-signature");
577
+ if (!signature) {
578
+ return null;
579
+ }
580
+ const webhookSecret = secret || process.env.ZENDFI_WEBHOOK_SECRET;
581
+ if (!webhookSecret) {
582
+ throw new Error("ZENDFI_WEBHOOK_SECRET not configured");
583
+ }
584
+ const isValid = zendfi.verifyWebhook({
585
+ payload,
586
+ signature,
587
+ secret: webhookSecret
588
+ });
589
+ if (!isValid) {
590
+ return null;
591
+ }
592
+ return JSON.parse(payload);
593
+ } catch {
594
+ return null;
595
+ }
596
+ }
597
+ async function verifyExpressWebhook(request, secret) {
598
+ try {
599
+ const payload = request.rawBody || JSON.stringify(request.body);
600
+ const signature = request.headers["x-zendfi-signature"];
601
+ if (!signature) {
602
+ return null;
603
+ }
604
+ const webhookSecret = secret || process.env.ZENDFI_WEBHOOK_SECRET;
605
+ if (!webhookSecret) {
606
+ throw new Error("ZENDFI_WEBHOOK_SECRET not configured");
607
+ }
608
+ const isValid = zendfi.verifyWebhook({
609
+ payload,
610
+ signature,
611
+ secret: webhookSecret
612
+ });
613
+ if (!isValid) {
614
+ return null;
615
+ }
616
+ return JSON.parse(payload);
617
+ } catch {
618
+ return null;
619
+ }
620
+ }
621
+ function verifyWebhookSignature(payload, signature, secret) {
622
+ return zendfi.verifyWebhook({
623
+ payload,
624
+ signature,
625
+ secret
626
+ });
627
+ }
628
+ export {
629
+ AuthenticationError,
630
+ ConfigLoader,
631
+ NetworkError,
632
+ RateLimitError,
633
+ ValidationError,
634
+ ZendFiClient,
635
+ ZendFiError,
636
+ verifyExpressWebhook,
637
+ verifyNextWebhook,
638
+ verifyWebhookSignature,
639
+ zendfi
640
+ };
package/package.json ADDED
@@ -0,0 +1,53 @@
1
+ {
2
+ "name": "@zendfi/sdk",
3
+ "version": "0.1.1",
4
+ "description": "Zero-config TypeScript SDK for ZendFi crypto payments",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist"
17
+ ],
18
+ "scripts": {
19
+ "build": "tsup src/index.ts --format esm,cjs --dts --clean",
20
+ "dev": "tsup src/index.ts --format esm,cjs --dts --watch",
21
+ "test": "vitest run",
22
+ "test:watch": "vitest",
23
+ "lint": "tsc --noEmit",
24
+ "clean": "rm -rf dist"
25
+ },
26
+ "keywords": [
27
+ "zendfi",
28
+ "crypto",
29
+ "payments",
30
+ "solana",
31
+ "sdk",
32
+ "typescript"
33
+ ],
34
+ "author": "ZendFi",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "https://github.com/zendfi/zendfi-toolkit.git",
39
+ "directory": "packages/sdk"
40
+ },
41
+ "dependencies": {
42
+ "cross-fetch": "^4.0.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/node": "^20.11.19",
46
+ "tsup": "^8.0.2",
47
+ "typescript": "^5.3.3",
48
+ "vitest": "^1.3.1"
49
+ },
50
+ "publishConfig": {
51
+ "access": "public"
52
+ }
53
+ }