Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .github/workflows/pr-checks.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
166 changes: 166 additions & 0 deletions scripts/test-package-exports.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
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 corePackage = '@openfeature/core';
const sdkPackages = ['@openfeature/server-sdk', '@openfeature/web-sdk'];
const smokeTestPackages = [corePackage, ...sdkPackages];

Comment thread
coderabbitai[bot] marked this conversation as resolved.
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) {
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() {
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 smokeTestPackages) {
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 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`)]);
}
} finally {
await rm(tempDirectory, { recursive: true, force: true });
}
}

await main();
Loading