@openpolicy/vite-auto-collect 0.0.18 → 0.0.19

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.d.ts CHANGED
@@ -17,6 +17,9 @@ type AutoCollectOptions = {
17
17
  * `.svelte-kit`, `.cache`).
18
18
  */
19
19
  ignore?: string[];
20
+ thirdParties?: {
21
+ usePackageJson?: boolean;
22
+ };
20
23
  };
21
24
  /**
22
25
  * Vite plugin that scans source files for `@openpolicy/sdk` `collecting()`
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;KAMY,kBAAA;;AAAZ;;;EAKC,MAAA;EAAA;;;EAIA,UAAA;EAMM;AAwCP;;;;EAxCC,MAAA;AAAA;;;;;;;;;;;;;iBAwCe,WAAA,CAAY,OAAA,GAAS,kBAAA,GAA0B,MAAA"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/index.ts"],"mappings":";;;KAOY,kBAAA;;AAAZ;;;EAKC,MAAA;EAAA;;;EAIA,UAAA;EASC;;;AAyCF;;EA5CC,MAAA;EAEA,YAAA;IACC,cAAA;EAAA;AAAA;;;;;;;;;;;;;iBAyCc,WAAA,CAAY,OAAA,GAAS,kBAAA,GAA0B,MAAA"}
package/dist/index.js CHANGED
@@ -4,65 +4,93 @@ import { parseSync } from "oxc-parser";
4
4
  //#region src/analyse.ts
5
5
  const SDK_SPECIFIER = "@openpolicy/sdk";
6
6
  const COLLECTING_NAME = "collecting";
7
+ const THIRD_PARTY_NAME = "thirdParty";
7
8
  /**
8
- * Extract `collecting()` call metadata from a single source file.
9
+ * Extract `collecting()` and `thirdParty()` call metadata from a single source file.
9
10
  *
10
- * Returns a `Record<category, labels[]>` for every detected call whose
11
- * arguments match the analysable shape. Files with no matching calls — or
12
- * that fail to parse — return `{}`.
11
+ * Returns an `ExtractResult` with `dataCollected` (category labels) and
12
+ * `thirdParties` (array of third-party entries). Files with no matching calls
13
+ * — or that fail to parse — return empty defaults.
13
14
  *
14
15
  * The analyser runs in two phases:
15
- * 1. Collect local names bound to `collecting` imported from `@openpolicy/sdk`
16
- * (handles renamed imports, skips type-only imports, ignores look-alikes
17
- * imported from other modules).
16
+ * 1. Collect local names bound to `collecting` / `thirdParty` imported from
17
+ * `@openpolicy/sdk` (handles renamed imports, skips type-only imports,
18
+ * ignores look-alikes imported from other modules).
18
19
  * 2. Walk the program body and inspect every `CallExpression` whose callee
19
20
  * is one of those tracked local names.
20
21
  */
21
- function extractCollecting(filename, code) {
22
+ function extractFromFile(filename, code) {
23
+ const empty = {
24
+ dataCollected: {},
25
+ thirdParties: []
26
+ };
22
27
  let result;
23
28
  try {
24
29
  result = parseSync(filename, code);
25
30
  } catch {
26
31
  console.warn(`[openpolicy-auto-collect] parse error in ${filename}`);
27
- return {};
32
+ return empty;
28
33
  }
29
34
  if (result.errors.length > 0) {
30
35
  if (result.errors.some((e) => e.severity === "Error")) {
31
36
  console.warn(`[openpolicy-auto-collect] parse error in ${filename}`);
32
- return {};
37
+ return empty;
33
38
  }
34
39
  }
35
40
  const program = result.program;
36
- const localNames = collectSdkCollectingBindings(program);
37
- if (localNames.size === 0) return {};
38
- const out = {};
41
+ const collectingNames = collectSdkBindings(program, COLLECTING_NAME);
42
+ const thirdPartyNames = collectSdkBindings(program, THIRD_PARTY_NAME);
43
+ if (collectingNames.size === 0 && thirdPartyNames.size === 0) return empty;
44
+ const dataCollected = {};
45
+ const thirdParties = [];
46
+ const seenThirdParties = /* @__PURE__ */ new Set();
39
47
  walk(program, (node) => {
40
48
  if (node.type !== "CallExpression") return;
41
49
  const callee = node.callee;
42
50
  if (!callee || callee.type !== "Identifier") return;
43
- if (!localNames.has(callee.name)) return;
51
+ const calleeName = callee.name;
44
52
  const args = node.arguments;
45
- if (!args || args.length < 3) return;
46
- const category = extractStringLiteral(args[0]);
47
- if (category === null) return;
48
- const labels = extractLabelKeys(args[2]);
49
- if (labels === null) return;
50
- const existing = out[category] ?? [];
51
- const seen = new Set(existing);
52
- for (const label of labels) if (!seen.has(label)) {
53
- existing.push(label);
54
- seen.add(label);
53
+ if (collectingNames.has(calleeName)) {
54
+ if (!args || args.length < 3) return;
55
+ const category = extractStringLiteral(args[0]);
56
+ if (category === null) return;
57
+ const labels = extractLabelKeys(args[2]);
58
+ if (labels === null) return;
59
+ const existing = dataCollected[category] ?? [];
60
+ const seen = new Set(existing);
61
+ for (const label of labels) if (!seen.has(label)) {
62
+ existing.push(label);
63
+ seen.add(label);
64
+ }
65
+ dataCollected[category] = existing;
66
+ } else if (thirdPartyNames.has(calleeName)) {
67
+ if (!args || args.length < 3) return;
68
+ const name = extractStringLiteral(args[0]);
69
+ if (name === null) return;
70
+ const purpose = extractStringLiteral(args[1]);
71
+ if (purpose === null) return;
72
+ const policyUrl = extractStringLiteral(args[2]);
73
+ if (policyUrl === null) return;
74
+ if (seenThirdParties.has(name)) return;
75
+ seenThirdParties.add(name);
76
+ thirdParties.push({
77
+ name,
78
+ purpose,
79
+ policyUrl
80
+ });
55
81
  }
56
- out[category] = existing;
57
82
  });
58
- return out;
83
+ return {
84
+ dataCollected,
85
+ thirdParties
86
+ };
59
87
  }
60
88
  /**
61
- * Walk `ImportDeclaration` nodes and return the local names bound to
62
- * `collecting` imported from `@openpolicy/sdk`. Skips type-only imports and
63
- * specifiers whose imported name isn't literally `collecting`.
89
+ * Walk `ImportDeclaration` nodes and return the local names bound to the given
90
+ * `exportName` imported from `@openpolicy/sdk`. Skips type-only imports and
91
+ * specifiers whose imported name doesn't match.
64
92
  */
65
- function collectSdkCollectingBindings(program) {
93
+ function collectSdkBindings(program, exportName) {
66
94
  const names = /* @__PURE__ */ new Set();
67
95
  const body = program.body;
68
96
  if (!body) return names;
@@ -78,7 +106,7 @@ function collectSdkCollectingBindings(program) {
78
106
  if (spec.importKind === "type") continue;
79
107
  const imported = spec.imported;
80
108
  if (!imported) continue;
81
- if ((imported.type === "Identifier" ? imported.name : imported.type === "Literal" ? typeof imported.value === "string" ? imported.value : void 0 : void 0) !== COLLECTING_NAME) continue;
109
+ if ((imported.type === "Identifier" ? imported.name : imported.type === "Literal" ? typeof imported.value === "string" ? imported.value : void 0 : void 0) !== exportName) continue;
82
110
  const local = spec.local;
83
111
  if (!local || local.type !== "Identifier") continue;
84
112
  names.add(local.name);
@@ -132,6 +160,140 @@ function walk(node, visit) {
132
160
  }
133
161
  }
134
162
  //#endregion
163
+ //#region src/known-packages.ts
164
+ /**
165
+ * Registry of known npm packages mapped to their ThirdPartyEntry metadata.
166
+ * Multiple package names can point to the same service — deduplication by
167
+ * `ThirdPartyEntry.name` is handled at merge time in the caller.
168
+ */
169
+ const KNOWN_PACKAGES = new Map([
170
+ ["stripe", {
171
+ name: "Stripe",
172
+ purpose: "Payment processing",
173
+ policyUrl: "https://stripe.com/privacy"
174
+ }],
175
+ ["@stripe/stripe-js", {
176
+ name: "Stripe",
177
+ purpose: "Payment processing",
178
+ policyUrl: "https://stripe.com/privacy"
179
+ }],
180
+ ["braintree", {
181
+ name: "Braintree",
182
+ purpose: "Payment processing",
183
+ policyUrl: "https://www.braintreepayments.com/legal/braintree-privacy-policy"
184
+ }],
185
+ ["@braintree/browser-drop-in", {
186
+ name: "Braintree",
187
+ purpose: "Payment processing",
188
+ policyUrl: "https://www.braintreepayments.com/legal/braintree-privacy-policy"
189
+ }],
190
+ ["@sentry/browser", {
191
+ name: "Sentry",
192
+ purpose: "Error tracking",
193
+ policyUrl: "https://sentry.io/privacy/"
194
+ }],
195
+ ["@sentry/node", {
196
+ name: "Sentry",
197
+ purpose: "Error tracking",
198
+ policyUrl: "https://sentry.io/privacy/"
199
+ }],
200
+ ["@sentry/nextjs", {
201
+ name: "Sentry",
202
+ purpose: "Error tracking",
203
+ policyUrl: "https://sentry.io/privacy/"
204
+ }],
205
+ ["@sentry/react", {
206
+ name: "Sentry",
207
+ purpose: "Error tracking",
208
+ policyUrl: "https://sentry.io/privacy/"
209
+ }],
210
+ ["@sentry/vue", {
211
+ name: "Sentry",
212
+ purpose: "Error tracking",
213
+ policyUrl: "https://sentry.io/privacy/"
214
+ }],
215
+ ["@datadog/browser-rum", {
216
+ name: "Datadog",
217
+ purpose: "Monitoring",
218
+ policyUrl: "https://www.datadoghq.com/legal/privacy/"
219
+ }],
220
+ ["dd-trace", {
221
+ name: "Datadog",
222
+ purpose: "Monitoring",
223
+ policyUrl: "https://www.datadoghq.com/legal/privacy/"
224
+ }],
225
+ ["posthog-js", {
226
+ name: "PostHog",
227
+ purpose: "Product analytics",
228
+ policyUrl: "https://posthog.com/privacy"
229
+ }],
230
+ ["posthog-node", {
231
+ name: "PostHog",
232
+ purpose: "Product analytics",
233
+ policyUrl: "https://posthog.com/privacy"
234
+ }],
235
+ ["mixpanel-browser", {
236
+ name: "Mixpanel",
237
+ purpose: "Product analytics",
238
+ policyUrl: "https://mixpanel.com/legal/privacy-policy/"
239
+ }],
240
+ ["@segment/analytics-next", {
241
+ name: "Segment",
242
+ purpose: "Customer data platform",
243
+ policyUrl: "https://www.twilio.com/en-us/legal/privacy"
244
+ }],
245
+ ["@amplitude/analytics-browser", {
246
+ name: "Amplitude",
247
+ purpose: "Product analytics",
248
+ policyUrl: "https://amplitude.com/privacy"
249
+ }],
250
+ ["amplitude-js", {
251
+ name: "Amplitude",
252
+ purpose: "Product analytics",
253
+ policyUrl: "https://amplitude.com/privacy"
254
+ }],
255
+ ["@vercel/analytics", {
256
+ name: "Vercel Analytics",
257
+ purpose: "Web analytics",
258
+ policyUrl: "https://vercel.com/legal/privacy-policy"
259
+ }],
260
+ ["plausible-tracker", {
261
+ name: "Plausible",
262
+ purpose: "Web analytics",
263
+ policyUrl: "https://plausible.io/privacy"
264
+ }],
265
+ ["logrocket", {
266
+ name: "LogRocket",
267
+ purpose: "Session recording",
268
+ policyUrl: "https://logrocket.com/privacy/"
269
+ }],
270
+ ["@hotjar/browser", {
271
+ name: "Hotjar",
272
+ purpose: "Session recording",
273
+ policyUrl: "https://www.hotjar.com/legal/policies/privacy/"
274
+ }],
275
+ ["resend", {
276
+ name: "Resend",
277
+ purpose: "Transactional email",
278
+ policyUrl: "https://resend.com/legal/privacy-policy"
279
+ }],
280
+ ["@sendgrid/mail", {
281
+ name: "SendGrid",
282
+ purpose: "Transactional email",
283
+ policyUrl: "https://www.twilio.com/en-us/legal/privacy"
284
+ }],
285
+ ["intercom-client", {
286
+ name: "Intercom",
287
+ purpose: "Customer messaging",
288
+ policyUrl: "https://www.intercom.com/legal/privacy"
289
+ }],
290
+ ["@intercom/messenger-js-sdk", {
291
+ name: "Intercom",
292
+ purpose: "Customer messaging",
293
+ policyUrl: "https://www.intercom.com/legal/privacy"
294
+ }]
295
+ ]);
296
+ //#endregion
135
297
  //#region src/scan.ts
136
298
  const DEFAULT_IGNORES = new Set([
137
299
  "node_modules",
@@ -215,11 +377,46 @@ function autoCollect(options = {}) {
215
377
  const srcDirOpt = options.srcDir ?? "src";
216
378
  const extensions = options.extensions ?? [".ts", ".tsx"];
217
379
  const ignore = options.ignore ?? [];
380
+ const usePackageJsonOpt = options.thirdParties?.usePackageJson ?? false;
381
+ let resolvedRoot;
218
382
  let resolvedSrcDir;
219
- let scanned = {};
383
+ let scanned = {
384
+ dataCollected: {},
385
+ thirdParties: []
386
+ };
387
+ async function detectFromPackageJson(root) {
388
+ let raw;
389
+ try {
390
+ raw = await readFile(resolve(root, "package.json"), "utf8");
391
+ } catch {
392
+ return [];
393
+ }
394
+ let pkg;
395
+ try {
396
+ pkg = JSON.parse(raw);
397
+ } catch {
398
+ return [];
399
+ }
400
+ const allDeps = {
401
+ ...pkg.dependencies,
402
+ ...pkg.devDependencies
403
+ };
404
+ const entries = [];
405
+ const seenNames = /* @__PURE__ */ new Set();
406
+ for (const pkgName of Object.keys(allDeps)) {
407
+ const entry = KNOWN_PACKAGES.get(pkgName);
408
+ if (entry && !seenNames.has(entry.name)) {
409
+ seenNames.add(entry.name);
410
+ entries.push(entry);
411
+ }
412
+ }
413
+ return entries;
414
+ }
220
415
  async function scanAndMerge() {
221
416
  const files = await walkSources(resolvedSrcDir, extensions, ignore);
222
- const merged = {};
417
+ const mergedData = {};
418
+ const mergedParties = [];
419
+ const seenParties = /* @__PURE__ */ new Set();
223
420
  for (const file of files) {
224
421
  let code;
225
422
  try {
@@ -227,18 +424,32 @@ function autoCollect(options = {}) {
227
424
  } catch {
228
425
  continue;
229
426
  }
230
- const extracted = extractCollecting(file, code);
231
- for (const [category, labels] of Object.entries(extracted)) {
232
- const existing = merged[category] ?? [];
427
+ const extracted = extractFromFile(file, code);
428
+ for (const [category, labels] of Object.entries(extracted.dataCollected)) {
429
+ const existing = mergedData[category] ?? [];
233
430
  const seen = new Set(existing);
234
431
  for (const label of labels) if (!seen.has(label)) {
235
432
  existing.push(label);
236
433
  seen.add(label);
237
434
  }
238
- merged[category] = existing;
435
+ mergedData[category] = existing;
436
+ }
437
+ for (const entry of extracted.thirdParties) if (!seenParties.has(entry.name)) {
438
+ seenParties.add(entry.name);
439
+ mergedParties.push(entry);
440
+ }
441
+ }
442
+ if (usePackageJsonOpt) {
443
+ const pkgEntries = await detectFromPackageJson(resolvedRoot);
444
+ for (const entry of pkgEntries) if (!seenParties.has(entry.name)) {
445
+ seenParties.add(entry.name);
446
+ mergedParties.push(entry);
239
447
  }
240
448
  }
241
- return merged;
449
+ return {
450
+ dataCollected: mergedData,
451
+ thirdParties: mergedParties
452
+ };
242
453
  }
243
454
  /**
244
455
  * Returns true when `file` lives inside `resolvedSrcDir` and has one of
@@ -269,6 +480,7 @@ function autoCollect(options = {}) {
269
480
  name: "openpolicy-auto-collect",
270
481
  enforce: "pre",
271
482
  configResolved(config) {
483
+ resolvedRoot = config.root;
272
484
  resolvedSrcDir = resolve(config.root, srcDirOpt);
273
485
  },
274
486
  async buildStart() {
@@ -300,7 +512,7 @@ function autoCollect(options = {}) {
300
512
  },
301
513
  load(id) {
302
514
  if (id !== RESOLVED_VIRTUAL_ID) return null;
303
- return `export const dataCollected = ${JSON.stringify(scanned)};\n`;
515
+ return `export const dataCollected = ${JSON.stringify(scanned.dataCollected)};\nexport const thirdParties = ${JSON.stringify(scanned.thirdParties)};\n`;
304
516
  }
305
517
  };
306
518
  }
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":[],"sources":["../src/analyse.ts","../src/scan.ts","../src/index.ts"],"sourcesContent":["import { parseSync } from \"oxc-parser\";\n\nconst SDK_SPECIFIER = \"@openpolicy/sdk\";\nconst COLLECTING_NAME = \"collecting\";\n\ntype AnyNode = { type: string; [key: string]: unknown };\n\n/**\n * Extract `collecting()` call metadata from a single source file.\n *\n * Returns a `Record<category, labels[]>` for every detected call whose\n * arguments match the analysable shape. Files with no matching calls — or\n * that fail to parse — return `{}`.\n *\n * The analyser runs in two phases:\n * 1. Collect local names bound to `collecting` imported from `@openpolicy/sdk`\n * (handles renamed imports, skips type-only imports, ignores look-alikes\n * imported from other modules).\n * 2. Walk the program body and inspect every `CallExpression` whose callee\n * is one of those tracked local names.\n */\nexport function extractCollecting(\n\tfilename: string,\n\tcode: string,\n): Record<string, string[]> {\n\tlet result: ReturnType<typeof parseSync>;\n\ttry {\n\t\tresult = parseSync(filename, code);\n\t} catch {\n\t\tconsole.warn(`[openpolicy-auto-collect] parse error in ${filename}`);\n\t\treturn {};\n\t}\n\n\tif (result.errors.length > 0) {\n\t\t// Hard parse failures only — oxc reports recoverable errors but still\n\t\t// produces a usable AST, so we keep going and let the walker decide.\n\t\tconst fatal = result.errors.some((e) => e.severity === (\"Error\" as never));\n\t\tif (fatal) {\n\t\t\tconsole.warn(`[openpolicy-auto-collect] parse error in ${filename}`);\n\t\t\treturn {};\n\t\t}\n\t}\n\n\tconst program = result.program as unknown as AnyNode;\n\tconst localNames = collectSdkCollectingBindings(program);\n\tif (localNames.size === 0) return {};\n\n\tconst out: Record<string, string[]> = {};\n\twalk(program, (node) => {\n\t\tif (node.type !== \"CallExpression\") return;\n\t\tconst callee = node.callee as AnyNode | undefined;\n\t\tif (!callee || callee.type !== \"Identifier\") return;\n\t\tif (!localNames.has(callee.name as string)) return;\n\n\t\tconst args = node.arguments as AnyNode[] | undefined;\n\t\tif (!args || args.length < 3) return;\n\n\t\tconst category = extractStringLiteral(args[0]);\n\t\tif (category === null) return;\n\n\t\tconst labels = extractLabelKeys(args[2]);\n\t\tif (labels === null) return;\n\n\t\tconst existing = out[category] ?? [];\n\t\tconst seen = new Set(existing);\n\t\tfor (const label of labels) {\n\t\t\tif (!seen.has(label)) {\n\t\t\t\texisting.push(label);\n\t\t\t\tseen.add(label);\n\t\t\t}\n\t\t}\n\t\tout[category] = existing;\n\t});\n\treturn out;\n}\n\n/**\n * Walk `ImportDeclaration` nodes and return the local names bound to\n * `collecting` imported from `@openpolicy/sdk`. Skips type-only imports and\n * specifiers whose imported name isn't literally `collecting`.\n */\nfunction collectSdkCollectingBindings(program: AnyNode): Set<string> {\n\tconst names = new Set<string>();\n\tconst body = program.body as AnyNode[] | undefined;\n\tif (!body) return names;\n\tfor (const node of body) {\n\t\tif (node.type !== \"ImportDeclaration\") continue;\n\t\tif ((node.importKind as string | undefined) === \"type\") continue;\n\t\tconst source = node.source as AnyNode | undefined;\n\t\tif (!source || source.value !== SDK_SPECIFIER) continue;\n\t\tconst specifiers = node.specifiers as AnyNode[] | undefined;\n\t\tif (!specifiers) continue;\n\t\tfor (const spec of specifiers) {\n\t\t\tif (spec.type !== \"ImportSpecifier\") continue;\n\t\t\tif ((spec.importKind as string | undefined) === \"type\") continue;\n\t\t\tconst imported = spec.imported as AnyNode | undefined;\n\t\t\tif (!imported) continue;\n\t\t\t// ESTree allows either an Identifier (name) or a string Literal (value)\n\t\t\t// for the imported binding (e.g. `import { \"foo\" as bar }`).\n\t\t\tconst importedName =\n\t\t\t\timported.type === \"Identifier\"\n\t\t\t\t\t? (imported.name as string | undefined)\n\t\t\t\t\t: imported.type === \"Literal\"\n\t\t\t\t\t\t? typeof imported.value === \"string\"\n\t\t\t\t\t\t\t? imported.value\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t: undefined;\n\t\t\tif (importedName !== COLLECTING_NAME) continue;\n\t\t\tconst local = spec.local as AnyNode | undefined;\n\t\t\tif (!local || local.type !== \"Identifier\") continue;\n\t\t\tnames.add(local.name as string);\n\t\t}\n\t}\n\treturn names;\n}\n\n/**\n * If `node` is a string `Literal`, return its string value. Otherwise\n * return `null` so the caller silently skips the call.\n */\nfunction extractStringLiteral(node: AnyNode | undefined): string | null {\n\tif (!node) return null;\n\tif (node.type !== \"Literal\") return null;\n\tif (typeof node.value !== \"string\") return null;\n\treturn node.value;\n}\n\n/**\n * Extract the string values from a plain `{ fieldName: \"Human Label\" }`\n * object literal. Returns an array of label strings, deduped while\n * preserving insertion order. Returns `null` if the shape doesn't match.\n */\nfunction extractLabelKeys(node: AnyNode | undefined): string[] | null {\n\tif (!node || node.type !== \"ObjectExpression\") return null;\n\tconst properties = node.properties as AnyNode[] | undefined;\n\tif (!properties) return null;\n\n\tconst labels: string[] = [];\n\tconst seen = new Set<string>();\n\tfor (const prop of properties) {\n\t\tif (prop.type !== \"Property\") continue; // drop SpreadElement silently\n\t\tconst val = prop.value as AnyNode | undefined;\n\t\tif (!val || val.type !== \"Literal\" || typeof val.value !== \"string\")\n\t\t\tcontinue;\n\t\tif (seen.has(val.value)) continue;\n\t\tseen.add(val.value);\n\t\tlabels.push(val.value);\n\t}\n\treturn labels;\n}\n\n/**\n * Recursive AST walker. Visits every `AnyNode` (depth-first) reachable\n * through array / nested-object children and invokes `visit` on each.\n */\nfunction walk(node: AnyNode, visit: (node: AnyNode) => void): void {\n\tvisit(node);\n\tfor (const key of Object.keys(node)) {\n\t\tif (key === \"parent\") continue;\n\t\tconst value = node[key];\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\tif (item && typeof item === \"object\" && typeof item.type === \"string\") {\n\t\t\t\t\twalk(item as AnyNode, visit);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (\n\t\t\tvalue &&\n\t\t\ttypeof value === \"object\" &&\n\t\t\ttypeof (value as { type?: unknown }).type === \"string\"\n\t\t) {\n\t\t\twalk(value as AnyNode, visit);\n\t\t}\n\t}\n}\n","import type { Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { extname, join } from \"node:path\";\n\nconst DEFAULT_IGNORES: ReadonlySet<string> = new Set([\n\t\"node_modules\",\n\t\"dist\",\n\t\".git\",\n\t\".next\",\n\t\".output\",\n\t\".svelte-kit\",\n\t\".cache\",\n]);\n\n/**\n * Recursively walks `root`, returning absolute paths of every regular file\n * whose extension is in `extensions`. Directories whose basename appears in\n * the built-in ignore list (or the extra `ignore` argument) are skipped\n * entirely.\n *\n * Missing roots resolve to an empty array — the plugin must not throw if the\n * user's `srcDir` hasn't been created yet.\n */\nexport async function walkSources(\n\troot: string,\n\textensions: readonly string[],\n\tignore: readonly string[] = [],\n): Promise<string[]> {\n\tconst ignored = new Set<string>([...DEFAULT_IGNORES, ...ignore]);\n\tconst exts = new Set(extensions);\n\tconst results: string[] = [];\n\n\tasync function walk(dir: string): Promise<void> {\n\t\tlet entries: Dirent[];\n\t\ttry {\n\t\t\tentries = (await readdir(dir, { withFileTypes: true })) as Dirent[];\n\t\t} catch (err) {\n\t\t\tconst code = (err as NodeJS.ErrnoException).code;\n\t\t\tif (code === \"ENOENT\" || code === \"ENOTDIR\") return;\n\t\t\tthrow err;\n\t\t}\n\t\tfor (const entry of entries) {\n\t\t\tif (ignored.has(entry.name)) continue;\n\t\t\tconst full = join(dir, entry.name);\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tawait walk(full);\n\t\t\t} else if (entry.isFile() && exts.has(extname(entry.name))) {\n\t\t\t\tresults.push(full);\n\t\t\t}\n\t\t}\n\t}\n\n\tawait walk(root);\n\tresults.sort();\n\treturn results;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { relative, resolve } from \"node:path\";\nimport type { Plugin, ViteDevServer } from \"vite\";\nimport { extractCollecting } from \"./analyse\";\nimport { walkSources } from \"./scan\";\n\nexport type AutoCollectOptions = {\n\t/**\n\t * Directory walked for `collecting()` calls. Resolved relative to the\n\t * Vite project root. Defaults to `\"src\"`.\n\t */\n\tsrcDir?: string;\n\t/**\n\t * File extensions scanned. Defaults to `[\".ts\", \".tsx\"]`.\n\t */\n\textensions?: string[];\n\t/**\n\t * Extra directory names skipped during the walk. Appended to the built-in\n\t * defaults (`node_modules`, `dist`, `.git`, `.next`, `.output`,\n\t * `.svelte-kit`, `.cache`).\n\t */\n\tignore?: string[];\n};\n\n/**\n * Marker returned from `resolveId` so `load` can recognise a hit. The leading\n * NUL prefix is the Rollup/Vite convention for virtual IDs so other plugins\n * leave it alone.\n */\nconst RESOLVED_VIRTUAL_ID = \"\\0virtual:openpolicy/auto-collected\";\n\n/**\n * Matches any path that lives inside the `@openpolicy/sdk` package, whether\n * it's resolved via a workspace symlink (`.../packages/sdk/...`) or a\n * published `node_modules` install (`.../@openpolicy/sdk/...`). Used to scope\n * the `./auto-collected` relative-import interception to the SDK itself.\n */\nconst SDK_PATH_PATTERN = /[\\\\/](?:@openpolicy[\\\\/]sdk|packages[\\\\/]sdk)[\\\\/]/;\n\n/**\n * Matches the relative specifier the SDK uses for its own internal\n * `./auto-collected` import. Both the source form (`./auto-collected`) and\n * the published dist form (`./auto-collected.js`) need to be intercepted:\n * the former applies when consumers resolve the SDK via its workspace\n * source, the latter when resolving against `dist/` with the separate\n * `auto-collected.js` chunk.\n */\nconst AUTO_COLLECTED_SPECIFIER = /^\\.\\/auto-collected(?:\\.js)?$/;\n\n/**\n * Vite plugin that scans source files for `@openpolicy/sdk` `collecting()`\n * calls at the start of each build and inlines the discovered categories into\n * the SDK's `dataCollected` sentinel.\n *\n * Internally the plugin intercepts `@openpolicy/sdk`'s own relative import of\n * `./auto-collected` and redirects it to a virtual module whose body is a\n * literal `export const dataCollected = { ... }`. Because the replacement\n * becomes part of the consumer's own module graph, the scanned data survives\n * any downstream bundler boundary (e.g. nitro's SSR output), which a shared\n * module-level registry would not.\n */\nexport function autoCollect(options: AutoCollectOptions = {}): Plugin {\n\tconst srcDirOpt = options.srcDir ?? \"src\";\n\tconst extensions = options.extensions ?? [\".ts\", \".tsx\"];\n\tconst ignore = options.ignore ?? [];\n\tlet resolvedSrcDir: string;\n\tlet scanned: Record<string, string[]> = {};\n\n\tasync function scanAndMerge(): Promise<Record<string, string[]>> {\n\t\tconst files = await walkSources(resolvedSrcDir, extensions, ignore);\n\t\tconst merged: Record<string, string[]> = {};\n\t\tfor (const file of files) {\n\t\t\tlet code: string;\n\t\t\ttry {\n\t\t\t\tcode = await readFile(file, \"utf8\");\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst extracted = extractCollecting(file, code);\n\t\t\tfor (const [category, labels] of Object.entries(extracted)) {\n\t\t\t\tconst existing = merged[category] ?? [];\n\t\t\t\tconst seen = new Set(existing);\n\t\t\t\tfor (const label of labels) {\n\t\t\t\t\tif (!seen.has(label)) {\n\t\t\t\t\t\texisting.push(label);\n\t\t\t\t\t\tseen.add(label);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmerged[category] = existing;\n\t\t\t}\n\t\t}\n\t\treturn merged;\n\t}\n\n\t/**\n\t * Returns true when `file` lives inside `resolvedSrcDir` and has one of\n\t * the tracked extensions. Used by the dev-server watcher to skip events\n\t * for unrelated files (configs, public assets, other packages, etc.).\n\t */\n\tfunction isTrackedSource(file: string): boolean {\n\t\tconst rel = relative(resolvedSrcDir, file);\n\t\tif (!rel || rel.startsWith(\"..\")) return false;\n\t\treturn extensions.some((ext) => file.endsWith(ext));\n\t}\n\n\t/**\n\t * Re-runs the scan and, if anything changed, invalidates the virtual\n\t * module and triggers a full page reload. A full reload is used because\n\t * `dataCollected` is spread into the policy config at module-evaluation\n\t * time and the result is captured by the React tree as a prop — there's\n\t * no clean way to hot-swap it in place.\n\t */\n\tasync function rescanAndRefresh(server: ViteDevServer): Promise<void> {\n\t\tconst next = await scanAndMerge();\n\t\tif (JSON.stringify(next) === JSON.stringify(scanned)) return;\n\t\tscanned = next;\n\t\tconst mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ID);\n\t\tif (mod) server.moduleGraph.invalidateModule(mod);\n\t\tserver.ws.send({ type: \"full-reload\" });\n\t}\n\n\treturn {\n\t\tname: \"openpolicy-auto-collect\",\n\t\tenforce: \"pre\",\n\t\tconfigResolved(config) {\n\t\t\tresolvedSrcDir = resolve(config.root, srcDirOpt);\n\t\t},\n\t\tasync buildStart() {\n\t\t\tscanned = await scanAndMerge();\n\t\t},\n\t\tconfigureServer(server) {\n\t\t\t// Make sure chokidar watches the whole src tree, not just files\n\t\t\t// already in the module graph. Without this, creating a brand-new\n\t\t\t// source file that nothing imports yet wouldn't fire a watcher\n\t\t\t// event — the very case we most need to re-scan on.\n\t\t\tserver.watcher.add(resolvedSrcDir);\n\n\t\t\tconst handler = async (file: string): Promise<void> => {\n\t\t\t\tif (!isTrackedSource(file)) return;\n\t\t\t\t// Surface errors via the logger but don't rethrow — an\n\t\t\t\t// unhandled rejection would crash the watcher process.\n\t\t\t\ttry {\n\t\t\t\t\tawait rescanAndRefresh(server);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tserver.config.logger.error(\n\t\t\t\t\t\t`[openpolicy-auto-collect] rescan failed: ${error}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tserver.watcher.on(\"change\", handler);\n\t\t\tserver.watcher.on(\"add\", handler);\n\t\t\tserver.watcher.on(\"unlink\", handler);\n\t\t},\n\t\tasync resolveId(source, importer, resolveOptions) {\n\t\t\tif (!importer || !AUTO_COLLECTED_SPECIFIER.test(source)) return null;\n\t\t\t// Defer to Vite's resolver first so we only redirect when the\n\t\t\t// relative specifier actually points inside @openpolicy/sdk.\n\t\t\tconst resolved = await this.resolve(source, importer, {\n\t\t\t\t...resolveOptions,\n\t\t\t\tskipSelf: true,\n\t\t\t});\n\t\t\tif (!resolved) return null;\n\t\t\tif (!SDK_PATH_PATTERN.test(resolved.id)) return null;\n\t\t\treturn RESOLVED_VIRTUAL_ID;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== RESOLVED_VIRTUAL_ID) return null;\n\t\t\treturn `export const dataCollected = ${JSON.stringify(scanned)};\\n`;\n\t\t},\n\t};\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":[],"sources":["../src/analyse.ts","../src/known-packages.ts","../src/scan.ts","../src/index.ts"],"sourcesContent":["import { parseSync } from \"oxc-parser\";\n\nconst SDK_SPECIFIER = \"@openpolicy/sdk\";\nconst COLLECTING_NAME = \"collecting\";\nconst THIRD_PARTY_NAME = \"thirdParty\";\n\ntype AnyNode = { type: string; [key: string]: unknown };\n\nexport type ThirdPartyEntry = {\n\tname: string;\n\tpurpose: string;\n\tpolicyUrl: string;\n};\n\nexport type ExtractResult = {\n\tdataCollected: Record<string, string[]>;\n\tthirdParties: ThirdPartyEntry[];\n};\n\n/**\n * Extract `collecting()` and `thirdParty()` call metadata from a single source file.\n *\n * Returns an `ExtractResult` with `dataCollected` (category → labels) and\n * `thirdParties` (array of third-party entries). Files with no matching calls\n * — or that fail to parse — return empty defaults.\n *\n * The analyser runs in two phases:\n * 1. Collect local names bound to `collecting` / `thirdParty` imported from\n * `@openpolicy/sdk` (handles renamed imports, skips type-only imports,\n * ignores look-alikes imported from other modules).\n * 2. Walk the program body and inspect every `CallExpression` whose callee\n * is one of those tracked local names.\n */\nexport function extractFromFile(filename: string, code: string): ExtractResult {\n\tconst empty: ExtractResult = { dataCollected: {}, thirdParties: [] };\n\tlet result: ReturnType<typeof parseSync>;\n\ttry {\n\t\tresult = parseSync(filename, code);\n\t} catch {\n\t\tconsole.warn(`[openpolicy-auto-collect] parse error in ${filename}`);\n\t\treturn empty;\n\t}\n\n\tif (result.errors.length > 0) {\n\t\t// Hard parse failures only — oxc reports recoverable errors but still\n\t\t// produces a usable AST, so we keep going and let the walker decide.\n\t\tconst fatal = result.errors.some((e) => e.severity === (\"Error\" as never));\n\t\tif (fatal) {\n\t\t\tconsole.warn(`[openpolicy-auto-collect] parse error in ${filename}`);\n\t\t\treturn empty;\n\t\t}\n\t}\n\n\tconst program = result.program as unknown as AnyNode;\n\tconst collectingNames = collectSdkBindings(program, COLLECTING_NAME);\n\tconst thirdPartyNames = collectSdkBindings(program, THIRD_PARTY_NAME);\n\tif (collectingNames.size === 0 && thirdPartyNames.size === 0) return empty;\n\n\tconst dataCollected: Record<string, string[]> = {};\n\tconst thirdParties: ThirdPartyEntry[] = [];\n\tconst seenThirdParties = new Set<string>();\n\n\twalk(program, (node) => {\n\t\tif (node.type !== \"CallExpression\") return;\n\t\tconst callee = node.callee as AnyNode | undefined;\n\t\tif (!callee || callee.type !== \"Identifier\") return;\n\t\tconst calleeName = callee.name as string;\n\t\tconst args = node.arguments as AnyNode[] | undefined;\n\n\t\tif (collectingNames.has(calleeName)) {\n\t\t\tif (!args || args.length < 3) return;\n\t\t\tconst category = extractStringLiteral(args[0]);\n\t\t\tif (category === null) return;\n\t\t\tconst labels = extractLabelKeys(args[2]);\n\t\t\tif (labels === null) return;\n\t\t\tconst existing = dataCollected[category] ?? [];\n\t\t\tconst seen = new Set(existing);\n\t\t\tfor (const label of labels) {\n\t\t\t\tif (!seen.has(label)) {\n\t\t\t\t\texisting.push(label);\n\t\t\t\t\tseen.add(label);\n\t\t\t\t}\n\t\t\t}\n\t\t\tdataCollected[category] = existing;\n\t\t} else if (thirdPartyNames.has(calleeName)) {\n\t\t\tif (!args || args.length < 3) return;\n\t\t\tconst name = extractStringLiteral(args[0]);\n\t\t\tif (name === null) return;\n\t\t\tconst purpose = extractStringLiteral(args[1]);\n\t\t\tif (purpose === null) return;\n\t\t\tconst policyUrl = extractStringLiteral(args[2]);\n\t\t\tif (policyUrl === null) return;\n\t\t\t// Deduplicate by name — first occurrence wins (files walked in sorted order)\n\t\t\tif (seenThirdParties.has(name)) return;\n\t\t\tseenThirdParties.add(name);\n\t\t\tthirdParties.push({ name, purpose, policyUrl });\n\t\t}\n\t});\n\n\treturn { dataCollected, thirdParties };\n}\n\n/**\n * Walk `ImportDeclaration` nodes and return the local names bound to the given\n * `exportName` imported from `@openpolicy/sdk`. Skips type-only imports and\n * specifiers whose imported name doesn't match.\n */\nfunction collectSdkBindings(program: AnyNode, exportName: string): Set<string> {\n\tconst names = new Set<string>();\n\tconst body = program.body as AnyNode[] | undefined;\n\tif (!body) return names;\n\tfor (const node of body) {\n\t\tif (node.type !== \"ImportDeclaration\") continue;\n\t\tif ((node.importKind as string | undefined) === \"type\") continue;\n\t\tconst source = node.source as AnyNode | undefined;\n\t\tif (!source || source.value !== SDK_SPECIFIER) continue;\n\t\tconst specifiers = node.specifiers as AnyNode[] | undefined;\n\t\tif (!specifiers) continue;\n\t\tfor (const spec of specifiers) {\n\t\t\tif (spec.type !== \"ImportSpecifier\") continue;\n\t\t\tif ((spec.importKind as string | undefined) === \"type\") continue;\n\t\t\tconst imported = spec.imported as AnyNode | undefined;\n\t\t\tif (!imported) continue;\n\t\t\t// ESTree allows either an Identifier (name) or a string Literal (value)\n\t\t\t// for the imported binding (e.g. `import { \"foo\" as bar }`).\n\t\t\tconst importedName =\n\t\t\t\timported.type === \"Identifier\"\n\t\t\t\t\t? (imported.name as string | undefined)\n\t\t\t\t\t: imported.type === \"Literal\"\n\t\t\t\t\t\t? typeof imported.value === \"string\"\n\t\t\t\t\t\t\t? imported.value\n\t\t\t\t\t\t\t: undefined\n\t\t\t\t\t\t: undefined;\n\t\t\tif (importedName !== exportName) continue;\n\t\t\tconst local = spec.local as AnyNode | undefined;\n\t\t\tif (!local || local.type !== \"Identifier\") continue;\n\t\t\tnames.add(local.name as string);\n\t\t}\n\t}\n\treturn names;\n}\n\n/**\n * If `node` is a string `Literal`, return its string value. Otherwise\n * return `null` so the caller silently skips the call.\n */\nfunction extractStringLiteral(node: AnyNode | undefined): string | null {\n\tif (!node) return null;\n\tif (node.type !== \"Literal\") return null;\n\tif (typeof node.value !== \"string\") return null;\n\treturn node.value;\n}\n\n/**\n * Extract the string values from a plain `{ fieldName: \"Human Label\" }`\n * object literal. Returns an array of label strings, deduped while\n * preserving insertion order. Returns `null` if the shape doesn't match.\n */\nfunction extractLabelKeys(node: AnyNode | undefined): string[] | null {\n\tif (!node || node.type !== \"ObjectExpression\") return null;\n\tconst properties = node.properties as AnyNode[] | undefined;\n\tif (!properties) return null;\n\n\tconst labels: string[] = [];\n\tconst seen = new Set<string>();\n\tfor (const prop of properties) {\n\t\tif (prop.type !== \"Property\") continue; // drop SpreadElement silently\n\t\tconst val = prop.value as AnyNode | undefined;\n\t\tif (!val || val.type !== \"Literal\" || typeof val.value !== \"string\")\n\t\t\tcontinue;\n\t\tif (seen.has(val.value)) continue;\n\t\tseen.add(val.value);\n\t\tlabels.push(val.value);\n\t}\n\treturn labels;\n}\n\n/**\n * Recursive AST walker. Visits every `AnyNode` (depth-first) reachable\n * through array / nested-object children and invokes `visit` on each.\n */\nfunction walk(node: AnyNode, visit: (node: AnyNode) => void): void {\n\tvisit(node);\n\tfor (const key of Object.keys(node)) {\n\t\tif (key === \"parent\") continue;\n\t\tconst value = node[key];\n\t\tif (Array.isArray(value)) {\n\t\t\tfor (const item of value) {\n\t\t\t\tif (item && typeof item === \"object\" && typeof item.type === \"string\") {\n\t\t\t\t\twalk(item as AnyNode, visit);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (\n\t\t\tvalue &&\n\t\t\ttypeof value === \"object\" &&\n\t\t\ttypeof (value as { type?: unknown }).type === \"string\"\n\t\t) {\n\t\t\twalk(value as AnyNode, visit);\n\t\t}\n\t}\n}\n","import type { ThirdPartyEntry } from \"./analyse\";\n\n/**\n * Registry of known npm packages mapped to their ThirdPartyEntry metadata.\n * Multiple package names can point to the same service — deduplication by\n * `ThirdPartyEntry.name` is handled at merge time in the caller.\n */\nexport const KNOWN_PACKAGES: ReadonlyMap<string, ThirdPartyEntry> = new Map([\n\t[\n\t\t\"stripe\",\n\t\t{\n\t\t\tname: \"Stripe\",\n\t\t\tpurpose: \"Payment processing\",\n\t\t\tpolicyUrl: \"https://stripe.com/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"@stripe/stripe-js\",\n\t\t{\n\t\t\tname: \"Stripe\",\n\t\t\tpurpose: \"Payment processing\",\n\t\t\tpolicyUrl: \"https://stripe.com/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"braintree\",\n\t\t{\n\t\t\tname: \"Braintree\",\n\t\t\tpurpose: \"Payment processing\",\n\t\t\tpolicyUrl:\n\t\t\t\t\"https://www.braintreepayments.com/legal/braintree-privacy-policy\",\n\t\t},\n\t],\n\t[\n\t\t\"@braintree/browser-drop-in\",\n\t\t{\n\t\t\tname: \"Braintree\",\n\t\t\tpurpose: \"Payment processing\",\n\t\t\tpolicyUrl:\n\t\t\t\t\"https://www.braintreepayments.com/legal/braintree-privacy-policy\",\n\t\t},\n\t],\n\t[\n\t\t\"@sentry/browser\",\n\t\t{\n\t\t\tname: \"Sentry\",\n\t\t\tpurpose: \"Error tracking\",\n\t\t\tpolicyUrl: \"https://sentry.io/privacy/\",\n\t\t},\n\t],\n\t[\n\t\t\"@sentry/node\",\n\t\t{\n\t\t\tname: \"Sentry\",\n\t\t\tpurpose: \"Error tracking\",\n\t\t\tpolicyUrl: \"https://sentry.io/privacy/\",\n\t\t},\n\t],\n\t[\n\t\t\"@sentry/nextjs\",\n\t\t{\n\t\t\tname: \"Sentry\",\n\t\t\tpurpose: \"Error tracking\",\n\t\t\tpolicyUrl: \"https://sentry.io/privacy/\",\n\t\t},\n\t],\n\t[\n\t\t\"@sentry/react\",\n\t\t{\n\t\t\tname: \"Sentry\",\n\t\t\tpurpose: \"Error tracking\",\n\t\t\tpolicyUrl: \"https://sentry.io/privacy/\",\n\t\t},\n\t],\n\t[\n\t\t\"@sentry/vue\",\n\t\t{\n\t\t\tname: \"Sentry\",\n\t\t\tpurpose: \"Error tracking\",\n\t\t\tpolicyUrl: \"https://sentry.io/privacy/\",\n\t\t},\n\t],\n\t[\n\t\t\"@datadog/browser-rum\",\n\t\t{\n\t\t\tname: \"Datadog\",\n\t\t\tpurpose: \"Monitoring\",\n\t\t\tpolicyUrl: \"https://www.datadoghq.com/legal/privacy/\",\n\t\t},\n\t],\n\t[\n\t\t\"dd-trace\",\n\t\t{\n\t\t\tname: \"Datadog\",\n\t\t\tpurpose: \"Monitoring\",\n\t\t\tpolicyUrl: \"https://www.datadoghq.com/legal/privacy/\",\n\t\t},\n\t],\n\t[\n\t\t\"posthog-js\",\n\t\t{\n\t\t\tname: \"PostHog\",\n\t\t\tpurpose: \"Product analytics\",\n\t\t\tpolicyUrl: \"https://posthog.com/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"posthog-node\",\n\t\t{\n\t\t\tname: \"PostHog\",\n\t\t\tpurpose: \"Product analytics\",\n\t\t\tpolicyUrl: \"https://posthog.com/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"mixpanel-browser\",\n\t\t{\n\t\t\tname: \"Mixpanel\",\n\t\t\tpurpose: \"Product analytics\",\n\t\t\tpolicyUrl: \"https://mixpanel.com/legal/privacy-policy/\",\n\t\t},\n\t],\n\t[\n\t\t\"@segment/analytics-next\",\n\t\t{\n\t\t\tname: \"Segment\",\n\t\t\tpurpose: \"Customer data platform\",\n\t\t\tpolicyUrl: \"https://www.twilio.com/en-us/legal/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"@amplitude/analytics-browser\",\n\t\t{\n\t\t\tname: \"Amplitude\",\n\t\t\tpurpose: \"Product analytics\",\n\t\t\tpolicyUrl: \"https://amplitude.com/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"amplitude-js\",\n\t\t{\n\t\t\tname: \"Amplitude\",\n\t\t\tpurpose: \"Product analytics\",\n\t\t\tpolicyUrl: \"https://amplitude.com/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"@vercel/analytics\",\n\t\t{\n\t\t\tname: \"Vercel Analytics\",\n\t\t\tpurpose: \"Web analytics\",\n\t\t\tpolicyUrl: \"https://vercel.com/legal/privacy-policy\",\n\t\t},\n\t],\n\t[\n\t\t\"plausible-tracker\",\n\t\t{\n\t\t\tname: \"Plausible\",\n\t\t\tpurpose: \"Web analytics\",\n\t\t\tpolicyUrl: \"https://plausible.io/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"logrocket\",\n\t\t{\n\t\t\tname: \"LogRocket\",\n\t\t\tpurpose: \"Session recording\",\n\t\t\tpolicyUrl: \"https://logrocket.com/privacy/\",\n\t\t},\n\t],\n\t[\n\t\t\"@hotjar/browser\",\n\t\t{\n\t\t\tname: \"Hotjar\",\n\t\t\tpurpose: \"Session recording\",\n\t\t\tpolicyUrl: \"https://www.hotjar.com/legal/policies/privacy/\",\n\t\t},\n\t],\n\t[\n\t\t\"resend\",\n\t\t{\n\t\t\tname: \"Resend\",\n\t\t\tpurpose: \"Transactional email\",\n\t\t\tpolicyUrl: \"https://resend.com/legal/privacy-policy\",\n\t\t},\n\t],\n\t[\n\t\t\"@sendgrid/mail\",\n\t\t{\n\t\t\tname: \"SendGrid\",\n\t\t\tpurpose: \"Transactional email\",\n\t\t\tpolicyUrl: \"https://www.twilio.com/en-us/legal/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"intercom-client\",\n\t\t{\n\t\t\tname: \"Intercom\",\n\t\t\tpurpose: \"Customer messaging\",\n\t\t\tpolicyUrl: \"https://www.intercom.com/legal/privacy\",\n\t\t},\n\t],\n\t[\n\t\t\"@intercom/messenger-js-sdk\",\n\t\t{\n\t\t\tname: \"Intercom\",\n\t\t\tpurpose: \"Customer messaging\",\n\t\t\tpolicyUrl: \"https://www.intercom.com/legal/privacy\",\n\t\t},\n\t],\n]);\n","import type { Dirent } from \"node:fs\";\nimport { readdir } from \"node:fs/promises\";\nimport { extname, join } from \"node:path\";\n\nconst DEFAULT_IGNORES: ReadonlySet<string> = new Set([\n\t\"node_modules\",\n\t\"dist\",\n\t\".git\",\n\t\".next\",\n\t\".output\",\n\t\".svelte-kit\",\n\t\".cache\",\n]);\n\n/**\n * Recursively walks `root`, returning absolute paths of every regular file\n * whose extension is in `extensions`. Directories whose basename appears in\n * the built-in ignore list (or the extra `ignore` argument) are skipped\n * entirely.\n *\n * Missing roots resolve to an empty array — the plugin must not throw if the\n * user's `srcDir` hasn't been created yet.\n */\nexport async function walkSources(\n\troot: string,\n\textensions: readonly string[],\n\tignore: readonly string[] = [],\n): Promise<string[]> {\n\tconst ignored = new Set<string>([...DEFAULT_IGNORES, ...ignore]);\n\tconst exts = new Set(extensions);\n\tconst results: string[] = [];\n\n\tasync function walk(dir: string): Promise<void> {\n\t\tlet entries: Dirent[];\n\t\ttry {\n\t\t\tentries = (await readdir(dir, { withFileTypes: true })) as Dirent[];\n\t\t} catch (err) {\n\t\t\tconst code = (err as NodeJS.ErrnoException).code;\n\t\t\tif (code === \"ENOENT\" || code === \"ENOTDIR\") return;\n\t\t\tthrow err;\n\t\t}\n\t\tfor (const entry of entries) {\n\t\t\tif (ignored.has(entry.name)) continue;\n\t\t\tconst full = join(dir, entry.name);\n\t\t\tif (entry.isDirectory()) {\n\t\t\t\tawait walk(full);\n\t\t\t} else if (entry.isFile() && exts.has(extname(entry.name))) {\n\t\t\t\tresults.push(full);\n\t\t\t}\n\t\t}\n\t}\n\n\tawait walk(root);\n\tresults.sort();\n\treturn results;\n}\n","import { readFile } from \"node:fs/promises\";\nimport { relative, resolve } from \"node:path\";\nimport type { Plugin, ViteDevServer } from \"vite\";\nimport { extractFromFile, type ThirdPartyEntry } from \"./analyse\";\nimport { KNOWN_PACKAGES } from \"./known-packages\";\nimport { walkSources } from \"./scan\";\n\nexport type AutoCollectOptions = {\n\t/**\n\t * Directory walked for `collecting()` calls. Resolved relative to the\n\t * Vite project root. Defaults to `\"src\"`.\n\t */\n\tsrcDir?: string;\n\t/**\n\t * File extensions scanned. Defaults to `[\".ts\", \".tsx\"]`.\n\t */\n\textensions?: string[];\n\t/**\n\t * Extra directory names skipped during the walk. Appended to the built-in\n\t * defaults (`node_modules`, `dist`, `.git`, `.next`, `.output`,\n\t * `.svelte-kit`, `.cache`).\n\t */\n\tignore?: string[];\n\n\tthirdParties?: {\n\t\tusePackageJson?: boolean;\n\t};\n};\n\n/**\n * Marker returned from `resolveId` so `load` can recognise a hit. The leading\n * NUL prefix is the Rollup/Vite convention for virtual IDs so other plugins\n * leave it alone.\n */\nconst RESOLVED_VIRTUAL_ID = \"\\0virtual:openpolicy/auto-collected\";\n\n/**\n * Matches any path that lives inside the `@openpolicy/sdk` package, whether\n * it's resolved via a workspace symlink (`.../packages/sdk/...`) or a\n * published `node_modules` install (`.../@openpolicy/sdk/...`). Used to scope\n * the `./auto-collected` relative-import interception to the SDK itself.\n */\nconst SDK_PATH_PATTERN = /[\\\\/](?:@openpolicy[\\\\/]sdk|packages[\\\\/]sdk)[\\\\/]/;\n\n/**\n * Matches the relative specifier the SDK uses for its own internal\n * `./auto-collected` import. Both the source form (`./auto-collected`) and\n * the published dist form (`./auto-collected.js`) need to be intercepted:\n * the former applies when consumers resolve the SDK via its workspace\n * source, the latter when resolving against `dist/` with the separate\n * `auto-collected.js` chunk.\n */\nconst AUTO_COLLECTED_SPECIFIER = /^\\.\\/auto-collected(?:\\.js)?$/;\n\n/**\n * Vite plugin that scans source files for `@openpolicy/sdk` `collecting()`\n * calls at the start of each build and inlines the discovered categories into\n * the SDK's `dataCollected` sentinel.\n *\n * Internally the plugin intercepts `@openpolicy/sdk`'s own relative import of\n * `./auto-collected` and redirects it to a virtual module whose body is a\n * literal `export const dataCollected = { ... }`. Because the replacement\n * becomes part of the consumer's own module graph, the scanned data survives\n * any downstream bundler boundary (e.g. nitro's SSR output), which a shared\n * module-level registry would not.\n */\nexport function autoCollect(options: AutoCollectOptions = {}): Plugin {\n\tconst srcDirOpt = options.srcDir ?? \"src\";\n\tconst extensions = options.extensions ?? [\".ts\", \".tsx\"];\n\tconst ignore = options.ignore ?? [];\n\tconst usePackageJsonOpt = options.thirdParties?.usePackageJson ?? false;\n\tlet resolvedRoot: string;\n\tlet resolvedSrcDir: string;\n\tlet scanned: {\n\t\tdataCollected: Record<string, string[]>;\n\t\tthirdParties: ThirdPartyEntry[];\n\t} = { dataCollected: {}, thirdParties: [] };\n\n\tasync function detectFromPackageJson(\n\t\troot: string,\n\t): Promise<ThirdPartyEntry[]> {\n\t\tlet raw: string;\n\t\ttry {\n\t\t\traw = await readFile(resolve(root, \"package.json\"), \"utf8\");\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t\tlet pkg: {\n\t\t\tdependencies?: Record<string, string>;\n\t\t\tdevDependencies?: Record<string, string>;\n\t\t};\n\t\ttry {\n\t\t\tpkg = JSON.parse(raw) as typeof pkg;\n\t\t} catch {\n\t\t\treturn [];\n\t\t}\n\t\tconst allDeps = {\n\t\t\t...pkg.dependencies,\n\t\t\t...pkg.devDependencies,\n\t\t};\n\t\tconst entries: ThirdPartyEntry[] = [];\n\t\tconst seenNames = new Set<string>();\n\t\tfor (const pkgName of Object.keys(allDeps)) {\n\t\t\tconst entry = KNOWN_PACKAGES.get(pkgName);\n\t\t\tif (entry && !seenNames.has(entry.name)) {\n\t\t\t\tseenNames.add(entry.name);\n\t\t\t\tentries.push(entry);\n\t\t\t}\n\t\t}\n\t\treturn entries;\n\t}\n\n\tasync function scanAndMerge(): Promise<typeof scanned> {\n\t\tconst files = await walkSources(resolvedSrcDir, extensions, ignore);\n\t\tconst mergedData: Record<string, string[]> = {};\n\t\tconst mergedParties: ThirdPartyEntry[] = [];\n\t\tconst seenParties = new Set<string>();\n\t\tfor (const file of files) {\n\t\t\tlet code: string;\n\t\t\ttry {\n\t\t\t\tcode = await readFile(file, \"utf8\");\n\t\t\t} catch {\n\t\t\t\tcontinue;\n\t\t\t}\n\t\t\tconst extracted = extractFromFile(file, code);\n\t\t\tfor (const [category, labels] of Object.entries(\n\t\t\t\textracted.dataCollected,\n\t\t\t)) {\n\t\t\t\tconst existing = mergedData[category] ?? [];\n\t\t\t\tconst seen = new Set(existing);\n\t\t\t\tfor (const label of labels) {\n\t\t\t\t\tif (!seen.has(label)) {\n\t\t\t\t\t\texisting.push(label);\n\t\t\t\t\t\tseen.add(label);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tmergedData[category] = existing;\n\t\t\t}\n\t\t\tfor (const entry of extracted.thirdParties) {\n\t\t\t\tif (!seenParties.has(entry.name)) {\n\t\t\t\t\tseenParties.add(entry.name);\n\t\t\t\t\tmergedParties.push(entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tif (usePackageJsonOpt) {\n\t\t\tconst pkgEntries = await detectFromPackageJson(resolvedRoot);\n\t\t\tfor (const entry of pkgEntries) {\n\t\t\t\tif (!seenParties.has(entry.name)) {\n\t\t\t\t\tseenParties.add(entry.name);\n\t\t\t\t\tmergedParties.push(entry);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\treturn { dataCollected: mergedData, thirdParties: mergedParties };\n\t}\n\n\t/**\n\t * Returns true when `file` lives inside `resolvedSrcDir` and has one of\n\t * the tracked extensions. Used by the dev-server watcher to skip events\n\t * for unrelated files (configs, public assets, other packages, etc.).\n\t */\n\tfunction isTrackedSource(file: string): boolean {\n\t\tconst rel = relative(resolvedSrcDir, file);\n\t\tif (!rel || rel.startsWith(\"..\")) return false;\n\t\treturn extensions.some((ext) => file.endsWith(ext));\n\t}\n\n\t/**\n\t * Re-runs the scan and, if anything changed, invalidates the virtual\n\t * module and triggers a full page reload. A full reload is used because\n\t * `dataCollected` is spread into the policy config at module-evaluation\n\t * time and the result is captured by the React tree as a prop — there's\n\t * no clean way to hot-swap it in place.\n\t */\n\tasync function rescanAndRefresh(server: ViteDevServer): Promise<void> {\n\t\tconst next = await scanAndMerge();\n\t\tif (JSON.stringify(next) === JSON.stringify(scanned)) return;\n\t\tscanned = next;\n\t\tconst mod = server.moduleGraph.getModuleById(RESOLVED_VIRTUAL_ID);\n\t\tif (mod) server.moduleGraph.invalidateModule(mod);\n\t\tserver.ws.send({ type: \"full-reload\" });\n\t}\n\n\treturn {\n\t\tname: \"openpolicy-auto-collect\",\n\t\tenforce: \"pre\",\n\t\tconfigResolved(config) {\n\t\t\tresolvedRoot = config.root;\n\t\t\tresolvedSrcDir = resolve(config.root, srcDirOpt);\n\t\t},\n\t\tasync buildStart() {\n\t\t\tscanned = await scanAndMerge();\n\t\t},\n\t\tconfigureServer(server) {\n\t\t\t// Make sure chokidar watches the whole src tree, not just files\n\t\t\t// already in the module graph. Without this, creating a brand-new\n\t\t\t// source file that nothing imports yet wouldn't fire a watcher\n\t\t\t// event — the very case we most need to re-scan on.\n\t\t\tserver.watcher.add(resolvedSrcDir);\n\n\t\t\tconst handler = async (file: string): Promise<void> => {\n\t\t\t\tif (!isTrackedSource(file)) return;\n\t\t\t\t// Surface errors via the logger but don't rethrow — an\n\t\t\t\t// unhandled rejection would crash the watcher process.\n\t\t\t\ttry {\n\t\t\t\t\tawait rescanAndRefresh(server);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tserver.config.logger.error(\n\t\t\t\t\t\t`[openpolicy-auto-collect] rescan failed: ${error}`,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t};\n\n\t\t\tserver.watcher.on(\"change\", handler);\n\t\t\tserver.watcher.on(\"add\", handler);\n\t\t\tserver.watcher.on(\"unlink\", handler);\n\t\t},\n\t\tasync resolveId(source, importer, resolveOptions) {\n\t\t\tif (!importer || !AUTO_COLLECTED_SPECIFIER.test(source)) return null;\n\t\t\t// Defer to Vite's resolver first so we only redirect when the\n\t\t\t// relative specifier actually points inside @openpolicy/sdk.\n\t\t\tconst resolved = await this.resolve(source, importer, {\n\t\t\t\t...resolveOptions,\n\t\t\t\tskipSelf: true,\n\t\t\t});\n\t\t\tif (!resolved) return null;\n\t\t\tif (!SDK_PATH_PATTERN.test(resolved.id)) return null;\n\t\t\treturn RESOLVED_VIRTUAL_ID;\n\t\t},\n\t\tload(id) {\n\t\t\tif (id !== RESOLVED_VIRTUAL_ID) return null;\n\t\t\treturn (\n\t\t\t\t`export const dataCollected = ${JSON.stringify(\n\t\t\t\t\tscanned.dataCollected,\n\t\t\t\t)};\\n` +\n\t\t\t\t`export const thirdParties = ${JSON.stringify(scanned.thirdParties)};\\n`\n\t\t\t);\n\t\t},\n\t};\n}\n"],"mappings":";;;;AAEA,MAAM,gBAAgB;AACtB,MAAM,kBAAkB;AACxB,MAAM,mBAAmB;;;;;;;;;;;;;;;AA6BzB,SAAgB,gBAAgB,UAAkB,MAA6B;CAC9E,MAAM,QAAuB;EAAE,eAAe,EAAE;EAAE,cAAc,EAAE;EAAE;CACpE,IAAI;AACJ,KAAI;AACH,WAAS,UAAU,UAAU,KAAK;SAC3B;AACP,UAAQ,KAAK,4CAA4C,WAAW;AACpE,SAAO;;AAGR,KAAI,OAAO,OAAO,SAAS;MAGZ,OAAO,OAAO,MAAM,MAAM,EAAE,aAAc,QAAkB,EAC/D;AACV,WAAQ,KAAK,4CAA4C,WAAW;AACpE,UAAO;;;CAIT,MAAM,UAAU,OAAO;CACvB,MAAM,kBAAkB,mBAAmB,SAAS,gBAAgB;CACpE,MAAM,kBAAkB,mBAAmB,SAAS,iBAAiB;AACrE,KAAI,gBAAgB,SAAS,KAAK,gBAAgB,SAAS,EAAG,QAAO;CAErE,MAAM,gBAA0C,EAAE;CAClD,MAAM,eAAkC,EAAE;CAC1C,MAAM,mCAAmB,IAAI,KAAa;AAE1C,MAAK,UAAU,SAAS;AACvB,MAAI,KAAK,SAAS,iBAAkB;EACpC,MAAM,SAAS,KAAK;AACpB,MAAI,CAAC,UAAU,OAAO,SAAS,aAAc;EAC7C,MAAM,aAAa,OAAO;EAC1B,MAAM,OAAO,KAAK;AAElB,MAAI,gBAAgB,IAAI,WAAW,EAAE;AACpC,OAAI,CAAC,QAAQ,KAAK,SAAS,EAAG;GAC9B,MAAM,WAAW,qBAAqB,KAAK,GAAG;AAC9C,OAAI,aAAa,KAAM;GACvB,MAAM,SAAS,iBAAiB,KAAK,GAAG;AACxC,OAAI,WAAW,KAAM;GACrB,MAAM,WAAW,cAAc,aAAa,EAAE;GAC9C,MAAM,OAAO,IAAI,IAAI,SAAS;AAC9B,QAAK,MAAM,SAAS,OACnB,KAAI,CAAC,KAAK,IAAI,MAAM,EAAE;AACrB,aAAS,KAAK,MAAM;AACpB,SAAK,IAAI,MAAM;;AAGjB,iBAAc,YAAY;aAChB,gBAAgB,IAAI,WAAW,EAAE;AAC3C,OAAI,CAAC,QAAQ,KAAK,SAAS,EAAG;GAC9B,MAAM,OAAO,qBAAqB,KAAK,GAAG;AAC1C,OAAI,SAAS,KAAM;GACnB,MAAM,UAAU,qBAAqB,KAAK,GAAG;AAC7C,OAAI,YAAY,KAAM;GACtB,MAAM,YAAY,qBAAqB,KAAK,GAAG;AAC/C,OAAI,cAAc,KAAM;AAExB,OAAI,iBAAiB,IAAI,KAAK,CAAE;AAChC,oBAAiB,IAAI,KAAK;AAC1B,gBAAa,KAAK;IAAE;IAAM;IAAS;IAAW,CAAC;;GAE/C;AAEF,QAAO;EAAE;EAAe;EAAc;;;;;;;AAQvC,SAAS,mBAAmB,SAAkB,YAAiC;CAC9E,MAAM,wBAAQ,IAAI,KAAa;CAC/B,MAAM,OAAO,QAAQ;AACrB,KAAI,CAAC,KAAM,QAAO;AAClB,MAAK,MAAM,QAAQ,MAAM;AACxB,MAAI,KAAK,SAAS,oBAAqB;AACvC,MAAK,KAAK,eAAsC,OAAQ;EACxD,MAAM,SAAS,KAAK;AACpB,MAAI,CAAC,UAAU,OAAO,UAAU,cAAe;EAC/C,MAAM,aAAa,KAAK;AACxB,MAAI,CAAC,WAAY;AACjB,OAAK,MAAM,QAAQ,YAAY;AAC9B,OAAI,KAAK,SAAS,kBAAmB;AACrC,OAAK,KAAK,eAAsC,OAAQ;GACxD,MAAM,WAAW,KAAK;AACtB,OAAI,CAAC,SAAU;AAWf,QAPC,SAAS,SAAS,eACd,SAAS,OACV,SAAS,SAAS,YACjB,OAAO,SAAS,UAAU,WACzB,SAAS,QACT,KAAA,IACD,KAAA,OACgB,WAAY;GACjC,MAAM,QAAQ,KAAK;AACnB,OAAI,CAAC,SAAS,MAAM,SAAS,aAAc;AAC3C,SAAM,IAAI,MAAM,KAAe;;;AAGjC,QAAO;;;;;;AAOR,SAAS,qBAAqB,MAA0C;AACvE,KAAI,CAAC,KAAM,QAAO;AAClB,KAAI,KAAK,SAAS,UAAW,QAAO;AACpC,KAAI,OAAO,KAAK,UAAU,SAAU,QAAO;AAC3C,QAAO,KAAK;;;;;;;AAQb,SAAS,iBAAiB,MAA4C;AACrE,KAAI,CAAC,QAAQ,KAAK,SAAS,mBAAoB,QAAO;CACtD,MAAM,aAAa,KAAK;AACxB,KAAI,CAAC,WAAY,QAAO;CAExB,MAAM,SAAmB,EAAE;CAC3B,MAAM,uBAAO,IAAI,KAAa;AAC9B,MAAK,MAAM,QAAQ,YAAY;AAC9B,MAAI,KAAK,SAAS,WAAY;EAC9B,MAAM,MAAM,KAAK;AACjB,MAAI,CAAC,OAAO,IAAI,SAAS,aAAa,OAAO,IAAI,UAAU,SAC1D;AACD,MAAI,KAAK,IAAI,IAAI,MAAM,CAAE;AACzB,OAAK,IAAI,IAAI,MAAM;AACnB,SAAO,KAAK,IAAI,MAAM;;AAEvB,QAAO;;;;;;AAOR,SAAS,KAAK,MAAe,OAAsC;AAClE,OAAM,KAAK;AACX,MAAK,MAAM,OAAO,OAAO,KAAK,KAAK,EAAE;AACpC,MAAI,QAAQ,SAAU;EACtB,MAAM,QAAQ,KAAK;AACnB,MAAI,MAAM,QAAQ,MAAM;QAClB,MAAM,QAAQ,MAClB,KAAI,QAAQ,OAAO,SAAS,YAAY,OAAO,KAAK,SAAS,SAC5D,MAAK,MAAiB,MAAM;aAI9B,SACA,OAAO,UAAU,YACjB,OAAQ,MAA6B,SAAS,SAE9C,MAAK,OAAkB,MAAM;;;;;;;;;;AC9LhC,MAAa,iBAAuD,IAAI,IAAI;CAC3E,CACC,UACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,qBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,aACA;EACC,MAAM;EACN,SAAS;EACT,WACC;EACD,CACD;CACD,CACC,8BACA;EACC,MAAM;EACN,SAAS;EACT,WACC;EACD,CACD;CACD,CACC,mBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,gBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,kBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,iBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,eACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,wBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,YACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,cACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,gBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,oBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,2BACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,gCACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,gBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,qBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,qBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,aACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,mBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,UACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,kBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,mBACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CACC,8BACA;EACC,MAAM;EACN,SAAS;EACT,WAAW;EACX,CACD;CACD,CAAC;;;AC9MF,MAAM,kBAAuC,IAAI,IAAI;CACpD;CACA;CACA;CACA;CACA;CACA;CACA;CACA,CAAC;;;;;;;;;;AAWF,eAAsB,YACrB,MACA,YACA,SAA4B,EAAE,EACV;CACpB,MAAM,UAAU,IAAI,IAAY,CAAC,GAAG,iBAAiB,GAAG,OAAO,CAAC;CAChE,MAAM,OAAO,IAAI,IAAI,WAAW;CAChC,MAAM,UAAoB,EAAE;CAE5B,eAAe,KAAK,KAA4B;EAC/C,IAAI;AACJ,MAAI;AACH,aAAW,MAAM,QAAQ,KAAK,EAAE,eAAe,MAAM,CAAC;WAC9C,KAAK;GACb,MAAM,OAAQ,IAA8B;AAC5C,OAAI,SAAS,YAAY,SAAS,UAAW;AAC7C,SAAM;;AAEP,OAAK,MAAM,SAAS,SAAS;AAC5B,OAAI,QAAQ,IAAI,MAAM,KAAK,CAAE;GAC7B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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@openpolicy/vite-auto-collect",
3
- "version": "0.0.18",
3
+ "version": "0.0.19",
4
4
  "type": "module",
5
5
  "description": "Vite plugin that scans source files for @openpolicy/sdk collecting() calls and populates autoCollected() at build time",
6
6
  "license": "GPL-3.0-only",