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
5 changes: 5 additions & 0 deletions .changeset/tidy-dependency-scanning.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@reflag/cli": patch
---

Update Express and Zod to patched versions. Preserve CLI schema parsing behavior when migrating to Zod 4.
5 changes: 5 additions & 0 deletions .github/workflows/package-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,11 @@ jobs:
- id: test
name: Test the project
run: yarn test:ci
- name: Check standalone examples
run: |
for example in packages/node-sdk/examples/cloudflare-worker packages/openfeature-node-provider/example; do
(cd "$example" && yarn install --immutable && yarn build && yarn test:ci)
done
- name: Report test results
uses: dorny/test-reporter@v1.6.0
with:
Expand Down
2 changes: 1 addition & 1 deletion .nvmrc
Original file line number Diff line number Diff line change
@@ -1 +1 @@
24.13.0
24.22.0
9 changes: 7 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
"devDependencies": {
"@changesets/cli": "^2.29.6",
"husky": "^9.1.7",
"lerna": "^8.1.3",
"lerna": "^10.1.1",
"lint-staged": "^16.2.7",
"oxfmt": "^0.27.0",
"oxlint": "^1.42.0",
Expand All @@ -45,8 +45,13 @@
"typescript": "^5.7.3"
},
"resolutions": {
"minimatch@npm:9.0.1": "npm:9.0.9",
"postcss@npm:8.4.31": "npm:8.5.32",
"postcss@npm:~8.4.32": "npm:8.5.32",
"react": "19.1.0",
"react-dom": "19.1.0"
"react-dom": "19.1.0",
"tinypool@npm:2.0.0": "npm:2.2.0",
"undici@npm:7.29.0": "npm:7.30.2"
},
"lint-staged": {
"*.{ts,tsx,js,jsx,mjs,cjs,mts,cts,vue}": [
Expand Down
10 changes: 5 additions & 5 deletions packages/browser-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,24 +46,24 @@
"preact": "^10.22.1"
},
"devDependencies": {
"@playwright/test": "^1.49.1",
"@playwright/test": "^1.64.1",
"@reflag/tsconfig": "0.0.2",
"@types/js-cookie": "^3.0.6",
"@types/node": "^22.12.0",
"@vitest/coverage-v8": "4.1.11",
"@vitest/coverage-v8": "4.1.12",
"c8": "~10.1.3",
"http-server": "^14.1.1",
"jsdom": "^24.1.0",
"msw": "^2.3.4",
"msw": "^2.15.0",
"nock": "^14.0.1",
"postcss": "^8.4.33",
"postcss-nesting": "^12.0.2",
"postcss-preset-env": "^9.3.0",
"typescript": "^5.7.3",
"vite": "^6.4.2",
"vite-plugin-dts": "^4.0.0-beta.1",
"vite-plugin-dts": "^4.5.4",
"vite-plugin-static-copy": "^2.3.2",
"vitest": "^4.1.11"
"vitest": "^4.1.12"
},
"packageManager": "yarn@4.1.1"
}
21 changes: 13 additions & 8 deletions packages/browser-sdk/test/e2e/feedback-widget.browser.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,10 +246,13 @@ test("Opens a feedback widget in the bottom right by default", async ({

await expect(container).toBeAttached();

const bbox = await container.locator(".dialog").boundingBox();
expect(bbox?.x).toEqual(WINDOW_WIDTH - bbox!.width - 16);
expect(bbox?.y).toBeGreaterThan(WINDOW_HEIGHT - bbox!.height - 30); // Account for browser differences
expect(bbox?.y).toBeLessThan(WINDOW_HEIGHT - bbox!.height);
// Attachment happens before the entrance animation has reached its position.
await expect(async () => {
const bbox = await container.locator(".dialog").boundingBox();
expect(bbox?.x).toEqual(WINDOW_WIDTH - bbox!.width - 16);
expect(bbox?.y).toBeGreaterThan(WINDOW_HEIGHT - bbox!.height - 30); // Account for browser differences
expect(bbox?.y).toBeLessThan(WINDOW_HEIGHT - bbox!.height);
}).toPass({ timeout: 5000 });
});

test("Opens a feedback widget in the correct position when overridden", async ({
Expand All @@ -268,10 +271,12 @@ test("Opens a feedback widget in the correct position when overridden", async ({

await expect(container).toBeAttached();

const bbox = await container.locator(".dialog").boundingBox();
expect(bbox?.x).toEqual(16);
expect(bbox?.y).toBeGreaterThan(0); // Account for browser differences
expect(bbox?.y).toBeLessThanOrEqual(16);
await expect(async () => {
const bbox = await container.locator(".dialog").boundingBox();
expect(bbox?.x).toEqual(16);
expect(bbox?.y).toBeGreaterThan(0); // Account for browser differences
expect(bbox?.y).toBeLessThanOrEqual(16);
}).toPass({ timeout: 5000 });
});

test("Opens a feedback widget with the correct translations", async ({
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,14 +46,14 @@
"change-case": "^5.4.4",
"commander": "^12.1.0",
"comment-json": "^4.2.5",
"express": "^4.21.2",
"express": "^4.22.3",
"fast-deep-equal": "^3.1.3",
"find-up": "^7.0.0",
"open": "^10.1.0",
"ora": "^8.1.0",
"semver": "^7.7.2",
"slug": "^10.0.0",
"zod": "^3.24.2"
"zod": "^4.6.7"
},
"devDependencies": {
"@reflag/tsconfig": "^0.0.2",
Expand All @@ -63,7 +63,7 @@
"@types/slug": "^5.0.9",
"shx": "^0.3.4",
"typescript": "^5.5.4",
"vitest": "^4.1.11"
"vitest": "^4.1.12"
},
"engines": {
"node": ">=18.0.0"
Expand Down
47 changes: 47 additions & 0 deletions packages/cli/test/schemas.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { describe, expect, it } from "vitest";

import { FlagCreateSchema, FlagsQuerySchema } from "../services/flags.js";
import { booleanish, PaginationQueryBaseSchema } from "../utils/schemas.js";

describe("CLI schemas", () => {
it.each([
["true", true],
["1", true],
["false", false],
["0", false],
[true, true],
[false, false],
])("parses boolean-like input %s as %s", (input, expected) => {
expect(booleanish.parse(input)).toBe(expected);
});

it("preserves flag query defaults", () => {
expect(FlagsQuerySchema.parse({ envId: "env_test" })).toEqual({
envId: "env_test",
sortBy: "key",
sortOrder: "asc",
includeRemoteConfigs: false,
});
});

it("rejects unknown query and flag fields", () => {
expect(
FlagsQuerySchema.safeParse({ envId: "env_test", unknown: true }).success,
).toBe(false);
expect(
FlagCreateSchema.safeParse({ name: "Flag", key: "flag", unknown: true })
.success,
).toBe(false);
});

it("coerces pagination input and enforces page-size bounds", () => {
const schema = PaginationQueryBaseSchema();
expect(schema.parse({ pageIndex: "2", pageSize: "10" })).toEqual({
sortOrder: "asc",
pageIndex: 2,
pageSize: 10,
});
expect(schema.safeParse({ pageSize: 0 }).success).toBe(false);
expect(schema.safeParse({ pageSize: 101 }).success).toBe(false);
});
});
3 changes: 1 addition & 2 deletions packages/cli/utils/schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ export const booleanish = z.preprocess((value) => {
return value === "true" || value === "1";
}
return Boolean(value);
}, z.boolean().describe("Boolean value that can be parsed from strings like 'true' or '1'")) as z.ZodEffects<
z.ZodBoolean,
}, z.boolean().describe("Boolean value that can be parsed from strings like 'true' or '1'")) as z.ZodType<
boolean,
boolean
>;
Expand Down
2 changes: 1 addition & 1 deletion packages/flag-evaluation/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@
"@types/node": "^22.12.0",
"ts-node": "^10.9.2",
"typescript": "^5.7.3",
"vitest": "^4.1.11"
"vitest": "^4.1.12"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
},
"dependencies": {
"@reflag/management-sdk": "workspace:*",
"next": "15.5.24",
"next": "15.5.29",
"react": "18.3.1",
"react-dom": "18.3.1"
}
Expand Down
6 changes: 3 additions & 3 deletions packages/management-sdk/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,11 +30,11 @@
"fmt:fix": "oxfmt --write ."
},
"devDependencies": {
"@openapitools/openapi-generator-cli": "^2.13.5",
"@openapitools/openapi-generator-cli": "^2.42.0",
"@reflag/tsconfig": "~0.0.2",
"@types/node": "^22.12.0",
"@vitest/coverage-v8": "4.1.11",
"@vitest/coverage-v8": "4.1.12",
"typescript": "^5.7.3",
"vitest": "~4.1.11"
"vitest": "~4.1.12"
}
}
11 changes: 7 additions & 4 deletions packages/node-sdk/examples/cloudflare-worker/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,15 @@
"dev": "wrangler dev",
"start": "wrangler dev",
"test": "vitest",
"test:ci": "vitest run",
"build": "tsc --noEmit && wrangler deploy --dry-run --outdir dist",
"cf-typegen": "wrangler types"
},
"devDependencies": {
"@cloudflare/vitest-pool-workers": "^0.8.19",
"@cloudflare/vitest-plugin": "^1.6.0",
"typescript": "^5.5.2",
"vitest": "~3.2.6",
"wrangler": "^4.59.1"
}
"vitest": "~4.1.12",
"wrangler": "^4.158.0"
},
"packageManager": "yarn@4.10.3"
}
54 changes: 54 additions & 0 deletions packages/node-sdk/examples/cloudflare-worker/src/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import {
createExecutionContext,
waitOnExecutionContext,
} from "cloudflare:test";
import { beforeEach, expect, it, vi } from "vitest";

import worker from "./index";

const sdk = vi.hoisted(() => ({
initialize: vi.fn().mockResolvedValue(undefined),
getFlags: vi.fn().mockReturnValue({ "example-flag": { isEnabled: true } }),
flush: vi.fn().mockResolvedValue(undefined),
}));

// Exercise the Worker handler without credentials or outbound Reflag requests.
vi.mock("../../../", () => ({
EdgeClient: class {
initialize = sdk.initialize;
getFlags = sdk.getFlags;
flush = sdk.flush;
},
}));

beforeEach(() => vi.clearAllMocks());

it("evaluates flags for the requested context and flushes background work", async () => {
const ctx = createExecutionContext();
const response = await worker.fetch(
new Request("https://example.test/?user.id=user42&company.id=company99"),
{} as Env,
ctx,
);
await waitOnExecutionContext(ctx);

expect(response.status).toBe(200);
expect(await response.text()).toContain('"example-flag"');
expect(sdk.initialize).toHaveBeenCalledOnce();
expect(sdk.getFlags).toHaveBeenCalledWith({
user: { id: "user42" },
company: { id: "company99" },
});
expect(sdk.flush).toHaveBeenCalledOnce();
});

it("allows requests without user or company IDs", async () => {
const ctx = createExecutionContext();
await worker.fetch(new Request("https://example.test/"), {} as Env, ctx);
await waitOnExecutionContext(ctx);

expect(sdk.getFlags).toHaveBeenCalledWith({
user: { id: undefined },
company: { id: undefined },
});
});
2 changes: 1 addition & 1 deletion packages/node-sdk/examples/cloudflare-worker/tsconfig.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@

/* Skip type checking all .d.ts files. */
"skipLibCheck": true,
"types": ["./worker-configuration.d.ts"]
"types": ["./worker-configuration.d.ts", "@cloudflare/vitest-plugin/types"]
},
"exclude": ["test"],
"include": ["worker-configuration.d.ts", "src/**/*.ts"]
Expand Down
13 changes: 4 additions & 9 deletions packages/node-sdk/examples/cloudflare-worker/vitest.config.mts
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
import { defineWorkersConfig } from "@cloudflare/vitest-pool-workers/config";
import { cloudflareTest } from "@cloudflare/vitest-plugin";
import { defineConfig } from "vitest/config";

export default defineWorkersConfig({
test: {
poolOptions: {
workers: {
wrangler: { configPath: "./wrangler.jsonc" },
},
},
},
export default defineConfig({
plugins: [cloudflareTest({ wrangler: { configPath: "./wrangler.jsonc" } })],
});
Loading
Loading