From f07c66ecbe48b3db2dfce3b9afca3c1d46c06dbd Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Wed, 21 Oct 2026 14:10:24 -0400 Subject: [PATCH 1/9] feat(#2865): drawing service skeleton + listEvents (Admin-gated) --- srv/drawing-service.cds | 12 ++++++ srv/drawing-service.js | 28 +++++++++++++ test/drawing-service.test.js | 81 ++++++++++++++++++++++++++++++++++++ 3 files changed, 121 insertions(+) create mode 100644 srv/drawing-service.cds create mode 100644 srv/drawing-service.js create mode 100644 test/drawing-service.test.js diff --git a/srv/drawing-service.cds b/srv/drawing-service.cds new file mode 100644 index 000000000..d98ce538d --- /dev/null +++ b/srv/drawing-service.cds @@ -0,0 +1,12 @@ +using { com.sap.developers.ims as ims } from '../db/schema'; + +@path: '/drawings' +@requires: 'Admin' +service DrawingService { + + function listEvents() returns many { + legacyId : Integer; + name : String; + startDate : Timestamp; + }; +} diff --git a/srv/drawing-service.js b/srv/drawing-service.js new file mode 100644 index 000000000..43b573032 --- /dev/null +++ b/srv/drawing-service.js @@ -0,0 +1,28 @@ +import cds from '@sap/cds'; + +export default class DrawingService extends cds.ApplicationService { + + async init() { + const { Events, PrizeRecords } = cds.entities('com.sap.developers.ims'); + + this.on('listEvents', async () => { + // Find distinct event IDs that have at least one CLAIMED PrizeRecord + const claimed = await SELECT.distinct.from(PrizeRecords) + .columns('event_ID') + .where({ status: 'CLAIMED' }); + + if (!claimed.length) return []; + + const eventIds = claimed.map(r => r.event_ID); + + const events = await SELECT.from(Events) + .columns('legacyId', 'name', 'startDate') + .where({ ID: { in: eventIds } }) + .orderBy({ startDate: 'desc' }); + + return events; + }); + + await super.init(); + } +} diff --git a/test/drawing-service.test.js b/test/drawing-service.test.js new file mode 100644 index 000000000..baa838ba4 --- /dev/null +++ b/test/drawing-service.test.js @@ -0,0 +1,81 @@ +import { describe, it, expect, beforeAll } from 'vitest'; +import cds from '@sap/cds'; + +const project = cds.test('serve', '--project', '.', '--in-memory'); + +const adminAuth = { auth: { username: 'admin', password: 'admin' } }; +const devAuth = { auth: { username: 'developer', password: 'developer' } }; + +describe('DrawingService', () => { + + describe('Authorization', () => { + it('rejects non-Admin users', async () => { + const { status } = await project.get('/drawings/listEvents()', { + ...devAuth, validateStatus: () => true + }); + expect(status).toBe(403); + }); + }); + + describe('listEvents', () => { + beforeAll(async () => { + const { Events, Users, PrizeRecords } = cds.entities('com.sap.developers.ims'); + + await INSERT.into(Events).entries([ + { + ID: 'bbbbbbbb-2222-0000-0000-000000000001', + name: 'Drawing Event A', + startDate: '2026-06-01T00:00:00Z', + endDate: '2026-06-03T23:59:59Z', + legacyId: 7001 + }, + { + ID: 'bbbbbbbb-2222-0000-0000-000000000002', + name: 'Drawing Event B', + startDate: '2026-07-01T00:00:00Z', + endDate: '2026-07-03T23:59:59Z', + legacyId: 7002 + } + ]); + + await INSERT.into(Users).entries([ + { ID: 'bbbbbbbb-2222-0000-0000-000000000011', uuid: 'draw-u1', legacyId: 7011, displayName: 'Winner1' }, + { ID: 'bbbbbbbb-2222-0000-0000-000000000012', uuid: 'draw-u2', legacyId: 7012, displayName: 'Winner2' }, + { ID: 'bbbbbbbb-2222-0000-0000-000000000013', uuid: 'draw-u3', legacyId: 7013, displayName: 'Winner3' } + ]); + + // Only event 7001 has a CLAIMED PrizeRecord + // Event 7002 has no CLAIMED records (only PENDING) + await INSERT.into(PrizeRecords).entries([ + { + ID: 'bbbbbbbb-2222-0000-0000-000000000021', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000011', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000001', + status: 'CLAIMED', + legacyId: 7021 + }, + { + ID: 'bbbbbbbb-2222-0000-0000-000000000022', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000012', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000001', + status: 'CLAIMED', + legacyId: 7022 + }, + { + ID: 'bbbbbbbb-2222-0000-0000-000000000023', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000013', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000002', + status: 'PENDING', + legacyId: 7023 + } + ]); + }); + + it('listEvents returns only events with CLAIMED records, newest-first', async () => { + const { status, data } = await project.get('/drawings/listEvents()', adminAuth); + expect(status).toBe(200); + expect(data.value).toHaveLength(1); + expect(data.value[0].legacyId).toBe(7001); + }); + }); +}); From 69423b26333e95ef1ded6fcdbd63b48b7c2bee42 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Wed, 21 Oct 2026 14:14:56 -0400 Subject: [PATCH 2/9] test(#2865): strengthen listEvents ordering assertion Add event 7003 (2026-08-01, CLAIMED) so the newest-first sort is exercised with two eligible events; assert 7003 before 7001. --- test/drawing-service.test.js | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/test/drawing-service.test.js b/test/drawing-service.test.js index baa838ba4..183b2a4aa 100644 --- a/test/drawing-service.test.js +++ b/test/drawing-service.test.js @@ -35,6 +35,13 @@ describe('DrawingService', () => { startDate: '2026-07-01T00:00:00Z', endDate: '2026-07-03T23:59:59Z', legacyId: 7002 + }, + { + ID: 'bbbbbbbb-2222-0000-0000-000000000003', + name: 'Drawing Event C', + startDate: '2026-08-01T00:00:00Z', + endDate: '2026-08-03T23:59:59Z', + legacyId: 7003 } ]); @@ -44,8 +51,8 @@ describe('DrawingService', () => { { ID: 'bbbbbbbb-2222-0000-0000-000000000013', uuid: 'draw-u3', legacyId: 7013, displayName: 'Winner3' } ]); - // Only event 7001 has a CLAIMED PrizeRecord - // Event 7002 has no CLAIMED records (only PENDING) + // Events 7001 and 7003 have CLAIMED PrizeRecords; event 7002 has only PENDING. + // This ensures the filter (only CLAIMED events) AND the sort (newest-first) are both exercised. await INSERT.into(PrizeRecords).entries([ { ID: 'bbbbbbbb-2222-0000-0000-000000000021', @@ -67,6 +74,13 @@ describe('DrawingService', () => { event_ID: 'bbbbbbbb-2222-0000-0000-000000000002', status: 'PENDING', legacyId: 7023 + }, + { + ID: 'bbbbbbbb-2222-0000-0000-000000000024', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000011', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000003', + status: 'CLAIMED', + legacyId: 7024 } ]); }); @@ -74,8 +88,10 @@ describe('DrawingService', () => { it('listEvents returns only events with CLAIMED records, newest-first', async () => { const { status, data } = await project.get('/drawings/listEvents()', adminAuth); expect(status).toBe(200); - expect(data.value).toHaveLength(1); - expect(data.value[0].legacyId).toBe(7001); + // 7003 (2026-08-01) and 7001 (2026-06-01) have CLAIMED records; 7002 (PENDING only) is excluded + expect(data.value).toHaveLength(2); + expect(data.value[0].legacyId).toBe(7003); // newest first + expect(data.value[1].legacyId).toBe(7001); }); }); }); From de4d9827902640f8eab8083e5cd0162f296d89d6 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Wed, 21 Oct 2026 14:25:03 -0400 Subject: [PATCH 3/9] feat(#2865): pool + draw functions, anonymized-user exclusion --- srv/drawing-service.cds | 13 ++++ srv/drawing-service.js | 53 ++++++++++++- test/drawing-service.test.js | 147 +++++++++++++++++++++++++++++++++++ 3 files changed, 212 insertions(+), 1 deletion(-) diff --git a/srv/drawing-service.cds b/srv/drawing-service.cds index d98ce538d..6c03d40de 100644 --- a/srv/drawing-service.cds +++ b/srv/drawing-service.cds @@ -9,4 +9,17 @@ service DrawingService { name : String; startDate : Timestamp; }; + + function pool(eventId: Integer) returns { + eventId : Integer; + eligibleCount : Integer; + }; + + function draw(eventId: Integer) returns { + winner : { + displayName : String; + email : String; + }; + eligibleCount : Integer; + }; } diff --git a/srv/drawing-service.js b/srv/drawing-service.js index 43b573032..b7bae60ad 100644 --- a/srv/drawing-service.js +++ b/srv/drawing-service.js @@ -3,7 +3,31 @@ import cds from '@sap/cds'; export default class DrawingService extends cds.ApplicationService { async init() { - const { Events, PrizeRecords } = cds.entities('com.sap.developers.ims'); + const { Events, PrizeRecords, Users } = cds.entities('com.sap.developers.ims'); + + // Resolve an event by legacyId; rejects with 404 if not found (mirrors display-service.js pattern). + async function resolveEvent(eventLegacyId, req) { + const event = await SELECT.one.from(Events).where({ legacyId: eventLegacyId }); + if (!event) { req.reject(404, `Event not found: ${eventLegacyId}`); return null; } + return event; + } + + // Returns distinct non-anonymized Users who have at least one CLAIMED PrizeRecord for the given event UUID. + async function eligibleUserIds(eventID) { + const claimed = await SELECT.from(PrizeRecords) + .columns('user_ID') + .where({ event_ID: eventID, status: 'CLAIMED' }); + + const ids = [...new Set(claimed.map(r => r.user_ID))]; + if (!ids.length) return []; + + const users = await SELECT.from(Users) + .columns('ID', 'displayName', 'email') + .where({ ID: { in: ids } }); + + // Exclude anonymized users + return users.filter(u => u.displayName !== 'ANONYMIZED'); + } this.on('listEvents', async () => { // Find distinct event IDs that have at least one CLAIMED PrizeRecord @@ -23,6 +47,33 @@ export default class DrawingService extends cds.ApplicationService { return events; }); + this.on('pool', async (req) => { + const { eventId } = req.data; + const event = await resolveEvent(eventId, req); + if (!event) return; + + const eligible = await eligibleUserIds(event.ID); + return { eventId, eligibleCount: eligible.length }; + }); + + this.on('draw', async (req) => { + const { eventId } = req.data; + const event = await resolveEvent(eventId, req); + if (!event) return; + + const eligible = await eligibleUserIds(event.ID); + + if (!eligible.length) { + return { winner: null, eligibleCount: 0 }; + } + + const winner = eligible[Math.floor(Math.random() * eligible.length)]; + return { + winner: { displayName: winner.displayName, email: winner.email ?? null }, + eligibleCount: eligible.length + }; + }); + await super.init(); } } diff --git a/test/drawing-service.test.js b/test/drawing-service.test.js index 183b2a4aa..c5bc166de 100644 --- a/test/drawing-service.test.js +++ b/test/drawing-service.test.js @@ -94,4 +94,151 @@ describe('DrawingService', () => { expect(data.value[1].legacyId).toBe(7001); }); }); + + // --------------------------------------------------------------------------- + // pool and draw + // --------------------------------------------------------------------------- + // Seed layout (uses dedicated events 7010/7011/7012 to avoid contamination + // from the listEvents seed above which already inserted CLAIMED rows for 7001): + // Event 7010 (ID: ...010): + // User A (ID: ...031, displayName:'UserA', email:'a@test.com') — 2 CLAIMED rows (counts once) + // User B (ID: ...032, displayName:'UserB', email:null) — 1 CLAIMED row + // User C (ID: ...033, displayName:'ANONYMIZED', email:null) — 1 CLAIMED row (excluded) + // User D (ID: ...034, displayName:'UserD', email:'d@test.com') — 1 PENDING row only (excluded) + // Event 7011: no CLAIMED rows — pool = 0 + // Event 7012: only User B CLAIMED — deterministic draw with null email + // --------------------------------------------------------------------------- + describe('pool and draw', () => { + beforeAll(async () => { + const { Events, Users, PrizeRecords } = cds.entities('com.sap.developers.ims'); + + // Dedicated events for pool/draw tests + await INSERT.into(Events).entries([ + { + ID: 'bbbbbbbb-2222-0000-0000-000000000010', + name: 'Pool Draw Event A', + startDate: '2026-09-01T00:00:00Z', + endDate: '2026-09-03T23:59:59Z', + legacyId: 7010 + }, + { + ID: 'bbbbbbbb-2222-0000-0000-000000000020', + name: 'Pool Draw Event B (empty)', + startDate: '2026-09-04T00:00:00Z', + endDate: '2026-09-06T23:59:59Z', + legacyId: 7011 + }, + { + ID: 'bbbbbbbb-2222-0000-0000-000000000030', + name: 'Pool Draw Event C (B only)', + startDate: '2026-09-07T00:00:00Z', + endDate: '2026-09-09T23:59:59Z', + legacyId: 7012 + } + ]); + + // Users A-D for pool/draw tests + await INSERT.into(Users).entries([ + { ID: 'bbbbbbbb-2222-0000-0000-000000000031', uuid: 'draw-pool-u1', legacyId: 8031, displayName: 'UserA', email: 'a@test.com' }, + { ID: 'bbbbbbbb-2222-0000-0000-000000000032', uuid: 'draw-pool-u2', legacyId: 8032, displayName: 'UserB', email: null }, + { ID: 'bbbbbbbb-2222-0000-0000-000000000033', uuid: 'draw-pool-u3', legacyId: 8033, displayName: 'ANONYMIZED', email: null }, + { ID: 'bbbbbbbb-2222-0000-0000-000000000034', uuid: 'draw-pool-u4', legacyId: 8034, displayName: 'UserD', email: 'd@test.com' } + ]); + + await INSERT.into(PrizeRecords).entries([ + // Event 7010 — User A, 2 CLAIMED rows (should count as 1 distinct) + { + ID: 'bbbbbbbb-2222-0000-0000-000000000041', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000031', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000010', + status: 'CLAIMED', + legacyId: 8041 + }, + { + ID: 'bbbbbbbb-2222-0000-0000-000000000042', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000031', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000010', + status: 'CLAIMED', + legacyId: 8042 + }, + // Event 7010 — User B, 1 CLAIMED, email null + { + ID: 'bbbbbbbb-2222-0000-0000-000000000043', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000032', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000010', + status: 'CLAIMED', + legacyId: 8043 + }, + // Event 7010 — User C (ANONYMIZED), 1 CLAIMED — must be excluded from pool + { + ID: 'bbbbbbbb-2222-0000-0000-000000000044', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000033', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000010', + status: 'CLAIMED', + legacyId: 8044 + }, + // Event 7010 — User D, PENDING only — must be excluded from pool + { + ID: 'bbbbbbbb-2222-0000-0000-000000000045', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000034', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000010', + status: 'PENDING', + legacyId: 8045 + }, + // Event 7012 — only User B (null email) — ensures draw works with null email + { + ID: 'bbbbbbbb-2222-0000-0000-000000000046', + user_ID: 'bbbbbbbb-2222-0000-0000-000000000032', + event_ID: 'bbbbbbbb-2222-0000-0000-000000000030', + status: 'CLAIMED', + legacyId: 8046 + } + ]); + }); + + it('pool counts distinct non-anonymized claimers', async () => { + const { status, data } = await project.get('/drawings/pool(eventId=7010)', adminAuth); + expect(status).toBe(200); + // UserA (counted once despite 2 rows) + UserB = 2; UserC (ANONYMIZED) and UserD (PENDING) excluded + expect(data.eligibleCount).toBe(2); + expect(data.eventId).toBe(7010); + }); + + it('pool is 0 for event with no claims', async () => { + const { status, data } = await project.get('/drawings/pool(eventId=7011)', adminAuth); + expect(status).toBe(200); + expect(data.eligibleCount).toBe(0); + }); + + it('pool 404s for unknown event', async () => { + const { status } = await project.get('/drawings/pool(eventId=99999)', { + ...adminAuth, validateStatus: () => true + }); + expect(status).toBe(404); + }); + + it('draw returns a pool member', async () => { + const { status, data } = await project.get('/drawings/draw(eventId=7010)', adminAuth); + expect(status).toBe(200); + expect(data.eligibleCount).toBe(2); + expect(['UserA', 'UserB']).toContain(data.winner.displayName); + }); + + it('draw returns null winner for empty pool', async () => { + const { status, data } = await project.get('/drawings/draw(eventId=7011)', adminAuth); + expect(status).toBe(200); + expect(data.winner).toBeNull(); + expect(data.eligibleCount).toBe(0); + }); + + it('draw can return a winner with null email', async () => { + // Event 7012 has only UserB (email:null) as eligible claimer + const { status, data } = await project.get('/drawings/draw(eventId=7012)', adminAuth); + expect(status).toBe(200); + expect(data.winner.displayName).toBe('UserB'); + expect(data.winner.email).toBeNull(); + expect(data.eligibleCount).toBe(1); + }); + }); }); + From 129500be11edee98504bf4782e63b2a9b26a69fc Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Wed, 21 Oct 2026 14:32:41 -0400 Subject: [PATCH 4/9] test(#2865): add draw 404-on-unknown-event test --- test/drawing-service.test.js | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/test/drawing-service.test.js b/test/drawing-service.test.js index c5bc166de..6f88fe637 100644 --- a/test/drawing-service.test.js +++ b/test/drawing-service.test.js @@ -217,6 +217,13 @@ describe('DrawingService', () => { expect(status).toBe(404); }); + it('draw 404s for unknown event', async () => { + const { status } = await project.get('/drawings/draw(eventId=99999)', { + ...adminAuth, validateStatus: () => true + }); + expect(status).toBe(404); + }); + it('draw returns a pool member', async () => { const { status, data } = await project.get('/drawings/draw(eventId=7010)', adminAuth); expect(status).toBe(200); From e6131bcd9a6ff918b62e86d81bd1b4f9cfed9ac1 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Wed, 21 Oct 2026 14:56:13 -0400 Subject: [PATCH 5/9] feat(#2865): scaffold drawings Vue display app --- app/display-app-drawings/index.html | 16 + app/display-app-drawings/package-lock.json | 1397 ++++++++++++++++++++ app/display-app-drawings/package.json | 19 + app/display-app-drawings/public/README.md | 9 + app/display-app-drawings/src/App.vue | 5 + app/display-app-drawings/src/env.d.ts | 5 + app/display-app-drawings/src/main.ts | 5 + app/display-app-drawings/src/style.css | 2 + app/display-app-drawings/tsconfig.json | 17 + app/display-app-drawings/vite.config.ts | 23 + 10 files changed, 1498 insertions(+) create mode 100644 app/display-app-drawings/index.html create mode 100644 app/display-app-drawings/package-lock.json create mode 100644 app/display-app-drawings/package.json create mode 100644 app/display-app-drawings/public/README.md create mode 100644 app/display-app-drawings/src/App.vue create mode 100644 app/display-app-drawings/src/env.d.ts create mode 100644 app/display-app-drawings/src/main.ts create mode 100644 app/display-app-drawings/src/style.css create mode 100644 app/display-app-drawings/tsconfig.json create mode 100644 app/display-app-drawings/vite.config.ts diff --git a/app/display-app-drawings/index.html b/app/display-app-drawings/index.html new file mode 100644 index 000000000..48208cb2b --- /dev/null +++ b/app/display-app-drawings/index.html @@ -0,0 +1,16 @@ + + + + + + + Tutorial Drawings + + + + + +
+ + + diff --git a/app/display-app-drawings/package-lock.json b/app/display-app-drawings/package-lock.json new file mode 100644 index 000000000..4fa21e01b --- /dev/null +++ b/app/display-app-drawings/package-lock.json @@ -0,0 +1,1397 @@ +{ + "name": "tutorial-drawings", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "tutorial-drawings", + "version": "1.0.0", + "dependencies": { + "vue": "^3.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "typescript": "^5.6.0", + "vite": "^6.0.0" + } + }, + "node_modules/@babel/helper-string-parser": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/parser": { + "version": "7.29.9", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.9.tgz", + "integrity": "sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.8" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@babel/types": { + "version": "7.29.8", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.8.tgz", + "integrity": "sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz", + "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==", + "license": "MIT" + }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.64.12.tgz", + "integrity": "sha512-Z8lz+5UOiCJLonjeqwJlhUtHA/Sr6R1AidkccQxdWF0nigNzP81aLoU+udgOaGfL5s1uzFXzcux/pCwGAMu0lw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.64.12.tgz", + "integrity": "sha512-+tKxNa2psFmiTb0KfXlXs4Yj7vkEhzAvZ0DrQ5+0gWHQJM10jSleQ3l1FIVm5uvD53YM8lHfM4mgCLDDN9f1Gg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.64.12.tgz", + "integrity": "sha512-iiripdfvyRBugV55/Dk7GplNXQtN/PG85OcKeveU1z2UkTg0MofbBF1qk//qgPWiqJbVvQcXAVCfE56oDGO4EQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.64.12.tgz", + "integrity": "sha512-BmATib8swSMAagWNIfDZMnb+k34RXR4bbGOUfZgK8lLlniuMyVoSDDXCOGkdhtUmBXL4aygDh7bhjQ+TmhTvIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.64.12.tgz", + "integrity": "sha512-GzrO0CiUq8vcAI/hYJuIcp6wNm3T3YhOpumYtf9Z3Fz+cfN1VESCVfy7EPcM3/m2K8l+Gn2U+4XtNZjwhUXctQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.64.12.tgz", + "integrity": "sha512-Dpta6gigm4o6SYioXtTkZBlGLA/Sl6OpzgLqY+51uafJE2blPejAtR1w82CVLPN+F9cvo1+/em92x/zcOgY0uQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.64.12.tgz", + "integrity": "sha512-YpeDwQ5uFaogRvytNC8RK0keDO65kI5wNEhGSs+4GiJc3KHhxj4j5cnQh2nCu9ybJj6RP0/ycB6bkN0yFJ9I+w==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.64.12.tgz", + "integrity": "sha512-LQrB3/sImIr1ycrlEyaApOLH1rAy8JV4/dH4eoK817aWATdcs4fESXz53/w72NwDnwPM5zEUpoL7socH4ZDnwA==", + "cpu": [ + "arm" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.64.12.tgz", + "integrity": "sha512-iAF2iYu9S4SBfNsb4k0ewNBjO83WxHEltIuKQBGMzEKFgrYfmJn+gatBDXR2t/0zJN0UW0kzR4rfvQ1UHzkZ1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.64.12.tgz", + "integrity": "sha512-0MQdRtdbirJCXPIVeW/hd/LXLR8Qz96pc+2EBptuuV5q/zSGOzND7SZt8gBTE58NC9cUG/8UxcUXQiKRZ2WzzA==", + "cpu": [ + "arm64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.64.12.tgz", + "integrity": "sha512-yGDEwn0AGnrY/KCz2oY8R9KKNiQnTcwNuCWXeSXF0VLxXzlqoCgaS5QHsf1lbQvKWwPuN2Czkjk1wXRCBONTOQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.64.12.tgz", + "integrity": "sha512-AjlbD0efZEhHaJ3bsNjxSFDoguNq6VZvC+FKGJGBCA+ZN7BrZq2TuuSWFa6AI8IpOeuvhp2LoYcP7jiZwmtU0Q==", + "cpu": [ + "loong64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.64.12.tgz", + "integrity": "sha512-sDW0CiPoUCEEUV25uBG5sJ2gKe75DJqLpj91m1eK1vKffywyY7v4i6q1450ZuR6QW4NArXg7JpbMGgKU8di3HQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.64.12.tgz", + "integrity": "sha512-VTQYUhMjl8kv999VuYqScgv7P9cR9KuxI/6bkUsEa3bsswqG0+otFjuLZmJ1U+ffEP4IAz0jxOeV0DX6NRBhjg==", + "cpu": [ + "ppc64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.64.12.tgz", + "integrity": "sha512-ppsKwpSjNrzJ8hge3Z3uiCcoRgAAQk2tU7auy3MFF348ffWRldFPnWIT/a/enK7huijlTfnwn4DYpDoapEb45A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.64.12.tgz", + "integrity": "sha512-Gc3cVRC1trFH+GFWWTsJz6YuD4u75fzr/q99jqQGimonN/zvN6Wecck5WGzllOHj+XYXHcq0Ibor0tanGjfI6A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.64.12.tgz", + "integrity": "sha512-pjmDTFStvwFE3shTeUDvSDiSp8/qAf5bQXejcEUDDavWz4jEhTIpCdpS06G0PEwSCSNK0Lngjz2v9l4hk3Z74A==", + "cpu": [ + "s390x" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.64.12.tgz", + "integrity": "sha512-mkbjxPqpR91u1JD7xY4aLZhr/Mqka4/RqGcG4WNGgN/lC1o7TzyCyqJoSJXp9qVVxMwpJGFlEF1VlERb+Ghw1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "glibc" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.64.12.tgz", + "integrity": "sha512-vIeut2LkuVc3gcsawLJPahNAzvzBcFRhu2O+yVb//BuQKz7PIItMF0piXRbN/lIDO3d68TSDd368Vg6YwgTybw==", + "cpu": [ + "x64" + ], + "dev": true, + "libc": [ + "musl" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.64.12.tgz", + "integrity": "sha512-3Sx+D3r9oPkTdNUcPrIRM2Am+vzEV6VGWoImHG4ZVK2jJeOW+eDjLeTm6nvu/z/m0fiX2YmGLXIypblc336MXg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.64.12.tgz", + "integrity": "sha512-Nn2xh0teFnl71EiiUrKvECWcdGXtWF8q0uGclYwXGVw6QClGAcME0/bJSGdK+xIvd6MLEKYmjkTUvCBV+7m5Kw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.64.12.tgz", + "integrity": "sha512-n1/0H9TS+kBa4xp6n9aV7yiynBNfmerjkj2nAMIfY0ZHv1zShWRXnsAuAPAH0sfEK4ZSMK+z24q2RjAPFLCN1w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.64.12.tgz", + "integrity": "sha512-AdlDbQpsLfGlsHr1NfLZFtdnvxCKNp9TwlYVYExs8JJLWjLafUQEKJk0r9zUbmU57mw9nglMEvzqPHuLWYmaKw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.64.12.tgz", + "integrity": "sha512-u5SSUJBHod51UamjkigSr+JPre6xMy5vaWTTV2K0SnFG4RuKyYpf66dTSLFKX2cwaldpwyBurXPUCxq8riQMEg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.64.12.tgz", + "integrity": "sha512-j5rXD4R/4DM5+V991AA9xOnG2Rwu2g3p5TPgu1Eu7apju0NUrLl+Bn/KpWc6wYJtlXqzwz1r+LNV1JeBU4VGvw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-vue": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz", + "integrity": "sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "vite": "^5.0.0 || ^6.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vue/compiler-core": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.5.44.tgz", + "integrity": "sha512-k34nCytbK8PQ1gR2mGROYOw6Pnv/mumqIdrZ3x2tbyXZDWm7ViKGLTfdB+DRrWqEQsLgGJvn0WN9jBif5zCTLQ==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.9", + "@vue/shared": "3.5.44", + "entities": "^7.0.1", + "estree-walker": "^2.0.2", + "source-map-js": "^1.2.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.5.44.tgz", + "integrity": "sha512-W5AoBFWdw1lQAKiHowkfgFKFab82oSRT6sGsPfw/q1GRjsy/ZJFLHGhJyBRIFN9APLTUduDH9U2JAiPOIyKuYg==", + "license": "MIT", + "dependencies": { + "@vue/compiler-core": "3.5.44", + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.5.44.tgz", + "integrity": "sha512-dzHmQ4jXTpdr5FjIQUeykVakN/uzqFbUo9/C5ikBFg3a3PeM6uRxeczx1ZbXBML0/zJstEh3ygYipzR5Z7b2Pw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.9", + "@vue/compiler-core": "3.5.44", + "@vue/compiler-dom": "3.5.44", + "@vue/compiler-ssr": "3.5.44", + "@vue/shared": "3.5.44", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.21", + "postcss": "^8.5.29", + "source-map-js": "^1.2.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.5.44.tgz", + "integrity": "sha512-xr5gb1JVHlfXJwSlRz1FeP6uxoazdzHuMTVLE7up+4Vrqxk219CvxLPxD9NXBt47yvZW7+p4DXfyp4O1fkByIA==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.44", + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.5.44.tgz", + "integrity": "sha512-u+QJdKeBBEADkPdevsL+6E12QlYVfBD8Zcw5qjDQKtamI6l14KyqazJMobhCKelhWN5zpyKUiEDoTyHE93cqWw==", + "license": "MIT", + "dependencies": { + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.5.44.tgz", + "integrity": "sha512-uEa/ndwjhrrhnOfb2p+sZy6zeoamJOISqaLSJGk/8R6nABT1ZldpzcJoPzKj+zHVomVBPcmQYyC2r4LtvDV6CQ==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.44", + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.5.44.tgz", + "integrity": "sha512-3fc4ARcXVhVBaJ3ujWiNZJARD6pkyRcwG+ZYhMzjKtKUp7MAY0HLwr7r3Smep3Li6ZaWT6ewdmx8XqdaD5oNZg==", + "license": "MIT", + "dependencies": { + "@vue/reactivity": "3.5.44", + "@vue/runtime-core": "3.5.44", + "@vue/shared": "3.5.44", + "csstype": "^3.2.3" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.5.44.tgz", + "integrity": "sha512-R4S1UoH4h5Frrauv8Z5XE5TxV9oBiUSgWIC4TyVK1FK+RocRnOLwFA5Xcmo2011RlgJp+AbZCnw6afrhlgRuUQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-ssr": "3.5.44", + "@vue/runtime-dom": "3.5.44", + "@vue/shared": "3.5.44" + } + }, + "node_modules/@vue/shared": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.5.44.tgz", + "integrity": "sha512-H82XLH0DjmSOdbEwO15x3QTo1f5PWEtv237eLpYKc0mqLu+B8hnq16lFWmuBmNurkeiG3wr+oM1FhOll/YVFRg==", + "license": "MIT" + }, + "node_modules/csstype": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", + "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", + "license": "MIT" + }, + "node_modules/entities": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/entities/-/entities-7.0.1.tgz", + "integrity": "sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, + "node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==", + "license": "MIT" + }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/nanoid": { + "version": "3.3.20", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", + "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/picomatch": { + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.7.tgz", + "integrity": "sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/postcss": { + "version": "8.5.31", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.31.tgz", + "integrity": "sha512-YUbWYCgErwnMAVkjUnYa+KlMBzGpHwiIt/DE7f0G33AuX2LH9UcTk6KgWxd3zRU+pN3u/C+Xl3EnMLM4Hea7Sg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.20", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/rollup": { + "version": "4.64.12", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.64.12.tgz", + "integrity": "sha512-GhAu8ZglZ61VWSX/ZW3K/qkazgURBIQL0/Yy51QsS8DPyebR5Cf/Z2PANJFpTU0aVZ9byJcyqR6xvBbkXhdGYQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.64.12", + "@rollup/rollup-android-arm64": "4.64.12", + "@rollup/rollup-darwin-arm64": "4.64.12", + "@rollup/rollup-darwin-x64": "4.64.12", + "@rollup/rollup-freebsd-arm64": "4.64.12", + "@rollup/rollup-freebsd-x64": "4.64.12", + "@rollup/rollup-linux-arm-gnueabihf": "4.64.12", + "@rollup/rollup-linux-arm-musleabihf": "4.64.12", + "@rollup/rollup-linux-arm64-gnu": "4.64.12", + "@rollup/rollup-linux-arm64-musl": "4.64.12", + "@rollup/rollup-linux-loong64-gnu": "4.64.12", + "@rollup/rollup-linux-loong64-musl": "4.64.12", + "@rollup/rollup-linux-ppc64-gnu": "4.64.12", + "@rollup/rollup-linux-ppc64-musl": "4.64.12", + "@rollup/rollup-linux-riscv64-gnu": "4.64.12", + "@rollup/rollup-linux-riscv64-musl": "4.64.12", + "@rollup/rollup-linux-s390x-gnu": "4.64.12", + "@rollup/rollup-linux-x64-gnu": "4.64.12", + "@rollup/rollup-linux-x64-musl": "4.64.12", + "@rollup/rollup-openbsd-x64": "4.64.12", + "@rollup/rollup-openharmony-arm64": "4.64.12", + "@rollup/rollup-win32-arm64-msvc": "4.64.12", + "@rollup/rollup-win32-ia32-msvc": "4.64.12", + "@rollup/rollup-win32-x64-gnu": "4.64.12", + "@rollup/rollup-win32-x64-msvc": "4.64.12", + "fsevents": "~2.3.2" + } + }, + "node_modules/source-map-js": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz", + "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/typescript": { + "version": "5.9.3", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", + "devOptional": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/vite": { + "version": "6.4.4", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.4.tgz", + "integrity": "sha512-xGjU/SyjeNyb0E4KxXBvYz/1C+3JXuujvf2hXNOtnlcE+58o5ajd9N2/n2VhkiJvpCP2qQasnUpx5PXNeI+p1Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vue": { + "version": "3.5.44", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.5.44.tgz", + "integrity": "sha512-dvvmaOCG7lAMkJTPqPwWeGMsPS6DIsFsfZDUgBp9V6mHf9b3+/wyVwYR4Es+WV6uJC0FKRxp81Obcq0ZwUcGFQ==", + "license": "MIT", + "dependencies": { + "@vue/compiler-dom": "3.5.44", + "@vue/compiler-sfc": "3.5.44", + "@vue/runtime-dom": "3.5.44", + "@vue/server-renderer": "3.5.44", + "@vue/shared": "3.5.44" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + } + } +} diff --git a/app/display-app-drawings/package.json b/app/display-app-drawings/package.json new file mode 100644 index 000000000..872a802bd --- /dev/null +++ b/app/display-app-drawings/package.json @@ -0,0 +1,19 @@ +{ + "name": "tutorial-drawings", + "private": true, + "version": "1.0.0", + "type": "module", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "vue": "^3.5.0" + }, + "devDependencies": { + "@vitejs/plugin-vue": "^5.0.0", + "typescript": "^5.6.0", + "vite": "^6.0.0" + } +} diff --git a/app/display-app-drawings/public/README.md b/app/display-app-drawings/public/README.md new file mode 100644 index 000000000..97865b056 --- /dev/null +++ b/app/display-app-drawings/public/README.md @@ -0,0 +1,9 @@ +# Mascot Assets + +## kasimir-tuxedo.png + +**PLACEHOLDER** — This is a minimal 1x1 transparent PNG created as a build-safe placeholder. + +Replace with the real tuxedo-Kasimir mascot image (issue #2865). + +The app references this asset at `/drawings-ui/kasimir-tuxedo.png`. diff --git a/app/display-app-drawings/src/App.vue b/app/display-app-drawings/src/App.vue new file mode 100644 index 000000000..7fd437c2b --- /dev/null +++ b/app/display-app-drawings/src/App.vue @@ -0,0 +1,5 @@ + + + diff --git a/app/display-app-drawings/src/env.d.ts b/app/display-app-drawings/src/env.d.ts new file mode 100644 index 000000000..2b97bd961 --- /dev/null +++ b/app/display-app-drawings/src/env.d.ts @@ -0,0 +1,5 @@ +declare module '*.vue' { + import type { DefineComponent } from 'vue' + const component: DefineComponent<{}, {}, any> + export default component +} diff --git a/app/display-app-drawings/src/main.ts b/app/display-app-drawings/src/main.ts new file mode 100644 index 000000000..fe5bae398 --- /dev/null +++ b/app/display-app-drawings/src/main.ts @@ -0,0 +1,5 @@ +import { createApp } from 'vue' +import App from './App.vue' +import './style.css' + +createApp(App).mount('#app') diff --git a/app/display-app-drawings/src/style.css b/app/display-app-drawings/src/style.css new file mode 100644 index 000000000..952f4b15f --- /dev/null +++ b/app/display-app-drawings/src/style.css @@ -0,0 +1,2 @@ +* { margin: 0; padding: 0; box-sizing: border-box; } +html, body { width: 100%; height: 100%; overflow: hidden; } diff --git a/app/display-app-drawings/tsconfig.json b/app/display-app-drawings/tsconfig.json new file mode 100644 index 000000000..ff5009d38 --- /dev/null +++ b/app/display-app-drawings/tsconfig.json @@ -0,0 +1,17 @@ +{ + "compilerOptions": { + "target": "ES2020", + "module": "ESNext", + "moduleResolution": "bundler", + "strict": true, + "jsx": "preserve", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + "noEmit": true, + "paths": { "@/*": ["./src/*"] } + }, + "include": ["src/**/*.ts", "src/**/*.vue"] +} diff --git a/app/display-app-drawings/vite.config.ts b/app/display-app-drawings/vite.config.ts new file mode 100644 index 000000000..afbe62195 --- /dev/null +++ b/app/display-app-drawings/vite.config.ts @@ -0,0 +1,23 @@ +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' + +export default defineConfig({ + plugins: [vue()], + base: '/drawings-ui/', + build: { + outDir: 'dist', + emptyOutDir: true, + }, + server: { + proxy: { + '/rest': { + target: 'http://localhost:4004', + changeOrigin: true, + }, + '/drawings': { + target: 'http://localhost:4004', + changeOrigin: true, + }, + }, + }, +}) From 153345495de071c425e3388611a97c8289d713de Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Wed, 21 Oct 2026 15:00:05 -0400 Subject: [PATCH 6/9] fix(#2865): add .gitignore exception for PNG, create index.template.html --- .gitignore | 1 + app/display-app-drawings/index.template.html | 20 ++++++++++++++++++ .../public/kasimir-tuxedo.png | Bin 0 -> 68 bytes 3 files changed, 21 insertions(+) create mode 100644 app/display-app-drawings/index.template.html create mode 100644 app/display-app-drawings/public/kasimir-tuxedo.png diff --git a/.gitignore b/.gitignore index 944678f5f..ca2e51dc0 100644 --- a/.gitignore +++ b/.gitignore @@ -56,6 +56,7 @@ hugo/static-qa/ !hugo/static/images/devtoberfest/selfie/thumbnails/*.png !hugo/static/images/devtoberfest/selfie/stickers/*.png !hugo/static/images/devtoberfest/selfie/backgrounds/*.png +!app/display-app-drawings/public/*.png hugo/static/js/* !hugo/static/js/joule.js !hugo/static/js/joule-render.js diff --git a/app/display-app-drawings/index.template.html b/app/display-app-drawings/index.template.html new file mode 100644 index 000000000..26bbb92c2 --- /dev/null +++ b/app/display-app-drawings/index.template.html @@ -0,0 +1,20 @@ + + + + + + + Tutorial Drawings + + + + + + +
+ + + diff --git a/app/display-app-drawings/public/kasimir-tuxedo.png b/app/display-app-drawings/public/kasimir-tuxedo.png new file mode 100644 index 0000000000000000000000000000000000000000..35a340b6565bfc92ef9d53ef01e30267f65d4ec2 GIT binary patch literal 68 zcmeAS@N?(olHy`uVBq!ia0vp^j3CUx1|;Q0k8}blZci7-kcv6U2@FgO46F={RdK8B Pfx--)u6{1-oD!M Date: Wed, 21 Oct 2026 15:08:11 -0400 Subject: [PATCH 7/9] =?UTF-8?q?feat(#2865):=20drawings=20UI=20=E2=80=94=20?= =?UTF-8?q?event=20picker,=20draw,=20winner=20reveal=20with=20Kasimir?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- app/display-app-drawings/public/README.md | 5 + app/display-app-drawings/src/App.vue | 596 +++++++++++++++++- .../src/composables/useConfetti.ts | 58 ++ 3 files changed, 657 insertions(+), 2 deletions(-) create mode 100644 app/display-app-drawings/src/composables/useConfetti.ts diff --git a/app/display-app-drawings/public/README.md b/app/display-app-drawings/public/README.md index 97865b056..b981f85ab 100644 --- a/app/display-app-drawings/public/README.md +++ b/app/display-app-drawings/public/README.md @@ -7,3 +7,8 @@ Replace with the real tuxedo-Kasimir mascot image (issue #2865). The app references this asset at `/drawings-ui/kasimir-tuxedo.png`. + +## Post-deploy smoke check + +The post-deploy `e2e` job should hit `/drawings-ui/` to verify the app loads; it is an XSUAA-protected operator screen so full interaction requires auth. + diff --git a/app/display-app-drawings/src/App.vue b/app/display-app-drawings/src/App.vue index 7fd437c2b..a3f477e9b 100644 --- a/app/display-app-drawings/src/App.vue +++ b/app/display-app-drawings/src/App.vue @@ -1,5 +1,597 @@ - + + + diff --git a/app/display-app-drawings/src/composables/useConfetti.ts b/app/display-app-drawings/src/composables/useConfetti.ts new file mode 100644 index 000000000..bdfba1b7a --- /dev/null +++ b/app/display-app-drawings/src/composables/useConfetti.ts @@ -0,0 +1,58 @@ +import { ref } from 'vue' + +export interface ConfettiParticle { + id: number + x: number + y: number + color: string + size: number + rotation: number + delay: number + shape: 'square' | 'circle' | 'strip' +} + +function getThemeColors(): string[] { + const el = document.documentElement + const style = getComputedStyle(el) + return [ + style.getPropertyValue('--d-confetti-1').trim() || '#0070f2', + style.getPropertyValue('--d-confetti-2').trim() || '#0064d9', + style.getPropertyValue('--d-confetti-3').trim() || '#107e3e', + ] +} + +export function useConfetti() { + const particles = ref([]) + const active = ref(false) + let idCounter = 0 + + function burst() { + const colors = getThemeColors() + const shapes: ConfettiParticle['shape'][] = ['square', 'circle', 'strip'] + const count = 70 + Math.floor(Math.random() * 30) + const newParticles: ConfettiParticle[] = [] + + for (let i = 0; i < count; i++) { + newParticles.push({ + id: ++idCounter, + x: 10 + Math.random() * 80, + y: -10 - Math.random() * 20, + color: colors[Math.floor(Math.random() * colors.length)], + size: 6 + Math.random() * 12, + rotation: Math.random() * 360, + delay: Math.random() * 0.8, + shape: shapes[Math.floor(Math.random() * shapes.length)], + }) + } + + particles.value = newParticles + active.value = true + + setTimeout(() => { + particles.value = [] + active.value = false + }, 4000) + } + + return { particles, active, burst } +} From 9f59e1b2ef3627cdf182c7716182dda36135f9e7 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Wed, 21 Oct 2026 15:12:01 -0400 Subject: [PATCH 8/9] feat(#2865): wire /drawings-ui approuter route + MTA builder (Admin scope) --- .deploy/mta.yaml | 6 ++++++ approuter/xs-app.json | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/.deploy/mta.yaml b/.deploy/mta.yaml index 21112c10c..6a870c94d 100644 --- a/.deploy/mta.yaml +++ b/.deploy/mta.yaml @@ -283,6 +283,12 @@ modules: - npm --prefix ../app/display-app run build - mkdir -p static/display-app - cp -r ../app/display-app/dist/. static/display-app/ + # Drawings UI (#2865) — Vue 3 + Vite, Admin scope, base '/drawings-ui/'. + # Served from static/drawings-ui via the /drawings-ui/ approuter route. + - npm --prefix ../app/display-app-drawings install + - npm --prefix ../app/display-app-drawings run build + - mkdir -p static/drawings-ui + - cp -r ../app/display-app-drawings/dist/. static/drawings-ui/ # Data Inspector UI (#999) — bundled inside @cap-js/data-inspector's # node_modules. Rewrite the UI5 bootstrap CDN in index.html from # sapui5.hana.ondemand.com (plugin default) to ui5.sap.com — the diff --git a/approuter/xs-app.json b/approuter/xs-app.json index 15c52b12b..c4101e21a 100644 --- a/approuter/xs-app.json +++ b/approuter/xs-app.json @@ -76,6 +76,13 @@ "authenticationType": "xsuaa", "scope": "$XSAPPNAME.DisplayApp" }, + { + "source": "^/drawings-ui/(.*)$", + "target": "/drawings-ui/$1", + "localDir": "static", + "authenticationType": "xsuaa", + "scope": "$XSAPPNAME.Admin" + }, { "source": "^/analytics-ui/(.*)$", "target": "/analytics-ui/$1", @@ -353,6 +360,13 @@ "authenticationType": "xsuaa", "scope": "$XSAPPNAME.DisplayApp" }, + { + "source": "^/drawings/(.*)$", + "target": "/drawings/$1", + "destination": "srv-api", + "authenticationType": "xsuaa", + "scope": "$XSAPPNAME.Admin" + }, { "source": "^/api/v1/(.*)$", "target": "/api/v1/$1", From 5fc7e59944670330d9626520c4da7914c22e0eb9 Mon Sep 17 00:00:00 2001 From: Thomas Jung Date: Wed, 21 Oct 2026 15:14:25 -0400 Subject: [PATCH 9/9] docs(#2865): document drawings UI + DrawingService endpoints --- docs/developers/operations/testing-endpoints.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/docs/developers/operations/testing-endpoints.md b/docs/developers/operations/testing-endpoints.md index 61592e2ef..5c1cad796 100644 --- a/docs/developers/operations/testing-endpoints.md +++ b/docs/developers/operations/testing-endpoints.md @@ -66,6 +66,18 @@ All admin routes require the **`Admin`** XSUAA scope. --- +## Drawings UI (Vue 3 — Prize Drawing) + +All drawings routes require the **`Admin`** XSUAA scope. + +| URL | Description | Auth | +|-----|-------------|------| +| `/drawings-ui/` | Standalone event drawing app — operator selects event, draws random winner from eligible prize claimants, reveals winner name and email | XSUAA + `Admin` | + +> The drawings-ui is a Vue 3 application deployed as static files at `approuter/static/drawings-ui/`. It calls the `DrawingService` at `/drawings` to list events with claimed prizes, query the eligible winner pool, and perform the random winner selection. Winner contact information (displayName, email) is returned directly and gated by the Admin scope due to PII exposure. + +--- + ## Analytics Explorer (Vue 3 + Monaco — Ad-hoc Analytics) | URL | Description | Auth | @@ -148,6 +160,8 @@ When `EXPOSE_CAP_UI=true` is set on the CAP srv app, these are accessible throug | `/admin/exports/` | ExportsService — `exportLegacyData(format)` action | XSUAA + `Admin` | | `/display/$metadata` | DisplayService OData metadata | XSUAA | | `/display/` | DisplayService — read-only data for event dashboards | XSUAA | +| `/drawings/$metadata` | DrawingService OData metadata | XSUAA + `Admin` | +| `/drawings/` | DrawingService — prize drawing functions (events, winner pool, random selection) | XSUAA + `Admin` | | `/api/v1/$metadata` | ConsolidationService (v1 compat) OData metadata | XSUAA | | `/api/v1/` | ConsolidationService — legacy API compatibility | XSUAA | | `/search/$metadata` | SearchService OData metadata | None | @@ -187,6 +201,9 @@ When `EXPOSE_CAP_UI=true` is set on the CAP srv app, these are accessible throug | `/api/codecheck` | POST | AI code-check spike (issue #171, gated on `ChatSettings.codeCheckEnabled`). Body: `{ tutorialSlug, stepNumber, submittedCode, language? }`. Returns `{ verdict: 'pass'\|'partial'\|'fail', summary, suggestions[], correctAspects[] }`. 503 when flag off; 429 with `Retry-After` on per-user 30/hr or per-(user,slug,step) 5/5min cap. | XSUAA | | `/author/generateOsVariants` | POST | AI-assisted OS variant generation for the VS Code authoring plugin (issue #173). Body: `{ sourceMarkdown, sourceOS, targetOSes[], context? }`. Returns `{ variants[], model, tokensUsed, requestId }`. 60/hr per author. See spec [#173](../../superpowers/specs/2026-06-09-173-os-conditional-content-design.md) §5. | XSUAA + `Tutorial.Author` | | `/admin/embeddings/stats` | GET | Tutorial embedding coverage / drift statistics | XSUAA + `Admin` | +| `/drawings/listEvents()` | GET | OData function — events with ≥1 claimed prize (newest-first). Returns `{ legacyId, name, startDate }`. | XSUAA + `Admin` | +| `/drawings/pool(eventId=)` | GET | OData function — eligible winner pool statistics for an event. Returns `{ eventId, eligibleCount }` (count of distinct non-anonymized users with CLAIMED prize claims, excludes PII). | XSUAA + `Admin` | +| `/drawings/draw(eventId=)` | GET | OData function — select one random winner and return their contact info. Returns `{ winner: { displayName, email } \| null, eligibleCount }`. Server-side winner selection; returns PII. | XSUAA + `Admin` | | `/api/alerts` | GET | Active alerts with audience=ALL. 60 s cache. | None | | `/api/alerts/me` | GET | ALL + AUTHENTICATED + ADMIN (if admin). 30 s private cache. | XSUAA | | `/api/homepage/events` | GET | 3-4 upcoming events (merged from DB + events calendar). 60 s cache. | None |