diff --git a/.github/workflows/pr-check.yaml b/.github/workflows/pr-check.yaml index b4fc437..5f33345 100644 --- a/.github/workflows/pr-check.yaml +++ b/.github/workflows/pr-check.yaml @@ -17,13 +17,17 @@ jobs: - name: Checkout uses: actions/checkout@v4 - - name: Setup Node - uses: actions/setup-node@v4 + - name: Setup mise + uses: jdx/mise-action@v2 + + - name: Cache npm packages + uses: actions/cache@v4 with: - node-version-file: '.nvmrc' - cache: 'npm' + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: ${{ runner.os }}-npm- - - name: Restore cache + - name: Restore Next.js cache uses: actions/cache@v4 with: path: .next/cache @@ -45,4 +49,33 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Build with Next.js - run: npm run build \ No newline at end of file + run: npm run build + + check-generated: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup mise + uses: jdx/mise-action@v2 + + - name: Cache npm packages + uses: actions/cache@v4 + with: + path: ~/.npm + key: ${{ runner.os }}-npm-${{ hashFiles('**/package-lock.json') }} + restore-keys: ${{ runner.os }}-npm- + + - name: Install dependencies + run: npm ci + + - name: Regenerate hooks + run: npm run generate-flags + + - name: Check for uncommitted changes + run: | + if ! git diff --exit-code src/generated; then + echo "Generated files are out of date. Run 'npm run generate-flags' and commit the result." + exit 1 + fi diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..e561b2c --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,47 @@ +# Contributing to ToggleShop + +ToggleShop is an OpenFeature demo app. Contributions are welcome, whether that's fixing a bug, improving a scenario, or testing new OpenFeature functionality. + +## Developer setup + +We use [mise](https://mise.jdx.dev) to manage tool versions. It pins both Node.js and the OpenFeature CLI per-project, so every contributor gets the same environment. + +1. [Install mise](https://mise.jdx.dev/getting-started.html) +2. Install project tools: + ```sh + mise install + ``` +3. Install dependencies: + ```sh + npm install + ``` +4. Start the app: + ```sh + npm run dev + ``` + +If you prefer nvm, the `.nvmrc` file is still present. You'll need to [install the OpenFeature CLI](https://github.com/open-feature/cli#installation) separately. + +## Generated files + +The React hooks and Node.js client in `src/generated/` are generated by the OpenFeature CLI from `flags.json` and `.openfeature.yaml`. If you change flag definitions, regenerate them before committing: + +```sh +npm run generate-flags +``` + +CI will fail if `src/generated/` is out of date with the flag definitions. + +## DCO sign-off + +All commits to OpenFeature repositories require a [Developer Certificate of Origin (DCO)](https://developercertificate.org/) sign-off: + +```sh +git commit -s -m "your commit message" +``` + +## Running tests + +```sh +npm test +``` diff --git a/Dockerfile b/Dockerfile index 6f0950c..f82684e 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax=docker.io/docker/dockerfile:1 -FROM node:18-alpine AS base +FROM node:20-alpine AS base # 1. Install dependencies only when needed FROM base AS deps diff --git a/README.md b/README.md index 8f1efac..31a2b2e 100644 --- a/README.md +++ b/README.md @@ -113,3 +113,7 @@ Deploy the ToggleShop and supporting infrastructure: ```sh kubectl -n default apply -f kubernetes/toggle-shop.yaml && kubectl wait --timeout=60s deployment --for condition=Available=True -l 'app=toggle-shop' -n 'default' ``` + +## Contributing + +See [CONTRIBUTING.md](./CONTRIBUTING.md) for dev setup, how to regenerate flag hooks, and DCO sign-off requirements. diff --git a/mise.toml b/mise.toml new file mode 100644 index 0000000..6afd62a --- /dev/null +++ b/mise.toml @@ -0,0 +1,4 @@ +[tools] +node = "20" +"github:open-feature/cli" = "0.4.1" + diff --git a/renovate.json b/renovate.json new file mode 100644 index 0000000..daaea0b --- /dev/null +++ b/renovate.json @@ -0,0 +1,6 @@ +{ + "$schema": "https://docs.renovatebot.com/renovate-schema.json", + "extends": [ + "github>open-feature/community-tooling" + ] +} diff --git a/src/generated/nodejs/openfeature.ts b/src/generated/nodejs/openfeature.ts index b5b6f92..39e9415 100644 --- a/src/generated/nodejs/openfeature.ts +++ b/src/generated/nodejs/openfeature.ts @@ -3,6 +3,7 @@ import { OpenFeature, stringOrUndefined, objectOrUndefined, + JsonValue, } from "@openfeature/server-sdk"; import type { EvaluationContext, @@ -10,6 +11,18 @@ import type { FlagEvaluationOptions, } from "@openfeature/server-sdk"; +// Flag key constants for programmatic access +export const FlagKeys = { + /** Flag key for Add free shipping to the UI. */ + OFFER_FREE_SHIPPING: "offer-free-shipping", + /** Flag key for Make the header stay at the top of the page while scrolling. */ + STICKY_HEADER: "sticky-header", + /** Flag key for When on, use postgres else sqlite. */ + USE_DISTRIBUTED_DB: "use-distributed-db", + /** Flag key for When on, use a secure connection to the database. This only applies when use-distributed-db is on. */ + USE_SECURE_PROTOCOL: "use-secure-protocol", +} as const; + export interface GeneratedClient { /** * Add free shipping to the UI. diff --git a/src/generated/react/openfeature.ts b/src/generated/react/openfeature.ts index f657a39..1ee58af 100644 --- a/src/generated/react/openfeature.ts +++ b/src/generated/react/openfeature.ts @@ -3,10 +3,25 @@ import { type ReactFlagEvaluationOptions, type ReactFlagEvaluationNoSuspenseOptions, + type FlagQuery, useFlag, useSuspenseFlag, + JsonValue } from "@openfeature/react-sdk"; +// Flag key constants for programmatic access +export const FlagKeys = { + /** Flag key for Add free shipping to the UI. */ + OFFER_FREE_SHIPPING: "offer-free-shipping", + /** Flag key for Make the header stay at the top of the page while scrolling. */ + STICKY_HEADER: "sticky-header", + /** Flag key for When on, use postgres else sqlite. */ + USE_DISTRIBUTED_DB: "use-distributed-db", + /** Flag key for When on, use a secure connection to the database. This only applies when use-distributed-db is on. */ + USE_SECURE_PROTOCOL: "use-secure-protocol", +} as const; + + /** * Add free shipping to the UI. * @@ -15,7 +30,7 @@ import { * - default value: `false` * - type: `boolean` */ -export const useOfferFreeShipping = (options?: ReactFlagEvaluationOptions) => { +export const useOfferFreeShipping = (options?: ReactFlagEvaluationOptions): FlagQuery => { return useFlag("offer-free-shipping", false, options); }; @@ -30,7 +45,7 @@ export const useOfferFreeShipping = (options?: ReactFlagEvaluationOptions) => { * Equivalent to useFlag with options: `{ suspend: true }` * @experimental — Suspense is an experimental feature subject to change in future versions. */ -export const useSuspenseOfferFreeShipping = (options?: ReactFlagEvaluationNoSuspenseOptions) => { +export const useSuspenseOfferFreeShipping = (options?: ReactFlagEvaluationNoSuspenseOptions): FlagQuery => { return useSuspenseFlag("offer-free-shipping", false, options); }; @@ -42,7 +57,7 @@ export const useSuspenseOfferFreeShipping = (options?: ReactFlagEvaluationNoSusp * - default value: `false` * - type: `boolean` */ -export const useStickyHeader = (options?: ReactFlagEvaluationOptions) => { +export const useStickyHeader = (options?: ReactFlagEvaluationOptions): FlagQuery => { return useFlag("sticky-header", false, options); }; @@ -57,7 +72,7 @@ export const useStickyHeader = (options?: ReactFlagEvaluationOptions) => { * Equivalent to useFlag with options: `{ suspend: true }` * @experimental — Suspense is an experimental feature subject to change in future versions. */ -export const useSuspenseStickyHeader = (options?: ReactFlagEvaluationNoSuspenseOptions) => { +export const useSuspenseStickyHeader = (options?: ReactFlagEvaluationNoSuspenseOptions): FlagQuery => { return useSuspenseFlag("sticky-header", false, options); }; @@ -69,7 +84,7 @@ export const useSuspenseStickyHeader = (options?: ReactFlagEvaluationNoSuspenseO * - default value: `false` * - type: `boolean` */ -export const useUseDistributedDb = (options?: ReactFlagEvaluationOptions) => { +export const useUseDistributedDb = (options?: ReactFlagEvaluationOptions): FlagQuery => { return useFlag("use-distributed-db", false, options); }; @@ -84,7 +99,7 @@ export const useUseDistributedDb = (options?: ReactFlagEvaluationOptions) => { * Equivalent to useFlag with options: `{ suspend: true }` * @experimental — Suspense is an experimental feature subject to change in future versions. */ -export const useSuspenseUseDistributedDb = (options?: ReactFlagEvaluationNoSuspenseOptions) => { +export const useSuspenseUseDistributedDb = (options?: ReactFlagEvaluationNoSuspenseOptions): FlagQuery => { return useSuspenseFlag("use-distributed-db", false, options); }; @@ -96,7 +111,7 @@ export const useSuspenseUseDistributedDb = (options?: ReactFlagEvaluationNoSuspe * - default value: `false` * - type: `boolean` */ -export const useUseSecureProtocol = (options?: ReactFlagEvaluationOptions) => { +export const useUseSecureProtocol = (options?: ReactFlagEvaluationOptions): FlagQuery => { return useFlag("use-secure-protocol", false, options); }; @@ -111,6 +126,6 @@ export const useUseSecureProtocol = (options?: ReactFlagEvaluationOptions) => { * Equivalent to useFlag with options: `{ suspend: true }` * @experimental — Suspense is an experimental feature subject to change in future versions. */ -export const useSuspenseUseSecureProtocol = (options?: ReactFlagEvaluationNoSuspenseOptions) => { +export const useSuspenseUseSecureProtocol = (options?: ReactFlagEvaluationNoSuspenseOptions): FlagQuery => { return useSuspenseFlag("use-secure-protocol", false, options); };