@proveanything/smartlinks 2.0.5 → 2.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/dist/api/ai.d.ts +1 -1
  2. package/dist/api/ai.js +1 -1
  3. package/dist/api/analytics.d.ts +1 -1
  4. package/dist/api/analytics.js +1 -1
  5. package/dist/api/appConfiguration.d.ts +3 -3
  6. package/dist/api/appConfiguration.js +3 -3
  7. package/dist/api/appObjects.d.ts +1 -1
  8. package/dist/api/appObjects.js +1 -1
  9. package/dist/api/asset.d.ts +1 -1
  10. package/dist/api/asset.js +2 -2
  11. package/dist/api/async.d.ts +1 -1
  12. package/dist/api/async.js +1 -1
  13. package/dist/api/attestation.d.ts +1 -1
  14. package/dist/api/attestation.js +1 -1
  15. package/dist/api/attestations.d.ts +1 -1
  16. package/dist/api/attestations.js +1 -1
  17. package/dist/api/auth.d.ts +2 -2
  18. package/dist/api/auth.js +2 -2
  19. package/dist/api/authKit.d.ts +1 -1
  20. package/dist/api/authKit.js +1 -1
  21. package/dist/api/batch.d.ts +1 -1
  22. package/dist/api/batch.js +1 -1
  23. package/dist/api/broadcasts.d.ts +2 -2
  24. package/dist/api/broadcasts.js +1 -1
  25. package/dist/api/claimSet.d.ts +1 -1
  26. package/dist/api/claimSet.js +1 -1
  27. package/dist/api/collection.d.ts +1 -1
  28. package/dist/api/collection.js +1 -1
  29. package/dist/api/comms.d.ts +15 -15
  30. package/dist/api/comms.js +1 -1
  31. package/dist/api/config.d.ts +1 -1
  32. package/dist/api/config.js +1 -1
  33. package/dist/api/contact.d.ts +1 -1
  34. package/dist/api/contact.js +1 -1
  35. package/dist/api/containers.d.ts +1 -1
  36. package/dist/api/containers.js +1 -1
  37. package/dist/api/crate.d.ts +1 -1
  38. package/dist/api/crate.js +1 -1
  39. package/dist/api/facets.d.ts +1 -1
  40. package/dist/api/facets.js +1 -1
  41. package/dist/api/form.js +1 -1
  42. package/dist/api/http.js +1 -1
  43. package/dist/api/index.d.ts +46 -46
  44. package/dist/api/index.js +46 -46
  45. package/dist/api/integrations.d.ts +1 -1
  46. package/dist/api/integrations.js +1 -1
  47. package/dist/api/interactions.d.ts +1 -1
  48. package/dist/api/interactions.js +1 -1
  49. package/dist/api/jobs.d.ts +1 -1
  50. package/dist/api/jobs.js +1 -1
  51. package/dist/api/journeys.d.ts +1 -1
  52. package/dist/api/journeys.js +1 -1
  53. package/dist/api/journeysAnalytics.d.ts +1 -1
  54. package/dist/api/journeysAnalytics.js +1 -1
  55. package/dist/api/location.d.ts +1 -1
  56. package/dist/api/location.js +1 -1
  57. package/dist/api/lots.d.ts +1 -1
  58. package/dist/api/lots.js +1 -1
  59. package/dist/api/loyalty.d.ts +1 -1
  60. package/dist/api/loyalty.js +1 -1
  61. package/dist/api/navigation.d.ts +1 -1
  62. package/dist/api/navigation.js +1 -1
  63. package/dist/api/nfc.d.ts +1 -1
  64. package/dist/api/nfc.js +1 -1
  65. package/dist/api/order.d.ts +1 -1
  66. package/dist/api/order.js +1 -1
  67. package/dist/api/product.d.ts +1 -1
  68. package/dist/api/product.js +1 -1
  69. package/dist/api/products.d.ts +1 -1
  70. package/dist/api/products.js +1 -1
  71. package/dist/api/proof.d.ts +1 -1
  72. package/dist/api/proof.js +1 -1
  73. package/dist/api/qr.d.ts +1 -1
  74. package/dist/api/qr.js +1 -1
  75. package/dist/api/realtime.d.ts +1 -1
  76. package/dist/api/realtime.js +1 -1
  77. package/dist/api/research.d.ts +1 -1
  78. package/dist/api/research.js +1 -1
  79. package/dist/api/secrets.d.ts +1 -1
  80. package/dist/api/secrets.js +1 -1
  81. package/dist/api/segments.d.ts +1 -1
  82. package/dist/api/segments.js +1 -1
  83. package/dist/api/sequence.js +1 -1
  84. package/dist/api/tags.d.ts +1 -1
  85. package/dist/api/tags.js +1 -1
  86. package/dist/api/template.d.ts +1 -1
  87. package/dist/api/template.js +1 -1
  88. package/dist/api/translations.d.ts +1 -1
  89. package/dist/api/translations.js +2 -2
  90. package/dist/api/variant.d.ts +1 -1
  91. package/dist/api/variant.js +1 -1
  92. package/dist/containers/types.d.ts +1 -1
  93. package/dist/docs/API_SUMMARY.md +7 -7
  94. package/dist/docs/agent-tools.md +111 -0
  95. package/dist/docs/ai.md +14 -520
  96. package/dist/docs/analytics.md +41 -2
  97. package/dist/docs/app-data-storage.md +0 -38
  98. package/dist/docs/app-manifest.md +104 -7
  99. package/dist/docs/app-objects.md +0 -148
  100. package/dist/docs/app-records-pattern.md +2 -2
  101. package/dist/docs/building-react-components.md +6 -14
  102. package/dist/docs/caching.md +20 -21
  103. package/dist/docs/container-tracking.md +2 -0
  104. package/dist/docs/containers.md +14 -66
  105. package/dist/docs/deploying-apps.md +8 -3
  106. package/dist/docs/executor.md +4 -4
  107. package/dist/docs/host-dependency-contract.md +159 -0
  108. package/dist/docs/iframe-responder.md +308 -0
  109. package/dist/docs/item-context.md +0 -2
  110. package/dist/docs/manifests.md +3 -3
  111. package/dist/docs/mobile-admin-container.md +4 -4
  112. package/dist/docs/mpa.md +5 -5
  113. package/dist/docs/native-facade.md +1 -1
  114. package/dist/docs/overview.md +36 -15
  115. package/dist/docs/portal-back-button.md +2 -3
  116. package/dist/docs/sequences.md +1 -1
  117. package/dist/docs/server-functions.md +2 -3
  118. package/dist/docs/widgets.md +11 -69
  119. package/dist/http.d.ts +24 -8
  120. package/dist/http.js +32 -14
  121. package/dist/iframe.d.ts +2 -2
  122. package/dist/iframe.js +1 -1
  123. package/dist/iframeResponder.d.ts +7 -1
  124. package/dist/iframeResponder.js +45 -4
  125. package/dist/index.d.ts +30 -27
  126. package/dist/index.js +10 -8
  127. package/dist/mobile-admin/errors.d.ts +1 -1
  128. package/dist/mobile-admin/types.d.ts +2 -2
  129. package/dist/openapi.yaml +12 -0
  130. package/dist/shared-dependencies.d.ts +37 -0
  131. package/dist/shared-dependencies.js +79 -0
  132. package/dist/testing/index.d.ts +1 -1
  133. package/dist/translationCache.d.ts +1 -1
  134. package/dist/types/appManifest.d.ts +23 -0
  135. package/dist/types/broadcasts.d.ts +1 -1
  136. package/dist/types/collection.d.ts +2 -2
  137. package/dist/types/comms.d.ts +5 -5
  138. package/dist/types/contact.d.ts +1 -1
  139. package/dist/types/facets.d.ts +1 -1
  140. package/dist/types/iframeResponder.d.ts +3 -3
  141. package/dist/types/index.d.ts +44 -44
  142. package/dist/types/index.js +44 -44
  143. package/dist/types/interaction.d.ts +1 -1
  144. package/dist/types/itemContext.d.ts +1 -1
  145. package/dist/types/journeysAnalytics.d.ts +1 -1
  146. package/dist/types/navigation.d.ts +1 -1
  147. package/dist/types/product.d.ts +1 -1
  148. package/dist/types/proof.d.ts +1 -1
  149. package/dist/types/segments.d.ts +1 -1
  150. package/dist/types/widgets.d.ts +2 -2
  151. package/dist/utils/conditions.d.ts +1 -1
  152. package/dist/utils/index.d.ts +3 -3
  153. package/dist/utils/index.js +3 -3
  154. package/dist/utils/paths.d.ts +4 -4
  155. package/docs/API_SUMMARY.md +7 -7
  156. package/docs/agent-tools.md +111 -0
  157. package/docs/ai.md +14 -520
  158. package/docs/analytics.md +41 -2
  159. package/docs/app-data-storage.md +0 -38
  160. package/docs/app-manifest.md +104 -7
  161. package/docs/app-objects.md +0 -148
  162. package/docs/app-records-pattern.md +2 -2
  163. package/docs/building-react-components.md +6 -14
  164. package/docs/caching.md +20 -21
  165. package/docs/container-tracking.md +2 -0
  166. package/docs/containers.md +14 -66
  167. package/docs/deploying-apps.md +8 -3
  168. package/docs/executor.md +4 -4
  169. package/docs/host-dependency-contract.md +159 -0
  170. package/docs/iframe-responder.md +308 -0
  171. package/docs/item-context.md +0 -2
  172. package/docs/mobile-admin-container.md +4 -4
  173. package/docs/mpa.md +5 -5
  174. package/docs/native-facade.md +1 -1
  175. package/docs/overview.md +36 -15
  176. package/docs/portal-back-button.md +2 -3
  177. package/docs/sequences.md +1 -1
  178. package/docs/server-functions.md +2 -3
  179. package/docs/widgets.md +11 -69
  180. package/openapi.yaml +12 -0
  181. package/package.json +17 -6
  182. package/scripts/doctor.mjs +171 -0
  183. package/docs/analytics-metadata-conventions.md +0 -88
  184. package/docs/iframe-streaming-parent-changes.md +0 -308
  185. package/docs/manifests.md +0 -204
package/docs/sequences.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Sequences & claim-order allocation
2
2
 
3
- > **Preview — SmartLinks SDK 2.0.0-alpha.** APIs may change before 2.0.0 stable.
3
+ > **SmartLinks SDK 2.x** (current `latest`). Install `@proveanything/smartlinks@^2`.
4
4
 
5
5
  A **sequence** hands out a guaranteed-unique, monotonic number — `1, 2, 3, …` — and stamps it
6
6
  onto a record. It's the primitive behind raffle tickets, "you're the Nth to claim", queue
@@ -1,8 +1,7 @@
1
1
  # Server functions ("edge functions")
2
2
 
3
- > **Preview — SmartLinks SDK 2.0.0-alpha.** Part of the installable-app platform being built
4
- > toward 2.0.0 stable (author → register → install → run → test). These APIs may change before
5
- > then. Published under the npm `next` tag; `latest` remains 1.x.
3
+ > **SmartLinks SDK 2.x** (current `latest`). Part of the installable-app platform
4
+ > (author → register → install → run → test). Install `@proveanything/smartlinks@^2`.
6
5
 
7
6
  A **server function** is arbitrary server-side JavaScript your app deploys directly into
8
7
  SmartLinks. It runs on the SmartLinks servers — with access to the full SDK, to your app's
package/docs/widgets.md CHANGED
@@ -14,7 +14,7 @@ Widgets are self-contained React components that:
14
14
 
15
15
  ```text
16
16
  ┌─────────────────────────────────────────────────────────────────┐
17
- │ Parent SmartLinks Portal (React 18) │
17
+ │ Parent SmartLinks Portal (React 19) │
18
18
  │ │
19
19
  │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
20
20
  │ │ Competition │ │ Music App │ │ Warranty │ │
@@ -53,53 +53,7 @@ Widgets are typically single-view components and don't need internal routing. If
53
53
 
54
54
  ### The `useAppContext()` Pattern
55
55
 
56
- To write widgets that work identically in both modes, use this abstraction pattern:
57
-
58
- ```tsx
59
- // src/hooks/useAppContext.ts
60
- import { useContext, createContext, useMemo } from 'react';
61
- import { useSearchParams } from 'react-router-dom';
62
-
63
- export interface AppContextValue {
64
- collectionId: string;
65
- appId: string;
66
- productId?: string;
67
- proofId?: string;
68
- pageId?: string;
69
- lang?: string;
70
- user?: { id: string; email: string; name?: string };
71
- SL: typeof import('@proveanything/smartlinks');
72
- onNavigate?: (request: any) => void;
73
- }
74
-
75
- export const AppContext = createContext<AppContextValue | null>(null);
76
-
77
- /**
78
- * Returns app context regardless of rendering mode.
79
- * - Direct component mode: reads from AppContext (props)
80
- * - Iframe mode: reads from URL search params
81
- */
82
- export function useAppContext(): AppContextValue {
83
- const ctx = useContext(AppContext);
84
-
85
- // If context exists, we're in direct-component mode
86
- if (ctx) return ctx;
87
-
88
- // Otherwise, we're in iframe mode — read from URL params
89
- const [searchParams] = useSearchParams();
90
- const SL = (window as any).SL ?? require('@proveanything/smartlinks');
91
-
92
- return useMemo(() => ({
93
- collectionId: searchParams.get('collectionId') ?? '',
94
- appId: searchParams.get('appId') ?? '',
95
- productId: searchParams.get('productId') ?? undefined,
96
- proofId: searchParams.get('proofId') ?? undefined,
97
- pageId: searchParams.get('pageId') ?? undefined,
98
- lang: searchParams.get('lang') ?? undefined,
99
- SL,
100
- }), [searchParams, SL]);
101
- }
102
- ```
56
+ Widgets read their context through the shared **`useAppContext()`** hook so the same code works in both direct-component and iframe modes. The hook and `AppContext` provider are defined once — see **[building-react-components.md](building-react-components.md#the-useappcontext-pattern)** for the full implementation (don't re-define it per app).
103
57
 
104
58
  **Usage in your widget:**
105
59
 
@@ -344,7 +298,7 @@ Declare support in `app.manifest.json`:
344
298
  "files": {
345
299
  "js": {
346
300
  "umd": "dist/widgets.umd.js",
347
- "esm": "dist/widgets.es.js"
301
+ "esm": "dist/widgets.esm.js"
348
302
  },
349
303
  "css": null
350
304
  },
@@ -511,7 +465,7 @@ export { MyWidget } from './MyWidget';
511
465
  // Update the manifest
512
466
  export const WIDGET_MANIFEST = {
513
467
  version: '1.0.0',
514
- reactVersion: '18.x',
468
+ reactVersion: '19.x',
515
469
  widgets: [
516
470
  // ... existing widgets
517
471
  {
@@ -570,7 +524,7 @@ The project includes a separate Vite config for building widgets:
570
524
  # Build widgets only
571
525
  vite build --config vite.config.widget.ts
572
526
 
573
- # Output: dist/widgets.es.js
527
+ # Output: dist/widgets.esm.js
574
528
  ```
575
529
 
576
530
  ### Build Configuration
@@ -582,23 +536,11 @@ The widget build:
582
536
  - Minifies with esbuild for production
583
537
  - Outputs to `/dist` alongside the main app (not a separate folder)
584
538
 
585
- ### Externalized Dependencies (Peer Dependencies)
586
-
587
- The widget bundle does **not** include these libraries—the parent app must provide them:
539
+ ### Externalized dependencies
588
540
 
589
- | Package | Why Externalized |
590
- |---------|------------------|
591
- | `react`, `react-dom` | Parent's React context |
592
- | `@proveanything/smartlinks` | Passed via props as `SL` |
593
- | `@proveanything/smartlinks-auth-ui` | Auth UI components (also available globally as `window.SmartlinksAuthUI`) |
594
- | `tailwind-merge` | Utility for merging Tailwind classes |
595
- | `clsx` | Utility for conditional class names |
596
- | `class-variance-authority` | Utility for component variants |
541
+ The widget bundle does **not** include the host's shared libraries (React, the SDK, Radix, LiquidJS, …) — it **externalizes** them and resolves them from the host at runtime, so the bundle stays tiny and there's exactly one shared instance.
597
542
 
598
- These are standard packages that any modern React + Tailwind app will have. Externalizing them:
599
- 1. Reduces bundle size significantly
600
- 2. Removes JSDoc comments that inflate the bundle
601
- 3. Ensures consistent behavior with parent's versions
543
+ **The canonical, versioned list is the shared-dependency contract — don't hand-maintain your own here.** Import it from the SDK (`SHARED_DEPENDENCY_SPECIFIERS`) for your build's `external` list, and see **[host-dependency-contract.md](host-dependency-contract.md)** for the full table, the Vite `external`/`globals` config, and the *never bundle your own React* rule. `@proveanything/smartlinks-auth-ui` is externalized too (host global `window.SmartlinksAuthUI`) — do not ship a second copy.
602
544
 
603
545
  ### Enabling Widget Builds
604
546
 
@@ -626,7 +568,7 @@ import * as SL from '@proveanything/smartlinks';
626
568
 
627
569
  // Dynamic import from app's CDN
628
570
  const CompetitionWidget = lazy(() =>
629
- import('https://competition-app.example.com/widgets.es.js')
571
+ import('https://competition-app.example.com/widgets.esm.js')
630
572
  .then(m => ({ default: m.CompetitionWidget }))
631
573
  );
632
574
 
@@ -670,7 +612,7 @@ import { WidgetWrapper, CompetitionWidget } from 'competition-app/widgets';
670
612
  import { WIDGET_MANIFEST } from 'competition-app/widgets';
671
613
 
672
614
  // Verify React version compatibility
673
- if (!WIDGET_MANIFEST.reactVersion.startsWith('18')) {
615
+ if (!WIDGET_MANIFEST.reactVersion.startsWith('19')) {
674
616
  console.warn('Widget React version mismatch');
675
617
  }
676
618
 
@@ -741,7 +683,7 @@ Each app exports a `WIDGET_MANIFEST` for discovery:
741
683
  ```typescript
742
684
  export const WIDGET_MANIFEST = {
743
685
  version: '1.0.0', // Widget bundle version
744
- reactVersion: '18.x', // Required React version
686
+ reactVersion: '19.x', // Required React version
745
687
  widgets: [
746
688
  {
747
689
  name: 'ExampleWidget',
package/openapi.yaml CHANGED
@@ -17471,6 +17471,18 @@ components:
17471
17471
  type: string
17472
17472
  appId:
17473
17473
  type: string
17474
+ moduleFormat:
17475
+ type: string
17476
+ enum:
17477
+ - umd
17478
+ - esm
17479
+ - dual
17480
+ sharedDependencies:
17481
+ type: string
17482
+ globals:
17483
+ type: object
17484
+ additionalProperties:
17485
+ type: string
17474
17486
  seo:
17475
17487
  type: object
17476
17488
  additionalProperties: true
package/package.json CHANGED
@@ -1,37 +1,48 @@
1
1
  {
2
2
  "name": "@proveanything/smartlinks",
3
- "version": "2.0.5",
3
+ "version": "2.0.9",
4
4
  "description": "Official JavaScript/TypeScript SDK for the Smartlinks API",
5
+ "type": "module",
5
6
  "main": "dist/index.js",
6
7
  "types": "dist/index.d.ts",
7
8
  "exports": {
8
9
  ".": {
9
10
  "types": "./dist/index.d.ts",
11
+ "import": "./dist/index.js",
10
12
  "default": "./dist/index.js"
11
13
  },
12
14
  "./testing": {
13
15
  "types": "./dist/testing/index.d.ts",
16
+ "import": "./dist/testing/index.js",
14
17
  "default": "./dist/testing/index.js"
15
- }
18
+ },
19
+ "./*": {
20
+ "types": "./dist/*.d.ts",
21
+ "import": "./dist/*.js",
22
+ "default": "./dist/*.js"
23
+ },
24
+ "./package.json": "./package.json"
16
25
  },
17
26
  "bin": {
18
27
  "smartlinks-register-release": "scripts/register-release.mjs",
19
- "smartlinks-publish": "scripts/publish.mjs"
28
+ "smartlinks-publish": "scripts/publish.mjs",
29
+ "smartlinks-doctor": "scripts/doctor.mjs"
20
30
  },
21
31
  "files": [
22
32
  "dist/",
23
33
  "docs/",
24
34
  "scripts/register-release.mjs",
25
35
  "scripts/publish.mjs",
36
+ "scripts/doctor.mjs",
26
37
  "openapi.yaml",
27
38
  "README.md"
28
39
  ],
29
40
  "scripts": {
30
41
  "test": "node test/run.cjs",
31
- "build": "tsc && node generate-api-summary.js && node generate-openapi.js && node scripts/copy-docs-to-dist.js",
42
+ "build": "tsc && node scripts/fix-esm-extensions.mjs && node generate-api-summary.cjs && node generate-openapi.cjs && node scripts/copy-docs-to-dist.cjs",
32
43
  "docs": "typedoc",
33
- "docs:summary": "node generate-api-summary.js",
34
- "docs:openapi": "node generate-openapi.js",
44
+ "docs:summary": "node generate-api-summary.cjs",
45
+ "docs:openapi": "node generate-openapi.cjs",
35
46
  "build:docs": "tsc build-docs.ts --outDir dist && node dist/build-docs.js",
36
47
  "prepublishOnly": "npm run build"
37
48
  },
@@ -0,0 +1,171 @@
1
+ #!/usr/bin/env node
2
+ // =============================================================================
3
+ // smartlinks doctor — verify an app's ESM bundles against the shared-dependency
4
+ // contract, so a modern (esm/dual) app can't ship a bare import the host won't
5
+ // resolve.
6
+ //
7
+ // A correctly-built externalized ESM bundle inlines everything EXCEPT the host's
8
+ // shared singletons. So every *bare* import left in the output must be a contract
9
+ // entry — anything else will either fail to resolve through the import map at
10
+ // runtime, or silently pull in a second copy (the duplicate-React class of bug).
11
+ //
12
+ // Usage:
13
+ // smartlinks-doctor [appDir] # defaults to cwd
14
+ // npx @proveanything/smartlinks doctor (once a unified `smartlinks` bin exists)
15
+ //
16
+ // Exit code: 0 = clean, 1 = violations (CI-friendly).
17
+ // =============================================================================
18
+
19
+ import { readFileSync, existsSync } from 'node:fs';
20
+ import { resolve, dirname, join } from 'node:path';
21
+ import { fileURLToPath, pathToFileURL } from 'node:url';
22
+
23
+ const here = dirname(fileURLToPath(import.meta.url));
24
+
25
+ // Read the contract from THIS SDK build — one source of truth with the runtime.
26
+ const contractUrl = pathToFileURL(resolve(here, '../dist/shared-dependencies.js')).href;
27
+ const { SHARED_DEPENDENCY_SPECIFIERS, SHARED_DEPENDENCY_CONTRACT_VERSION } = await import(contractUrl);
28
+ const CONTRACT = new Set(SHARED_DEPENDENCY_SPECIFIERS);
29
+
30
+ const RED = '\x1b[31m';
31
+ const GREEN = '\x1b[32m';
32
+ const YELLOW = '\x1b[33m';
33
+ const DIM = '\x1b[2m';
34
+ const BOLD = '\x1b[1m';
35
+ const RESET = '\x1b[0m';
36
+
37
+ function die(msg) {
38
+ console.error(`${RED}smartlinks doctor: ${msg}${RESET}`);
39
+ process.exit(1);
40
+ }
41
+
42
+ const appDir = resolve(process.argv[2] || process.cwd());
43
+ if (!existsSync(appDir)) die(`app directory not found: ${appDir}`);
44
+
45
+ // Locate the manifest.
46
+ const manifestPath = ['public/app.manifest.json', 'app.manifest.json', 'dist/app.manifest.json']
47
+ .map((p) => join(appDir, p))
48
+ .find(existsSync);
49
+ if (!manifestPath) die(`no app.manifest.json found under ${appDir} (looked in public/, ., dist/)`);
50
+
51
+ let manifest;
52
+ try {
53
+ manifest = JSON.parse(readFileSync(manifestPath, 'utf8'));
54
+ } catch (e) {
55
+ die(`could not parse ${manifestPath}: ${e.message}`);
56
+ }
57
+ const manifestDir = dirname(manifestPath);
58
+ const meta = manifest.meta || {};
59
+
60
+ console.log(`${BOLD}SmartLinks doctor${RESET} ${DIM}— ${appDir}${RESET}`);
61
+ console.log(`${DIM}manifest:${RESET} ${manifestPath.replace(appDir + '\\', '').replace(appDir + '/', '')}`);
62
+ console.log(`${DIM}contract:${RESET} ${SHARED_DEPENDENCY_CONTRACT_VERSION} (${CONTRACT.size} shared deps)`);
63
+ console.log(`${DIM}moduleFormat:${RESET} ${meta.moduleFormat || '(absent → umd)'} · ${DIM}sharedDependencies:${RESET} ${meta.sharedDependencies || '(none)'}\n`);
64
+
65
+ const problems = [];
66
+ const warnings = [];
67
+
68
+ // Manifest-level sanity.
69
+ const format = meta.moduleFormat || 'umd';
70
+ const checkEsm = format === 'esm' || format === 'dual';
71
+ if (checkEsm && !meta.sharedDependencies) {
72
+ warnings.push(`meta.moduleFormat is "${format}" but meta.sharedDependencies is not declared — set it to "${SHARED_DEPENDENCY_CONTRACT_VERSION}".`);
73
+ }
74
+ if (meta.sharedDependencies && meta.sharedDependencies !== SHARED_DEPENDENCY_CONTRACT_VERSION) {
75
+ warnings.push(`built against contract ${meta.sharedDependencies}, but this SDK ships ${SHARED_DEPENDENCY_CONTRACT_VERSION} — host may not serve a matching import map.`);
76
+ }
77
+
78
+ // Pull the bare imports out of a bundle (handles minified `from"x"` too).
79
+ function bareImportsOf(code) {
80
+ const specs = new Set();
81
+ const patterns = [
82
+ /\bfrom\s*["']([^"']+)["']/g, // static import/export ... from "x"
83
+ /\bimport\s*["']([^"']+)["']/g, // side-effect import "x"
84
+ /\bimport\(\s*["']([^"']+)["']\s*\)/g, // dynamic import("x")
85
+ ];
86
+ for (const re of patterns) {
87
+ let m;
88
+ while ((m = re.exec(code))) {
89
+ const spec = m[1];
90
+ if (spec.startsWith('.') || spec.startsWith('/')) continue; // internal/relative
91
+ if (/^https?:/.test(spec)) continue; // absolute URL import
92
+ specs.add(spec);
93
+ }
94
+ }
95
+ return [...specs];
96
+ }
97
+
98
+ // Resolve a manifest-relative bundle path against the likely roots (dist paths
99
+ // are sometimes relative to the app root, sometimes to the manifest dir).
100
+ function resolveBundle(rel) {
101
+ const bases = [manifestDir, appDir, join(appDir, 'public'), join(appDir, 'dist')];
102
+ for (const b of bases) {
103
+ const p = resolve(b, rel);
104
+ if (existsSync(p)) return p;
105
+ }
106
+ return null;
107
+ }
108
+
109
+ // Check each ESM surface declared in the manifest.
110
+ const surfaces = [
111
+ ['widgets', manifest.widgets],
112
+ ['containers', manifest.containers],
113
+ ['mobileAdmin', manifest.mobileAdmin],
114
+ ];
115
+
116
+ let checkedAny = false;
117
+ for (const [name, block] of surfaces) {
118
+ const esm = block?.files?.js?.esm;
119
+ if (!esm) continue;
120
+
121
+ // UMD-format app that still declares an ESM bundle: the host won't load it, so
122
+ // it's dead weight and a common scaffold-confusion source — warn, don't fail.
123
+ if (!checkEsm) {
124
+ warnings.push(`${name}: ESM bundle "${esm}" is declared but meta.moduleFormat is "${format}", so the host never loads it. Set moduleFormat to "dual" to use it, or drop the esm entry.`);
125
+ continue;
126
+ }
127
+
128
+ checkedAny = true;
129
+ const bundlePath = resolveBundle(esm);
130
+ if (!bundlePath) {
131
+ console.log(`${RED}✗${RESET} ${name} ${DIM}(${esm})${RESET} — declared ESM bundle not found on disk`);
132
+ problems.push({ surface: name, spec: null, msg: `declared ESM bundle not found: ${esm}` });
133
+ continue;
134
+ }
135
+ const code = readFileSync(bundlePath, 'utf8');
136
+ const imports = bareImportsOf(code);
137
+ const offenders = imports.filter((s) => !CONTRACT.has(s));
138
+ const ok = imports.length - offenders.length;
139
+
140
+ if (offenders.length === 0) {
141
+ console.log(`${GREEN}✓${RESET} ${name} ${DIM}(${esm})${RESET} — ${ok} bare import${ok === 1 ? '' : 's'}, all in contract`);
142
+ } else {
143
+ console.log(`${RED}✗${RESET} ${name} ${DIM}(${esm})${RESET} — ${offenders.length} outside the contract:`);
144
+ for (const spec of offenders) {
145
+ const hint = spec.startsWith('node:')
146
+ ? 'node built-in — must not appear in a browser bundle'
147
+ : 'not host-provided — bundle it (don\'t externalize) or it will fail to resolve / double-load';
148
+ console.log(` ${RED}${spec}${RESET} ${DIM}— ${hint}${RESET}`);
149
+ problems.push({ surface: name, spec, msg: hint });
150
+ }
151
+ }
152
+ }
153
+
154
+ if (checkEsm && !checkedAny) {
155
+ warnings.push(`moduleFormat is "${format}" but no surface declares files.js.esm — nothing to check.`);
156
+ }
157
+ if (!checkEsm && problems.length === 0) {
158
+ console.log(`${DIM}moduleFormat "${format}" — ESM path not in use; UMD bundles resolve shared deps from window globals.${RESET}`);
159
+ }
160
+
161
+ console.log('');
162
+ for (const w of warnings) console.log(`${YELLOW}⚠ ${w}${RESET}`);
163
+
164
+ if (problems.length) {
165
+ console.log(`\n${RED}${BOLD}FAIL${RESET} — ${problems.length} problem${problems.length === 1 ? '' : 's'} outside the shared-dependency contract.`);
166
+ console.log(`${DIM}Externalize only the ${CONTRACT.size} contract specifiers (import { SHARED_DEPENDENCY_SPECIFIERS } from '@proveanything/smartlinks'); bundle everything else.${RESET}`);
167
+ process.exit(1);
168
+ }
169
+
170
+ console.log(`${GREEN}${BOLD}OK${RESET} — bundles conform to shared-dependency contract ${SHARED_DEPENDENCY_CONTRACT_VERSION}.`);
171
+ process.exit(0);
@@ -1,88 +0,0 @@
1
- # Analytics Metadata Conventions
2
-
3
- Use these as the recommended standard analytics keys.
4
-
5
- Some of these are now promoted top-level analytics fields. Others remain good metadata keys for custom dimensions.
6
-
7
- ---
8
-
9
- ## Recommended Keys
10
-
11
- ### Promoted top-level fields
12
-
13
- - `visitorId`
14
- - `referrerHost`
15
- - `entryType`
16
- - `pageId`
17
- - `scanMethod`
18
- - `source` - collection/web-events only. Free-form client app identifier, e.g. `'portal'`, `'hub'`. No enum/whitelist. Not available on tag events - see the `analytics.tag.track(event)` section in [docs/analytics.md](analytics.md) for why.
19
- - `redirectMode` - tag-events only. Usually server-written; only set this yourself if you're logging a redirect-style event.
20
-
21
- These should be sent as top-level analytics fields, not inside `metadata`.
22
-
23
- ### Metadata-friendly keys
24
-
25
- - `referrer`
26
- - `utmSource`
27
- - `utmMedium`
28
- - `utmCampaign`
29
- - `utmContent`
30
- - `utmTerm`
31
- - `group`
32
- - `tag`
33
- - `campaign`
34
- - `placement`
35
- - `linkGroup`
36
- - `linkPlacement`
37
- - `linkPosition`
38
- - `linkTitle`
39
- - `destinationDomain`
40
- - `pagePath`
41
- - `qrCodeId`
42
-
43
- ---
44
-
45
- ## Why These Matter
46
-
47
- These keys give teams a shared vocabulary for:
48
-
49
- - inbound traffic attribution
50
- - outbound link analysis
51
- - link placement and link-tree performance
52
- - QR and page-level traffic grouping
53
- - physical scan source analysis
54
-
55
- ---
56
-
57
- ## Recommendation
58
-
59
- - Treat these as reserved standard keys.
60
- - Prefer these names before inventing custom alternatives.
61
- - Send promoted fields at top level.
62
- - Keep values flat and scalar where possible so they are easier to filter and break down later.
63
- - Promote a field to a first-class backend column only when it becomes a hot platform-wide dimension.
64
- - Note: `source` (the event column) and the query-time `source` parameter (`'events' | 'tag'`, which table to query) are unrelated fields that happen to share a name. When filtering by the `source` column, use the plural `sources` array - there is no singular `source` filter, precisely to avoid colliding with the table selector.
65
-
66
- ---
67
-
68
- ## Example
69
-
70
- ```typescript
71
- analytics.collection.track({
72
- sessionId: 1234567890,
73
- eventType: 'click_link',
74
- collectionId: 'demo-collection',
75
- visitorId: 'visitor_123',
76
- linkId: 'hero-cta',
77
- href: 'https://example.com/buy',
78
- referrerHost: 'instagram.com',
79
- placement: 'hero',
80
- campaign: 'summer-launch',
81
- utmSource: 'email',
82
- pageId: 'QR123',
83
- source: 'portal',
84
- metadata: {
85
- pagePath: '/c/demo-collection',
86
- },
87
- })
88
- ```