@svgrid/enterprise 2.5.1 → 2.5.3

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.
@@ -4,7 +4,7 @@
4
4
  <meta charset="UTF-8" />
5
5
  <meta name="viewport" content="width=device-width, initial-scale=1.0" />
6
6
  <title>SvGrid Studio - Designer</title>
7
- <script type="module" crossorigin src="./assets/index-OwdzSsmo.js"></script>
7
+ <script type="module" crossorigin src="./assets/index-CBYU-KNn.js"></script>
8
8
  <link rel="modulepreload" crossorigin href="./assets/rolldown-runtime-Dd_uD5pT.js">
9
9
  <link rel="modulepreload" crossorigin href="./assets/disclose-version-CIm4S9xS.js">
10
10
  <link rel="modulepreload" crossorigin href="./assets/export-format-DwjuTfrQ.js">
@@ -16,7 +16,7 @@
16
16
  <link rel="modulepreload" crossorigin href="./assets/easing-zaypKrr0.js">
17
17
  <link rel="modulepreload" crossorigin href="./assets/chart-DHEgt4lN.js">
18
18
  <link rel="modulepreload" crossorigin href="./assets/SvGridChart-BEDNTdc5.js">
19
- <link rel="modulepreload" crossorigin href="./assets/src-LRVqg7Sf.js">
19
+ <link rel="modulepreload" crossorigin href="./assets/src-i309BeJb.js">
20
20
  <link rel="modulepreload" crossorigin href="./assets/SvListBox-BVzVfCG3.js">
21
21
  <link rel="stylesheet" crossorigin href="./assets/dismissable-BF-9TJSd.css">
22
22
  <link rel="stylesheet" crossorigin href="./assets/SvDateTimePicker-Cygij-dr.css">
@@ -15130,7 +15130,7 @@ function hasFieldConditions(schema) {
15130
15130
  *
15131
15131
  * `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
15132
15132
  */
15133
- var SVGRID_VERSION = "2.5.1";
15133
+ var SVGRID_VERSION = "2.5.3";
15134
15134
  //#endregion
15135
15135
  //#region src/studio/emit-project.ts
15136
15136
  var has = (blocks, kind) => blocks.some((b) => b.config.kind === kind);
package/package.json CHANGED
@@ -4,7 +4,7 @@
4
4
  "type": "commercial",
5
5
  "url": "https://svgrid.com/pricing"
6
6
  },
7
- "version": "2.5.1",
7
+ "version": "2.5.3",
8
8
  "description": "Enterprise feature pack for SvGrid: Excel (xlsx), PDF, CSV, TSV and HTML export, Excel/CSV/JSON import, pivot tables with a drag-and-drop designer, paginated print, Kanban board and scheduler views. Requires a paid license key.",
9
9
  "license": "SEE LICENSE IN LICENSE",
10
10
  "author": "jQWidgets <sales@jqwidgets.com>",
@@ -9,10 +9,25 @@ import {
9
9
  setLicenseKey,
10
10
  } from './license'
11
11
  import { dismissUpgradePrompt } from './upgrade-prompt'
12
+ import { emitUnlicensedNudge } from './watermark'
13
+
14
+ // Keep everything else in the module real; only the watermark emitter is spied.
15
+ vi.mock('./watermark', async (importOriginal) => ({
16
+ ...(await importOriginal()),
17
+ emitUnlicensedNudge: vi.fn(),
18
+ }))
12
19
 
13
20
  const KEY = 'SVENTERPRISE-EVAL-GUNEI-5-20260904-RGY2TN'
14
21
  const CARD = '[data-svgrid-enterprise-upgrade]'
15
22
 
23
+ /** Calls to emitUnlicensedNudge() since the last reset.
24
+ *
25
+ * Spied rather than counted in the DOM: the real function attaches its badge
26
+ * to grid elements on a microtask, so in a document with no grid it renders
27
+ * nothing and a node count would read 0 for every case - passing even if the
28
+ * watermark were wired up wrongly. The call is the contract under test. */
29
+ const watermarkCalls = () => vi.mocked(emitUnlicensedNudge).mock.calls.length
30
+
16
31
  describe('trial key expiry parsing', () => {
17
32
  it('unlocks Enterprise today', () => {
18
33
  const info = checkLicenseKey(KEY, new Date('2026-08-21T12:00:00Z'))
@@ -51,6 +66,7 @@ describe('what happens after the trial ends', () => {
51
66
  vi.useRealTimers()
52
67
  clearLicenseKey()
53
68
  dismissUpgradePrompt()
69
+ vi.mocked(emitUnlicensedNudge).mockClear()
54
70
  document.body.innerHTML = ''
55
71
  })
56
72
 
@@ -81,6 +97,43 @@ describe('what happens after the trial ends', () => {
81
97
  expect(getLicenseExpiry()?.toISOString()).toBe('2026-09-04T23:59:59.999Z')
82
98
  })
83
99
 
100
+ it('a lapsed trial gets the watermark, the console notice AND the card', () => {
101
+ vi.useFakeTimers({ now: new Date('2026-09-20T10:00:00Z') })
102
+ const info = vi.spyOn(console, 'info').mockImplementation(() => {})
103
+ setLicenseKey(KEY)
104
+ assertEnterpriseLicensed('Export')
105
+
106
+ // The evaluation is over, so the app stops looking licensed on every
107
+ // surface - it just keeps working.
108
+ expect(document.querySelector(CARD)).not.toBeNull()
109
+ expect(watermarkCalls()).toBeGreaterThan(0)
110
+ expect(info.mock.calls.flat().join(' ')).toMatch(/evaluation license expired on 2026-09-04/)
111
+ info.mockRestore()
112
+ })
113
+
114
+ it('the expiry console notice fires once, not on every Pro call', () => {
115
+ vi.useFakeTimers({ now: new Date('2026-09-20T10:00:00Z') })
116
+ const info = vi.spyOn(console, 'info').mockImplementation(() => {})
117
+ setLicenseKey(KEY)
118
+ assertEnterpriseLicensed('Export')
119
+ assertEnterpriseLicensed('Export')
120
+ assertEnterpriseLicensed('Print')
121
+
122
+ const expiryNotices = info.mock.calls
123
+ .flat()
124
+ .filter((m) => typeof m === 'string' && m.includes('evaluation license expired'))
125
+ expect(expiryNotices).toHaveLength(1)
126
+ info.mockRestore()
127
+ })
128
+
129
+ it('an app with no key at all still gets the watermark', () => {
130
+ clearLicenseKey()
131
+ assertEnterpriseLicensed('Export')
132
+
133
+ expect(document.querySelector(CARD)).not.toBeNull()
134
+ expect(watermarkCalls()).toBeGreaterThan(0)
135
+ })
136
+
84
137
  it('a paid key never shows the card', () => {
85
138
  vi.useFakeTimers({ now: new Date('2026-09-20T10:00:00Z') })
86
139
  setLicenseKey('SVENTERPRISE-ACME-5-202705-ABC123')
package/src/license.ts CHANGED
@@ -12,10 +12,11 @@
12
12
  // in REVOKED_KEYS -> throws (revoked / leaked / expired)
13
13
  // starts with "SVENTERPRISE-DEV" or
14
14
  // "SVENTERPRISE-EVAL" -> works; one-time console.info notice
15
- // an EXPIRED dev/eval key -> still works, but nudges every session
16
- // (watermark + upgrade card). A trial
17
- // that ends must not break a running
18
- // app, so this stays a soft gate.
15
+ // an EXPIRED dev/eval key -> still works, but stops looking
16
+ // licensed: watermark + a one-time
17
+ // console notice naming the expiry date
18
+ // + the upgrade card. Stays a soft gate
19
+ // so a trial ending cannot break a build.
19
20
  // any other "SVENTERPRISE-..." -> works silently (paid production)
20
21
 
21
22
  import { checkLicenseKey, VALID_PREFIX, type LicenseInfo } from './license-core'
@@ -26,6 +27,22 @@ export { checkLicenseKey, type LicenseInfo, type LicenseStatus } from './license
26
27
 
27
28
  let currentKey: string | null = null
28
29
  let noticedDev = false
30
+ let noticedExpired = false
31
+
32
+ /** One-time console notice for a trial that has run out. Separate from the
33
+ * dev/eval notice: that one says "not for production", this one says the
34
+ * evaluation is over and names the date, which is the actionable part. */
35
+ function noticeExpired(info: LicenseInfo): void {
36
+ if (noticedExpired) return
37
+ noticedExpired = true
38
+ const on = info.expiresAt ? ` on ${info.expiresAt.toISOString().slice(0, 10)}` : ''
39
+ // eslint-disable-next-line no-console
40
+ console.info(
41
+ `@svgrid/enterprise: your evaluation license expired${on}. Everything still ` +
42
+ 'works, but the watermark and upgrade notice stay until a license key is set. ' +
43
+ 'Contact sales@jqwidgets.com or see https://svgrid.com/pricing',
44
+ )
45
+ }
29
46
 
30
47
  export function setLicenseKey(key: string): void {
31
48
  if (typeof key !== 'string' || key.length === 0) {
@@ -33,11 +50,13 @@ export function setLicenseKey(key: string): void {
33
50
  }
34
51
  currentKey = key
35
52
  noticedDev = false
53
+ noticedExpired = false
36
54
  }
37
55
 
38
56
  export function clearLicenseKey(): void {
39
57
  currentKey = null
40
58
  noticedDev = false
59
+ noticedExpired = false
41
60
  }
42
61
 
43
62
  export function getLicenseKey(): string | null {
@@ -84,8 +103,13 @@ export function nudgeEnterprise(feature?: EnterpriseFeatureLabel): void {
84
103
  // `valid` stays true past a trial's expiry (the feature keeps running), so
85
104
  // checking it alone would let a lapsed trial through silently forever.
86
105
  if (info.valid && !info.expired) return
106
+ const lapsedTrial = info.valid && info.expired === true
107
+ // A lapsed trial gets the same treatment as an unlicensed app - watermark,
108
+ // console notice and card. The evaluation is over; the app keeps running,
109
+ // but it stops looking licensed.
87
110
  emitUnlicensedNudge()
88
- showUpgradePrompt(feature, { expired: info.expired === true })
111
+ if (lapsedTrial) noticeExpired(info)
112
+ showUpgradePrompt(feature, { expired: lapsedTrial })
89
113
  }
90
114
 
91
115
  export function assertEnterpriseLicensed(feature?: EnterpriseFeatureLabel): void {
@@ -111,10 +135,11 @@ export function assertEnterpriseLicensed(feature?: EnterpriseFeatureLabel): void
111
135
  case 'dev':
112
136
  case 'eval':
113
137
  if (info.expired) {
114
- // The trial is over. Deliberately NOT a throw: the app keeps working
115
- // and instead gets the same surfaces an unlicensed one does, so the
116
- // developer sees it without their users hitting a broken build.
138
+ // The trial is over. Deliberately NOT a throw: the app keeps working,
139
+ // so the developer sees the nudges without their users hitting a broken
140
+ // build. Watermark + console notice + card, same as an unlicensed app.
117
141
  emitUnlicensedNudge()
142
+ noticeExpired(info)
118
143
  showUpgradePrompt(feature, { expired: true })
119
144
  return
120
145
  }
package/src/version.ts CHANGED
@@ -10,4 +10,4 @@
10
10
  *
11
11
  * `version.test.ts` asserts this matches `package.json`, so the two cannot drift.
12
12
  */
13
- export const SVGRID_VERSION = '2.5.1'
13
+ export const SVGRID_VERSION = '2.5.3'