Skip to content
Open
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
2 changes: 2 additions & 0 deletions .changeset/busy-icons-ask.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
---
---
15 changes: 15 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
# Shared YouVersion Platform configuration
YVP_APP_KEY=""
YVP_API_HOST="api.youversion.com"

# Optional Vite demo overrides
# Defaults to the demo's current origin.
# VITE_YVP_AUTH_REDIRECT_URL="http://localhost:5173"
# SDK UI language (BCP-47). Leave unset to follow the browser.
# VITE_YVP_LOCALE="es"
# Seeds the Reader version picker's Bible language. Distinct from locale.
# VITE_YVP_DEFAULT_LANGUAGE_ID="es"

# Optional Storybook override
# Defaults to Storybook's current origin.
# STORYBOOK_AUTH_REDIRECT_URL="http://localhost:6006"
12 changes: 5 additions & 7 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,17 +44,15 @@ You'll need to obtain an app key from <https://platform.youversion.com>

### Set up environment variables

Create an .env.local file in the `./packages/core` package and update the app key variable.
Copy the root environment template and add your YouVersion app key. This one file configures the
core tests, Storybook, and the Vite example app.

```bash
cp ./packages/core/.env.example ./packages/core/.env.local
cp .env.example .env
```

Create an .env.local file in the `./packages/ui` package and update the app key variable.

```bash
cp ./packages/ui/.env.example ./packages/ui/.env.local
```
Shell and CI variables take precedence over files. Existing package-local `.env.local` files remain
supported as optional harness-specific overrides; the root `.env` provides the default.

> [!NOTE]
> Our React hooks package does not require environment variables at this time.
Expand Down
29 changes: 16 additions & 13 deletions docs/cursor-cloud.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,32 +6,35 @@ Standard install/lint/test/build/dev commands live in `CONTRIBUTING.md` and root

## Env files (gitignored)

Core unit tests use MSW but still throw if `YVP_API_HOST` is unset (`packages/core/src/__tests__/handlers.ts`). Copy the examples before `pnpm test`:
Copy the root environment template and add a real app key when live API access is needed:

- `packages/core/.env.example` → `packages/core/.env.local` (`YVP_API_HOST=api.youversion.com`; a placeholder `YVP_APP_KEY` is enough for mocked tests)
- `packages/ui/.env.example` → `packages/ui/.env.local` (Storybook)
- `examples/vite-react/.env.example` → `examples/vite-react/.env.local` (`VITE_YVP_APP_KEY` required for live Bible content)
```bash
cp .env.example .env
```

The root `.env` configures core integration tests, Storybook, and the Vite demo. Mocked tests and
builds do not require it. Existing package-local `.env.local` files remain supported as optional
harness-specific overrides. Shell and CI variables take precedence over files.

Get a real app key from https://platform.youversion.com. Without `VITE_YVP_APP_KEY`, the demo renders the SDK missing-app-key panel instead of the Bible reader.
Get a real app key from https://platform.youversion.com. Without `YVP_APP_KEY` or a
harness-specific app-key override, the demo and Storybook render the SDK missing-app-key panel,
and live core integration tests remain skipped.

## Running the demo

`pnpm dev:web` is stale (it still filters a removed `nextjs` package). Start the demo with:
Start the demo and its workspace dependency watchers with:

```bash
pnpm --filter vite-react dev --host 127.0.0.1 --port 5173
pnpm dev:web
```

Do not put an extra `--` before `--host`. `pnpm --filter vite-react dev -- --host 127.0.0.1` becomes `vite -- --host 127.0.0.1`; Vite then ignores `--host` and listens on `localhost` (often `::1` only), so `curl http://127.0.0.1:5173` fails.

`pnpm --filter vite-react exec vite --host 127.0.0.1 --port 5173` is equivalent.

## Live core client

After `pnpm build`, source `packages/core/.env.local` and pass those values into `ApiClient`. Test scripts load the file via `dotenv-cli`; the runtime client reads only the config object you give it.
After `pnpm build`, source the root `.env` and pass those values into `ApiClient`. Test scripts load
the root file via `dotenv-cli`; the runtime client reads only the config object you give it.

```bash
set -a && . packages/core/.env.local && set +a
set -a && . .env && set +a
cd packages/core
```

Expand Down
7 changes: 0 additions & 7 deletions examples/vite-react/.env.example

This file was deleted.

8 changes: 5 additions & 3 deletions examples/vite-react/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,14 @@ A demo app showcasing `@youversion/platform-react-ui` components.

## Setup

Run these commands from the repository root:

```bash
cp .env.example .env.local
# Add your YouVersion App Key to .env.local
cp .env.example .env
# Add your YouVersion App Key to .env
# Optional: VITE_YVP_LOCALE and VITE_YVP_DEFAULT_LANGUAGE_ID (e.g. es)
pnpm install
pnpm dev
pnpm dev:web
```

## React SDK Components Used
Expand Down
1 change: 1 addition & 0 deletions examples/vite-react/src/vite-env.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ interface ImportMetaEnv {
readonly VITE_YVP_APP_KEY?: string;
readonly VITE_YVP_API_HOST?: string;
readonly VITE_YVP_AUTH_REDIRECT_URL?: string;
readonly VITE_YVP_API_TIMEOUT_MS?: string;
readonly VITE_YVP_LOCALE?: string;
readonly VITE_YVP_DEFAULT_LANGUAGE_ID?: string;
}
Expand Down
48 changes: 40 additions & 8 deletions examples/vite-react/vite.config.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,46 @@
import { existsSync, readFileSync } from 'node:fs';
import { parseEnv } from 'node:util';
import path from 'path';
import tailwindcss from '@tailwindcss/vite';
import react from '@vitejs/plugin-react';
import { defineConfig } from 'vite';
import { defineConfig, loadEnv } from 'vite';

export default defineConfig({
base: process.env.VITE_BASE_PATH ?? '/',
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
export default defineConfig(({ mode }) => {
const rootEnvPath = path.resolve(__dirname, '../..', '.env');
const rootEnv = existsSync(rootEnvPath) ? parseEnv(readFileSync(rootEnvPath, 'utf8')) : {};
const packageEnv = loadEnv(mode, __dirname, 'VITE_');
const exposedEnv = {
...Object.fromEntries(Object.entries(rootEnv).filter(([name]) => name.startsWith('VITE_'))),
...packageEnv,
// Explicit prefixed or generic process variables win, then legacy package-local
// values, then the root .env fallback.
VITE_YVP_APP_KEY:
process.env.VITE_YVP_APP_KEY ??
process.env.YVP_APP_KEY ??
packageEnv.VITE_YVP_APP_KEY ??
rootEnv.VITE_YVP_APP_KEY ??
rootEnv.YVP_APP_KEY,
VITE_YVP_API_HOST:
process.env.VITE_YVP_API_HOST ??
process.env.YVP_API_HOST ??
packageEnv.VITE_YVP_API_HOST ??
rootEnv.VITE_YVP_API_HOST ??
rootEnv.YVP_API_HOST,
};
const definedEnv = Object.fromEntries(
Object.entries(exposedEnv)
.filter((entry): entry is [string, string] => entry[1] !== undefined)
.map(([name, value]) => [`import.meta.env.${name}`, JSON.stringify(value)]),
);

return {
base: process.env.VITE_BASE_PATH ?? '/',
define: definedEnv,
plugins: [react(), tailwindcss()],
resolve: {
alias: {
'@': path.resolve(__dirname, './src'),
},
},
},
};
});
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"build": "turbo build",
"clean": "rimraf packages/*/dist && rimraf packages/*/node_modules && rimraf ./node_modules",
"dev": "turbo dev",
"dev:web": "pnpm turbo dev --filter @youversion/platform-react-ui --filter nextjs --filter core --concurrency=10",
"dev:web": "pnpm turbo dev --filter vite-react... --concurrency=10",
"test:core": "pnpm --filter @youversion/platform-core test",
"test:hooks": "pnpm --filter @youversion/platform-react-hooks test",
"test:react": "pnpm --filter @youversion/platform-react-ui test",
Expand Down
5 changes: 0 additions & 5 deletions packages/core/.env.example

This file was deleted.

8 changes: 4 additions & 4 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,10 @@
"build": "rm -rf dist && pnpm build:js && pnpm build:css && pnpm build:types",
"prepublishOnly": "YVP_PUBLISH_BUILD=true pnpm build && node ../../scripts/check-sdk-version-stamp.mjs core",
"typecheck": "tsc --noEmit",
"test": "dotenv -e .env.local -- vitest run",
"test:integration": "INTEGRATION_TESTS=true dotenv -e .env.local -- vitest run",
"test:watch": "dotenv -e .env.local -- vitest",
"test:coverage": "dotenv -e .env.local -- vitest run --coverage"
"test": "dotenv -e .env.local -e ../../.env -- vitest run",
"test:integration": "INTEGRATION_TESTS=true dotenv -e .env.local -e ../../.env -- vitest run",
"test:watch": "dotenv -e .env.local -e ../../.env -- vitest",
"test:coverage": "dotenv -e .env.local -e ../../.env -- vitest run --coverage"
},
"devDependencies": {
"@internal/tsconfig": "workspace:*",
Expand Down
3 changes: 3 additions & 0 deletions packages/core/vitest.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ export default defineConfig({
},
test: {
environment: 'node',
env: {
YVP_API_HOST: process.env.YVP_API_HOST || 'api.youversion.com',
},
setupFiles: ['./src/__tests__/polyfills.ts', './src/__tests__/setup.ts'],
testTimeout: 10_000,
coverage: {
Expand Down
8 changes: 0 additions & 8 deletions packages/ui/.env.example

This file was deleted.

36 changes: 36 additions & 0 deletions packages/ui/.storybook/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { StorybookConfig } from '@storybook/react-vite';
import { resolve, dirname } from 'path';
import { fileURLToPath } from 'url';
import { readFileSync, existsSync } from 'fs';
import { parseEnv } from 'util';
import { loadEnv } from 'vite';

const __dirname = dirname(fileURLToPath(import.meta.url));

Expand Down Expand Up @@ -43,8 +45,42 @@ const config: StorybookConfig = {
},
staticDirs: ['../public'], // This is for Storybook mock service worker
viteFinal: (config) => {
const rootEnvPath = resolve(__dirname, '../../..', '.env');
const rootEnv = existsSync(rootEnvPath) ? parseEnv(readFileSync(rootEnvPath, 'utf8')) : {};
const packageEnv = loadEnv(
config.mode ?? 'development',
resolve(__dirname, '..'),
'STORYBOOK_',
);
const exposedEnv = {
...Object.fromEntries(
Object.entries(rootEnv).filter(([name]) => name.startsWith('STORYBOOK_')),
),
...packageEnv,
// Explicit prefixed or generic process variables win, then legacy package-local
// values, then the root .env fallback.
STORYBOOK_YOUVERSION_APP_KEY:
process.env.STORYBOOK_YOUVERSION_APP_KEY ??
process.env.YVP_APP_KEY ??
packageEnv.STORYBOOK_YOUVERSION_APP_KEY ??
rootEnv.STORYBOOK_YOUVERSION_APP_KEY ??
rootEnv.YVP_APP_KEY,
STORYBOOK_YOUVERSION_API_HOST:
process.env.STORYBOOK_YOUVERSION_API_HOST ??
process.env.YVP_API_HOST ??
packageEnv.STORYBOOK_YOUVERSION_API_HOST ??
rootEnv.STORYBOOK_YOUVERSION_API_HOST ??
rootEnv.YVP_API_HOST,
};
const definedEnv = Object.fromEntries(
Object.entries(exposedEnv)
.filter((entry): entry is [string, string] => entry[1] !== undefined)
.map(([name, value]) => [`import.meta.env.${name}`, JSON.stringify(value)]),
);

config.define = {
...config.define,
...definedEnv,
__YV_STYLES__: yvStyles,
__YV_COMPONENT_STYLES__: yvComponentStyles,
__YV_READER_STYLES__: yvReaderStyles,
Expand Down
8 changes: 4 additions & 4 deletions packages/ui/.storybook/preview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -93,16 +93,16 @@ const preview: Preview = {
typeof context.parameters.locale === 'string'
? context.parameters.locale
: context.globals.locale;
const requiredEnvVars = includeAuth
? ['STORYBOOK_YOUVERSION_APP_KEY', 'STORYBOOK_AUTH_REDIRECT_URL']
: ['STORYBOOK_YOUVERSION_APP_KEY'];
const requiredEnvVars = ['STORYBOOK_YOUVERSION_APP_KEY'];

if (includeAuth) {
return (
<StorybookEnvCheck requiredEnvVars={requiredEnvVars}>
<YouVersionProvider
appKey={import.meta.env.STORYBOOK_YOUVERSION_APP_KEY || ''}
authRedirectUrl={import.meta.env.STORYBOOK_AUTH_REDIRECT_URL || ''}
authRedirectUrl={
import.meta.env.STORYBOOK_AUTH_REDIRECT_URL || window.location.origin
}
apiHost={import.meta.env.STORYBOOK_YOUVERSION_API_HOST}
includeAuth={true}
locale={locale}
Expand Down
6 changes: 3 additions & 3 deletions packages/ui/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
/// <reference types="vite/client" />

interface ImportMetaEnv {
readonly STORYBOOK_YOUVERSION_APP_KEY: string;
readonly STORYBOOK_AUTH_REDIRECT_URL: string;
readonly STORYBOOK_YOUVERSION_APP_KEY?: string;
readonly STORYBOOK_AUTH_REDIRECT_URL?: string;
/** The YouVersion API host. @default api.youversion.com */
readonly STORYBOOK_YOUVERSION_API_HOST: string;
readonly STORYBOOK_YOUVERSION_API_HOST?: string;
}

interface ImportMeta {
Expand Down
10 changes: 2 additions & 8 deletions packages/ui/src/test/StorybookEnvCheck.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,6 @@ function hasEnvVar(varName: string): boolean {
if (varName === 'STORYBOOK_YOUVERSION_APP_KEY') {
return Boolean(import.meta.env.STORYBOOK_YOUVERSION_APP_KEY);
}
if (varName === 'STORYBOOK_AUTH_REDIRECT_URL') {
return Boolean(import.meta.env.STORYBOOK_AUTH_REDIRECT_URL);
}
if (varName === 'STORYBOOK_YOUVERSION_API_HOST') {
return Boolean(import.meta.env.STORYBOOK_YOUVERSION_API_HOST);
}
return false;
}

Expand Down Expand Up @@ -42,8 +36,8 @@ export function StorybookEnvCheck({
))}
</ul>
<p className="text-sm text-yellow-700 dark:text-yellow-300">
Copy <code>.env.example</code> to <code>.env.local</code> in <code>packages/ui/</code>{' '}
and fill in the required values.
Copy the repository's root <code>.env.example</code> to <code>.env</code> and add your
app key.
</p>
</div>
</div>
Expand Down
21 changes: 19 additions & 2 deletions turbo.json
Original file line number Diff line number Diff line change
@@ -1,29 +1,46 @@
{
"$schema": "https://turbo.build/schema.json",
"ui": "tui",
"globalDependencies": ["**/.env*local", "tsconfig.json"],
"globalDependencies": [".env", "**/.env*local", "tsconfig.json"],
"tasks": {
"build": {
"dependsOn": ["^build"],
"outputs": ["dist/**", ".next/", "lib/**"],
"env": [
"NODE_ENV",
"YVP_PUBLISH_BUILD",
"YVP_APP_KEY",
"YVP_API_HOST",
"VITE_BASE_PATH",
"VITE_YVP_APP_KEY",
"VITE_YVP_API_HOST",
"VITE_YVP_AUTH_REDIRECT_URL"
"VITE_YVP_AUTH_REDIRECT_URL",
"VITE_YVP_API_TIMEOUT_MS",
"VITE_YVP_DEFAULT_LANGUAGE_ID",
"VITE_YVP_LOCALE"
],
"cache": true
},
"dev": {
"dependsOn": ["^build"],
"env": [
"YVP_APP_KEY",
"YVP_API_HOST",
"VITE_BASE_PATH",
"VITE_YVP_APP_KEY",
"VITE_YVP_API_HOST",
"VITE_YVP_AUTH_REDIRECT_URL",
"VITE_YVP_API_TIMEOUT_MS",
"VITE_YVP_DEFAULT_LANGUAGE_ID",
"VITE_YVP_LOCALE"
],
"cache": false,
"persistent": true
},

"test": {
"dependsOn": ["^build"],
"env": ["INTEGRATION_TESTS", "YVP_APP_KEY", "YVP_API_HOST"],
"cache": true
},
"test:watch": {
Expand Down
Loading