@optable/web-sdk 0.44.6 → 0.48.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.
@@ -8,7 +8,6 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
8
8
  });
9
9
  };
10
10
  /* eslint-disable no-param-reassign */
11
- /* eslint-disable no-console */
12
11
  const STATUS = {
13
12
  REQUESTED: "REQUESTED",
14
13
  RECEIVED: "RECEIVED",
@@ -27,6 +26,7 @@ class OptablePrebidAnalytics {
27
26
  // Store auction data
28
27
  this.auctions = {};
29
28
  this.maxAuctionDataSize = 20;
29
+ sessionStorage.optableSessionDepth = (Number(sessionStorage === null || sessionStorage === void 0 ? void 0 : sessionStorage.optableSessionDepth) || 0) + 1;
30
30
  this.log("OptablePrebidAnalytics initialized");
31
31
  }
32
32
  /**
@@ -34,7 +34,7 @@ class OptablePrebidAnalytics {
34
34
  */
35
35
  log(...args) {
36
36
  if (this.config.debug) {
37
- console.log("[OptablePrebidAnalytics]", ...args);
37
+ console.log("[OptablePrebidAnalytics]", ...args); /* eslint-disable-line no-console */
38
38
  }
39
39
  }
40
40
  /**
@@ -84,6 +84,7 @@ class OptablePrebidAnalytics {
84
84
  */
85
85
  hookIntoPrebid(prebidInstance = window.pbjs) {
86
86
  const pbjs = prebidInstance;
87
+ this.prebidInstance = pbjs;
87
88
  if (typeof pbjs === "undefined") {
88
89
  this.log("Prebid.js not found");
89
90
  return false;
@@ -99,14 +100,19 @@ class OptablePrebidAnalytics {
99
100
  }
100
101
  trackAuctionEnd(event, missed) {
101
102
  return __awaiter(this, void 0, void 0, function* () {
103
+ var _a, _b;
102
104
  const { auctionId, timeout, bidderRequests = [], bidsReceived = [], noBids = [], timeoutBids = [] } = event;
105
+ window.optable.pageAuctionsCount = (Number(window.optable.pageAuctionsCount) || 0) + 1;
103
106
  this.log(`Processing auction ${auctionId} with ${bidderRequests.length} bidder requests`);
104
107
  // Build auction object with bidder requests and EID flags
105
108
  const auction = {
106
109
  auctionId,
107
110
  timeout,
108
111
  bidderRequests: bidderRequests.map((br) => {
109
- const { bidderCode, bidderRequestId, ortb2: { site: { domain }, user: { ext: { eids }, }, }, bids = [], } = br;
112
+ var _a, _b, _c, _d;
113
+ const { bidderCode, bidderRequestId, ortb2, bids = [] } = br;
114
+ const domain = (_a = ortb2 === null || ortb2 === void 0 ? void 0 : ortb2.site) === null || _a === void 0 ? void 0 : _a.domain;
115
+ const eids = ((_c = (_b = ortb2 === null || ortb2 === void 0 ? void 0 : ortb2.user) === null || _b === void 0 ? void 0 : _b.ext) === null || _c === void 0 ? void 0 : _c.eids) || ((_d = ortb2.user) === null || _d === void 0 ? void 0 : _d.eids) || [];
110
116
  // Optable EIDs
111
117
  const optableEIDS = eids.filter((e) => e.inserter === "optable.co");
112
118
  const optableMatchers = [...new Set(optableEIDS.map((e) => e.matcher).filter(Boolean))];
@@ -127,7 +133,7 @@ class OptablePrebidAnalytics {
127
133
  liSources,
128
134
  status: STATUS.REQUESTED,
129
135
  bids: bids.map((b) => {
130
- var _a;
136
+ var _a, _b, _c, _d;
131
137
  return ({
132
138
  bidId: b.bidId,
133
139
  bidderRequestId,
@@ -136,6 +142,7 @@ class OptablePrebidAnalytics {
136
142
  transactionId: b.transactionId,
137
143
  src: b.src,
138
144
  floorMin: (_a = b.floorData) === null || _a === void 0 ? void 0 : _a.floorMin,
145
+ splitTestAssignment: (_d = (_c = (_b = b.ortb2Imp) === null || _b === void 0 ? void 0 : _b.ext) === null || _c === void 0 ? void 0 : _c.optable) === null || _d === void 0 ? void 0 : _d.splitTestAssignment,
139
146
  status: STATUS.REQUESTED,
140
147
  });
141
148
  }),
@@ -155,6 +162,7 @@ class OptablePrebidAnalytics {
155
162
  });
156
163
  // Merge in bidsReceived → update individual bids as RECEIVED
157
164
  bidsReceived.forEach((b) => {
165
+ var _a, _b, _c, _d, _e, _f;
158
166
  const bidId = b.requestId;
159
167
  const br = bidToRequest[bidId];
160
168
  if (!br) {
@@ -170,6 +178,7 @@ class OptablePrebidAnalytics {
170
178
  cpm: b.cpm,
171
179
  size: `${b.width}x${b.height}`,
172
180
  currency: b.currency,
181
+ splitTestAssignment: (_c = (_b = (_a = b.ortb2Imp) === null || _a === void 0 ? void 0 : _a.ext) === null || _b === void 0 ? void 0 : _b.optable) === null || _c === void 0 ? void 0 : _c.splitTestAssignment,
173
182
  });
174
183
  }
175
184
  else {
@@ -185,6 +194,7 @@ class OptablePrebidAnalytics {
185
194
  size: `${b.width}x${b.height}`,
186
195
  currency: b.currency,
187
196
  status: STATUS.RECEIVED,
197
+ splitTestAssignment: (_f = (_e = (_d = b.ortb2Imp) === null || _d === void 0 ? void 0 : _d.ext) === null || _e === void 0 ? void 0 : _e.optable) === null || _f === void 0 ? void 0 : _f.splitTestAssignment,
188
198
  };
189
199
  br.bids.push(bidObj);
190
200
  bidIndex[bidId] = bidObj;
@@ -244,6 +254,7 @@ class OptablePrebidAnalytics {
244
254
  cpm: b.cpm,
245
255
  size: b.size,
246
256
  bidId: b.bidId,
257
+ splitTestAssignment: b.splitTestAssignment,
247
258
  };
248
259
  }),
249
260
  };
@@ -258,8 +269,10 @@ class OptablePrebidAnalytics {
258
269
  missed,
259
270
  url: `${window.location.hostname}${window.location.pathname}`,
260
271
  tenant: this.config.tenant,
261
- // eslint-disable-next-line no-undef
262
- optableWrapperVersion: SDK_WRAPPER_VERSION,
272
+ optableWrapperVersion: SDK_WRAPPER_VERSION, // eslint-disable-line no-undef
273
+ prebidjsVersion: ((_a = this.prebidInstance) === null || _a === void 0 ? void 0 : _a.version) || "unknown",
274
+ sessionDepth: (sessionStorage === null || sessionStorage === void 0 ? void 0 : sessionStorage.optableSessionDepth) || 1,
275
+ pageAuctionsCount: ((_b = window.optable) === null || _b === void 0 ? void 0 : _b.pageAuctionsCount) || 1,
263
276
  };
264
277
  // Log summary with bid counts
265
278
  this.log(`Auction ${auctionId} processed: ${bidderRequests.length} requests, ${totalBids} total bids, ${bidsReceived.length} received, ${noBids.length} no-bids, ${timeoutBids.length} timeouts`);
@@ -16,5 +16,5 @@ OptableSDK.prototype.tryIdentifyFromParams = function (key, prefix) {
16
16
  if (eid == "" || (!prefix && !maybeValidEID(eid))) {
17
17
  return;
18
18
  }
19
- this.identify((prefix || "e") + ":" + eid.toLowerCase());
19
+ this.identify((prefix || "e") + ":" + eid);
20
20
  };
@@ -1,3 +1,3 @@
1
1
  {
2
- "version": "v0.44.6"
2
+ "version": "v0.48.0"
3
3
  }
@@ -1,4 +1,5 @@
1
1
  import type { CMPApiConfig, Consent } from "./core/regs/consent";
2
+ import type { PageContextConfig } from "./core/context";
2
3
  type Experiment = never;
3
4
  type MatcherOverride = {
4
5
  id: string;
@@ -9,6 +10,7 @@ type ABTestConfig = {
9
10
  trafficPercentage: number;
10
11
  matcher_override?: MatcherOverride[];
11
12
  skipMatchers?: string[];
13
+ skipResolvers?: string[];
12
14
  };
13
15
  type TargetingSignals = {
14
16
  ref?: boolean;
@@ -35,6 +37,7 @@ type InitConfig = {
35
37
  abTests?: ABTestConfig[];
36
38
  additionalTargetingSignals?: TargetingSignals;
37
39
  timeout?: string;
40
+ pageContext?: PageContextConfig | boolean;
38
41
  };
39
42
  type ResolvedConfig = {
40
43
  site: string;
@@ -70,5 +73,5 @@ declare const DCN_DEFAULTS: {
70
73
  };
71
74
  declare function getConfig(init: InitConfig): ResolvedConfig;
72
75
  declare function generateSessionID(): string;
73
- export type { InitConsent, CMPApiConfig, InitConfig, ResolvedConfig, ABTestConfig, MatcherOverride, Experiment };
76
+ export type { InitConsent, CMPApiConfig, InitConfig, ResolvedConfig, ABTestConfig, MatcherOverride, Experiment, PageContextConfig, };
74
77
  export { getConfig, DCN_DEFAULTS, generateSessionID };
@@ -0,0 +1,32 @@
1
+ type SemanticContent = {
2
+ title: string;
3
+ description?: string;
4
+ keywords?: string[];
5
+ canonicalUrl?: string;
6
+ ogTags?: Record<string, string>;
7
+ headings?: Array<{
8
+ level: number;
9
+ text: string;
10
+ }>;
11
+ content?: string;
12
+ jsonLd?: object[];
13
+ language?: string;
14
+ };
15
+ type ContextData = {
16
+ semantic: SemanticContent;
17
+ html?: string;
18
+ url: string;
19
+ referrer?: string;
20
+ extractedAt: number;
21
+ };
22
+ type PageContextConfig = {
23
+ includeHtml?: boolean;
24
+ contentSelector?: string;
25
+ maxContentLength?: number;
26
+ maxHtmlLength?: number;
27
+ };
28
+ declare function extractSemanticContent(config: PageContextConfig): SemanticContent;
29
+ declare function extractContext(config: PageContextConfig): ContextData;
30
+ declare function normalizeContextConfig(config: PageContextConfig | boolean | undefined): PageContextConfig | null;
31
+ export type { SemanticContent, ContextData, PageContextConfig };
32
+ export { extractContext, extractSemanticContent, normalizeContextConfig };
@@ -0,0 +1,146 @@
1
+ const DEFAULT_MAX_CONTENT_LENGTH = 5000;
2
+ const DEFAULT_MAX_HTML_LENGTH = 50000;
3
+ const MAX_HEADINGS = 20;
4
+ function extractSemanticContent(config) {
5
+ var _a;
6
+ const semantic = {
7
+ title: document.title || "",
8
+ };
9
+ // Extract meta description
10
+ const descriptionMeta = document.querySelector('meta[name="description"]');
11
+ if (descriptionMeta) {
12
+ const content = descriptionMeta.getAttribute("content");
13
+ if (content) {
14
+ semantic.description = content;
15
+ }
16
+ }
17
+ // Extract meta keywords
18
+ const keywordsMeta = document.querySelector('meta[name="keywords"]');
19
+ if (keywordsMeta) {
20
+ const content = keywordsMeta.getAttribute("content");
21
+ if (content) {
22
+ semantic.keywords = content
23
+ .split(",")
24
+ .map((k) => k.trim())
25
+ .filter(Boolean);
26
+ }
27
+ }
28
+ // Extract canonical URL
29
+ const canonicalLink = document.querySelector('link[rel="canonical"]');
30
+ if (canonicalLink) {
31
+ const href = canonicalLink.getAttribute("href");
32
+ if (href) {
33
+ semantic.canonicalUrl = href;
34
+ }
35
+ }
36
+ // Extract Open Graph tags
37
+ const ogTags = {};
38
+ document.querySelectorAll('meta[property^="og:"]').forEach((meta) => {
39
+ const property = meta.getAttribute("property");
40
+ const content = meta.getAttribute("content");
41
+ if (property && content) {
42
+ const key = property.replace("og:", "");
43
+ ogTags[key] = content;
44
+ }
45
+ });
46
+ if (Object.keys(ogTags).length > 0) {
47
+ semantic.ogTags = ogTags;
48
+ }
49
+ // Extract headings (h1-h3, max 20)
50
+ const headings = [];
51
+ document.querySelectorAll("h1, h2, h3").forEach((heading) => {
52
+ var _a;
53
+ if (headings.length >= MAX_HEADINGS)
54
+ return;
55
+ const level = parseInt(heading.tagName.substring(1), 10);
56
+ const text = (_a = heading.textContent) === null || _a === void 0 ? void 0 : _a.trim();
57
+ if (text) {
58
+ headings.push({ level, text });
59
+ }
60
+ });
61
+ if (headings.length > 0) {
62
+ semantic.headings = headings;
63
+ }
64
+ // Extract main content
65
+ const maxContentLength = (_a = config.maxContentLength) !== null && _a !== void 0 ? _a : DEFAULT_MAX_CONTENT_LENGTH;
66
+ const contentElement = findContentElement(config.contentSelector);
67
+ if (contentElement) {
68
+ const text = extractTextContent(contentElement);
69
+ if (text) {
70
+ semantic.content = text.substring(0, maxContentLength);
71
+ }
72
+ }
73
+ // Extract JSON-LD
74
+ const jsonLdScripts = document.querySelectorAll('script[type="application/ld+json"]');
75
+ const jsonLdData = [];
76
+ jsonLdScripts.forEach((script) => {
77
+ try {
78
+ const data = JSON.parse(script.textContent || "");
79
+ if (data && typeof data === "object") {
80
+ jsonLdData.push(data);
81
+ }
82
+ }
83
+ catch (_a) {
84
+ // Ignore invalid JSON-LD
85
+ }
86
+ });
87
+ if (jsonLdData.length > 0) {
88
+ semantic.jsonLd = jsonLdData;
89
+ }
90
+ // Extract language
91
+ const lang = document.documentElement.getAttribute("lang");
92
+ if (lang) {
93
+ semantic.language = lang;
94
+ }
95
+ return semantic;
96
+ }
97
+ function findContentElement(selector) {
98
+ // Use provided selector if available
99
+ if (selector) {
100
+ return document.querySelector(selector);
101
+ }
102
+ // Fall back to heuristics: main, article, or first large content block
103
+ const candidates = ["main", "article", '[role="main"]', ".content", "#content", ".post", ".article"];
104
+ for (const candidate of candidates) {
105
+ const element = document.querySelector(candidate);
106
+ if (element) {
107
+ return element;
108
+ }
109
+ }
110
+ // Last resort: body
111
+ return document.body;
112
+ }
113
+ function extractTextContent(element) {
114
+ if (element instanceof HTMLElement && element.innerText) {
115
+ return element.innerText.trim();
116
+ }
117
+ return "";
118
+ }
119
+ function extractContext(config) {
120
+ var _a;
121
+ const contextData = {
122
+ semantic: extractSemanticContent(config),
123
+ url: window.location.href,
124
+ extractedAt: Date.now(),
125
+ };
126
+ // Include referrer if available
127
+ if (document.referrer) {
128
+ contextData.referrer = document.referrer;
129
+ }
130
+ // Include HTML if configured
131
+ if (config.includeHtml) {
132
+ const maxHtmlLength = (_a = config.maxHtmlLength) !== null && _a !== void 0 ? _a : DEFAULT_MAX_HTML_LENGTH;
133
+ contextData.html = document.documentElement.outerHTML.substring(0, maxHtmlLength);
134
+ }
135
+ return contextData;
136
+ }
137
+ function normalizeContextConfig(config) {
138
+ if (!config) {
139
+ return null;
140
+ }
141
+ if (config === true) {
142
+ return {};
143
+ }
144
+ return config;
145
+ }
146
+ export { extractContext, extractSemanticContent, normalizeContextConfig };
@@ -2,6 +2,6 @@ import type { ResolvedConfig } from "../config";
2
2
  type ProfileTraits = {
3
3
  [key: string]: string | number | boolean;
4
4
  };
5
- declare function Profile(config: ResolvedConfig, traits: ProfileTraits): Promise<void>;
5
+ declare function Profile(config: ResolvedConfig, traits: ProfileTraits, id?: string | null, neighbors?: string[] | null): Promise<void>;
6
6
  export { Profile, ProfileTraits };
7
7
  export default Profile;
@@ -1,8 +1,6 @@
1
1
  import { fetch } from "../core/network";
2
- function Profile(config, traits) {
3
- const profile = {
4
- traits: traits,
5
- };
2
+ function Profile(config, traits, id = null, neighbors = null) {
3
+ const profile = Object.assign(Object.assign({ traits: traits }, (id && { id: id })), (neighbors && { neighbors: neighbors }));
6
4
  return fetch("/profile", config, {
7
5
  method: "POST",
8
6
  headers: {
@@ -52,6 +52,9 @@ function Targeting(config, req) {
52
52
  if (abTest.skipMatchers) {
53
53
  searchParams.append("skip_matchers", abTest.skipMatchers.join(","));
54
54
  }
55
+ if (abTest.skipResolvers) {
56
+ searchParams.append("skip_resolvers", abTest.skipResolvers.join(","));
57
+ }
55
58
  }
56
59
  if ((_a = config.additionalTargetingSignals) === null || _a === void 0 ? void 0 : _a.ref) {
57
60
  searchParams.append("ref", `${window.location.protocol}//${window.location.host}${window.location.pathname}`);
@@ -1,7 +1,10 @@
1
1
  import type { ResolvedConfig } from "../config";
2
+ import type { ContextData } from "../core/context";
2
3
  type WitnessProperties = {
3
- [key: string]: string | number | boolean;
4
+ [key: string]: string | number | boolean | unknown[] | null | {
5
+ [key: string]: unknown;
6
+ };
4
7
  };
5
- declare function Witness(config: ResolvedConfig, event: string, properties: WitnessProperties): Promise<void>;
8
+ declare function Witness(config: ResolvedConfig, event: string, properties: WitnessProperties, context?: ContextData): Promise<void>;
6
9
  export { Witness, WitnessProperties };
7
10
  export default Witness;
@@ -1,9 +1,12 @@
1
1
  import { fetch } from "../core/network";
2
- function Witness(config, event, properties) {
2
+ function Witness(config, event, properties, context) {
3
3
  const evt = {
4
4
  event: event,
5
5
  properties: properties,
6
6
  };
7
+ if (context) {
8
+ evt.pageContext = context;
9
+ }
7
10
  return fetch("/witness", config, {
8
11
  method: "POST",
9
12
  headers: {
package/lib/dist/sdk.d.ts CHANGED
@@ -10,6 +10,8 @@ declare class OptableSDK {
10
10
  static version: string;
11
11
  dcn: ResolvedConfig;
12
12
  protected init: Promise<void>;
13
+ private contextSent;
14
+ private contextConfig;
13
15
  constructor(dcn: InitConfig);
14
16
  initialize(): Promise<void>;
15
17
  identify(...ids: string[]): Promise<void>;
@@ -23,8 +25,11 @@ declare class OptableSDK {
23
25
  prebidORTB2FromCache(): PrebidORTB2;
24
26
  targetingKeyValues(): Promise<TargetingKeyValues>;
25
27
  targetingKeyValuesFromCache(): TargetingKeyValues;
26
- witness(event: string, properties?: WitnessProperties): Promise<void>;
27
- profile(traits: ProfileTraits): Promise<void>;
28
+ witness(event: string, properties?: WitnessProperties, options?: {
29
+ includeContext?: boolean;
30
+ }): Promise<void>;
31
+ resetContext(): void;
32
+ profile(traits: ProfileTraits, id?: string | null, neighbors?: string[] | null): Promise<void>;
28
33
  tokenize(id: string): Promise<TokenizeResponse>;
29
34
  resolve(id?: string): Promise<ResolveResponse>;
30
35
  static eid(email: string): string;
package/lib/dist/sdk.js CHANGED
@@ -9,6 +9,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge
9
9
  };
10
10
  import { default as buildInfo } from "./build.json";
11
11
  import { getConfig } from "./config";
12
+ import { extractContext, normalizeContextConfig } from "./core/context";
12
13
  import { Identify } from "./edge/identify";
13
14
  import { Uid2Token } from "./edge/uid2_token";
14
15
  import { Resolve } from "./edge/resolve";
@@ -21,7 +22,10 @@ import { sha256 } from "js-sha256";
21
22
  import { Tokenize } from "./edge/tokenize";
22
23
  class OptableSDK {
23
24
  constructor(dcn) {
25
+ this.contextSent = false;
26
+ this.contextConfig = null;
24
27
  this.dcn = getConfig(dcn);
28
+ this.contextConfig = normalizeContextConfig(dcn.pageContext);
25
29
  this.init = this.initialize();
26
30
  }
27
31
  initialize() {
@@ -86,15 +90,23 @@ class OptableSDK {
86
90
  return TargetingKeyValues(tdata);
87
91
  }
88
92
  witness(event_1) {
89
- return __awaiter(this, arguments, void 0, function* (event, properties = {}) {
93
+ return __awaiter(this, arguments, void 0, function* (event, properties = {}, options = {}) {
90
94
  yield this.init;
91
- return Witness(this.dcn, event, properties);
95
+ let context;
96
+ if (options.includeContext && this.contextConfig && !this.contextSent) {
97
+ context = extractContext(this.contextConfig);
98
+ this.contextSent = true;
99
+ }
100
+ return Witness(this.dcn, event, properties, context);
92
101
  });
93
102
  }
94
- profile(traits) {
95
- return __awaiter(this, void 0, void 0, function* () {
103
+ resetContext() {
104
+ this.contextSent = false;
105
+ }
106
+ profile(traits_1) {
107
+ return __awaiter(this, arguments, void 0, function* (traits, id = null, neighbors = null) {
96
108
  yield this.init;
97
- return Profile(this.dcn, traits);
109
+ return Profile(this.dcn, traits, id, neighbors);
98
110
  });
99
111
  }
100
112
  tokenize(id) {
package/package.json CHANGED
@@ -5,11 +5,11 @@
5
5
  "browser": "./lib/dist/sdk.js",
6
6
  "repository": {
7
7
  "type": "git",
8
- "url": "git+https://github.com/optable/optable-web-sdk.git"
8
+ "url": "git+https://github.com/Optable/optable-web-sdk.git"
9
9
  },
10
10
  "homepage": "https://optable.co",
11
11
  "license": "SEE LICENSE IN LICENSE",
12
- "version": "v0.44.6",
12
+ "version": "v0.48.0",
13
13
  "devDependencies": {
14
14
  "@babel/core": "^7.12.3",
15
15
  "@babel/plugin-proposal-class-properties": "^7.12.1",
@@ -18,6 +18,7 @@
18
18
  "@babel/preset-typescript": "^7.12.1",
19
19
  "@types/googletag": "^3.1.3",
20
20
  "@types/jest": "^26.0.15",
21
+ "@types/node": "^25.0.3",
21
22
  "babel-jest": "^29.7.0",
22
23
  "babel-loader": "^8.2.2",
23
24
  "core-js": "^3.7.0",
@@ -28,13 +29,15 @@
28
29
  "prettier": "^3.6.2",
29
30
  "shellcheck": "^3.0.0",
30
31
  "typescript": "^5.2.2",
31
- "webpack": "^5.97.1",
32
+ "webpack": "^5.104.1",
32
33
  "webpack-cli": "^5.1.4",
33
34
  "whatwg-fetch": "^3.6.20"
34
35
  },
35
36
  "dependencies": {
36
37
  "@babel/runtime": "^7.27.0",
37
38
  "@optable/web-sdk": "^0.40.0",
39
+ "bowser": "^2.12.1",
40
+ "iab-adcom": "^1.0.6",
38
41
  "iab-openrtb": "^1.0.1",
39
42
  "js-sha256": "^0.11.0",
40
43
  "regenerator-runtime": "^0.13.7"
@@ -47,12 +50,12 @@
47
50
  "LICENSE"
48
51
  ],
49
52
  "scripts": {
50
- "build": "npm run build-lib && npm run build-web",
53
+ "build": "pnpm build-lib && pnpm build-web",
51
54
  "start": "tsc -b browser --watch & webpack --config=./browser/webpack.config.js --mode=development --watch --devtool=source-map",
52
55
  "build-lib": "tsc -b lib",
53
56
  "build-web": "tsc -b browser && webpack --config=./browser/webpack.config.js",
54
57
  "test": "jest",
55
58
  "format": "prettier . --write",
56
- "lint-scripts": "npx shellcheck scripts/*.sh"
59
+ "lint-scripts": "pnpm exec shellcheck scripts/*.sh"
57
60
  }
58
- }
61
+ }