From a68edf1c41f95f22c3eebdf3352f64e7d5fb2bd4 Mon Sep 17 00:00:00 2001 From: Herrtian <70463940+Herrtian@users.noreply.github.com> Date: Sun, 24 May 2026 19:37:49 +0200 Subject: [PATCH 1/2] test: add package export smoke checks Signed-off-by: Herrtian <70463940+Herrtian@users.noreply.github.com> --- .github/workflows/pr-checks.yaml | 3 + package.json | 1 + scripts/test-package-exports.mjs | 151 +++++++++++++++++++++++++++++++ 3 files changed, 155 insertions(+) create mode 100644 scripts/test-package-exports.mjs diff --git a/.github/workflows/pr-checks.yaml b/.github/workflows/pr-checks.yaml index dbe6ec4f35..c447d9e40d 100644 --- a/.github/workflows/pr-checks.yaml +++ b/.github/workflows/pr-checks.yaml @@ -34,6 +34,9 @@ jobs: - name: Build run: npm run build + - name: Test package exports + run: npm run test:package-exports + - name: Test Jest Projects run: npm run test:jest diff --git a/package.json b/package.json index ef1441c003..386782e424 100644 --- a/package.json +++ b/package.json @@ -10,6 +10,7 @@ "test": "npm run test:jest && npm run test:angular", "test:jest": "jest --selectProjects=shared --selectProjects=server --selectProjects=web --selectProjects=react --selectProjects=nest --silent", "test:angular": "npm run test:coverage --workspace=packages/angular", + "test:package-exports": "node scripts/test-package-exports.mjs", "e2e-server": "git submodule update --init --recursive && shx cp spec/specification/assets/gherkin/evaluation.feature packages/server/e2e/features && jest --selectProjects=server-e2e --verbose", "e2e-web": "git submodule update --init --recursive && shx cp spec/specification/assets/gherkin/evaluation.feature packages/web/e2e/features && jest --selectProjects=web-e2e --verbose", "e2e": "npm run e2e-server && npm run e2e-web", diff --git a/scripts/test-package-exports.mjs b/scripts/test-package-exports.mjs new file mode 100644 index 0000000000..ac92f21c98 --- /dev/null +++ b/scripts/test-package-exports.mjs @@ -0,0 +1,151 @@ +import { execFile } from 'node:child_process'; +import { mkdtemp, rm, writeFile } from 'node:fs/promises'; +import { tmpdir } from 'node:os'; +import { dirname, join, resolve } from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; +import { promisify } from 'node:util'; + +const execFileAsync = promisify(execFile); +const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); + +const packageWorkspaces = ['packages/shared', 'packages/server', 'packages/web']; + +const sdkPackages = ['@openfeature/server-sdk', '@openfeature/web-sdk']; + +const fixtureConfig = { + 'format-check': { + variants: { + on: true, + off: false, + }, + defaultVariant: 'on', + disabled: false, + }, +}; + +async function run(command, args, options = {}) { + try { + return await execFileAsync(command, args, { + cwd: repoRoot, + maxBuffer: 1024 * 1024 * 10, + ...options, + }); + } catch (error) { + if (error.stdout) process.stdout.write(error.stdout); + if (error.stderr) process.stderr.write(error.stderr); + throw error; + } +} + +function npmCommand(args) { + if (process.platform === 'win32') { + return ['cmd.exe', ['/d', '/s', '/c', 'npm', ...args]]; + } + + return ['npm', args]; +} + +function runNpm(args, options) { + const [command, commandArgs] = npmCommand(args); + return run(command, commandArgs, options); +} + +async function packPackage(workspace, destination) { + const packageDirectory = join(repoRoot, workspace); + const { stdout } = await runNpm(['pack', '--json', '--pack-destination', destination], { + cwd: packageDirectory, + }); + const [packResult] = JSON.parse(stdout); + + return join(destination, packResult.filename); +} + +function sourceFor(packageName) { + return `import { InMemoryProvider, OpenFeature } from '${packageName}'; + +async function main() { + const provider = new InMemoryProvider(${JSON.stringify(fixtureConfig, null, 2)} as const); + await OpenFeature.setProviderAndWait(provider); + + const value = await OpenFeature.getClient().getBooleanValue('format-check', false); + + if (value !== true) { + throw new Error('${packageName} did not evaluate the test flag'); + } +} + +void main(); +`; +} + +async function writeFixture(fixtureDirectory) { + await writeFile( + join(fixtureDirectory, 'package.json'), + JSON.stringify( + { + name: 'openfeature-package-export-check', + private: true, + version: '0.0.0', + }, + null, + 2, + ), + ); + + await writeFile( + join(fixtureDirectory, 'tsconfig.json'), + JSON.stringify( + { + compilerOptions: { + target: 'ES2020', + module: 'Node16', + moduleResolution: 'Node16', + strict: true, + skipLibCheck: true, + outDir: 'dist', + }, + include: ['*.mts', '*.cts'], + }, + null, + 2, + ), + ); + + for (const packageName of sdkPackages) { + const label = packageName.replace('@openfeature/', '').replace('-sdk', ''); + await writeFile(join(fixtureDirectory, `${label}.mts`), sourceFor(packageName)); + await writeFile(join(fixtureDirectory, `${label}.cts`), sourceFor(packageName)); + } +} + +async function main() { + const tempDirectory = await mkdtemp(join(tmpdir(), 'openfeature-package-exports-')); + + try { + const tarballs = []; + + for (const packageWorkspace of packageWorkspaces) { + tarballs.push(await packPackage(packageWorkspace, tempDirectory)); + } + + await writeFixture(tempDirectory); + await runNpm(['install', '--ignore-scripts', '--no-audit', '--no-fund', '--no-package-lock', ...tarballs], { + cwd: tempDirectory, + }); + + await run(process.execPath, [join(repoRoot, 'node_modules/typescript/bin/tsc'), '--project', 'tsconfig.json'], { + cwd: tempDirectory, + }); + + for (const packageName of sdkPackages) { + const label = packageName.replace('@openfeature/', '').replace('-sdk', ''); + await run(process.execPath, [join(tempDirectory, 'dist', `${label}.mjs`)]); + await run(process.execPath, [join(tempDirectory, 'dist', `${label}.cjs`)]); + } + } finally { + await rm(tempDirectory, { recursive: true, force: true }); + } +} + +await main(); From 86e6c1d94610f234f7650dd7e08adc7d4baaaad4 Mon Sep 17 00:00:00 2001 From: Herrtian <70463940+Herrtian@users.noreply.github.com> Date: Fri, 19 Jun 2026 19:09:18 +0200 Subject: [PATCH 2/2] test: cover core package exports Signed-off-by: Herrtian <70463940+Herrtian@users.noreply.github.com> --- scripts/test-package-exports.mjs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/scripts/test-package-exports.mjs b/scripts/test-package-exports.mjs index ac92f21c98..4dd9d0f910 100644 --- a/scripts/test-package-exports.mjs +++ b/scripts/test-package-exports.mjs @@ -11,7 +11,9 @@ const repoRoot = resolve(dirname(fileURLToPath(import.meta.url)), '..'); const packageWorkspaces = ['packages/shared', 'packages/server', 'packages/web']; +const corePackage = '@openfeature/core'; const sdkPackages = ['@openfeature/server-sdk', '@openfeature/web-sdk']; +const smokeTestPackages = [corePackage, ...sdkPackages]; const fixtureConfig = { 'format-check': { @@ -62,6 +64,19 @@ async function packPackage(workspace, destination) { } function sourceFor(packageName) { + if (packageName === corePackage) { + return `import { ErrorCode, ServerProviderStatus } from '${packageName}'; + +if (ErrorCode.FLAG_NOT_FOUND !== 'FLAG_NOT_FOUND') { + throw new Error('${packageName} did not expose ErrorCode correctly'); +} + +if (ServerProviderStatus.NOT_READY !== 'NOT_READY') { + throw new Error('${packageName} did not expose ServerProviderStatus correctly'); +} +`; + } + return `import { InMemoryProvider, OpenFeature } from '${packageName}'; async function main() { @@ -112,7 +127,7 @@ async function writeFixture(fixtureDirectory) { ), ); - for (const packageName of sdkPackages) { + for (const packageName of smokeTestPackages) { const label = packageName.replace('@openfeature/', '').replace('-sdk', ''); await writeFile(join(fixtureDirectory, `${label}.mts`), sourceFor(packageName)); await writeFile(join(fixtureDirectory, `${label}.cts`), sourceFor(packageName)); @@ -138,7 +153,7 @@ async function main() { cwd: tempDirectory, }); - for (const packageName of sdkPackages) { + for (const packageName of smokeTestPackages) { const label = packageName.replace('@openfeature/', '').replace('-sdk', ''); await run(process.execPath, [join(tempDirectory, 'dist', `${label}.mjs`)]); await run(process.execPath, [join(tempDirectory, 'dist', `${label}.cjs`)]);