diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index aac92ab883..696ea6805d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -198,3 +198,55 @@ jobs: --- 🔄 Updated automatically on each push to this PR + + - name: Download feature bundle baseline + uses: dawidd6/action-download-artifact@v3 + with: + workflow: publish.yml + branch: main + name: bundle-feature-baseline + path: e2e/bundle-check-app + if_no_artifact_found: warn + continue-on-error: true + + - name: Run feature bundle check + id: bundle-features + run: | + pnpm nx nxBundle bundle-check-app --skip-sync --no-agents + { + echo 'report<> "$GITHUB_OUTPUT" + + - name: Find feature bundle comment + id: find-feature-comment + uses: peter-evans/find-comment@v4 + with: + issue-number: ${{ github.event.pull_request.number }} + comment-author: 'github-actions[bot]' + body-includes: + + - name: Create or update feature bundle comment + uses: peter-evans/create-or-update-comment@v5 + with: + comment-id: ${{ steps.find-feature-comment.outputs.comment-id }} + issue-number: ${{ github.event.pull_request.number }} + edit-mode: replace + body: | + + ## Tree-shaken Feature Bundle Sizes + + Minified + gzip level-9 cost of each SDK feature in isolation, as a consumer would receive it. + + ${{ steps.bundle-features.outputs.report }} + +
+ How these are measured + + Each fixture imports a single feature and is bundled with Rollup + esbuild + terser (full minification, ESM, tree-shaking on). Numbers reflect what a consumer ships and what their users download, not the raw dist size. + +
+ + --- + Updated automatically on each push to this PR diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ba1ee09e80..2a90f8cddd 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -93,6 +93,16 @@ jobs: path: previous_sizes.json retention-days: 30 + - name: Run feature bundle check + run: pnpm nx nxBundle bundle-check-app --skip-sync + + - name: Upload feature bundle baseline + uses: actions/upload-artifact@v5 + with: + name: bundle-feature-baseline + path: e2e/bundle-check-app/dist/bundle-feature-baseline.json + retention-days: 30 + snapshot: # Guard against publishing snapshots from the protected release branch. if: >- diff --git a/e2e/bundle-check-app/fixtures/davinci-client-flow.ts b/e2e/bundle-check-app/fixtures/davinci-client-flow.ts new file mode 100644 index 0000000000..a6cfebabb5 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/davinci-client-flow.ts @@ -0,0 +1,31 @@ +import { davinci } from '@forgerock/davinci-client'; + +const client = await davinci({ + config: { + serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' }, + clientId: 'test-client', + redirectUri: 'https://example.com/callback', + scope: 'openid profile', + }, +}); + +let node = await client.start(); + +// Walk the flow until it reaches a terminal node +while (node.status === 'continue') { + for (const collector of node.collectors) { + if (collector.category === 'SingleValueCollector' && collector.type === 'TextCollector') { + client.update(collector)('test-value'); + } + if (collector.category === 'SingleValueCollector' && collector.type === 'PasswordCollector') { + client.update(collector)('test-password'); + } + } + node = await client.next(); +} + +if (node.status === 'success') { + console.log('Login successful', node.session); +} else if (node.status === 'error' || node.status === 'failure') { + console.error('Login failed', node.error); +} diff --git a/e2e/bundle-check-app/fixtures/davinci-client.ts b/e2e/bundle-check-app/fixtures/davinci-client.ts new file mode 100644 index 0000000000..1dd7eee81e --- /dev/null +++ b/e2e/bundle-check-app/fixtures/davinci-client.ts @@ -0,0 +1,13 @@ +import { davinci } from '@forgerock/davinci-client'; + +const client = await davinci({ + config: { + serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' }, + clientId: 'test-client', + redirectUri: 'https://example.com/callback', + scope: 'openid profile', + }, +}); + +const node = await client.start(); +console.log(node); diff --git a/e2e/bundle-check-app/fixtures/device-client.ts b/e2e/bundle-check-app/fixtures/device-client.ts new file mode 100644 index 0000000000..8bd506fcd0 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/device-client.ts @@ -0,0 +1,20 @@ +import { deviceClient } from '@forgerock/device-client'; + +const client = deviceClient({ + serverConfig: { + baseUrl: 'https://example.com/am', + }, + realmPath: 'root', +}); + +// Retrieve OATH (TOTP/HOTP) devices for a user +const oathDevices = await client.oath.get({ + userId: 'user@example.com', +}); +console.log(oathDevices); + +// Retrieve Push notification devices +const pushDevices = await client.push.get({ + userId: 'user@example.com', +}); +console.log(pushDevices); diff --git a/e2e/bundle-check-app/fixtures/effect-barrel-option.ts b/e2e/bundle-check-app/fixtures/effect-barrel-option.ts new file mode 100644 index 0000000000..b76230a8f5 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/effect-barrel-option.ts @@ -0,0 +1,13 @@ +// Same usage but imports from the top-level 'effect' barrel instead of 'effect/Option' +// Tests whether the barrel import prevents tree-shaking vs the subpath +import { Option, pipe } from 'effect'; + +const result = pipe( + Option.fromNullable(Math.random() > 0.5 ? 'hello' : null), + Option.match({ + onNone: () => 'none', + onSome: (v) => v.toUpperCase(), + }), +); + +console.log(result); diff --git a/e2e/bundle-check-app/fixtures/effect-subpath-option.ts b/e2e/bundle-check-app/fixtures/effect-subpath-option.ts new file mode 100644 index 0000000000..d524b2c7cb --- /dev/null +++ b/e2e/bundle-check-app/fixtures/effect-subpath-option.ts @@ -0,0 +1,14 @@ +// Imports Option via the subpath: effect/Option +// Tests whether Rollup tree-shakes to only fromNullable + match +import * as Option from 'effect/Option'; +import { pipe } from 'effect/Function'; + +const result = pipe( + Option.fromNullable(Math.random() > 0.5 ? 'hello' : null), + Option.match({ + onNone: () => 'none', + onSome: (v) => v.toUpperCase(), + }), +); + +console.log(result); diff --git a/e2e/bundle-check-app/fixtures/journey-client-device.ts b/e2e/bundle-check-app/fixtures/journey-client-device.ts new file mode 100644 index 0000000000..a9ceb581a4 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/journey-client-device.ts @@ -0,0 +1,14 @@ +import { journey } from '@forgerock/journey-client'; +import { Device } from '@forgerock/journey-client/device'; + +const client = await journey({ + config: { serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' } }, +}); + +const step = await client.start(); + +if (step.type === 'Step') { + const device = new Device(); + const profile = await device.getProfile({ collectLocation: false }); + console.log(profile); +} diff --git a/e2e/bundle-check-app/fixtures/journey-client-policy.ts b/e2e/bundle-check-app/fixtures/journey-client-policy.ts new file mode 100644 index 0000000000..3d7c9b8740 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/journey-client-policy.ts @@ -0,0 +1,13 @@ +import { journey } from '@forgerock/journey-client'; +import { Policy } from '@forgerock/journey-client/policy'; + +const client = await journey({ + config: { serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' } }, +}); + +const step = await client.start(); + +if (step.type === 'Step') { + const errors = Policy.parseErrors(step.callbacks); + console.log(errors); +} diff --git a/e2e/bundle-check-app/fixtures/journey-client-qr-code.ts b/e2e/bundle-check-app/fixtures/journey-client-qr-code.ts new file mode 100644 index 0000000000..5aee099a81 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/journey-client-qr-code.ts @@ -0,0 +1,13 @@ +import { journey } from '@forgerock/journey-client'; +import { QRCode } from '@forgerock/journey-client/qr-code'; + +const client = await journey({ + config: { serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' } }, +}); + +const step = await client.start(); + +if (step.type === 'Step') { + const qrCode = QRCode.getQRCodeData(step); + console.log(qrCode); +} diff --git a/e2e/bundle-check-app/fixtures/journey-client-recovery-codes.ts b/e2e/bundle-check-app/fixtures/journey-client-recovery-codes.ts new file mode 100644 index 0000000000..c8d1e909d9 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/journey-client-recovery-codes.ts @@ -0,0 +1,13 @@ +import { journey } from '@forgerock/journey-client'; +import { RecoveryCodes } from '@forgerock/journey-client/recovery-codes'; + +const client = await journey({ + config: { serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' } }, +}); + +const step = await client.start(); + +if (step.type === 'Step') { + const codes = RecoveryCodes.getCodes(step); + console.log(codes); +} diff --git a/e2e/bundle-check-app/fixtures/journey-client-webauthn.ts b/e2e/bundle-check-app/fixtures/journey-client-webauthn.ts new file mode 100644 index 0000000000..2dcc16edbd --- /dev/null +++ b/e2e/bundle-check-app/fixtures/journey-client-webauthn.ts @@ -0,0 +1,16 @@ +import { journey } from '@forgerock/journey-client'; +import { WebAuthn, WebAuthnStepType } from '@forgerock/journey-client/webauthn'; + +const client = await journey({ + config: { serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' } }, +}); + +let step = await client.start(); + +while (step.type === 'Step') { + const stepType = WebAuthn.getWebAuthnStepType(step); + if (stepType === WebAuthnStepType.Authenticate || stepType === WebAuthnStepType.Register) { + await WebAuthn.getWebAuthnOutcome(step); + } + step = await client.next(step); +} diff --git a/e2e/bundle-check-app/fixtures/journey-client.ts b/e2e/bundle-check-app/fixtures/journey-client.ts new file mode 100644 index 0000000000..b484929f94 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/journey-client.ts @@ -0,0 +1,15 @@ +import { journey } from '@forgerock/journey-client'; + +const client = await journey({ + config: { serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' } }, +}); + +let step = await client.start(); + +while (step.type === 'Step') { + if (step.callbacks.some((cb) => cb.type === 'RedirectCallback')) { + await client.redirect(step); + break; + } + step = await client.next(step); +} diff --git a/e2e/bundle-check-app/fixtures/journey-full.ts b/e2e/bundle-check-app/fixtures/journey-full.ts new file mode 100644 index 0000000000..04a0a8be54 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/journey-full.ts @@ -0,0 +1,35 @@ +/** + * Realistic consumer pattern: journey authentication + WebAuthn + device fingerprinting. + * Mirrors what a real app would import for a complete login experience. + */ +import { journey } from '@forgerock/journey-client'; +import { WebAuthn } from '@forgerock/journey-client/webauthn'; +import { Device } from '@forgerock/journey-client/device'; + +const config = { + serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' }, +}; + +// Collect device profile before authentication +const device = new Device(); +const profile = await device.getProfile({ location: false, metadata: false }); +console.log('device profile collected', profile); + +const client = await journey({ config }); +let step = await client.start(); + +while (step.type === 'Step') { + // Check if this step requires WebAuthn + const webAuthnType = WebAuthn.getWebAuthnStepType(step); + if (webAuthnType !== 'None') { + console.log('webauthn step type', webAuthnType); + } + + step = await client.next(step); +} + +if (step.type === 'LoginSuccess') { + console.log('authenticated', step.getSessionToken()); +} else if (step.type === 'LoginFailure') { + console.error('authentication failed', step); +} diff --git a/e2e/bundle-check-app/fixtures/oidc-client-logout.ts b/e2e/bundle-check-app/fixtures/oidc-client-logout.ts new file mode 100644 index 0000000000..4088ee58f8 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/oidc-client-logout.ts @@ -0,0 +1,22 @@ +import { oidc } from '@forgerock/oidc-client'; + +const client = await oidc({ + config: { + serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' }, + clientId: 'test-client', + redirectUri: 'https://example.com/callback', + scope: 'openid profile', + }, +}); + +if ('error' in client) { + console.error(client.error); +} else { + // Revoke tokens (server-side invalidation) + const revokeResult = await client.token.revoke(); + console.log(revokeResult); + + // Full logout (revoke + end session endpoint) + const logoutResult = await client.logout(); + console.log(logoutResult); +} diff --git a/e2e/bundle-check-app/fixtures/oidc-client-token.ts b/e2e/bundle-check-app/fixtures/oidc-client-token.ts new file mode 100644 index 0000000000..838275a6c1 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/oidc-client-token.ts @@ -0,0 +1,30 @@ +import { oidc } from '@forgerock/oidc-client'; + +const client = await oidc({ + config: { + serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' }, + clientId: 'test-client', + redirectUri: 'https://example.com/callback', + scope: 'openid profile', + }, +}); + +if ('error' in client) { + console.error(client.error); +} else { + // Token exchange after authorization code callback + const tokens = await client.token.exchange('auth-code-123', 'state-abc'); + if ('error' in tokens) { + console.error(tokens.error); + } else { + console.log(tokens); + } + + // Retrieve stored tokens (with auto-renew if backgroundRenew is set) + const stored = await client.token.get({ backgroundRenew: false }); + if ('error' in stored) { + console.error(stored.error); + } else { + console.log(stored); + } +} diff --git a/e2e/bundle-check-app/fixtures/oidc-client.ts b/e2e/bundle-check-app/fixtures/oidc-client.ts new file mode 100644 index 0000000000..fc1d35ecaa --- /dev/null +++ b/e2e/bundle-check-app/fixtures/oidc-client.ts @@ -0,0 +1,13 @@ +import { oidc } from '@forgerock/oidc-client'; + +const client = await oidc({ + config: { + serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' }, + clientId: 'test-client', + redirectUri: 'https://example.com/callback', + scope: 'openid profile', + }, +}); + +const url = await client.getAuthorizationUrl(); +console.log(url); diff --git a/e2e/bundle-check-app/fixtures/protect.ts b/e2e/bundle-check-app/fixtures/protect.ts new file mode 100644 index 0000000000..3767335399 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/protect.ts @@ -0,0 +1,15 @@ +import { protect } from '@forgerock/protect'; + +const api = protect({ + envId: 'example-env-id', + behavioralDataCollection: true, +}); + +const startResult = await api.start(); + +if (startResult && 'error' in startResult) { + console.error(startResult.error); +} else { + const data = await api.getData(); + console.log(data); +} diff --git a/e2e/bundle-check-app/fixtures/redirect.ts b/e2e/bundle-check-app/fixtures/redirect.ts new file mode 100644 index 0000000000..f9c3144631 --- /dev/null +++ b/e2e/bundle-check-app/fixtures/redirect.ts @@ -0,0 +1,11 @@ +import { journey } from '@forgerock/journey-client'; + +const client = await journey({ + config: { serverConfig: { wellknown: 'https://example.com/.well-known/openid-configuration' } }, +}); + +const step = await client.start(); + +if (step.type === 'Step') { + await client.redirect(step); +} diff --git a/e2e/bundle-check-app/package.json b/e2e/bundle-check-app/package.json new file mode 100644 index 0000000000..e08c2d3d44 --- /dev/null +++ b/e2e/bundle-check-app/package.json @@ -0,0 +1,42 @@ +{ + "name": "@forgerock/bundle-check-app", + "version": "0.0.1", + "private": true, + "description": "Measures tree-shaken, gzip-level-9 bundle cost of journey-client features via isolated Rollup fixtures", + "type": "module", + "sideEffects": false, + "scripts": { + "bundle": "pnpm nx nxBundle bundle-check-app" + }, + "dependencies": { + "@forgerock/davinci-client": "workspace:*", + "@forgerock/device-client": "workspace:*", + "@forgerock/journey-client": "workspace:*", + "@forgerock/oidc-client": "workspace:*", + "@forgerock/protect": "workspace:*", + "effect": "catalog:effect" + }, + "devDependencies": { + "@rollup/plugin-node-resolve": "^16.0.0", + "@rollup/plugin-terser": "^1.0.0", + "rollup": "^4.59.0", + "rollup-plugin-esbuild": "^6.2.1", + "tsx": "^4.20.0" + }, + "nx": { + "tags": ["scope:e2e"], + "targets": { + "nxBundle": { + "dependsOn": ["^nxBuild"], + "command": "tsx {projectRoot}/src/bundle.ts", + "inputs": [ + "{projectRoot}/src/**/*.ts", + "{projectRoot}/fixtures/**/*.ts", + "{projectRoot}/bundle-feature-baseline.json", + "{workspaceRoot}/packages/journey-client/dist/**/*.js" + ], + "outputs": ["{projectRoot}/dist"] + } + } + } +} diff --git a/e2e/bundle-check-app/src/bundle.ts b/e2e/bundle-check-app/src/bundle.ts new file mode 100644 index 0000000000..43fceadf5f --- /dev/null +++ b/e2e/bundle-check-app/src/bundle.ts @@ -0,0 +1,164 @@ +/* + * Copyright (c) 2025 Ping Identity Corporation. All rights reserved. + * + * This software may be modified and distributed under the terms + * of the MIT license. See the LICENSE file for details. + */ + +/** + * Measures tree-shaken, fully-minified bundle cost of each fixture in `../fixtures/`. + * Reports both minified (raw) size and gzip level-9 size — the two numbers that matter + * to a consumer: what they ship and what their users download. + * + * Mirrors the approach used by the Effect team: + * https://github.com/Effect-TS/effect-smol/blob/main/packages/tools/bundle/src/Rollup.ts + * + * Run via: pnpm nx nxBundle bundle-check-app + */ + +import { createGzip } from 'node:zlib'; +import { mkdirSync, readdirSync, readFileSync, writeFileSync } from 'node:fs'; +import * as path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { rollup } from 'rollup'; +import { nodeResolve } from '@rollup/plugin-node-resolve'; +import terser from '@rollup/plugin-terser'; +import esbuild from 'rollup-plugin-esbuild'; + +const __dirname = path.dirname(fileURLToPath(import.meta.url)); +const fixturesDir = path.resolve(__dirname, '../fixtures'); +const outDir = path.resolve(__dirname, '../dist'); + +mkdirSync(outDir, { recursive: true }); + +const fixtures = readdirSync(fixturesDir) + .filter((f) => f.endsWith('.ts')) + .sort(); + +if (fixtures.length === 0) { + console.error('No fixtures found in', fixturesDir); + process.exit(1); +} + +console.log(`\nMeasuring ${fixtures.length} fixture(s)…\n`); + +/** Gzip a string buffer at level 9 and return the compressed byte count. */ +function gzipSize(code: string): Promise { + return new Promise((resolve, reject) => { + const gz = createGzip({ level: 9 }); + let total = 0; + gz.on('data', (chunk: Buffer) => { + total += chunk.length; + }); + gz.on('end', () => resolve(total)); + gz.on('error', reject); + gz.end(Buffer.from(code, 'utf8')); + }); +} + +function fmt(bytes: number): string { + return `${(bytes / 1000).toFixed(2)} kB`; +} + +type Row = { name: string; minifiedBytes: number; gzipBytes: number }; + +const rows: Row[] = []; + +for (const fixture of fixtures) { + const inputPath = path.join(fixturesDir, fixture); + const name = path.basename(fixture, '.ts'); + + const bundle = await rollup({ + input: inputPath, + plugins: [ + nodeResolve({ browser: true }), + esbuild({ target: 'esnext', format: 'esm', treeShaking: true }), + // @ts-ignore – NodeNext moduleResolution misidentifies the default export type for @rollup/plugin-terser + terser({ format: { comments: false }, compress: true, mangle: true }), + ], + onwarn: (warning, next) => { + if (warning.code === 'THIS_IS_UNDEFINED') return; + if (warning.code === 'CIRCULAR_DEPENDENCY') return; + next(warning); + }, + }); + + const { output } = await bundle.generate({ format: 'esm' }); + await bundle.close(); + + const code = output + .filter((chunk) => chunk.type === 'chunk') + .map((chunk) => chunk.code) + .join(''); + + // Write minified output for inspection + writeFileSync(path.join(outDir, `${name}.min.js`), code, 'utf8'); + + rows.push({ + name: fixture, + minifiedBytes: Buffer.byteLength(code, 'utf8'), + gzipBytes: await gzipSize(code), + }); +} + +// --- Baseline JSON (raw bytes, used for PR comparison) --- +type Baseline = Record; +const baseline: Baseline = Object.fromEntries( + rows.map((r) => [r.name, { minifiedBytes: r.minifiedBytes, gzipBytes: r.gzipBytes }]), +); +writeFileSync( + path.join(outDir, 'bundle-feature-baseline.json'), + JSON.stringify(baseline, null, 2) + '\n', + 'utf8', +); + +// --- Markdown table --- +const pad = (s: string, w: number) => s.padStart(w); +const padL = (s: string, w: number) => s.padEnd(w); + +// Load baseline for comparison if available +let prevBaseline: Baseline = {}; +const prevBaselinePath = path.resolve(__dirname, '../bundle-feature-baseline.json'); +try { + prevBaseline = JSON.parse(readFileSync(prevBaselinePath, 'utf8')) as Baseline; +} catch (err) { + if ((err as NodeJS.ErrnoException).code !== 'ENOENT') { + console.warn('Failed to load previous baseline:', err); + } +} + +function delta(current: number, prev: number | undefined): string { + if (prev === undefined) return ' 🆕'; + const diff = current - prev; + if (diff === 0) return ''; + const sign = diff > 0 ? '+' : ''; + return ` (${sign}${fmt(diff)} ${diff > 0 ? '🔺' : '🔻'})`; +} + +type TableRow = { name: string; minified: string; gzip: string }; +const tableRows: TableRow[] = rows.map((r) => { + const prev = prevBaseline[r.name]; + return { + name: r.name, + minified: fmt(r.minifiedBytes) + delta(r.minifiedBytes, prev?.minifiedBytes), + gzip: fmt(r.gzipBytes) + delta(r.gzipBytes, prev?.gzipBytes), + }; +}); + +const nameW = Math.max(...tableRows.map((r) => r.name.length)) + 2; // +2 for backticks +const minW = Math.max('Minified'.length, ...tableRows.map((r) => r.minified.length)); +const gzipW = Math.max('gzip (lvl 9)'.length, ...tableRows.map((r) => r.gzip.length)); + +const header = `| ${padL('Fixture', nameW)} | ${pad('Minified', minW)} | ${pad('gzip (lvl 9)', gzipW)} |`; +const divider = `|:${'-'.repeat(nameW)}-|-${'-'.repeat(minW)}:|-${'-'.repeat(gzipW)}:|`; +const mdRows = tableRows.map( + (r) => `| ${padL(`\`${r.name}\``, nameW)} | ${pad(r.minified, minW)} | ${pad(r.gzip, gzipW)} |`, +); + +const table = [header, divider, ...mdRows].join('\n'); + +console.log(table); +console.log(); + +// Write markdown report for CI consumption +writeFileSync(path.join(outDir, 'bundle-feature-report.md'), table + '\n', 'utf8'); diff --git a/e2e/bundle-check-app/tsconfig.json b/e2e/bundle-check-app/tsconfig.json new file mode 100644 index 0000000000..0582b61964 --- /dev/null +++ b/e2e/bundle-check-app/tsconfig.json @@ -0,0 +1,33 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "target": "ESNext", + "lib": ["ESNext", "DOM"], + "module": "NodeNext", + "moduleResolution": "NodeNext", + "strict": true, + "skipLibCheck": true, + "esModuleInterop": true, + "noUnusedLocals": true, + "noImplicitReturns": true, + "outDir": "dist" + }, + "include": ["src/**/*.ts"], + "references": [ + { + "path": "../../packages/protect" + }, + { + "path": "../../packages/oidc-client" + }, + { + "path": "../../packages/journey-client" + }, + { + "path": "../../packages/device-client" + }, + { + "path": "../../packages/davinci-client" + } + ] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index babd5d7de7..6d1a31e9da 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -295,6 +295,43 @@ importers: specifier: ^5.0.0 version: 5.0.5 + e2e/bundle-check-app: + dependencies: + '@forgerock/davinci-client': + specifier: workspace:* + version: link:../../packages/davinci-client + '@forgerock/device-client': + specifier: workspace:* + version: link:../../packages/device-client + '@forgerock/journey-client': + specifier: workspace:* + version: link:../../packages/journey-client + '@forgerock/oidc-client': + specifier: workspace:* + version: link:../../packages/oidc-client + '@forgerock/protect': + specifier: workspace:* + version: link:../../packages/protect + effect: + specifier: catalog:effect + version: 3.20.0 + devDependencies: + '@rollup/plugin-node-resolve': + specifier: ^16.0.0 + version: 16.0.3(rollup@4.59.0) + '@rollup/plugin-terser': + specifier: ^1.0.0 + version: 1.0.0(rollup@4.59.0) + rollup: + specifier: ^4.59.0 + version: 4.59.0 + rollup-plugin-esbuild: + specifier: ^6.2.1 + version: 6.2.1(esbuild@0.27.2)(rollup@4.59.0) + tsx: + specifier: ^4.20.0 + version: 4.21.0 + e2e/davinci-app: dependencies: '@forgerock/davinci-client': @@ -2843,6 +2880,33 @@ packages: react-redux: optional: true + '@rollup/plugin-node-resolve@16.0.3': + resolution: {integrity: sha512-lUYM3UBGuM93CnMPG1YocWu7X802BrNF3jW2zny5gQyLQgRFJhV1Sq0Zi74+dh/6NBx1DxFC4b4GXg9wUCG5Qg==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^4.59.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/plugin-terser@1.0.0': + resolution: {integrity: sha512-FnCxhTBx6bMOYQrar6C8h3scPt8/JwIzw3+AJ2K++6guogH5fYaIFia+zZuhqv0eo1RN7W1Pz630SyvLbDjhtQ==} + engines: {node: '>=20.0.0'} + peerDependencies: + rollup: ^4.59.0 + peerDependenciesMeta: + rollup: + optional: true + + '@rollup/pluginutils@5.3.0': + resolution: {integrity: sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==} + engines: {node: '>=14.0.0'} + peerDependencies: + rollup: ^4.59.0 + peerDependenciesMeta: + rollup: + optional: true + '@rollup/rollup-android-arm-eabi@4.59.0': resolution: {integrity: sha512-upnNBkA6ZH2VKGcBj9Fyl9IGNPULcjXRlg0LLeaioQWueH30p6IXtJEbKAgvyv+mJaMxSm1l6xwDXYjpEMiLMg==} cpu: [arm] @@ -3301,6 +3365,9 @@ packages: '@types/range-parser@1.2.7': resolution: {integrity: sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ==} + '@types/resolve@1.20.2': + resolution: {integrity: sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==} + '@types/responselike@1.0.0': resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} @@ -5863,6 +5930,9 @@ packages: resolution: {integrity: sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==} engines: {node: '>= 0.4'} + is-module@1.0.0: + resolution: {integrity: sha512-51ypPSPCoTEIN9dy5Oy+h4pShgJmPCygKfyRCISBI+JoWT/2oJvK8QPxmwv7b/p239jXrm9M1mlQbyKJ5A152g==} + is-negative-zero@2.0.3: resolution: {integrity: sha512-5KoIu2Ngpyek75jXodFvnafB6DJgr3u8uuK0LEZJjrU19DrMD3EVERaR8sjz8CCGgpZvxPl9SuE1GMVPFHx1mw==} engines: {node: '>= 0.4'} @@ -7356,6 +7426,13 @@ packages: resolution: {integrity: sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} + rollup-plugin-esbuild@6.2.1: + resolution: {integrity: sha512-jTNOMGoMRhs0JuueJrJqbW8tOwxumaWYq+V5i+PD+8ecSCVkuX27tGW7BXqDgoULQ55rO7IdNxPcnsWtshz3AA==} + engines: {node: '>=14.18.0'} + peerDependencies: + esbuild: '>=0.18.0' + rollup: ^4.59.0 + rollup@4.59.0: resolution: {integrity: sha512-2oMpl67a3zCH9H79LeMcbDhXW/UmWG/y2zuqnF2jQq5uq9TbM9TVyXvA4+t+ne2IIkBdrLpAaRQAvo7YI/Yyeg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} @@ -7468,6 +7545,10 @@ packages: resolution: {integrity: sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==} engines: {node: '>= 18'} + serialize-javascript@7.0.5: + resolution: {integrity: sha512-F4LcB0UqUl1zErq+1nYEEzSHJnIwb3AF2XWB94b+afhrekOUijwooAYqFyRbjYkm2PAKBabx6oYv/xDxNi8IBw==} + engines: {node: '>=20.0.0'} + serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} @@ -7554,6 +7635,10 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} + smob@1.6.1: + resolution: {integrity: sha512-KAkBqZl3c2GvNgNhcoyJae1aKldDW0LO279wF9bk1PnluRTETKBq0WyzRXxEhoQLk56yHaOY4JCBEKDuJIET5g==} + engines: {node: '>=20.0.0'} + smol-toml@1.6.1: resolution: {integrity: sha512-dWUG8F5sIIARXih1DTaQAX4SsiTXhInKf1buxdY9DIg4ZYPZK5nGM1VRIYmEbDbsHt7USo99xSLFu5Q1IqTmsg==} engines: {node: '>= 18'} @@ -8184,6 +8269,10 @@ packages: resolution: {integrity: sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==} engines: {node: '>= 0.8'} + unplugin-utils@0.2.5: + resolution: {integrity: sha512-gwXJnPRewT4rT7sBi/IvxKTjsms7jX7QIDLOClApuZwR49SXbrB1z2NLUZ+vDHyqCj/n58OzRRqaW+B8OZi8vg==} + engines: {node: '>=18.12.0'} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} @@ -8223,6 +8312,7 @@ packages: uuid@8.3.2: resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==} + deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028). hasBin: true v8-compile-cache-lib@3.0.1: @@ -11153,6 +11243,32 @@ snapshots: redux-thunk: 3.1.0(redux@5.0.1) reselect: 5.1.1 + '@rollup/plugin-node-resolve@16.0.3(rollup@4.59.0)': + dependencies: + '@rollup/pluginutils': 5.3.0(rollup@4.59.0) + '@types/resolve': 1.20.2 + deepmerge: 4.3.1 + is-module: 1.0.0 + resolve: 1.22.11 + optionalDependencies: + rollup: 4.59.0 + + '@rollup/plugin-terser@1.0.0(rollup@4.59.0)': + dependencies: + serialize-javascript: 7.0.5 + smob: 1.6.1 + terser: 5.46.2 + optionalDependencies: + rollup: 4.59.0 + + '@rollup/pluginutils@5.3.0(rollup@4.59.0)': + dependencies: + '@types/estree': 1.0.8 + estree-walker: 2.0.2 + picomatch: 4.0.4 + optionalDependencies: + rollup: 4.59.0 + '@rollup/rollup-android-arm-eabi@4.59.0': optional: true @@ -11579,6 +11695,8 @@ snapshots: '@types/range-parser@1.2.7': {} + '@types/resolve@1.20.2': {} + '@types/responselike@1.0.0': dependencies: '@types/node': 24.9.2 @@ -14685,6 +14803,8 @@ snapshots: is-map@2.0.3: {} + is-module@1.0.0: {} + is-negative-zero@2.0.3: {} is-node-process@1.2.0: {} @@ -16385,6 +16505,17 @@ snapshots: reusify@1.1.0: {} + rollup-plugin-esbuild@6.2.1(esbuild@0.27.2)(rollup@4.59.0): + dependencies: + debug: 4.4.3 + es-module-lexer: 1.7.0 + esbuild: 0.27.2 + get-tsconfig: 4.13.0 + rollup: 4.59.0 + unplugin-utils: 0.2.5 + transitivePeerDependencies: + - supports-color + rollup@4.59.0: dependencies: '@types/estree': 1.0.8 @@ -16541,6 +16672,8 @@ snapshots: transitivePeerDependencies: - supports-color + serialize-javascript@7.0.5: {} + serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -16651,6 +16784,8 @@ snapshots: slash@3.0.0: {} + smob@1.6.1: {} + smol-toml@1.6.1: {} sonic-boom@3.8.1: @@ -17302,6 +17437,11 @@ snapshots: unpipe@1.0.0: {} + unplugin-utils@0.2.5: + dependencies: + pathe: 2.0.3 + picomatch: 4.0.4 + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.4 diff --git a/tsconfig.json b/tsconfig.json index c22692ddac..21a3509c49 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -84,6 +84,9 @@ }, { "path": "./tools/api-report" + }, + { + "path": "./e2e/bundle-check-app" } ] }