diff --git a/.prettierignore b/.prettierignore index 3220186056..f37aeebac2 100644 --- a/.prettierignore +++ b/.prettierignore @@ -6,3 +6,5 @@ node_modules/ docs/ .yarn coverage +.next +.vscode diff --git a/package.json b/package.json index fda3549e38..050696dc3b 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,8 @@ "packages/sdk/server-node", "packages/sdk/cloudflare", "packages/sdk/cloudflare/example", - "packages/sdk/vercel" + "packages/sdk/vercel", + "packages/sdk/example-vercel" ], "private": true, "scripts": { diff --git a/packages/sdk/vercel/example-vercel/.gitignore b/packages/sdk/vercel/example-vercel/.gitignore new file mode 100644 index 0000000000..983df7c7d3 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/.gitignore @@ -0,0 +1,36 @@ +# See https://help.github.com/articles/ignoring-files/ for more about ignoring files. + +# dependencies +/node_modules +/.pnp +.pnp.js + +# testing +/coverage + +# next.js +/.next/ +/out/ + +# production +/build + +# misc +.DS_Store +*.pem + +# debug +npm-debug.log* +yarn-debug.log* +yarn-error.log* + +# local env files +.env*.local + +# vercel +.vercel + +# typescript +*.tsbuildinfo +next-env.d.ts +.vscode diff --git a/packages/sdk/vercel/example-vercel/README.md b/packages/sdk/vercel/example-vercel/README.md new file mode 100644 index 0000000000..5a60fc48eb --- /dev/null +++ b/packages/sdk/vercel/example-vercel/README.md @@ -0,0 +1,30 @@ +# Example test app for Vercel LaunchDarkly SDK + +This is an example test app to showcase the usage of the Vercel LaunchDarkly +SDK. This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). + +## Prerequisites + +A node environment of version 16 and yarn are required to develop in this repository. +You will also need the vercel cli installed and a Vercel account to setup +the test data required by this example. See the [Vercel docs](https://vercel.com/docs/storage/edge-config/get-started) on how to setup your edge config store. + +## Usage + +1. Follow the [Vercel docs](https://vercel.com/docs/storage/edge-config/get-started) to insert [testData.json](https://github.com/launchdarkly/js-core/blob/main/packages/sdk/vercel/testData.json) to your edge config store. + +2. After completing the guide above, you should have linked this example app to your Vercel project and created an `.env.development.local`. + +3. At the root of the js-core repo: + +```shell +yarn && yarn build +``` + +4. Then back in this example folder: + +```shell +yarn dev +``` + +5. Open [http://localhost:3000/api/hello](http://localhost:3000/api/hello). diff --git a/packages/sdk/vercel/example-vercel/next.config.js b/packages/sdk/vercel/example-vercel/next.config.js new file mode 100644 index 0000000000..4436b22b5b --- /dev/null +++ b/packages/sdk/vercel/example-vercel/next.config.js @@ -0,0 +1,8 @@ +/** @type {import('next').NextConfig} */ +const nextConfig = { + experimental: { + appDir: true, + }, +}; + +module.exports = nextConfig; diff --git a/packages/sdk/vercel/example-vercel/package.json b/packages/sdk/vercel/example-vercel/package.json new file mode 100644 index 0000000000..3160d8da59 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/package.json @@ -0,0 +1,27 @@ +{ + "name": "example-vercel", + "version": "0.0.0", + "packageManager": "yarn@3.4.1", + "scripts": { + "dev": "next dev", + "build": "tsc", + "start": "next start", + "lint": "next lint" + }, + "dependencies": { + "@launchdarkly/vercel-server-sdk": "0.2.2", + "@vercel/edge-config": "^0.1.8", + "next": "13.3.1", + "react": "18.2.0", + "react-dom": "18.2.0" + }, + "devDependencies": { + "@types/node": "18.16.2", + "@types/react": "18.2.0", + "@types/react-dom": "18.2.1", + "autoprefixer": "10.4.14", + "postcss": "8.4.23", + "tailwindcss": "3.3.2", + "typescript": "5.0.4" + } +} diff --git a/packages/sdk/vercel/example-vercel/postcss.config.js b/packages/sdk/vercel/example-vercel/postcss.config.js new file mode 100644 index 0000000000..12a703d900 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/postcss.config.js @@ -0,0 +1,6 @@ +module.exports = { + plugins: { + tailwindcss: {}, + autoprefixer: {}, + }, +}; diff --git a/packages/sdk/vercel/example-vercel/public/next.svg b/packages/sdk/vercel/example-vercel/public/next.svg new file mode 100644 index 0000000000..5174b28c56 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/public/next.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/sdk/vercel/example-vercel/public/vercel.svg b/packages/sdk/vercel/example-vercel/public/vercel.svg new file mode 100644 index 0000000000..d2f8422273 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/public/vercel.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/packages/sdk/vercel/example-vercel/src/app/api/hello/route.ts b/packages/sdk/vercel/example-vercel/src/app/api/hello/route.ts new file mode 100644 index 0000000000..391d75db28 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/src/app/api/hello/route.ts @@ -0,0 +1,37 @@ +import { NextResponse } from 'next/server'; +import { createClient } from '@vercel/edge-config'; +import { init as initLD } from '@launchdarkly/vercel-server-sdk'; + +export const config = { + runtime: 'edge', +}; + +export async function GET() { + const sdkKey = 'test-sdk-key'; + const flagKey = 'testFlag1'; + + // default fallthrough evaluates to true + const contextFallthrough = { kind: 'user', key: 'test-user-key-1' }; + + // matches email targeting rule evaluates to false + const contextEmailRule = { kind: 'user', key: 'test-user-key-1', email: 'test@gmail.com' }; + + const vercelClient = createClient(process.env.EDGE_CONFIG); + + // start using ld + const client = initLD(sdkKey, vercelClient); + await client.waitForInitialization(); + const fallthrough = await client.variation(flagKey, contextFallthrough, false); + const emailRule = await client.variation(flagKey, contextEmailRule, false); + const flagDetail = await client.variationDetail(flagKey, contextEmailRule, false); + const allFlags = await client.allFlagsState(contextEmailRule); + + return NextResponse.json({ + flagKey: `${flagKey}`, + contextKey: `${contextFallthrough.key}`, + fallthrough, + emailRule, + emailRuleDetail: `${JSON.stringify(flagDetail)}`, + allFlags: `${JSON.stringify(allFlags)}`, + }); +} diff --git a/packages/sdk/vercel/example-vercel/src/app/favicon.ico b/packages/sdk/vercel/example-vercel/src/app/favicon.ico new file mode 100644 index 0000000000..718d6fea48 Binary files /dev/null and b/packages/sdk/vercel/example-vercel/src/app/favicon.ico differ diff --git a/packages/sdk/vercel/example-vercel/src/app/globals.css b/packages/sdk/vercel/example-vercel/src/app/globals.css new file mode 100644 index 0000000000..e0936eda7d --- /dev/null +++ b/packages/sdk/vercel/example-vercel/src/app/globals.css @@ -0,0 +1,23 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + --foreground-rgb: 0, 0, 0; + --background-start-rgb: 214, 219, 220; + --background-end-rgb: 255, 255, 255; +} + +@media (prefers-color-scheme: dark) { + :root { + --foreground-rgb: 255, 255, 255; + --background-start-rgb: 0, 0, 0; + --background-end-rgb: 0, 0, 0; + } +} + +body { + color: rgb(var(--foreground-rgb)); + background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) + rgb(var(--background-start-rgb)); +} diff --git a/packages/sdk/vercel/example-vercel/src/app/layout.tsx b/packages/sdk/vercel/example-vercel/src/app/layout.tsx new file mode 100644 index 0000000000..04c1f6fa55 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/src/app/layout.tsx @@ -0,0 +1,17 @@ +import './globals.css'; +import { Inter } from 'next/font/google'; + +const inter = Inter({ subsets: ['latin'] }); + +export const metadata = { + title: 'Create Next App', + description: 'Generated by create next app', +}; + +export default function RootLayout({ children }: { children: React.ReactNode }) { + return ( + + {children} + + ); +} diff --git a/packages/sdk/vercel/example-vercel/src/app/page.tsx b/packages/sdk/vercel/example-vercel/src/app/page.tsx new file mode 100644 index 0000000000..b83bb1bb26 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/src/app/page.tsx @@ -0,0 +1,116 @@ +import Image from 'next/image'; +import { Inter } from 'next/font/google'; + +const inter = Inter({ subsets: ['latin'] }); + +export default function Home() { + return ( +
+
+

+ Get started by editing  + src/app/page.tsx +

+
+ + By{' '} + Vercel Logo + +
+
+ +
+ Next.js Logo +
+ +
+ +

+ Docs{' '} + + -> + +

+

+ Find in-depth information about Next.js features and API. +

+
+ + +

+ Learn{' '} + + -> + +

+

+ Learn about Next.js in an interactive course with quizzes! +

+
+ + +

+ Templates{' '} + + -> + +

+

+ Explore the Next.js 13 playground. +

+
+ + +

+ Deploy{' '} + + -> + +

+

+ Instantly deploy your Next.js site to a shareable URL with Vercel. +

+
+
+
+ ); +} diff --git a/packages/sdk/vercel/example-vercel/tailwind.config.js b/packages/sdk/vercel/example-vercel/tailwind.config.js new file mode 100644 index 0000000000..0fc0404cb3 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/tailwind.config.js @@ -0,0 +1,17 @@ +/** @type {import('tailwindcss').Config} */ +module.exports = { + content: [ + './src/pages/**/*.{js,ts,jsx,tsx,mdx}', + './src/components/**/*.{js,ts,jsx,tsx,mdx}', + './src/app/**/*.{js,ts,jsx,tsx,mdx}', + ], + theme: { + extend: { + backgroundImage: { + 'gradient-radial': 'radial-gradient(var(--tw-gradient-stops))', + 'gradient-conic': 'conic-gradient(from 180deg at 50% 50%, var(--tw-gradient-stops))', + }, + }, + }, + plugins: [], +}; diff --git a/packages/sdk/vercel/example-vercel/testData.json b/packages/sdk/vercel/example-vercel/testData.json new file mode 100644 index 0000000000..495819a24d --- /dev/null +++ b/packages/sdk/vercel/example-vercel/testData.json @@ -0,0 +1,171 @@ +{ + "flags": { + "testFlag1": { + "key": "testFlag1", + "on": true, + "prerequisites": [], + "targets": [], + "rules": [ + { + "variation": 1, + "id": "rule1", + "clauses": [ + { + "contextKind": "user", + "attribute": "/email", + "op": "contains", + "values": ["gmail"], + "negate": false + } + ], + "trackEvents": false, + "rollout": { + "bucketBy": "bucket", + "variations": [{ "variation": 1, "weight": 100 }] + } + } + ], + "fallthrough": { + "variation": 0 + }, + "offVariation": 1, + "variations": [true, false], + "clientSideAvailability": { + "usingMobileKey": true, + "usingEnvironmentId": true + }, + "clientSide": true, + "salt": "aef830243d6640d0a973be89988e008d", + "trackEvents": false, + "trackEventsFallthrough": false, + "debugEventsUntilDate": null, + "version": 2, + "deleted": false + }, + "testFlag2": { + "key": "testFlag2", + "on": true, + "prerequisites": [], + "targets": [], + "rules": [], + "fallthrough": { + "variation": 0, + "rollout": { + "bucketBy": "bucket", + "variations": [{ "variation": 1, "weight": 100 }], + "contextKind:": "user", + "attribute": "/email" + } + }, + "offVariation": 1, + "variations": [true, false], + "clientSideAvailability": { + "usingMobileKey": true, + "usingEnvironmentId": true + }, + "clientSide": true, + "salt": "aef830243d6640d0a973be89988e008d", + "trackEvents": false, + "trackEventsFallthrough": false, + "debugEventsUntilDate": null, + "version": 2, + "deleted": false + }, + "testFlag3": { + "key": "testFlag3", + "on": true, + "prerequisites": [], + "targets": [], + "rules": [ + { + "variation": 1, + "id": "rule1", + "clauses": [ + { + "op": "segmentMatch", + "values": ["testSegment1"], + "negate": false + } + ], + "trackEvents": false + } + ], + "fallthrough": { + "variation": 0 + }, + "offVariation": 1, + "variations": [true, false], + "clientSideAvailability": { + "usingMobileKey": true, + "usingEnvironmentId": true + }, + "clientSide": true, + "salt": "aef830243d6640d0a973be89988e008d", + "trackEvents": false, + "trackEventsFallthrough": false, + "debugEventsUntilDate": null, + "version": 2, + "deleted": false + } + }, + "segments": { + "testSegment1": { + "name": "testSegment1", + "tags": [], + "creationDate": 1676063792158, + "key": "testSegment1", + "included": [], + "excluded": [], + "includedContexts": [], + "excludedContexts": [], + "_links": { + "parent": { "href": "/api/v2/segments/default/test", "type": "application/json" }, + "self": { + "href": "/api/v2/segments/default/test/beta-users-1", + "type": "application/json" + }, + "site": { "href": "/default/test/segments/beta-users-1", "type": "text/html" } + }, + "rules": [ + { + "id": "rule-country", + "clauses": [ + { + "attribute": "country", + "op": "in", + "values": ["australia"], + "negate": false + } + ] + } + ], + "version": 1, + "deleted": false, + "_access": { "denied": [], "allowed": [] }, + "generation": 1 + }, + "testSegment2": { + "name": "testSegment2", + "tags": [], + "creationDate": 1676063792158, + "key": "testSegment2", + "included": [], + "excluded": [], + "includedContexts": [], + "excludedContexts": [], + "_links": { + "parent": { "href": "/api/v2/segments/default/test", "type": "application/json" }, + "self": { + "href": "/api/v2/segments/default/test/beta-users-1", + "type": "application/json" + }, + "site": { "href": "/default/test/segments/beta-users-1", "type": "text/html" } + }, + "rules": [], + "version": 1, + "deleted": false, + "_access": { "denied": [], "allowed": [] }, + "generation": 1 + } + } +} diff --git a/packages/sdk/vercel/example-vercel/tsconfig.eslint.json b/packages/sdk/vercel/example-vercel/tsconfig.eslint.json new file mode 100644 index 0000000000..8241f86c36 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/tsconfig.eslint.json @@ -0,0 +1,5 @@ +{ + "extends": "./tsconfig.json", + "include": ["/**/*.ts", "/**/*.tsx"], + "exclude": ["node_modules"] +} diff --git a/packages/sdk/vercel/example-vercel/tsconfig.json b/packages/sdk/vercel/example-vercel/tsconfig.json new file mode 100644 index 0000000000..0c7555fa76 --- /dev/null +++ b/packages/sdk/vercel/example-vercel/tsconfig.json @@ -0,0 +1,28 @@ +{ + "compilerOptions": { + "target": "es5", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "forceConsistentCasingInFileNames": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "node", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "preserve", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": ["next-env.d.ts", "**/*.ts", "**/*.tsx", ".next/types/**/*.ts"], + "exclude": ["node_modules"] +} diff --git a/packages/sdk/vercel/tsconfig.json b/packages/sdk/vercel/tsconfig.json index d52294d88d..b1cf0c57b2 100644 --- a/packages/sdk/vercel/tsconfig.json +++ b/packages/sdk/vercel/tsconfig.json @@ -18,5 +18,5 @@ "types": ["jest", "node"], "skipLibCheck": true }, - "exclude": ["**/*.test.ts", "dist", "node_modules", "__tests__", "example"] + "exclude": ["**/*.test.ts", "dist", "node_modules", "__tests__", "example-vercel"] }