diff --git a/.gitignore b/.gitignore index 8254734ee7..209973cfdd 100644 --- a/.gitignore +++ b/.gitignore @@ -28,5 +28,5 @@ stats.html .env.local .env.*.local .claude/worktrees -.claude/stacks +.claude/tmp .mcp.json diff --git a/package.json b/package.json index 61c1ebf768..e5d9bf7775 100644 --- a/package.json +++ b/package.json @@ -40,6 +40,7 @@ "packages/store/node-server-sdk-redis", "packages/store/node-server-sdk-dynamodb", "packages/telemetry/node-server-sdk-otel", + "packages/tooling/client-testing-plugin", "packages/tooling/contract-test-utils", "packages/tooling/jest", "packages/tooling/jest/example/react-native-example", diff --git a/packages/tooling/client-testing-plugin/README.md b/packages/tooling/client-testing-plugin/README.md new file mode 100644 index 0000000000..9a3fd94617 --- /dev/null +++ b/packages/tooling/client-testing-plugin/README.md @@ -0,0 +1,73 @@ +# LaunchDarkly Client Testing Plugin + +A testing plugin for LaunchDarkly client-side JavaScript SDKs. Use it to inject deterministic flag values into a real SDK client during unit tests, integration tests, and local development. + +## Install + +```bash +yarn add --dev @launchdarkly/client-testing-plugin @launchdarkly/js-client-sdk +``` + +## Usage + +```ts +import { createClient } from '@launchdarkly/js-client-sdk'; +import { TestData } from '@launchdarkly/client-testing-plugin'; + +// Seed with a base set of flag values. +const td = new TestData({ + 'new-ui': true, + greeting: 'Hello!', +}); + +const client = createClient( + '', // placeholder -- fill in only for real environments + { kind: 'user', key: 'tester' }, + { + plugins: [td], + sendEvents: false, + streaming: false, + }, +); + +await client.start({ bootstrap: {} }); + +client.boolVariation('new-ui', false); // true +client.stringVariation('greeting', '(default)'); // 'Hello!' + +// Update flags at any time -- the SDK fires change events. Setters chain. +td.setBool('new-ui', false).setString('greeting', 'Welcome'); +``` + +### Required LD client options +In order to successfully set up a LD client to use the testing plugin, you **MUST** set the following options: + +- **`plugins: [td]`** - registers the testing plugin so it can inject overrides. +- **`sendEvents: false`** - keeps analytics events off in tests. +- **`streaming: false`** - (required for `js-client-sdk` and its derivativs, eg `react-sdk`), having streaming on will cause the `js-client-sdk` to automatically open a streaming connection. +- **`bootstrap: {}` (passed to `start()`)** -- gives the SDK an empty initial flag set so it does not block on a network identify call. The plugin's overrides are applied immediately afterward. + +> Refer to the usage example above. + +## API + +### `TestData` + +```ts +class TestData implements LDPlugin { + constructor(initialFlags?: { [key: string]: LDFlagValue }); + + setBool(key: string, value: boolean): this; + setString(key: string, value: string): this; + setNumber(key: string, value: number): this; + setJson(key: string, value: object | unknown[]): this; + + remove(key: string): this; + clear(): this; +} +``` + +- **`new TestData(initialFlags?)`** -- seed the instance with a base map of flag keys to values. The values are applied to the SDK client when it initializes. +- **`setBool` / `setString` / `setNumber` / `setJson`** -- set or update a single flag. If the SDK is already running, the change propagates immediately and listeners receive a `change:` event. Every write applies the override, even when the value is unchanged -- mirroring a real connection that can re-deliver a flag and fire a `change` event without the value differing. +- **`remove(key)`** -- drop the override for a single key. If the SDK is connected, also calls `removeOverride`. +- **`clear()`** -- drop all overrides. Useful in `beforeEach` for shared `TestData` instances. diff --git a/packages/tooling/client-testing-plugin/__tests__/TestData.test.ts b/packages/tooling/client-testing-plugin/__tests__/TestData.test.ts new file mode 100644 index 0000000000..6e97c67f61 --- /dev/null +++ b/packages/tooling/client-testing-plugin/__tests__/TestData.test.ts @@ -0,0 +1,192 @@ +import type { LDDebugOverride } from '@launchdarkly/js-client-sdk-common'; + +import TestData from '../src/TestData'; + +function createMockDebugOverride(): LDDebugOverride & { + overrides: Record; +} { + const overrides: Record = {}; + return { + overrides, + setOverride: jest.fn((key: string, value: unknown) => { + overrides[key] = value; + }), + removeOverride: jest.fn((key: string) => { + delete overrides[key]; + }), + clearAllOverrides: jest.fn(() => { + Object.keys(overrides).forEach((k) => delete overrides[k]); + }), + getAllOverrides: jest.fn(() => ({})), + }; +} + +it('returns correct plugin metadata', () => { + expect(new TestData().getMetadata()).toEqual({ name: 'test-data' }); +}); + +it('register is a no-op', () => { + const td = new TestData(); + expect(() => + td.register(undefined, { + sdk: { name: 'test', version: '0.0.0' }, + clientSideId: 'test-key', + } as never), + ).not.toThrow(); +}); + +it('seeds initial flags from the constructor and applies them on registerDebug', () => { + const td = new TestData({ + 'show-banner': true, + greeting: 'Hello', + 'max-retries': 3, + config: { theme: 'dark' }, + }); + + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + expect(debugOverride.overrides['show-banner']).toBe(true); + expect(debugOverride.overrides.greeting).toBe('Hello'); + expect(debugOverride.overrides['max-retries']).toBe(3); + expect(debugOverride.overrides.config).toEqual({ theme: 'dark' }); +}); + +it('typed setters chain and apply pre-registration', () => { + const td = new TestData() + .setBool('show-banner', true) + .setString('greeting', 'Hello') + .setNumber('max-retries', 3) + .setJson('config', { theme: 'dark' }); + + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + expect(debugOverride.overrides['show-banner']).toBe(true); + expect(debugOverride.overrides.greeting).toBe('Hello'); + expect(debugOverride.overrides['max-retries']).toBe(3); + expect(debugOverride.overrides.config).toEqual({ theme: 'dark' }); +}); + +it('typed setters propagate live updates after registration', () => { + const td = new TestData(); + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + td.setBool('show-banner', true); + expect(debugOverride.setOverride).toHaveBeenCalledWith('show-banner', true); + + td.setString('greeting', 'Howdy'); + expect(debugOverride.setOverride).toHaveBeenCalledWith('greeting', 'Howdy'); + + td.setNumber('max-retries', 5); + expect(debugOverride.setOverride).toHaveBeenCalledWith('max-retries', 5); + + td.setJson('config', [1, 2, 3]); + expect(debugOverride.setOverride).toHaveBeenCalledWith('config', [1, 2, 3]); +}); + +it('fires setOverride on every write, including repeated identical values', () => { + const td = new TestData(); + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + td.setBool('flag', true); + td.setBool('flag', true); + td.setBool('flag', true); + + expect(debugOverride.setOverride).toHaveBeenCalledTimes(3); + expect(debugOverride.setOverride).toHaveBeenNthCalledWith(1, 'flag', true); + expect(debugOverride.setOverride).toHaveBeenNthCalledWith(3, 'flag', true); +}); + +it('fires setOverride for repeated NaN and object writes', () => { + const td = new TestData(); + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + td.setNumber('n', NaN); + td.setNumber('n', NaN); + expect(debugOverride.setOverride).toHaveBeenCalledTimes(2); + + const same = { showBanner: true }; + td.setJson('cfg', same); + td.setJson('cfg', same); + expect(debugOverride.setOverride).toHaveBeenCalledTimes(4); +}); + +it('remove clears stored state and the active override', () => { + const td = new TestData({ flag: true }); + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + td.remove('flag'); + + expect(debugOverride.removeOverride).toHaveBeenCalledWith('flag'); + expect(debugOverride.overrides.flag).toBeUndefined(); +}); + +it('remove before registerDebug prevents the flag from being applied later', () => { + const td = new TestData({ flag: true }); + td.remove('flag'); + + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + expect(debugOverride.setOverride).not.toHaveBeenCalled(); + expect(debugOverride.overrides.flag).toBeUndefined(); +}); + +it('clear resets all flags and clears the override interface', () => { + const td = new TestData({ a: true, b: 'x' }); + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + td.clear(); + + expect(debugOverride.clearAllOverrides).toHaveBeenCalledTimes(1); +}); + +it('clear before registerDebug drops queued flags', () => { + const td = new TestData({ a: true }); + td.clear(); + + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + expect(debugOverride.setOverride).not.toHaveBeenCalled(); +}); + +it('throws if registerDebug is called twice', () => { + const td = new TestData(); + td.registerDebug(createMockDebugOverride()); + + expect(() => td.registerDebug(createMockDebugOverride())).toThrow( + /already been registered/, + ); +}); + +it('setJson rejects undefined and other non-object values', () => { + const td = new TestData(); + expect(() => td.setJson('flag', undefined as unknown as object)).toThrow(TypeError); + expect(() => td.setJson('flag', null as unknown as object)).toThrow(TypeError); + expect(() => td.setJson('flag', 'string' as unknown as object)).toThrow(TypeError); + expect(() => td.setJson('flag', 42 as unknown as object)).toThrow(TypeError); +}); + +it('remove and clear return this for chaining', () => { + const td = new TestData({ a: true, b: 'x' }); + expect(td.remove('a')).toBe(td); + expect(td.clear()).toBe(td); +}); + +it('handles flag keys that collide with Object prototype names safely', () => { + const td = new TestData(); + const debugOverride = createMockDebugOverride(); + td.registerDebug(debugOverride); + + td.setString('toString', 'overridden').setNumber('hasOwnProperty', 42); + + expect(debugOverride.setOverride).toHaveBeenCalledWith('toString', 'overridden'); + expect(debugOverride.setOverride).toHaveBeenCalledWith('hasOwnProperty', 42); +}); diff --git a/packages/tooling/client-testing-plugin/__tests__/e2e/browser.test.ts b/packages/tooling/client-testing-plugin/__tests__/e2e/browser.test.ts new file mode 100644 index 0000000000..f2bb80f44b --- /dev/null +++ b/packages/tooling/client-testing-plugin/__tests__/e2e/browser.test.ts @@ -0,0 +1,149 @@ +/** + * @jest-environment jsdom + * + * E2E integration test: creates a real browser SDK client with TestData + * and verifies flag evaluation and dynamic updates work end-to-end. + * + * This demonstrates the exact usage pattern that library consumers would use + * when writing unit tests for their applications. + */ +import { createClient } from '@launchdarkly/js-client-sdk'; + +import { TestData } from '../../src/index'; + +describe('Browser SDK integration', () => { + let td: TestData; + let client: ReturnType; + + beforeEach(async () => { + td = new TestData({ + 'bool-flag': true, + 'string-flag': 'hello', + 'number-flag': 42, + 'json-flag': { key: 'value' }, + 'multi-variation': 'green', + }); + + client = createClient( + 'test-client-id', + { kind: 'user', key: 'test-user' }, + { + plugins: [td], + sendEvents: false, + diagnosticOptOut: true, + streaming: false, + }, + ); + + await client.start({ bootstrap: {} }); + }); + + afterEach(async () => { + await client.close(); + }); + + it('evaluates boolean flags', () => { + expect(client.boolVariation('bool-flag', false)).toBe(true); + }); + + it('evaluates boolean flags via boolVariationDetail', () => { + const detail = client.boolVariationDetail('bool-flag', false); + expect(detail.value).toBe(true); + // Override descriptors carry no variation index and no reason -- assert the + // shape the SDK actually produces so future regressions are caught. + expect(detail.variationIndex).toBeNull(); + expect(detail.reason).toBeUndefined(); + }); + + it('evaluates string flags', () => { + expect(client.stringVariation('string-flag', 'default')).toBe('hello'); + }); + + it('evaluates string flags via stringVariationDetail', () => { + const detail = client.stringVariationDetail('string-flag', 'default'); + expect(detail.value).toBe('hello'); + expect(detail.variationIndex).toBeNull(); + expect(detail.reason).toBeUndefined(); + }); + + it('evaluates number flags', () => { + expect(client.numberVariation('number-flag', 0)).toBe(42); + }); + + it('evaluates number flags via numberVariationDetail', () => { + const detail = client.numberVariationDetail('number-flag', 0); + expect(detail.value).toBe(42); + expect(detail.variationIndex).toBeNull(); + expect(detail.reason).toBeUndefined(); + }); + + it('evaluates json flags', () => { + expect(client.jsonVariation('json-flag', null)).toEqual({ key: 'value' }); + }); + + it('evaluates json flags via jsonVariationDetail', () => { + const detail = client.jsonVariationDetail('json-flag', null); + expect(detail.value).toEqual({ key: 'value' }); + expect(detail.variationIndex).toBeNull(); + expect(detail.reason).toBeUndefined(); + }); + + it('evaluates string flags overridden to one of several possible values', () => { + expect(client.stringVariation('multi-variation', 'default')).toBe('green'); + }); + + it('returns all flags via allFlags()', () => { + const flags = client.allFlags(); + expect(flags['bool-flag']).toBe(true); + expect(flags['string-flag']).toBe('hello'); + expect(flags['number-flag']).toBe(42); + expect(flags['multi-variation']).toBe('green'); + }); + + it('returns default when flag is not defined', () => { + expect(client.boolVariation('nonexistent', false)).toBe(false); + expect(client.stringVariation('nonexistent', 'fallback')).toBe('fallback'); + }); + + it('dynamically updates a flag and fires change event', async () => { + expect(client.boolVariation('bool-flag', false)).toBe(true); + + const changed = new Promise((resolve) => { + client.on('change:bool-flag', () => resolve()); + }); + + td.setBool('bool-flag', false); + + await changed; + + expect(client.boolVariation('bool-flag', true)).toBe(false); + }); + + it('adds a new flag dynamically after initialization', async () => { + expect(client.stringVariation('new-flag', 'default')).toBe('default'); + + const changed = new Promise((resolve) => { + client.on('change:new-flag', () => resolve()); + }); + + td.setString('new-flag', 'surprise'); + + await changed; + + expect(client.stringVariation('new-flag', 'default')).toBe('surprise'); + }); + + it('updates a string flag to a different value', async () => { + expect(client.stringVariation('multi-variation', 'default')).toBe('green'); + + const changed = new Promise((resolve) => { + client.on('change:multi-variation', () => resolve()); + }); + + td.setString('multi-variation', 'blue'); + + await changed; + + expect(client.stringVariation('multi-variation', 'default')).toBe('blue'); + }); +}); diff --git a/packages/tooling/client-testing-plugin/jest.config.json b/packages/tooling/client-testing-plugin/jest.config.json new file mode 100644 index 0000000000..e949d99750 --- /dev/null +++ b/packages/tooling/client-testing-plugin/jest.config.json @@ -0,0 +1,10 @@ +{ + "transform": { "^.+\\.tsx?$": ["ts-jest", { "tsconfig": { "esModuleInterop": true } }] }, + "testMatch": ["**/*.test.ts?(x)"], + "testPathIgnorePatterns": ["node_modules", "dist", "/example/"], + "modulePathIgnorePatterns": ["dist", "/example/"], + "testEnvironment": "jsdom", + "setupFiles": ["./setup-jest.js"], + "moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"], + "collectCoverageFrom": ["src/**/*.ts?(x)"] +} diff --git a/packages/tooling/client-testing-plugin/package.json b/packages/tooling/client-testing-plugin/package.json new file mode 100644 index 0000000000..fb764a7e76 --- /dev/null +++ b/packages/tooling/client-testing-plugin/package.json @@ -0,0 +1,55 @@ +{ + "name": "@launchdarkly/client-testing-plugin", + "version": "0.0.1", + "description": "Testing plugin for LaunchDarkly client-side JavaScript SDKs. Uses the experimental plugin debug-override mechanism to inject flag values for unit tests and local development.", + "homepage": "https://github.com/launchdarkly/js-core/tree/main/packages/tooling/client-testing-plugin", + "repository": { + "type": "git", + "url": "https://github.com/launchdarkly/js-core.git" + }, + "license": "Apache-2.0", + "packageManager": "yarn@4.2.2", + "keywords": [ + "launchdarkly", + "test", + "mock", + "testing", + "feature-flags" + ], + "type": "module", + "exports": { + ".": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js", + "require": "./dist/index.cjs", + "default": "./dist/index.js" + } + }, + "files": [ + "dist" + ], + "scripts": { + "clean": "rimraf dist", + "build": "tsup", + "test": "npx jest --ci", + "lint": "eslint . --ext .ts,.tsx" + }, + "dependencies": { + "@launchdarkly/js-client-sdk-common": "workspace:^" + }, + "devDependencies": { + "@launchdarkly/js-client-sdk": "workspace:^", + "@types/jest": "^29.5.0", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", + "eslint": "^8.45.0", + "eslint-plugin-import": "^2.27.5", + "eslint-plugin-jest": "^27.6.3", + "jest": "^30.2.0", + "jest-environment-jsdom": "^30.0.0", + "rimraf": "6.0.1", + "ts-jest": "^29.1.1", + "tsup": "^8.5.1", + "typescript": "5.1.6" + } +} diff --git a/packages/tooling/client-testing-plugin/setup-jest.js b/packages/tooling/client-testing-plugin/setup-jest.js new file mode 100644 index 0000000000..c932c6829d --- /dev/null +++ b/packages/tooling/client-testing-plugin/setup-jest.js @@ -0,0 +1,37 @@ +const { TextEncoder, TextDecoder } = require('node:util'); +const crypto = require('node:crypto'); + +global.TextEncoder = TextEncoder; + +Object.assign(window, { TextDecoder, TextEncoder }); + +// Stub EventSource for tests that register change listeners (which triggers +// automatic streaming in the browser SDK). The real EventSource isn't available +// in jsdom. +if (typeof global.EventSource === 'undefined') { + global.EventSource = class EventSource { + constructor() { + // no-op + } + addEventListener() {} + removeEventListener() {} + close() {} + }; +} + +// jsdom doesn't provide crypto.subtle, which the SDK needs for context hashing. +// Based on: https://stackoverflow.com/a/71750830 +Object.defineProperty(global.self, 'crypto', { + value: { + getRandomValues: (arr) => crypto.randomBytes(arr.length), + subtle: { + digest: (algorithm, data) => { + return new Promise((resolve) => + resolve( + crypto.createHash(algorithm.toLowerCase().replace('-', '')).update(data).digest(), + ), + ); + }, + }, + }, +}); diff --git a/packages/tooling/client-testing-plugin/src/TestData.ts b/packages/tooling/client-testing-plugin/src/TestData.ts new file mode 100644 index 0000000000..e9d4324d16 --- /dev/null +++ b/packages/tooling/client-testing-plugin/src/TestData.ts @@ -0,0 +1,176 @@ +import type { + LDDebugOverride, + LDFlagValue, + LDPluginBase, + LDPluginEnvironmentMetadata, + LDPluginMetadata, +} from '@launchdarkly/js-client-sdk-common'; + +const PLUGIN_NAME = 'test-data'; + +/** + * A mechanism for providing dynamically updatable feature flag values to an + * SDK client in test scenarios. + * + * `TestData` integrates with the SDK as a plugin and uses the + * debug override mechanism to inject flag values. Unlike streaming or polling + * data sources that connect to LaunchDarkly services, `TestData` lets you + * define flag values in code and update them during test execution without + * any network I/O. + * + * **Primary use cases:** + * - Unit tests that need predictable flag evaluation behavior + * - Integration tests that simulate various flag configurations + * - Local development environments without LaunchDarkly connectivity + * + * **Important:** `TestData` is intended exclusively for testing and + * development scenarios. It must not be used in production environments. + * + * @example + * ```typescript + * import { TestData } from '@launchdarkly/client-testing-plugin'; + * import { createClient } from '@launchdarkly/js-client-sdk'; + * + * const td = new TestData({ + * 'show-banner': true, + * greeting: 'Hello', + * }); + * + * const client = createClient('test-key', context, { + * plugins: [td], + * sendEvents: false, + * streaming: false, + * }); + * await client.start({ bootstrap: {} }); + * + * // Update flag values at any time: + * td.setBool('show-banner', false).setString('greeting', 'Welcome'); + * ``` + */ +export default class TestData implements LDPluginBase { + private _values: Record = Object.create(null); + private _debugOverride?: LDDebugOverride; + + /** + * Creates a new TestData instance, optionally seeded with a base set of + * flag values. The seed values are applied to the SDK client when it + * initializes. + * + * @param initialFlags optional map of flag keys to values + */ + constructor(initialFlags?: { [key: string]: LDFlagValue }) { + if (initialFlags) { + Object.entries(initialFlags).forEach(([key, value]) => { + this._values[key] = value; + }); + } + } + + /** + * Sets a boolean flag value. + * + * @returns this TestData for chaining + */ + setBool(key: string, value: boolean): this { + return this._set(key, value); + } + + /** + * Sets a string flag value. + * + * @returns this TestData for chaining + */ + setString(key: string, value: string): this { + return this._set(key, value); + } + + /** + * Sets a numeric flag value. + * + * @returns this TestData for chaining + */ + setNumber(key: string, value: number): this { + return this._set(key, value); + } + + /** + * Sets a JSON flag value (object or array). + * + * @returns this TestData for chaining + */ + setJson(key: string, value: object | unknown[]): this { + if (value === null || typeof value !== 'object') { + throw new TypeError( + `setJson("${key}", ...) requires an object or array; got ${value === null ? 'null' : typeof value}`, + ); + } + return this._set(key, value); + } + + /** + * Removes the flag with the given key. If the SDK client is connected, + * the override for this flag is also cleared. + * + * @returns this TestData for chaining + */ + remove(key: string): this { + delete this._values[key]; + this._debugOverride?.removeOverride(key); + return this; + } + + /** + * Removes all flags. If the SDK client is connected, all overrides are + * cleared. Useful for test isolation in `beforeEach`. + * + * @returns this TestData for chaining + */ + clear(): this { + this._values = Object.create(null); + this._debugOverride?.clearAllOverrides(); + return this; + } + + getMetadata(): LDPluginMetadata { + return { name: PLUGIN_NAME }; + } + + register(_client: unknown, _environmentMetadata: LDPluginEnvironmentMetadata): void { + // No-op: this plugin only needs the LDDebugOverride handed to registerDebug. + } + + /** + * A given `TestData` instance must be paired with at most one client. + * Calling `registerDebug` a second time throws. The SDK plugin runner + * catches this throw and logs an error rather than failing initialization, + * so the second client will silently still work but will not receive + * subsequent flag updates from this `TestData`. + */ + registerDebug(debugOverride: LDDebugOverride): void { + if (this._debugOverride) { + throw new Error( + 'TestData has already been registered with a client. ' + + 'Construct a separate TestData instance for each client.', + ); + } + this._debugOverride = debugOverride; + + Object.keys(this._values).forEach((key) => { + debugOverride.setOverride(key, this._values[key]); + }); + } + + /** + * @internal + * + * Shared write path for the typed setters. Stores the value and, if the SDK + * client is connected, applies the override. Every write fires + * `setOverride`, mirroring a real connection that can re-deliver a flag and + * fire a `change` event even when the value is unchanged. + */ + private _set(key: string, value: LDFlagValue): this { + this._values[key] = value; + this._debugOverride?.setOverride(key, value); + return this; + } +} diff --git a/packages/tooling/client-testing-plugin/src/index.ts b/packages/tooling/client-testing-plugin/src/index.ts new file mode 100644 index 0000000000..fcf26360ce --- /dev/null +++ b/packages/tooling/client-testing-plugin/src/index.ts @@ -0,0 +1 @@ +export { default as TestData } from './TestData'; diff --git a/packages/tooling/client-testing-plugin/tsconfig.json b/packages/tooling/client-testing-plugin/tsconfig.json new file mode 100644 index 0000000000..f083ccc43d --- /dev/null +++ b/packages/tooling/client-testing-plugin/tsconfig.json @@ -0,0 +1,22 @@ +{ + "compilerOptions": { + "allowSyntheticDefaultImports": true, + "declaration": true, + "declarationMap": true, + "lib": ["es6", "dom"], + "module": "ESNext", + "moduleResolution": "Bundler", + "noImplicitOverride": true, + "outDir": "dist", + "resolveJsonModule": true, + "rootDir": ".", + "skipLibCheck": true, + "sourceMap": true, + "strict": true, + "stripInternal": true, + "target": "ES2017", + "types": ["jest", "node"] + }, + "include": ["src/**/*"], + "exclude": ["**/*.test.ts", "**/*.test.tsx", "dist", "node_modules", "__tests__"] +} diff --git a/packages/tooling/client-testing-plugin/tsconfig.ref.json b/packages/tooling/client-testing-plugin/tsconfig.ref.json new file mode 100644 index 0000000000..34a1cb607a --- /dev/null +++ b/packages/tooling/client-testing-plugin/tsconfig.ref.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "include": ["src/**/*", "package.json"], + "compilerOptions": { + "composite": true + } +} diff --git a/packages/tooling/client-testing-plugin/tsup.config.js b/packages/tooling/client-testing-plugin/tsup.config.js new file mode 100644 index 0000000000..fb954052d9 --- /dev/null +++ b/packages/tooling/client-testing-plugin/tsup.config.js @@ -0,0 +1,13 @@ +// eslint-disable-next-line import/no-extraneous-dependencies +import { defineConfig } from 'tsup'; + +export default defineConfig([ + { + entry: { index: 'src/index.ts' }, + format: ['esm', 'cjs'], + dts: true, + clean: true, + sourcemap: true, + minify: false, + }, +]); diff --git a/tsconfig.json b/tsconfig.json index 0d86dcbb09..2d1f02f893 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -52,6 +52,9 @@ { "path": "./packages/telemetry/node-server-sdk-otel/tsconfig.ref.json" }, + { + "path": "./packages/tooling/client-testing-plugin/tsconfig.ref.json" + }, { "path": "./packages/tooling/jest/tsconfig.ref.json" },