@attryio/react-native 0.1.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.
package/README.md ADDED
@@ -0,0 +1,22 @@
1
+ # @attryio/react-native
2
+
3
+ React Native SDK for Attry mobile attribution.
4
+
5
+ ```ts
6
+ import { createAttryReactNative } from "@attryio/react-native";
7
+
8
+ const attry = await createAttryReactNative({
9
+ appId: "457064853",
10
+ apiKey: "attry_live_..."
11
+ });
12
+
13
+ await attry.track("paywall_viewed", {
14
+ properties: {
15
+ plan: "pro"
16
+ }
17
+ });
18
+
19
+ await attry.revenue("subscription_started", 1999, "USD", "pro_monthly");
20
+ ```
21
+
22
+ The SDK auto-tracks first SDK install, app opens, sessions, foreground/background transitions, deep link opens, Apple AdServices tokens on iOS, and Google Play Install Referrer on Android when the native modules are available.
@@ -0,0 +1,47 @@
1
+ package io.attry.reactnative
2
+
3
+ import com.android.installreferrer.api.InstallReferrerClient
4
+ import com.android.installreferrer.api.InstallReferrerStateListener
5
+ import com.android.installreferrer.api.InstallReferrerClient.InstallReferrerResponse
6
+ import com.facebook.react.bridge.Promise
7
+ import com.facebook.react.bridge.ReactApplicationContext
8
+ import com.facebook.react.bridge.ReactContextBaseJavaModule
9
+ import com.facebook.react.bridge.ReactMethod
10
+ import com.facebook.react.bridge.WritableNativeMap
11
+
12
+ class AttryInstallReferrerModule(private val reactContext: ReactApplicationContext) :
13
+ ReactContextBaseJavaModule(reactContext) {
14
+ override fun getName(): String = "AttryInstallReferrer"
15
+
16
+ @ReactMethod
17
+ fun getInstallReferrer(promise: Promise) {
18
+ val client = InstallReferrerClient.newBuilder(reactContext).build()
19
+ client.startConnection(object : InstallReferrerStateListener {
20
+ override fun onInstallReferrerSetupFinished(responseCode: Int) {
21
+ if (responseCode != InstallReferrerResponse.OK) {
22
+ client.endConnection()
23
+ promise.resolve(null)
24
+ return
25
+ }
26
+
27
+ try {
28
+ val response = client.installReferrer
29
+ val payload = WritableNativeMap()
30
+ payload.putString("installReferrer", response.installReferrer)
31
+ payload.putDouble("referrerClickTimestampSeconds", response.referrerClickTimestampSeconds.toDouble())
32
+ payload.putDouble("installBeginTimestampSeconds", response.installBeginTimestampSeconds.toDouble())
33
+ payload.putBoolean("googlePlayInstantParam", response.googlePlayInstantParam)
34
+ promise.resolve(payload)
35
+ } catch (error: Exception) {
36
+ promise.reject("attry_install_referrer_error", "Unable to read Google Play Install Referrer.", error)
37
+ } finally {
38
+ client.endConnection()
39
+ }
40
+ }
41
+
42
+ override fun onInstallReferrerServiceDisconnected() {
43
+ promise.resolve(null)
44
+ }
45
+ })
46
+ }
47
+ }
@@ -0,0 +1,16 @@
1
+ package io.attry.reactnative
2
+
3
+ import com.facebook.react.ReactPackage
4
+ import com.facebook.react.bridge.NativeModule
5
+ import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.uimanager.ViewManager
7
+
8
+ class AttryPackage : ReactPackage {
9
+ override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
10
+ return listOf(AttryInstallReferrerModule(reactContext))
11
+ }
12
+
13
+ override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
14
+ return emptyList()
15
+ }
16
+ }
@@ -0,0 +1,63 @@
1
+ import { Attry, parseAttryUrl, type AttryConfig, type AttryContext, type AttryStorage } from "@attryio/sdk-core";
2
+ type ReactNativeLike = {
3
+ AppState?: {
4
+ currentState?: string;
5
+ addEventListener?: (event: "change", listener: (state: string) => void) => {
6
+ remove?: () => void;
7
+ };
8
+ };
9
+ Dimensions?: {
10
+ get?: (dimension: "window" | "screen") => {
11
+ width?: number;
12
+ height?: number;
13
+ scale?: number;
14
+ };
15
+ };
16
+ I18nManager?: {
17
+ localeIdentifier?: string;
18
+ };
19
+ Linking?: {
20
+ getInitialURL?: () => Promise<string | null>;
21
+ addEventListener?: (event: "url", listener: (payload: {
22
+ url: string;
23
+ }) => void) => {
24
+ remove?: () => void;
25
+ };
26
+ };
27
+ NativeModules?: {
28
+ AttryAppleAds?: {
29
+ attributionToken: () => Promise<string | null>;
30
+ };
31
+ AttryInstallReferrer?: {
32
+ getInstallReferrer: () => Promise<InstallReferrerPayload | null>;
33
+ };
34
+ [key: string]: unknown;
35
+ };
36
+ Platform?: {
37
+ OS?: "ios" | "android" | string;
38
+ Version?: string | number;
39
+ };
40
+ };
41
+ export interface InstallReferrerPayload {
42
+ installReferrer?: string;
43
+ referrerClickTimestampSeconds?: number;
44
+ installBeginTimestampSeconds?: number;
45
+ googlePlayInstantParam?: boolean;
46
+ }
47
+ export interface ReactNativeAttryConfig extends Omit<AttryConfig, "platform" | "storage"> {
48
+ storage?: AttryConfig["storage"];
49
+ autoTrackDeepLinks?: boolean;
50
+ autoTrackLifecycleEvents?: boolean;
51
+ autoCollectInstallAttribution?: boolean;
52
+ sessionTimeoutMs?: number;
53
+ }
54
+ export { parseAttryUrl };
55
+ export declare function createAttryReactNative(config: ReactNativeAttryConfig): Promise<Attry>;
56
+ export declare function collectReactNativeContext(rn?: ReactNativeLike | undefined, configuredContext?: AttryConfig["context"]): Promise<AttryContext>;
57
+ export declare function attachReactNativeLifecycleTracking(client: Attry, rn: ReactNativeLike | undefined, storage: AttryStorage | undefined, options: {
58
+ appId: string;
59
+ sessionTimeoutMs?: number;
60
+ }): Promise<void>;
61
+ export declare function attachDeepLinkTracking(client: Attry, rn?: ReactNativeLike | undefined): Promise<void>;
62
+ export declare function collectInstallAttribution(client: Attry, rn?: ReactNativeLike | undefined): Promise<void>;
63
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/dist/index.js ADDED
@@ -0,0 +1,362 @@
1
+ import { Attry, MemoryStorage, parseAttryUrl } from "@attryio/sdk-core";
2
+ const SDK_NAME = "attry-react-native";
3
+ const SDK_VERSION = "0.1.0";
4
+ export { parseAttryUrl };
5
+ export async function createAttryReactNative(config) {
6
+ const rn = getReactNative();
7
+ const platform = rn?.Platform?.OS === "ios" || rn?.Platform?.OS === "android"
8
+ ? rn.Platform.OS
9
+ : "unknown";
10
+ const storage = config.storage ?? createAsyncStorageAdapter() ?? new MemoryStorage();
11
+ const context = await collectReactNativeContext(rn, config.context);
12
+ const client = await new Attry({
13
+ ...config,
14
+ platform,
15
+ storage,
16
+ context
17
+ }).init();
18
+ if (config.autoCollectInstallAttribution ?? true) {
19
+ await collectInstallAttribution(client, rn);
20
+ }
21
+ if (config.autoTrackLifecycleEvents ?? true) {
22
+ await attachReactNativeLifecycleTracking(client, rn, storage, {
23
+ appId: config.appId,
24
+ sessionTimeoutMs: config.sessionTimeoutMs
25
+ });
26
+ }
27
+ if (config.autoTrackDeepLinks ?? true) {
28
+ await attachDeepLinkTracking(client, rn);
29
+ }
30
+ return client;
31
+ }
32
+ export async function collectReactNativeContext(rn = getReactNative(), configuredContext = {}) {
33
+ const modules = rn?.NativeModules;
34
+ const deviceInfo = modules?.RNDeviceInfo;
35
+ const expoConstants = modules?.ExpoConstants ?? modules?.ExponentConstants;
36
+ const platformConstants = modules?.PlatformConstants;
37
+ const settings = modules?.SettingsManager;
38
+ const screen = rn?.Dimensions?.get?.("screen");
39
+ const locale = configuredContext?.locale ??
40
+ readNestedString(settings?.settings, ["AppleLocale"]) ??
41
+ readFirstString(readNestedUnknown(settings?.settings, ["AppleLanguages"])) ??
42
+ readNestedString(deviceInfo, ["deviceLocale"]) ??
43
+ rn?.I18nManager?.localeIdentifier;
44
+ return compactContext({
45
+ sdkName: SDK_NAME,
46
+ sdkVersion: SDK_VERSION,
47
+ appName: configuredContext?.appName ??
48
+ (await readMaybeString(deviceInfo?.appName)) ??
49
+ readNestedString(expoConstants, ["expoConfig", "name"]) ??
50
+ readNestedString(expoConstants, ["manifest", "name"]),
51
+ appIdentifier: configuredContext?.appIdentifier ??
52
+ (await readMaybeString(deviceInfo?.bundleId)) ??
53
+ (await readMaybeString(deviceInfo?.getBundleId)) ??
54
+ readNestedString(expoConstants, ["expoConfig", "ios", "bundleIdentifier"]) ??
55
+ readNestedString(expoConstants, [
56
+ "expoConfig",
57
+ "android",
58
+ "package"
59
+ ]),
60
+ appVersion: configuredContext?.appVersion ??
61
+ (await readMaybeString(deviceInfo?.appVersion)) ??
62
+ (await readMaybeString(deviceInfo?.version)) ??
63
+ (await readMaybeString(deviceInfo?.getVersion)) ??
64
+ readNestedString(expoConstants, ["expoConfig", "version"]) ??
65
+ readNestedString(expoConstants, ["manifest", "version"]) ??
66
+ readNestedString(expoConstants, [
67
+ "manifest2",
68
+ "extra",
69
+ "expoClient",
70
+ "version"
71
+ ]),
72
+ appBuild: configuredContext?.appBuild ??
73
+ (await readMaybeString(deviceInfo?.buildNumber)) ??
74
+ (await readMaybeString(deviceInfo?.getBuildNumber)) ??
75
+ readNestedString(expoConstants, ["expoConfig", "ios", "buildNumber"]) ??
76
+ readNestedString(expoConstants, ["expoConfig", "android", "versionCode"]),
77
+ osVersion: rn?.Platform?.Version ? String(rn.Platform.Version) : undefined,
78
+ systemName: configuredContext?.systemName ??
79
+ (await readMaybeString(deviceInfo?.systemName)) ??
80
+ (rn?.Platform?.OS ? String(rn.Platform.OS) : undefined),
81
+ deviceModel: configuredContext?.deviceModel ??
82
+ (await readMaybeString(deviceInfo?.model)) ??
83
+ (await readMaybeString(deviceInfo?.getModel)) ??
84
+ readNestedString(platformConstants, ["Model"]) ??
85
+ readNestedString(platformConstants, ["model"]),
86
+ deviceName: configuredContext?.deviceName ??
87
+ (await readMaybeString(deviceInfo?.deviceName)) ??
88
+ (await readMaybeString(deviceInfo?.getDeviceName)),
89
+ deviceBrand: configuredContext?.deviceBrand ??
90
+ (await readMaybeString(deviceInfo?.brand)) ??
91
+ (await readMaybeString(deviceInfo?.getBrand)),
92
+ deviceManufacturer: configuredContext?.deviceManufacturer ??
93
+ (await readMaybeString(deviceInfo?.manufacturer)) ??
94
+ (await readMaybeString(deviceInfo?.getManufacturer)),
95
+ deviceType: configuredContext?.deviceType ??
96
+ (await readMaybeString(deviceInfo?.deviceType)) ??
97
+ (await readMaybeString(deviceInfo?.getDeviceType)),
98
+ isTablet: configuredContext?.isTablet ??
99
+ (await readMaybeBoolean(deviceInfo?.isTablet)) ??
100
+ (await readMaybeBoolean(deviceInfo?.getDeviceTypeIsTablet)),
101
+ isEmulator: configuredContext?.isEmulator ??
102
+ (await readMaybeBoolean(deviceInfo?.isEmulator)),
103
+ screenWidth: screen?.width,
104
+ screenHeight: screen?.height,
105
+ screenScale: screen?.scale,
106
+ locale,
107
+ language: configuredContext?.language ??
108
+ (typeof locale === "string" ? locale.split(/[-_]/)[0] : undefined),
109
+ timezone: configuredContext?.timezone ??
110
+ safeTimezone() ??
111
+ (await readMaybeString(deviceInfo?.timezone)) ??
112
+ (await readMaybeString(deviceInfo?.getTimezone)),
113
+ country: configuredContext?.country,
114
+ countryCode: configuredContext?.countryCode ??
115
+ readCountryCodeFromLocale(typeof locale === "string" ? locale : undefined),
116
+ region: configuredContext?.region,
117
+ city: configuredContext?.city,
118
+ carrier: configuredContext?.carrier ??
119
+ (await readMaybeString(deviceInfo?.carrier)) ??
120
+ (await readMaybeString(deviceInfo?.getCarrier)),
121
+ networkType: configuredContext?.networkType ??
122
+ (await readMaybeString(deviceInfo?.networkType)) ??
123
+ (await readMaybeString(deviceInfo?.getNetworkType)),
124
+ ...configuredContext
125
+ });
126
+ }
127
+ export async function attachReactNativeLifecycleTracking(client, rn = getReactNative(), storage = new MemoryStorage(), options) {
128
+ const installKey = `attry.${options.appId}.install_tracked`;
129
+ const sessionStartedAt = Date.now();
130
+ const sessionId = `ses_${sessionStartedAt.toString(36)}`;
131
+ if (!(await storage.getItem(installKey))) {
132
+ await storage.setItem(installKey, new Date().toISOString());
133
+ await client.track("install", {
134
+ properties: {
135
+ auto: true,
136
+ firstSdkOpen: true
137
+ },
138
+ context: {
139
+ sessionId
140
+ }
141
+ });
142
+ }
143
+ await client.track("app_open", {
144
+ properties: {
145
+ auto: true,
146
+ appState: rn?.AppState?.currentState ?? "active"
147
+ },
148
+ context: {
149
+ sessionId
150
+ }
151
+ });
152
+ await client.track("session_started", {
153
+ properties: {
154
+ auto: true
155
+ },
156
+ context: {
157
+ sessionId
158
+ }
159
+ });
160
+ let active = rn?.AppState?.currentState !== "background";
161
+ let lastActiveAt = sessionStartedAt;
162
+ const sessionTimeoutMs = options.sessionTimeoutMs ?? 30 * 60 * 1000;
163
+ rn?.AppState?.addEventListener?.("change", (state) => {
164
+ const now = Date.now();
165
+ if (state === "active" && !active) {
166
+ active = true;
167
+ lastActiveAt = now;
168
+ void client.track("app_foreground", {
169
+ properties: {
170
+ auto: true
171
+ },
172
+ context: {
173
+ sessionId
174
+ }
175
+ });
176
+ return;
177
+ }
178
+ if ((state === "background" || state === "inactive") && active) {
179
+ active = false;
180
+ const durationMs = Math.max(0, now - lastActiveAt);
181
+ void client.track("app_background", {
182
+ properties: {
183
+ auto: true,
184
+ durationMs
185
+ },
186
+ context: {
187
+ sessionId
188
+ }
189
+ });
190
+ if (durationMs >= sessionTimeoutMs || state === "background") {
191
+ void client.track("session_ended", {
192
+ properties: {
193
+ auto: true,
194
+ durationMs: Math.max(0, now - sessionStartedAt)
195
+ },
196
+ context: {
197
+ sessionId
198
+ }
199
+ });
200
+ void client.flush();
201
+ }
202
+ }
203
+ });
204
+ }
205
+ export async function attachDeepLinkTracking(client, rn = getReactNative()) {
206
+ const initialUrl = await rn?.Linking?.getInitialURL?.();
207
+ if (initialUrl) {
208
+ await trackDeepLinkOpen(client, initialUrl, "initial_url", rn);
209
+ }
210
+ rn?.Linking?.addEventListener?.("url", ({ url }) => {
211
+ void trackDeepLinkOpen(client, url, "linking_event", rn);
212
+ });
213
+ }
214
+ export async function collectInstallAttribution(client, rn = getReactNative()) {
215
+ if (rn?.Platform?.OS === "ios") {
216
+ const token = await rn.NativeModules?.AttryAppleAds?.attributionToken?.();
217
+ if (token) {
218
+ const response = await client.submitAppleAdsToken(token);
219
+ await client.track("apple_ads_token_collected", {
220
+ attribution: response?.attribution ?? {
221
+ source: "apple_ads_adservices",
222
+ confidence: "deterministic"
223
+ }
224
+ });
225
+ }
226
+ return;
227
+ }
228
+ if (rn?.Platform?.OS === "android") {
229
+ const payload = await rn.NativeModules?.AttryInstallReferrer?.getInstallReferrer?.();
230
+ if (payload?.installReferrer) {
231
+ const response = await client.resolveInstall({
232
+ installReferrer: payload.installReferrer
233
+ });
234
+ await client.track("install_referrer_collected", {
235
+ properties: payload,
236
+ attribution: response?.attribution ?? {
237
+ source: "google_play_install_referrer",
238
+ installReferrer: payload.installReferrer,
239
+ confidence: "deterministic"
240
+ }
241
+ });
242
+ }
243
+ }
244
+ }
245
+ async function trackDeepLinkOpen(client, url, openType, rn = getReactNative()) {
246
+ const parsed = parseAttryUrl(url);
247
+ const openedFromUniversalLink = rn?.Platform?.OS === "ios";
248
+ const openedFromAndroidAppLink = rn?.Platform?.OS === "android";
249
+ const resolved = await client.resolveDeepLink({
250
+ url,
251
+ openedFromUniversalLink,
252
+ openedFromAndroidAppLink
253
+ });
254
+ const resolvedLink = typeof resolved?.link === "object" && resolved.link
255
+ ? resolved.link
256
+ : undefined;
257
+ if (parsed.clickId) {
258
+ await client.resolveInstall({
259
+ attryClickId: parsed.clickId,
260
+ openedFromUniversalLink,
261
+ openedFromAndroidAppLink
262
+ });
263
+ }
264
+ await client.track("deep_link_opened", {
265
+ properties: {
266
+ openType,
267
+ url: parsed.url,
268
+ host: parsed.host,
269
+ path: parsed.path,
270
+ destinationPath: typeof resolvedLink?.destinationPath === "string"
271
+ ? resolvedLink.destinationPath
272
+ : undefined,
273
+ linkData: resolvedLink?.data &&
274
+ typeof resolvedLink.data === "object" &&
275
+ !Array.isArray(resolvedLink.data)
276
+ ? resolvedLink.data
277
+ : undefined
278
+ },
279
+ attribution: resolved?.attribution ?? {
280
+ source: openedFromAndroidAppLink ? "app_link" : "universal_link",
281
+ clickId: parsed.clickId,
282
+ campaign: parsed.campaign,
283
+ medium: parsed.medium,
284
+ referrerSource: parsed.source,
285
+ confidence: "deterministic"
286
+ }
287
+ });
288
+ }
289
+ async function readMaybeString(value) {
290
+ try {
291
+ const resolved = typeof value === "function" ? await value() : value;
292
+ return typeof resolved === "string" && resolved.trim()
293
+ ? resolved.trim()
294
+ : undefined;
295
+ }
296
+ catch {
297
+ return undefined;
298
+ }
299
+ }
300
+ async function readMaybeBoolean(value) {
301
+ try {
302
+ const resolved = typeof value === "function" ? await value() : value;
303
+ return typeof resolved === "boolean" ? resolved : undefined;
304
+ }
305
+ catch {
306
+ return undefined;
307
+ }
308
+ }
309
+ function readNestedString(record, path) {
310
+ const value = readNestedUnknown(record, path);
311
+ return typeof value === "string" && value.trim() ? value.trim() : undefined;
312
+ }
313
+ function readNestedUnknown(record, path) {
314
+ let current = record;
315
+ for (const key of path) {
316
+ if (!current || typeof current !== "object" || !(key in current)) {
317
+ return undefined;
318
+ }
319
+ current = current[key];
320
+ }
321
+ return current;
322
+ }
323
+ function readFirstString(value) {
324
+ return Array.isArray(value) && typeof value[0] === "string"
325
+ ? value[0]
326
+ : undefined;
327
+ }
328
+ function compactContext(context) {
329
+ return Object.fromEntries(Object.entries(context).filter(([, value]) => value !== undefined && value !== null && value !== ""));
330
+ }
331
+ function readCountryCodeFromLocale(locale) {
332
+ const normalized = locale?.replace("_", "-");
333
+ const country = normalized?.split("-")[1];
334
+ return country && country.length === 2 ? country.toUpperCase() : undefined;
335
+ }
336
+ function safeTimezone() {
337
+ try {
338
+ return Intl.DateTimeFormat().resolvedOptions().timeZone;
339
+ }
340
+ catch {
341
+ return undefined;
342
+ }
343
+ }
344
+ function getReactNative() {
345
+ try {
346
+ const dynamicRequire = eval("require");
347
+ return dynamicRequire("react-native");
348
+ }
349
+ catch {
350
+ return undefined;
351
+ }
352
+ }
353
+ function createAsyncStorageAdapter() {
354
+ try {
355
+ const dynamicRequire = eval("require");
356
+ const asyncStorage = dynamicRequire("@react-native-async-storage/async-storage").default;
357
+ return asyncStorage;
358
+ }
359
+ catch {
360
+ return undefined;
361
+ }
362
+ }
@@ -0,0 +1,5 @@
1
+ #import <React/RCTBridgeModule.h>
2
+
3
+ @interface RCT_EXTERN_MODULE(AttryAppleAds, NSObject)
4
+ RCT_EXTERN_METHOD(attributionToken:(RCTPromiseResolveBlock)resolve rejecter:(RCTPromiseRejectBlock)reject)
5
+ @end
@@ -0,0 +1,31 @@
1
+ import Foundation
2
+
3
+ #if canImport(AdServices)
4
+ import AdServices
5
+ #endif
6
+
7
+ @objc(AttryAppleAds)
8
+ class AttryAppleAds: NSObject {
9
+ @objc
10
+ static func requiresMainQueueSetup() -> Bool {
11
+ return false
12
+ }
13
+
14
+ @objc(attributionToken:rejecter:)
15
+ func attributionToken(resolve: RCTPromiseResolveBlock, rejecter reject: RCTPromiseRejectBlock) {
16
+ #if canImport(AdServices)
17
+ if #available(iOS 14.3, *) {
18
+ do {
19
+ let token = try AAAttribution.attributionToken()
20
+ resolve(token)
21
+ } catch {
22
+ reject("attry_adservices_error", "Unable to read Apple AdServices attribution token.", error)
23
+ }
24
+ } else {
25
+ resolve(nil)
26
+ }
27
+ #else
28
+ resolve(nil)
29
+ #endif
30
+ }
31
+ }
package/package.json ADDED
@@ -0,0 +1,37 @@
1
+ {
2
+ "name": "@attryio/react-native",
3
+ "version": "0.1.0",
4
+ "private": false,
5
+ "license": "Apache-2.0",
6
+ "type": "module",
7
+ "main": "./dist/index.js",
8
+ "types": "./dist/index.d.ts",
9
+ "exports": {
10
+ ".": {
11
+ "types": "./dist/index.d.ts",
12
+ "import": "./dist/index.js"
13
+ }
14
+ },
15
+ "files": [
16
+ "dist",
17
+ "ios",
18
+ "android",
19
+ "README.md",
20
+ "package.json"
21
+ ],
22
+ "publishConfig": {
23
+ "access": "public"
24
+ },
25
+ "dependencies": {
26
+ "@attryio/sdk-core": "0.1.0"
27
+ },
28
+ "peerDependencies": {
29
+ "react-native": ">=0.72"
30
+ },
31
+ "scripts": {
32
+ "build": "tsc -p tsconfig.json",
33
+ "lint": "eslint .",
34
+ "test": "vitest run",
35
+ "typecheck": "tsc -p tsconfig.json --noEmit"
36
+ }
37
+ }