From ac520ba725f5230ea85f166f914dcae9309555fc Mon Sep 17 00:00:00 2001 From: Kevin Williams Date: Wed, 4 Nov 2026 00:54:22 +0000 Subject: [PATCH 1/2] perf(ui): Load the portal's shared modules instead of bundling copies The plugin bundled its own copy of every module it shares with cloud-portal: react, react-dom, react-router, react-query and the curated datum-ui subset. The host's copy always wins at runtime, but the browser still fetched and parsed each fallback on every load. Builds now mark those modules import: false, so they rely on the host, which registers every one of them. Dev keeps the fallbacks so the standalone preview runs without a host. react/jsx-runtime keeps its own small copy: the host does not share that subpath, and left implicit it would inherit import: false from react and fail to load. The built bundle drops from 80 to 46 JavaScript files, 2.4MB to 2.0MB raw and 770KB to 622KB gzipped. Ported from #481, which predates #606 and the move to pnpm. --- ui/consumer/vite.config.ts | 178 ++++++++++++++++++++----------------- 1 file changed, 97 insertions(+), 81 deletions(-) diff --git a/ui/consumer/vite.config.ts b/ui/consumer/vite.config.ts index 326eed82..8751388f 100644 --- a/ui/consumer/vite.config.ts +++ b/ui/consumer/vite.config.ts @@ -79,87 +79,103 @@ function consoleWasm(): Plugin { // browser never contacts this origin directly. MF's automatic publicPath makes // federated chunks resolve relative to remoteEntry.js, which is what lets them // load correctly through that same-origin proxy prefix. -export default defineConfig({ - server: { - port: 7778, - strictPort: true, - // Allow cross-origin fetches of the manifest/remote during Tier 0/standalone. - cors: true, - }, - preview: { - port: 7778, - // Bind IPv4 explicitly — "localhost"-only binding resolves to IPv6-only - // ([::1]) on some hosts, and the portal's server-side manifest fetch to - // http://localhost:7778 then gets ECONNREFUSED on IPv4, silently dropping - // the plugin out of the registry with no visible error. - host: '127.0.0.1', - strictPort: true, - cors: true, - }, - build: { - target: 'esnext', - // Minified: the portal downloads and parses every page's chunks on first - // visit, and minifying cuts the bundle from ~6.0MB to ~2.5MB (~1.3MB to - // ~0.8MB gzipped). Source maps keep it readable in devtools; the browser - // fetches them only when devtools is open. - sourcemap: true, - }, - experimental: { - renderBuiltUrl(_filename, { hostType }) { - return hostType === 'js' ? { relative: true } : undefined; +export default defineConfig(({ command }) => { + const hostShared = (requiredVersion: string | false) => ({ + singleton: true, + requiredVersion, + ...(command === 'build' ? { import: false as const } : {}), + }); + + return { + server: { + port: 7778, + strictPort: true, + // Allow cross-origin fetches of the manifest/remote during Tier 0/standalone. + cors: true, }, - }, - plugins: [ - react(), - consoleWasm(), - federation({ - // MUST equal the manifest `name` — the host keys the remote by this id. - name: 'workload.compute.datumapis.com', - // The manifest's `remoteEntry` field points the host at this filename, - // requested through the asset proxy as /api/plugins/compute/remoteEntry.js. - filename: 'remoteEntry.js', - manifest: true, - // Exposed keys map 1:1 to the manifest's `exposedModules` keys / $codeRefs. - // The host loads e.g. loadRemote('workload.compute.datumapis.com/WorkloadList'). - exposes: { - './WorkloadList': './src/pages/workload-list.tsx', - './WorkloadDetail': './src/pages/workload-detail.tsx', - './InstanceDetail': './src/pages/instance-detail.tsx', - './InstanceShellWindow': './src/pages/instance-shell-window.tsx', - './WorkloadsHomeColumn': './src/cards/workloads-home-column.tsx', - }, - // Host-pinned singletons. requiredVersion tracks the host's majors - // (react 19, react-router 8, react-query 5) — cloud-portal moved - // react-router 7 -> 8 in fabef049 without bumping this, which silently - // broke every plugin page here (Module Federation rejects a singleton - // whose requiredVersion the host's actual version doesn't satisfy). - // Keep this in lockstep with cloud-portal's package.json majors. - // singleton:true guarantees one instance — the host provides all of - // these, so plugin queries share the host's QueryClient cache. - shared: { - react: { singleton: true, requiredVersion: '^19.0.0' }, - 'react-dom': { singleton: true, requiredVersion: '^19.0.0' }, - 'react-router': { singleton: true, requiredVersion: '^8.0.0' }, - '@tanstack/react-query': { singleton: true, requiredVersion: '^5.0.0' }, - // Curated datum-ui subset shared by the host (see the host's - // federation-host.ts DATUM_UI_SHARED). requiredVersion:false — the - // host's copy always wins, which is what keeps styling identical to - // built-in pages; the local install is types + standalone fallback. - '@datum-cloud/datum-ui/badge': { singleton: true, requiredVersion: false }, - '@datum-cloud/datum-ui/button': { singleton: true, requiredVersion: false }, - '@datum-cloud/datum-ui/card': { singleton: true, requiredVersion: false }, - '@datum-cloud/datum-ui/empty-content': { singleton: true, requiredVersion: false }, - '@datum-cloud/datum-ui/icons': { singleton: true, requiredVersion: false }, - // Do not share `logs`: MF colocates lucide-react / date-fns into the - // logs loadShare chunk, so a host-provided logs module replaces those - // exports and crashes WorkloadDetail / related pages. - // `select` is shared by the host (see cloud-portal `federation-host.ts`) so - // the overview range control renders the same Select as ALB. - '@datum-cloud/datum-ui/select': { singleton: true, requiredVersion: false }, - '@datum-cloud/datum-ui/separator': { singleton: true, requiredVersion: false }, - '@datum-cloud/datum-ui/skeleton': { singleton: true, requiredVersion: false }, - '@datum-cloud/datum-ui/table': { singleton: true, requiredVersion: false }, + preview: { + port: 7778, + // Bind IPv4 explicitly — "localhost"-only binding resolves to IPv6-only + // ([::1]) on some hosts, and the portal's server-side manifest fetch to + // http://localhost:7778 then gets ECONNREFUSED on IPv4, silently dropping + // the plugin out of the registry with no visible error. + host: '127.0.0.1', + strictPort: true, + cors: true, + }, + build: { + target: 'esnext', + // Minified: the portal downloads and parses every page's chunks on first + // visit, and minifying cuts the bundle from ~6.0MB to ~2.5MB (~1.3MB to + // ~0.8MB gzipped). Source maps keep it readable in devtools; the browser + // fetches them only when devtools is open. + sourcemap: true, + }, + experimental: { + renderBuiltUrl(_filename, { hostType }) { + return hostType === 'js' ? { relative: true } : undefined; }, - }), - ], + }, + plugins: [ + react(), + consoleWasm(), + federation({ + // MUST equal the manifest `name` — the host keys the remote by this id. + name: 'workload.compute.datumapis.com', + // The manifest's `remoteEntry` field points the host at this filename, + // requested through the asset proxy as /api/plugins/compute/remoteEntry.js. + filename: 'remoteEntry.js', + manifest: true, + // Exposed keys map 1:1 to the manifest's `exposedModules` keys / $codeRefs. + // The host loads e.g. loadRemote('workload.compute.datumapis.com/WorkloadList'). + exposes: { + './WorkloadList': './src/pages/workload-list.tsx', + './WorkloadDetail': './src/pages/workload-detail.tsx', + './InstanceDetail': './src/pages/instance-detail.tsx', + './InstanceShellWindow': './src/pages/instance-shell-window.tsx', + './WorkloadsHomeColumn': './src/cards/workloads-home-column.tsx', + }, + // Host-pinned singletons. requiredVersion tracks the host's majors + // (react 19, react-router 8, react-query 5) — cloud-portal moved + // react-router 7 -> 8 in fabef049 without bumping this, which silently + // broke every plugin page here (Module Federation rejects a singleton + // whose requiredVersion the host's actual version doesn't satisfy). + // Keep this in lockstep with cloud-portal's package.json majors. + // singleton:true guarantees one instance — the host provides all of + // these, so plugin queries share the host's QueryClient cache. + // Builds rely on the host for these (import: false), so they bundle no + // fallback copy, which the browser would otherwise fetch and parse on + // every load though the host's copy always wins. Dev keeps the + // fallbacks so the standalone preview runs without a host. + shared: { + react: hostShared('^19.0.0'), + // The host does not share this subpath, so it keeps its (small) + // fallback; left implicit it would inherit import: false from + // `react` and throw "must be provided by host". + 'react/jsx-runtime': { singleton: true, requiredVersion: '^19.0.0' }, + 'react-dom': hostShared('^19.0.0'), + 'react-router': hostShared('^8.0.0'), + '@tanstack/react-query': hostShared('^5.0.0'), + // Curated datum-ui subset shared by the host (see the host's + // federation-host.ts DATUM_UI_SHARED). requiredVersion:false — the + // host's copy always wins, which is what keeps styling identical to + // built-in pages; the local install is types + standalone fallback. + '@datum-cloud/datum-ui/badge': hostShared(false), + '@datum-cloud/datum-ui/button': hostShared(false), + '@datum-cloud/datum-ui/card': hostShared(false), + '@datum-cloud/datum-ui/empty-content': hostShared(false), + '@datum-cloud/datum-ui/icons': hostShared(false), + // Do not share `logs`: MF colocates lucide-react / date-fns into the + // logs loadShare chunk, so a host-provided logs module replaces those + // exports and crashes WorkloadDetail / related pages. + // `select` is shared by the host (see cloud-portal `federation-host.ts`) so + // the overview range control renders the same Select as ALB. + '@datum-cloud/datum-ui/select': hostShared(false), + '@datum-cloud/datum-ui/separator': hostShared(false), + '@datum-cloud/datum-ui/skeleton': hostShared(false), + '@datum-cloud/datum-ui/table': hostShared(false), + }, + }), + ], + }; }); From dec5f881876fee1f646cbc3204d1f947f53ec62e Mon Sep 17 00:00:00 2001 From: Kevin Williams Date: Wed, 4 Nov 2026 00:54:22 +0000 Subject: [PATCH 2/2] feat(ui): Upload the consumer plugin's source maps to Sentry The plugin runs inside cloud-portal, so its errors land in the portal's Sentry project with minified frames. The build now uploads its maps there, matched by debug ID rather than release, since the portal's release names its own build. The maps stay in the image as well, so devtools keep showing source as they have since #606. - @sentry/vite-plugin 5.4.1, the version cloud-portal resolves, uploads only when SENTRY_AUTH_TOKEN is set. Local and fork builds skip it, and a failed upload logs without failing the build. - @sentry/cli may run its install script locally. CI and the image install with --ignore-scripts and use its prebuilt Linux packages. - The token reaches the image build as a BuildKit secret through publish-docker's build-secrets. The consumer job drops `secrets: inherit`, which would also forward the token as a build argument that persists in the exported build cache. Ported from #481. --- .github/workflows/publish.yaml | 7 +- ui/consumer/Dockerfile | 6 +- ui/consumer/package.json | 1 + ui/consumer/pnpm-lock.yaml | 335 ++++++++++++++++++++++++++++++++ ui/consumer/pnpm-workspace.yaml | 5 +- ui/consumer/vite.config.ts | 13 ++ 6 files changed, 363 insertions(+), 4 deletions(-) diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 0659574a..53141295 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -30,7 +30,12 @@ jobs: image-name: compute-consumer-ui-plugin context: . dockerfile-path: ui/consumer/Dockerfile - secrets: inherit + # The Sentry token goes in as a BuildKit secret only. `secrets: inherit` + # would also pass it to publish-docker's SENTRY_AUTH_TOKEN, which it + # forwards as a build-arg, and build-args persist in the exported cache. + secrets: + build-secrets: | + "SENTRY_AUTH_TOKEN=${{ secrets.SENTRY_AUTH_TOKEN }}" # Deployed as a Component (config/components/provider-ui-plugin) folded # into compute-manager's own kustomize bundle/Flux Kustomization — same diff --git a/ui/consumer/Dockerfile b/ui/consumer/Dockerfile index 2f4663cd..c4808e3d 100644 --- a/ui/consumer/Dockerfile +++ b/ui/consumer/Dockerfile @@ -44,7 +44,11 @@ RUN npm install -g "$(node -p 'require("./package.json").packageManager')" --no- COPY ui/consumer/ . COPY --from=console-wasm /out/ /console-wasm/ ENV CONSOLE_WASM_DIR=/console-wasm -RUN pnpm build +# The Sentry token is a BuildKit secret (publish-docker's build-secrets), so it +# never lands in a layer, the image history or the exported build cache. When +# absent the file is missing, the variable is empty and the upload is skipped. +RUN --mount=type=secret,id=SENTRY_AUTH_TOKEN,required=false \ + SENTRY_AUTH_TOKEN="$(cat /run/secrets/SENTRY_AUTH_TOKEN 2>/dev/null)" pnpm build FROM node:26.13.0-alpine3.24@sha256:c6ad9e9c1d80f6eb607c26b4b0a132ff7a2344fad1c78ea3baa2a7d4c8de6d94 # Node keeps its own copy of the CA roots apart from Alpine's bundle. Fail the diff --git a/ui/consumer/package.json b/ui/consumer/package.json index cf63f851..b5a53967 100644 --- a/ui/consumer/package.json +++ b/ui/consumer/package.json @@ -34,6 +34,7 @@ }, "devDependencies": { "@module-federation/vite": "^1.23.9", + "@sentry/vite-plugin": "5.4.1", "@types/node": "^26.6.6", "@types/react": "^19.3.0", "@types/react-dom": "^19.3.0", diff --git a/ui/consumer/pnpm-lock.yaml b/ui/consumer/pnpm-lock.yaml index 00c06662..d600b664 100644 --- a/ui/consumer/pnpm-lock.yaml +++ b/ui/consumer/pnpm-lock.yaml @@ -69,6 +69,9 @@ importers: '@module-federation/vite': specifier: ^1.23.9 version: 1.23.9(typescript@7.0.2)(vite@7.3.7(@types/node@26.6.6)) + '@sentry/vite-plugin': + specifier: 5.4.1 + version: 5.4.1(rollup@4.62.2) '@types/node': specifier: ^26.6.6 version: 26.6.6 @@ -1231,6 +1234,82 @@ packages: cpu: [x64] os: [win32] + '@sentry/bundler-plugins@10.78.0': + resolution: {integrity: sha512-vI52/j8xjDewXfbfdL0V/wKPMUUAp6e5rf5Xww8lJB9cNhCT5nQ71c78GE48wFuDEn7PuD9e9t6gvYujrESZkA==} + engines: {node: '>= 18'} + peerDependencies: + rollup: '>=3.2.0' + webpack: '>=5.0.0' + peerDependenciesMeta: + rollup: + optional: true + webpack: + optional: true + + '@sentry/cli-darwin@2.58.6': + resolution: {integrity: sha512-udAVvcyfNa0R+95GvPz/+43/N3TC0TYKdkQ7D7jhPSzbcMc7l2fxRNN5yB3UpCA5fWFnW4toeaqwDBhb/Wh3LA==} + engines: {node: '>=10'} + os: [darwin] + + '@sentry/cli-linux-arm64@2.58.6': + resolution: {integrity: sha512-q8mEcNNmeXMy5i+jWT30TVpH7LcP4HD21CD5XRSPAd/a912HF6EpK0ybf/1USO14WOhoXbAGi9txwaWabSe33g==} + engines: {node: '>=10'} + cpu: [arm64] + os: [linux, freebsd, android] + + '@sentry/cli-linux-arm@2.58.6': + resolution: {integrity: sha512-pD0LAt5PcUzAinBwvDqc66x9+2CabHEv486yP0gRjWO7SakbaxmfVq/EXd8VLq/Tzi39LAu422UYK1lpW3MILw==} + engines: {node: '>=10'} + cpu: [arm] + os: [linux, freebsd, android] + + '@sentry/cli-linux-i686@2.58.6': + resolution: {integrity: sha512-q8vNJi1eOV/4vxAFWBsEwLHoSYapaZHIf4j76KJGJXFKTkEbsjCOOsKbwUIBTQQhRgV4DFWh3ryfsPS/que4Kg==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [linux, freebsd, android] + + '@sentry/cli-linux-x64@2.58.6': + resolution: {integrity: sha512-DZu956Mhi3ZRjTBe1WdbGV46ldVbA8d2rgp/fh51GsI25zjBHah4wZnPTSzpc+YqxU6pJpg579B/r3jrIK530Q==} + engines: {node: '>=10'} + cpu: [x64] + os: [linux, freebsd, android] + + '@sentry/cli-win32-arm64@2.58.6': + resolution: {integrity: sha512-nj0Ff/kmAB73EPDhR8B4O9r+NUHK5GkPCkGWC+kXVemqAJWL5jcJ5KdxG0l/S0z6RoEoltID8/43/B+TaMlT7A==} + engines: {node: '>=10'} + cpu: [arm64] + os: [win32] + + '@sentry/cli-win32-i686@2.58.6': + resolution: {integrity: sha512-WNZiDzPbgsEMQWq4avsQ391v/xWKJDIWWWo9GYl+N/w5qcYKkoDW7wQG7T9FasI6ENn68phChTOAPXXxbfAdOg==} + engines: {node: '>=10'} + cpu: [x86, ia32] + os: [win32] + + '@sentry/cli-win32-x64@2.58.6': + resolution: {integrity: sha512-R35WJ17oF4D2eqI1DR2sQQqr0fjRTt5xoP16WrTu91XM2lndRMFsnjh+/GttbxapLCBNlrjzia99MJ0PZHZpgA==} + engines: {node: '>=10'} + cpu: [x64] + os: [win32] + + '@sentry/cli@2.58.6': + resolution: {integrity: sha512-baBcNPLLfUi9WuL+Tpri9BFaAdvugZIKelC5X0tt0Zdy+K0K+PCVSrnNmwMWU/HyaF/SEv6b6UHnXIdqanBlcg==} + engines: {node: '>= 10'} + hasBin: true + + '@sentry/conventions@0.16.0': + resolution: {integrity: sha512-fO9PLmHdVURcSPUpWCItWAtgKiMwGdJHbovoSEyLplX5sxs2ugvI4CBPTrkkgqhObnZOD0CnWBKDzSVQYBKEyQ==} + engines: {node: '>=14'} + + '@sentry/core@10.78.0': + resolution: {integrity: sha512-MMR4ME+agrjbvQ0DtTYJJypm+QlCoz9UJgWN8gjcXqlyDjYk69LwPjnaocYN1JyQMy2dx5Hk4S/Rt12gTQ5cGw==} + engines: {node: '>=18'} + + '@sentry/vite-plugin@5.4.1': + resolution: {integrity: sha512-poFfn3sQbSFaZpXH8CG27D0eNtDD64wFEniQCb853e7huko9vOl4UP3BjT3vsrnmVuRqagnbxItza10MOC7ysA==} + engines: {node: '>= 18'} + '@standard-schema/spec@1.1.0': resolution: {integrity: sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==} @@ -1495,6 +1574,10 @@ packages: resolution: {integrity: sha512-Xwrja8nx9e5o2N1my4DsKCeKpdrnACyr1wtbPxBDgGzKzKyE9kRtBFA8mWldI+RVlD7CBZNWY/wQ2+ydwOR6kQ==} engines: {node: '>=14.0'} + agent-base@6.0.2: + resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} + engines: {node: '>= 6.0.0'} + ai@7.0.161: resolution: {integrity: sha512-DcHxTJKuAqGFZST4RgE5nCfciY+P6ym9/TKq/5UHh0U/0d4V1k+1xvYSpMR5Rq+akjeerw5teL6AXJVwmoludg==} engines: {node: '>=22'} @@ -1509,6 +1592,10 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + baseline-browser-mapping@2.11.1: resolution: {integrity: sha512-HYXq73DDpCtNzOmrFsm9eSwCvWCql0RzqjpDzXN9EadiLJ4DNat0nsZ/Bzmy+Ud12mb4/zKDY0cQ805ZzN+i0A==} engines: {node: '>=6.0.0'} @@ -1517,6 +1604,10 @@ packages: bidi-js@1.0.3: resolution: {integrity: sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==} + brace-expansion@5.0.12: + resolution: {integrity: sha512-YovQ3rzhaLMIrDjNDMkNS01tea93qhEhG5xy8f6+R0l+dw3Ki+5sCoIoI942iuLZTHWogWktgwVDhU09iNEimQ==} + engines: {node: 20 || >=22} + browserslist@4.28.7: resolution: {integrity: sha512-JxV13hNrFxqjOc8alRbq9dK1MM79NEXYpma2B2J4wAtpWS5zIEIKqWPGCl7N4o7Uc7B7itylh7SuDujATRyyTw==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -1632,6 +1723,10 @@ packages: dompurify@3.4.17: resolution: {integrity: sha512-B/pSlYVHQ58kqISD/yDjuJ1yfzI34m1gF4lAod2bwSRhiN4K5Cl3H8Af7hqB2PmTL911tdEudnnXn5FnhRoz0w==} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} + engines: {node: '>=12'} + electron-to-chromium@1.5.395: resolution: {integrity: sha512-7zt9Aw+SrmxLWLN0zhaTWZQiCdryLVrYTq5R7iZakLvi2UQPYMMsROYV/2qVCzMeCiSXHwKOU+sZ4zOVVlrtKA==} @@ -1677,6 +1772,10 @@ packages: picomatch: optional: true + find-up@5.0.0: + resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==} + engines: {node: '>=10'} + framer-motion@13.5.1: resolution: {integrity: sha512-HeWOaeHD3kyTSIEMT6+nw7fBn4saIfxxabZyAITbycAl4xr1kzGJKIvGt/OWghSlvIA9qTnZ1zTGelHAe6S8DA==} peerDependencies: @@ -1701,10 +1800,18 @@ packages: resolution: {integrity: sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==} engines: {node: '>=6'} + glob@13.0.6: + resolution: {integrity: sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==} + engines: {node: 18 || 20 || >=22} + html-encoding-sniffer@6.0.0: resolution: {integrity: sha512-CV9TW3Y3f8/wT0BRFc1/KAVQ3TUHiXmaAb6VW9vtiMFf7SLoMd1PdAc4W3KFOFETBJUb90KatHqlsZMWV+R9Gg==} engines: {node: ^20.19.0 || ^22.12.0 || >=24.0.0} + https-proxy-agent@5.0.1: + resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==} + engines: {node: '>= 6'} + immer@11.1.18: resolution: {integrity: sha512-EQyQtLiYW029lyoczMl/Hh4Xu7cDecSc58JRYpHyL4tIAu3eqd1yJzQX04d2BZHDkzFFvm6qJEJWOtfDSWAXbQ==} @@ -1715,6 +1822,9 @@ packages: is-potential-custom-element-name@1.0.1: resolution: {integrity: sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==} + isexe@2.0.0: + resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} + isomorphic-dompurify@3.22.0: resolution: {integrity: sha512-cz/BkSODnQir4IV2quqbuEHhAGz4MAYGvhoemsxuwMpY/oZbRHlNjPLPyIQ0fdULiwDPUE+OHPwWb6XTEiNk1A==} engines: {node: ^22.22.2 || ^24.15.0 || >=26.0.0} @@ -1750,6 +1860,10 @@ packages: engines: {node: '>=6'} hasBin: true + locate-path@6.0.0: + resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} + engines: {node: '>=10'} + lru-cache@11.5.2: resolution: {integrity: sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==} engines: {node: 20 || >=22} @@ -1771,12 +1885,23 @@ packages: '@types/react': optional: true + magic-string@0.30.21: + resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==} + magic-string@1.4.5: resolution: {integrity: sha512-oLLC2+YvQUfAa+vxoufE/yc1icb87M9iXIRqAoR/2EhE/8cb3SF/+Gi0c2By1B+5Vz2e2vQtUvHiu1+63Znh+w==} mdn-data@2.27.1: resolution: {integrity: sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==} + minimatch@10.2.6: + resolution: {integrity: sha512-vpLQEs+VLCr1nU0BXS07maYoFwlDAH0gngQuuttxIwutDFEMHq2blX+8vpgxDdK3J1PwjCJiep77OitTZ4Ll1A==} + engines: {node: 18 || 20 || >=22} + + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} + engines: {node: '>=16 || 14 >=14.17'} + monaco-editor@0.52.2: resolution: {integrity: sha512-GEQWEZmfkOGLdd3XK8ryrfWz3AIP8YymVXiPHEdewrUq7mh0qrKrfHLNCXcbB6sTnMLnOZ3ztSiKcciFUkIJwQ==} @@ -1805,6 +1930,15 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true + node-fetch@2.7.0: + resolution: {integrity: sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==} + engines: {node: 4.x || >=6.0.0} + peerDependencies: + encoding: ^0.1.0 + peerDependenciesMeta: + encoding: + optional: true + node-releases@2.0.51: resolution: {integrity: sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==} engines: {node: '>=18'} @@ -1834,9 +1968,25 @@ packages: resolution: {integrity: sha512-5vvB5+W7ePv+p3uqxi+RcW1XAzLW0/hxt3/4X4Lc4qHudzOhmBiBwOY6DRob4WnanAEGvNcLjF+KNOufrUoEQw==} engines: {node: '>=12.20.0'} + p-limit@3.1.0: + resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==} + engines: {node: '>=10'} + + p-locate@5.0.0: + resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==} + engines: {node: '>=10'} + parse5@8.0.1: resolution: {integrity: sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==} + path-exists@4.0.0: + resolution: {integrity: sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==} + engines: {node: '>=8'} + + path-scurry@2.0.2: + resolution: {integrity: sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==} + engines: {node: 18 || 20 || >=22} + picocolors@1.1.1: resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} @@ -1848,6 +1998,13 @@ packages: resolution: {integrity: sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==} engines: {node: ^10 || ^12 || >=14} + progress@2.0.3: + resolution: {integrity: sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==} + engines: {node: '>=0.4.0'} + + proxy-from-env@1.1.0: + resolution: {integrity: sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==} + punycode@2.3.1: resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==} engines: {node: '>=6'} @@ -2021,6 +2178,9 @@ packages: resolution: {integrity: sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==} engines: {node: '>=16'} + tr46@0.0.3: + resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} + tr46@6.0.0: resolution: {integrity: sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==} engines: {node: '>=20'} @@ -2166,6 +2326,9 @@ packages: resolution: {integrity: sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==} engines: {node: '>=18'} + webidl-conversions@3.0.1: + resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==} + webidl-conversions@8.0.1: resolution: {integrity: sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==} engines: {node: '>=20'} @@ -2182,6 +2345,14 @@ packages: resolution: {integrity: sha512-3GeworPmc2ZfEEHP7lEbUfBX/L75wdEsi0rLNhXcXxnoN5jyq0SL5gCy06SGW2cyTIZdTvWIDQNQoza++vKeaw==} engines: {node: ^22.14.0 || >=24.0.0} + whatwg-url@5.0.0: + resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==} + + which@2.0.2: + resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==} + engines: {node: '>= 8'} + hasBin: true + why-is-node-running@3.2.1: resolution: {integrity: sha512-Tb2FUhB4vUsGQlfSquQLYkApkuPAFQXGFzxWKHHumVz2dK+X1RUm/HnID4+TfIGYJ1kTcwOaCk/buYCEJr6YjQ==} engines: {node: '>=20.11'} @@ -2209,6 +2380,10 @@ packages: yallist@3.1.1: resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==} + yocto-queue@0.1.0: + resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} + engines: {node: '>=10'} + zod@4.6.7: resolution: {integrity: sha512-g8W2D2kU6a8untc053fX1uVBKzM7yqAxW/3zwk83+6RyF2+SKOuV+c0wBDhUwzV1PaGapmZH+rmcUzPf+3CGwg==} @@ -3203,6 +3378,80 @@ snapshots: '@rollup/rollup-win32-x64-msvc@4.62.2': optional: true + '@sentry/bundler-plugins@10.78.0(rollup@4.62.2)': + dependencies: + '@babel/core': 7.29.7 + '@sentry/cli': 2.58.6 + '@sentry/core': 10.78.0 + dotenv: 17.4.2 + find-up: 5.0.0 + glob: 13.0.6 + magic-string: 0.30.21 + optionalDependencies: + rollup: 4.62.2 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/cli-darwin@2.58.6': + optional: true + + '@sentry/cli-linux-arm64@2.58.6': + optional: true + + '@sentry/cli-linux-arm@2.58.6': + optional: true + + '@sentry/cli-linux-i686@2.58.6': + optional: true + + '@sentry/cli-linux-x64@2.58.6': + optional: true + + '@sentry/cli-win32-arm64@2.58.6': + optional: true + + '@sentry/cli-win32-i686@2.58.6': + optional: true + + '@sentry/cli-win32-x64@2.58.6': + optional: true + + '@sentry/cli@2.58.6': + dependencies: + https-proxy-agent: 5.0.1 + node-fetch: 2.7.0 + progress: 2.0.3 + proxy-from-env: 1.1.0 + which: 2.0.2 + optionalDependencies: + '@sentry/cli-darwin': 2.58.6 + '@sentry/cli-linux-arm': 2.58.6 + '@sentry/cli-linux-arm64': 2.58.6 + '@sentry/cli-linux-i686': 2.58.6 + '@sentry/cli-linux-x64': 2.58.6 + '@sentry/cli-win32-arm64': 2.58.6 + '@sentry/cli-win32-i686': 2.58.6 + '@sentry/cli-win32-x64': 2.58.6 + transitivePeerDependencies: + - encoding + - supports-color + + '@sentry/conventions@0.16.0': {} + + '@sentry/core@10.78.0': + dependencies: + '@sentry/conventions': 0.16.0 + + '@sentry/vite-plugin@5.4.1(rollup@4.62.2)': + dependencies: + '@sentry/bundler-plugins': 10.78.0(rollup@4.62.2) + transitivePeerDependencies: + - encoding + - rollup + - supports-color + - webpack + '@standard-schema/spec@1.1.0': {} '@standard-schema/utils@0.3.0': {} @@ -3407,6 +3656,12 @@ snapshots: adm-zip@0.6.1: {} + agent-base@6.0.2: + dependencies: + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + ai@7.0.161(zod@4.6.7): dependencies: '@ai-sdk/gateway': 4.0.134(zod@4.6.7) @@ -3420,12 +3675,18 @@ snapshots: assertion-error@2.0.1: {} + balanced-match@4.0.4: {} + baseline-browser-mapping@2.11.1: {} bidi-js@1.0.3: dependencies: require-from-string: 2.0.2 + brace-expansion@5.0.12: + dependencies: + balanced-match: 4.0.4 + browserslist@4.28.7: dependencies: baseline-browser-mapping: 2.11.1 @@ -3532,6 +3793,8 @@ snapshots: optionalDependencies: '@types/trusted-types': 2.0.7 + dotenv@17.4.2: {} + electron-to-chromium@1.5.395: {} entities@8.0.0: {} @@ -3585,6 +3848,11 @@ snapshots: optionalDependencies: picomatch: 4.0.7 + find-up@5.0.0: + dependencies: + locate-path: 6.0.0 + path-exists: 4.0.0 + framer-motion@13.5.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0): dependencies: motion-dom: 13.5.1 @@ -3601,18 +3869,33 @@ snapshots: get-nonce@1.0.1: {} + glob@13.0.6: + dependencies: + minimatch: 10.2.6 + minipass: 7.1.3 + path-scurry: 2.0.2 + html-encoding-sniffer@6.0.0: dependencies: '@exodus/bytes': 1.15.1 transitivePeerDependencies: - '@noble/hashes' + https-proxy-agent@5.0.1: + dependencies: + agent-base: 6.0.2 + debug: 4.4.3 + transitivePeerDependencies: + - supports-color + immer@11.1.18: {} internmap@2.0.3: {} is-potential-custom-element-name@1.0.1: {} + isexe@2.0.0: {} + isomorphic-dompurify@3.22.0: dependencies: dompurify: 3.4.17 @@ -3659,6 +3942,10 @@ snapshots: json5@2.2.3: {} + locate-path@6.0.0: + dependencies: + p-locate: 5.0.0 + lru-cache@11.5.2: {} lru-cache@5.1.1: @@ -3675,12 +3962,22 @@ snapshots: optionalDependencies: '@types/react': 19.3.0 + magic-string@0.30.21: + dependencies: + '@jridgewell/sourcemap-codec': 1.6.0 + magic-string@1.4.5: dependencies: '@jridgewell/sourcemap-codec': 1.6.0 mdn-data@2.27.1: {} + minimatch@10.2.6: + dependencies: + brace-expansion: 5.0.12 + + minipass@7.1.3: {} + monaco-editor@0.52.2: {} motion-dom@13.5.1: @@ -3701,6 +3998,10 @@ snapshots: nanoid@3.3.20: {} + node-fetch@2.7.0: + dependencies: + whatwg-url: 5.0.0 + node-releases@2.0.51: {} nuqs@2.10.2(react-router@8.3.1(react-dom@19.3.0(react@19.3.0))(react@19.3.0))(react@19.3.0): @@ -3712,10 +4013,25 @@ snapshots: obug@3.0.0: {} + p-limit@3.1.0: + dependencies: + yocto-queue: 0.1.0 + + p-locate@5.0.0: + dependencies: + p-limit: 3.1.0 + parse5@8.0.1: dependencies: entities: 8.0.0 + path-exists@4.0.0: {} + + path-scurry@2.0.2: + dependencies: + lru-cache: 11.5.2 + minipass: 7.1.3 + picocolors@1.1.1: {} picomatch@4.0.7: {} @@ -3726,6 +4042,10 @@ snapshots: picocolors: 1.1.1 source-map-js: 1.2.2 + progress@2.0.3: {} + + proxy-from-env@1.1.0: {} + punycode@2.3.1: {} react-day-picker@10.0.2(@types/react@19.3.0)(react@19.3.0): @@ -3897,6 +4217,8 @@ snapshots: dependencies: tldts: 7.4.9 + tr46@0.0.3: {} + tr46@6.0.0: dependencies: punycode: 2.3.1 @@ -4014,6 +4336,8 @@ snapshots: dependencies: xml-name-validator: 5.0.0 + webidl-conversions@3.0.1: {} + webidl-conversions@8.0.1: {} whatwg-mimetype@5.0.0: {} @@ -4034,6 +4358,15 @@ snapshots: transitivePeerDependencies: - '@noble/hashes' + whatwg-url@5.0.0: + dependencies: + tr46: 0.0.3 + webidl-conversions: 3.0.1 + + which@2.0.2: + dependencies: + isexe: 2.0.0 + why-is-node-running@3.2.1: {} ws@8.21.3: {} @@ -4044,4 +4377,6 @@ snapshots: yallist@3.1.1: {} + yocto-queue@0.1.0: {} + zod@4.6.7: {} diff --git a/ui/consumer/pnpm-workspace.yaml b/ui/consumer/pnpm-workspace.yaml index d08daa52..b14ac1ab 100644 --- a/ui/consumer/pnpm-workspace.yaml +++ b/ui/consumer/pnpm-workspace.yaml @@ -20,9 +20,10 @@ minimumReleaseAgeExclude: - '@tanstack/virtual-core@3.19.11' - lucide-react@1.67.0 -# esbuild checks its platform binary on install. CI and the image install with -# --ignore-scripts. +# esbuild and @sentry/cli check their platform binaries on install. CI and the +# image install with --ignore-scripts. allowBuilds: + '@sentry/cli': true esbuild: true fsevents: false diff --git a/ui/consumer/vite.config.ts b/ui/consumer/vite.config.ts index 8751388f..96c9612b 100644 --- a/ui/consumer/vite.config.ts +++ b/ui/consumer/vite.config.ts @@ -1,5 +1,6 @@ /// import { federation } from '@module-federation/vite'; +import { sentryVitePlugin } from '@sentry/vite-plugin'; import react from '@vitejs/plugin-react'; import { existsSync, readFileSync } from 'node:fs'; import { resolve } from 'node:path'; @@ -176,6 +177,18 @@ export default defineConfig(({ command }) => { '@datum-cloud/datum-ui/table': hostShared(false), }, }), + // The plugin runs inside cloud-portal, so its errors reach the portal's + // Sentry project. Debug IDs tie frames to these maps without a release, + // since the portal's release names its own build, not this plugin's. + // No token (local builds, forks) skips the upload. + sentryVitePlugin({ + org: 'sentry', + project: 'cloud-portal', + authToken: process.env.SENTRY_AUTH_TOKEN, + disable: command !== 'build' || !process.env.SENTRY_AUTH_TOKEN, + release: { create: false, finalize: false, inject: false }, + telemetry: false, + }), ], }; });