fix(ui): sidebar icon centering and local dev setup (#68)
This commit is contained in:
parent
337117f895
commit
89b0f8e88e
@ -1,104 +1,112 @@
|
|||||||
# Local Development Setup
|
# Local Development Setup
|
||||||
|
|
||||||
This directory contains patches and scripts to enable local development without WorkOS authentication.
|
This directory contains files for running Compass locally without Cloudflare or WorkOS credentials.
|
||||||
|
|
||||||
## What This Does
|
## What This Does
|
||||||
|
|
||||||
These patches modify Compass to run in development mode without requiring WorkOS SSO authentication:
|
1. **Bypasses WorkOS auth** - Middleware skips auth when API keys aren't configured
|
||||||
|
2. **Local SQLite database** - Uses sql.js (pure JS SQLite) instead of Cloudflare D1
|
||||||
|
3. **Dev user** - `dev@compass.io` with admin role
|
||||||
|
4. **Full database support** - Real queries work, not just mocks
|
||||||
|
|
||||||
1. **Bypasses WorkOS auth checks** - Middleware redirects `/` to `/dashboard` when WorkOS isn't configured
|
## Quick Start
|
||||||
2. **Mock D1 database** - Returns empty arrays for queries instead of throwing errors
|
|
||||||
3. **Wraps Cloudflare context** - Returns mock `{ env: { DB: null }, ctx: {} }` when WorkOS isn't configured
|
|
||||||
4. **Skips OpenNext initialization** - Only runs when WorkOS API keys are properly set
|
|
||||||
|
|
||||||
## How to Use
|
|
||||||
|
|
||||||
### Quick Start
|
|
||||||
|
|
||||||
From the compass directory:
|
From the compass directory:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
.dev-setup/apply-dev.sh
|
.dev-setup/apply-dev.sh
|
||||||
|
bun dev
|
||||||
```
|
```
|
||||||
|
|
||||||
This will apply all necessary patches to enable local development.
|
## Manual Setup
|
||||||
|
|
||||||
### Manual Application
|
If the script fails, apply manually:
|
||||||
|
|
||||||
If the automated script fails, you can apply patches manually:
|
|
||||||
|
|
||||||
1. **middleware.ts** - Redirects root to dashboard, allows all requests without auth
|
|
||||||
2. **lib/auth.ts** - Checks for "your_" and "placeholder" in WorkOS keys
|
|
||||||
3. **lib/cloudflare-context.ts** - New file that wraps `getCloudflareContext()`
|
|
||||||
4. **db/index.ts** - Returns mock DB when `d1` parameter is `null`
|
|
||||||
5. **next.config.ts** - Only initializes OpenNext Cloudflare when WorkOS is configured
|
|
||||||
6. **.gitignore** - Ignores `src/lib/cloudflare-context.ts` so it's not committed
|
|
||||||
|
|
||||||
### Undoing Changes
|
|
||||||
|
|
||||||
To remove dev setup and restore original behavior:
|
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
git restore src/middleware.ts
|
# 1. Install sql.js
|
||||||
git restore src/lib/auth.ts
|
bun add sql.js
|
||||||
git restore src/db/index.ts
|
|
||||||
git restore next.config.ts
|
# 2. Copy dev files
|
||||||
git restore .gitignore
|
cp .dev-setup/files/middleware.ts src/middleware.ts
|
||||||
rm src/lib/cloudflare-context.ts
|
cp .dev-setup/files/next.config.ts next.config.ts
|
||||||
|
cp .dev-setup/files/cloudflare-context.ts src/lib/cloudflare-context.ts
|
||||||
|
cp .dev-setup/files/db.ts src/lib/db.ts
|
||||||
|
mkdir -p scripts
|
||||||
|
cp .dev-setup/files/init-local-db.ts scripts/init-local-db.ts
|
||||||
|
|
||||||
|
# 3. Replace imports
|
||||||
|
find src -name "*.ts" -o -name "*.tsx" | xargs sed -i '' 's|from "@opennextjs/cloudflare"|from "@/lib/db"|g'
|
||||||
|
|
||||||
|
# 4. Add script to package.json
|
||||||
|
node -e 'const fs=require("fs");const p=JSON.parse(fs.readFileSync("package.json"));p.scripts["db:init-local"]="bun scripts/init-local-db.ts";fs.writeFileSync("package.json",JSON.stringify(p,null,2)+"\n")'
|
||||||
|
|
||||||
|
# 5. Initialize database
|
||||||
|
bun run db:init-local
|
||||||
|
|
||||||
|
# 6. Start dev server
|
||||||
|
bun dev
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Reverting
|
||||||
|
|
||||||
|
```bash
|
||||||
|
.dev-setup/restore-dev.sh
|
||||||
|
```
|
||||||
|
|
||||||
|
Or manually:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
git checkout HEAD -- src/middleware.ts next.config.ts package.json bun.lock
|
||||||
|
rm -f src/lib/cloudflare-context.ts src/lib/db.ts scripts/init-local-db.ts
|
||||||
|
find src -name "*.ts" -o -name "*.tsx" | xargs sed -i '' 's|from "@/lib/db"|from "@opennextjs/cloudflare"|g'
|
||||||
|
bun remove sql.js
|
||||||
|
rm -f local.db local.db-wal local.db-shm
|
||||||
|
```
|
||||||
|
|
||||||
|
## Files
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `files/middleware.ts` | Bypasses WorkOS when not configured |
|
||||||
|
| `files/next.config.ts` | Removes Cloudflare dev proxy |
|
||||||
|
| `files/cloudflare-context.ts` | sql.js wrapper mimicking D1 |
|
||||||
|
| `files/db.ts` | Conditional import wrapper |
|
||||||
|
| `files/init-local-db.ts` | Migration runner for local DB |
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
To configure WorkOS auth properly (to disable dev mode):
|
To use real WorkOS auth (disables dev mode):
|
||||||
|
|
||||||
```env
|
```env
|
||||||
WORKOS_API_KEY=sk_dev_xxxxx
|
WORKOS_API_KEY=sk_dev_xxxxx
|
||||||
WORKOS_CLIENT_ID=client_xxxxx
|
WORKOS_CLIENT_ID=client_xxxxx
|
||||||
WORKOS_REDIRECT_URI=http://localhost:3000
|
|
||||||
```
|
```
|
||||||
|
|
||||||
With these set, the dev patches will automatically skip and use real WorkOS authentication.
|
Values containing "placeholder" trigger dev mode.
|
||||||
|
|
||||||
## Dev Mode Indicators
|
## Database
|
||||||
|
|
||||||
When in dev mode (WorkOS not configured):
|
- Stored in `local.db` at repo root
|
||||||
- Dashboard loads directly without login redirect
|
- Migrations applied from `drizzle/` directory
|
||||||
- Database queries return empty arrays instead of errors
|
- Persisted between sessions
|
||||||
- Cloudflare context returns null DB instead of throwing
|
|
||||||
|
|
||||||
## Files Created/Modified
|
## Limitations
|
||||||
|
|
||||||
### New Files (dev only)
|
Features requiring external APIs won't work offline:
|
||||||
- `src/lib/cloudflare-context.ts` - Wraps `getCloudflareContext()` with dev bypass
|
- NetSuite sync
|
||||||
|
- Google Drive integration
|
||||||
### Modified Files
|
- AI agent (needs OPENROUTER_API_KEY)
|
||||||
- `src/middleware.ts` - Added WorkOS detection and dev bypass
|
- Push notifications
|
||||||
- `src/lib/auth.ts` - Enhanced WorkOS configuration detection
|
|
||||||
- `src/db/index.ts` - Added mock DB support for null D1 parameter
|
|
||||||
- `next.config.ts` - Conditional OpenNext initialization
|
|
||||||
- `.gitignore` - Added `cloudflare-context.ts` to prevent commits
|
|
||||||
|
|
||||||
## Future Development
|
|
||||||
|
|
||||||
Place any new dev-only components or patches in this directory following the same pattern:
|
|
||||||
|
|
||||||
1. **Patch files** - Store in `patches/` with `.patch` extension
|
|
||||||
2. **New files** - Store in `files/` directory
|
|
||||||
3. **Update apply-dev.sh** - Add your patches to the apply script
|
|
||||||
4. **Document here** - Update this README with changes
|
|
||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
**Build errors after applying patches:**
|
**"Cannot find module 'sql.js'"**
|
||||||
- Check that `src/lib/cloudflare-context.ts` exists
|
- Run `bun add sql.js`
|
||||||
- Verify all patches applied cleanly
|
|
||||||
- Try manual patch application if automated script fails
|
|
||||||
|
|
||||||
**Auth still required:**
|
**Database errors**
|
||||||
- Verify `.env.local` or `.dev.vars` doesn't have placeholder values
|
- Re-run `bun run db:init-local`
|
||||||
- Check that WorkOS environment variables aren't set (if you want dev mode)
|
- Check `local.db` exists
|
||||||
- Restart dev server after applying patches
|
|
||||||
|
|
||||||
**Database errors:**
|
**Auth still required**
|
||||||
- Ensure `src/db/index.ts` patch was applied
|
- Verify no WORKOS_API_KEY set
|
||||||
- Check that mock DB is being returned when `d1` is null
|
- Check middleware.ts was copied
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "🔧 Applying local development setup patches..."
|
echo "🔧 Applying local development setup..."
|
||||||
|
|
||||||
# Check if we're in the compass directory
|
|
||||||
if [ ! -f "package.json" ] || [ ! -d "src" ]; then
|
if [ ! -f "package.json" ] || [ ! -d "src" ]; then
|
||||||
echo "❌ Error: Please run this script from the compass directory"
|
echo "❌ Error: Please run this script from the compass directory"
|
||||||
exit 1
|
exit 1
|
||||||
@ -11,55 +10,45 @@ fi
|
|||||||
|
|
||||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||||
|
|
||||||
# Apply middleware patch
|
# 1. Install sql.js
|
||||||
echo "📦 Applying middleware.ts patch..."
|
echo "📦 Installing sql.js..."
|
||||||
patch -p1 < "$SCRIPT_DIR/patches/middleware.patch" || {
|
bun add sql.js
|
||||||
echo "⚠️ middleware.ts patch failed, applying manually..."
|
|
||||||
cat "$SCRIPT_DIR/patches/middleware.patch"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Apply auth patch
|
# 2. Copy dev files
|
||||||
echo "📦 Applying auth.ts patch..."
|
echo "📦 Copying dev files..."
|
||||||
patch -p1 < "$SCRIPT_DIR/patches/auth.patch" || {
|
cp "$SCRIPT_DIR/files/middleware.ts" src/middleware.ts
|
||||||
echo "⚠️ auth.ts patch failed, applying manually..."
|
cp "$SCRIPT_DIR/files/next.config.ts" next.config.ts
|
||||||
cat "$SCRIPT_DIR/patches/auth.patch"
|
cp "$SCRIPT_DIR/files/cloudflare-context.ts" src/lib/cloudflare-context.ts
|
||||||
}
|
cp "$SCRIPT_DIR/files/db.ts" src/lib/db.ts
|
||||||
|
mkdir -p scripts
|
||||||
|
cp "$SCRIPT_DIR/files/init-local-db.ts" scripts/init-local-db.ts
|
||||||
|
|
||||||
# Apply cloudflare-context (create the wrapper file)
|
# 3. Replace all @opennextjs/cloudflare imports with local wrapper
|
||||||
echo "📦 Applying cloudflare-context.ts..."
|
echo "📦 Updating imports..."
|
||||||
if [ ! -f "src/lib/cloudflare-context.ts" ]; then
|
find src -name "*.ts" -o -name "*.tsx" | xargs sed -i '' 's|from "@opennextjs/cloudflare"|from "@/lib/db"|g'
|
||||||
mkdir -p src/lib
|
|
||||||
cp "$SCRIPT_DIR/files/cloudflare-context.ts" src/lib/
|
# 4. Add db:init-local script to package.json if not present
|
||||||
echo "✓ Created src/lib/cloudflare-context.ts"
|
if ! grep -q '"db:init-local"' package.json; then
|
||||||
else
|
echo "📦 Adding db:init-local script..."
|
||||||
echo "⚠️ cloudflare-context.ts already exists, skipping"
|
# Use node to update package.json
|
||||||
|
node -e '
|
||||||
|
const fs = require("fs");
|
||||||
|
const pkg = JSON.parse(fs.readFileSync("package.json"));
|
||||||
|
pkg.scripts["db:init-local"] = "bun scripts/init-local-db.ts";
|
||||||
|
fs.writeFileSync("package.json", JSON.stringify(pkg, null, 2) + "\n");
|
||||||
|
'
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Apply db-index patch
|
# 5. Initialize local database
|
||||||
echo "📦 Applying db/index.ts patch..."
|
echo "📦 Initializing local database..."
|
||||||
patch -p1 < "$SCRIPT_DIR/patches/db-index.patch" || {
|
bun run db:init-local
|
||||||
echo "⚠️ db/index.ts patch failed, applying manually..."
|
|
||||||
cat "$SCRIPT_DIR/patches/db-index.patch"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Apply next-config patch
|
|
||||||
echo "📦 Applying next.config.ts patch..."
|
|
||||||
patch -p1 < "$SCRIPT_DIR/patches/next-config.patch" || {
|
|
||||||
echo "⚠️ next.config.ts patch failed, applying manually..."
|
|
||||||
cat "$SCRIPT_DIR/patches/next-config.patch"
|
|
||||||
}
|
|
||||||
|
|
||||||
# Update .gitignore
|
|
||||||
echo "📦 Updating .gitignore..."
|
|
||||||
patch -p1 < "$SCRIPT_DIR/patches/gitignore.patch" || {
|
|
||||||
echo "⚠️ .gitignore patch failed, applying manually..."
|
|
||||||
cat "$SCRIPT_DIR/patches/gitignore.patch"
|
|
||||||
}
|
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ Development setup complete!"
|
echo "✅ Development setup complete!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "📝 Notes:"
|
echo "📝 Notes:"
|
||||||
echo " - These changes allow local development without WorkOS authentication"
|
echo " - Local database stored in local.db"
|
||||||
echo " - To use WorkOS auth, remove these changes or revert the patches"
|
echo " - Dev user: dev@compass.io (admin role)"
|
||||||
echo " - Modified files: src/lib/cloudflare-context.ts, src/middleware.ts, src/lib/auth.ts, src/db/index.ts, next.config.ts, .gitignore"
|
echo " - Run 'bun dev' to start"
|
||||||
|
echo ""
|
||||||
|
echo "🔄 To revert: run .dev-setup/restore-dev.sh"
|
||||||
|
|||||||
@ -1,20 +1,189 @@
|
|||||||
import { getCloudflareContext as originalGetCloudflareContext } from "@opennextjs/cloudflare"
|
import initSqlJs, { Database as SqlJsDatabase } from "sql.js"
|
||||||
|
import { readFileSync, writeFileSync, existsSync } from "fs"
|
||||||
|
import { createRequire } from "module"
|
||||||
|
import { getDb } from "@/db"
|
||||||
|
|
||||||
const isWorkOSConfigured =
|
const require = createRequire(import.meta.url)
|
||||||
process.env.WORKOS_API_KEY &&
|
const sqlJsWasm = require.resolve("sql.js/dist/sql-wasm.wasm")
|
||||||
process.env.WORKOS_CLIENT_ID &&
|
|
||||||
!process.env.WORKOS_API_KEY.includes("your_") &&
|
const DB_PATH = process.env.LOCAL_DB_PATH || "local.db"
|
||||||
!process.env.WORKOS_API_KEY.includes("placeholder")
|
|
||||||
|
let sqlJs: Awaited<ReturnType<typeof initSqlJs>> | null = null
|
||||||
|
let db: SqlJsDatabase | null = null
|
||||||
|
|
||||||
|
async function getLocalDb(): Promise<SqlJsDatabase> {
|
||||||
|
if (!sqlJs) {
|
||||||
|
sqlJs = await initSqlJs({
|
||||||
|
locateFile: (file: string) => {
|
||||||
|
if (file.endsWith(".wasm")) {
|
||||||
|
return sqlJsWasm
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if (!db) {
|
||||||
|
if (existsSync(DB_PATH)) {
|
||||||
|
const buffer = readFileSync(DB_PATH)
|
||||||
|
db = new sqlJs.Database(buffer)
|
||||||
|
} else {
|
||||||
|
db = new sqlJs.Database()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return db
|
||||||
|
}
|
||||||
|
|
||||||
|
function saveDb(db: SqlJsDatabase) {
|
||||||
|
const data = db.export()
|
||||||
|
const buffer = Buffer.from(data)
|
||||||
|
writeFileSync(DB_PATH, buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
interface D1Result<T = unknown> {
|
||||||
|
results: T[]
|
||||||
|
success: boolean
|
||||||
|
meta: {
|
||||||
|
duration: number
|
||||||
|
changes: number
|
||||||
|
last_row_id: number
|
||||||
|
rows_read: number
|
||||||
|
rows_written: number
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalPreparedStatement {
|
||||||
|
private db: SqlJsDatabase
|
||||||
|
private query: string
|
||||||
|
private boundValues: unknown[] = []
|
||||||
|
|
||||||
|
constructor(db: SqlJsDatabase, query: string) {
|
||||||
|
this.db = db
|
||||||
|
this.query = query
|
||||||
|
}
|
||||||
|
|
||||||
|
bind(...values: unknown[]): this {
|
||||||
|
this.boundValues = values
|
||||||
|
return this
|
||||||
|
}
|
||||||
|
|
||||||
|
async first<T = unknown>(): Promise<T | null> {
|
||||||
|
const stmt = this.db.prepare(this.query)
|
||||||
|
if (this.boundValues.length > 0) {
|
||||||
|
stmt.bind(this.boundValues as number[])
|
||||||
|
}
|
||||||
|
if (stmt.step()) {
|
||||||
|
const row = stmt.getAsObject() as T
|
||||||
|
stmt.free()
|
||||||
|
return row
|
||||||
|
}
|
||||||
|
stmt.free()
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
async run(): Promise<D1Result> {
|
||||||
|
this.db.run(this.query, this.boundValues as number[])
|
||||||
|
saveDb(this.db)
|
||||||
|
return {
|
||||||
|
results: [],
|
||||||
|
success: true,
|
||||||
|
meta: {
|
||||||
|
duration: 0,
|
||||||
|
changes: this.db.getRowsModified(),
|
||||||
|
last_row_id: Number(this.db.exec("SELECT last_insert_rowid()")[0]?.values[0]?.[0] || 0),
|
||||||
|
rows_read: 0,
|
||||||
|
rows_written: this.db.getRowsModified(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async all<T = unknown>(): Promise<D1Result<T>> {
|
||||||
|
const stmt = this.db.prepare(this.query)
|
||||||
|
if (this.boundValues.length > 0) {
|
||||||
|
stmt.bind(this.boundValues as number[])
|
||||||
|
}
|
||||||
|
const results: T[] = []
|
||||||
|
while (stmt.step()) {
|
||||||
|
results.push(stmt.getAsObject() as T)
|
||||||
|
}
|
||||||
|
stmt.free()
|
||||||
|
return {
|
||||||
|
results,
|
||||||
|
success: true,
|
||||||
|
meta: {
|
||||||
|
duration: 0,
|
||||||
|
changes: 0,
|
||||||
|
last_row_id: 0,
|
||||||
|
rows_read: results.length,
|
||||||
|
rows_written: 0,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async raw<T = unknown>(): Promise<T[]> {
|
||||||
|
const results = this.db.exec(this.query, this.boundValues as number[])
|
||||||
|
return results[0]?.values as T[] ?? []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class LocalD1Database {
|
||||||
|
constructor(private db: SqlJsDatabase) {}
|
||||||
|
|
||||||
|
prepare(query: string): LocalPreparedStatement {
|
||||||
|
return new LocalPreparedStatement(this.db, query)
|
||||||
|
}
|
||||||
|
|
||||||
|
async batch<T = unknown>(
|
||||||
|
statements: LocalPreparedStatement[]
|
||||||
|
): Promise<D1Result<T>[]> {
|
||||||
|
const results: D1Result<T>[] = []
|
||||||
|
for (const stmt of statements) {
|
||||||
|
results.push(await stmt.all<T>())
|
||||||
|
}
|
||||||
|
return results
|
||||||
|
}
|
||||||
|
|
||||||
|
async exec(query: string): Promise<D1Result> {
|
||||||
|
this.db.run(query)
|
||||||
|
saveDb(this.db)
|
||||||
|
return {
|
||||||
|
results: [],
|
||||||
|
success: true,
|
||||||
|
meta: {
|
||||||
|
duration: 0,
|
||||||
|
changes: this.db.getRowsModified(),
|
||||||
|
last_row_id: 0,
|
||||||
|
rows_read: 0,
|
||||||
|
rows_written: this.db.getRowsModified(),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async dump(): Promise<ArrayBuffer> {
|
||||||
|
const data = this.db.export()
|
||||||
|
return data.buffer as ArrayBuffer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export async function getCloudflareContext(): Promise<{
|
||||||
|
env: {
|
||||||
|
DB: D1Database
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
ctx: {
|
||||||
|
waitUntil: (promise: Promise<unknown>) => void
|
||||||
|
}
|
||||||
|
cf: unknown
|
||||||
|
}> {
|
||||||
|
const localDb = await getLocalDb()
|
||||||
|
const d1 = new LocalD1Database(localDb) as unknown as D1Database
|
||||||
|
|
||||||
export async function getCloudflareContext() {
|
|
||||||
if (!isWorkOSConfigured) {
|
|
||||||
return {
|
return {
|
||||||
env: {
|
env: {
|
||||||
DB: null,
|
DB: d1,
|
||||||
},
|
},
|
||||||
ctx: {},
|
ctx: {
|
||||||
} as Awaited<ReturnType<typeof originalGetCloudflareContext>>
|
waitUntil: () => {},
|
||||||
|
},
|
||||||
|
cf: {},
|
||||||
}
|
}
|
||||||
|
|
||||||
return originalGetCloudflareContext()
|
|
||||||
}
|
}
|
||||||
|
|||||||
27
.dev-setup/files/db.ts
Normal file
27
.dev-setup/files/db.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
export async function getCloudflareContext(): Promise<{
|
||||||
|
env: {
|
||||||
|
DB: D1Database
|
||||||
|
[key: string]: unknown
|
||||||
|
}
|
||||||
|
ctx: {
|
||||||
|
waitUntil: (promise: Promise<unknown>) => void
|
||||||
|
}
|
||||||
|
cf: unknown
|
||||||
|
}> {
|
||||||
|
const isLocalDev =
|
||||||
|
process.env.NODE_ENV === "development" &&
|
||||||
|
(!process.env.WORKOS_API_KEY ||
|
||||||
|
process.env.WORKOS_API_KEY.includes("placeholder"))
|
||||||
|
|
||||||
|
if (isLocalDev) {
|
||||||
|
const { getCloudflareContext: getLocalContext } = await import(
|
||||||
|
"./cloudflare-context"
|
||||||
|
)
|
||||||
|
return getLocalContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { getCloudflareContext: getCfContext } = await import(
|
||||||
|
"@opennextjs/cloudflare"
|
||||||
|
)
|
||||||
|
return getCfContext()
|
||||||
|
}
|
||||||
51
.dev-setup/files/init-local-db.ts
Normal file
51
.dev-setup/files/init-local-db.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import initSqlJs from "sql.js"
|
||||||
|
import { readFileSync, readdirSync, writeFileSync, existsSync } from "fs"
|
||||||
|
import { join, dirname } from "path"
|
||||||
|
import { createRequire } from "module"
|
||||||
|
import { fileURLToPath } from "url"
|
||||||
|
|
||||||
|
const require = createRequire(import.meta.url)
|
||||||
|
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||||
|
const sqlJsWasm = require.resolve("sql.js/dist/sql-wasm.wasm")
|
||||||
|
|
||||||
|
const DB_PATH = process.env.LOCAL_DB_PATH || "local.db"
|
||||||
|
const MIGRATIONS_DIR = join(process.cwd(), "drizzle")
|
||||||
|
|
||||||
|
async function main() {
|
||||||
|
const sqlJs = await initSqlJs({
|
||||||
|
locateFile: (file: string) => {
|
||||||
|
if (file.endsWith(".wasm")) {
|
||||||
|
return sqlJsWasm
|
||||||
|
}
|
||||||
|
return file
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
let db: ReturnType<typeof sqlJs.Database>
|
||||||
|
if (existsSync(DB_PATH)) {
|
||||||
|
const buffer = readFileSync(DB_PATH)
|
||||||
|
db = new sqlJs.Database(buffer)
|
||||||
|
} else {
|
||||||
|
db = new sqlJs.Database()
|
||||||
|
}
|
||||||
|
|
||||||
|
const migrations = readdirSync(MIGRATIONS_DIR)
|
||||||
|
.filter((f) => f.endsWith(".sql") && !f.includes("seed"))
|
||||||
|
.sort()
|
||||||
|
|
||||||
|
console.log(`Running ${migrations.length} migrations on ${DB_PATH}...`)
|
||||||
|
|
||||||
|
for (const migration of migrations) {
|
||||||
|
const sql = readFileSync(join(MIGRATIONS_DIR, migration), "utf-8")
|
||||||
|
console.log(` ${migration}`)
|
||||||
|
db.run(sql)
|
||||||
|
}
|
||||||
|
|
||||||
|
const data = db.export()
|
||||||
|
const buffer = Buffer.from(data)
|
||||||
|
writeFileSync(DB_PATH, buffer)
|
||||||
|
|
||||||
|
console.log("Done!")
|
||||||
|
}
|
||||||
|
|
||||||
|
main().catch(console.error)
|
||||||
61
.dev-setup/files/middleware.ts
Normal file
61
.dev-setup/files/middleware.ts
Normal file
@ -0,0 +1,61 @@
|
|||||||
|
import { NextRequest, NextResponse } from "next/server"
|
||||||
|
import { authkit, handleAuthkitHeaders } from "@workos-inc/authkit-nextjs"
|
||||||
|
|
||||||
|
const publicPaths = [
|
||||||
|
"/",
|
||||||
|
"/login",
|
||||||
|
"/signup",
|
||||||
|
"/reset-password",
|
||||||
|
"/verify-email",
|
||||||
|
"/invite",
|
||||||
|
"/callback",
|
||||||
|
]
|
||||||
|
|
||||||
|
const bridgePaths = [
|
||||||
|
"/api/bridge/register",
|
||||||
|
"/api/bridge/tools",
|
||||||
|
"/api/bridge/context",
|
||||||
|
]
|
||||||
|
|
||||||
|
function isPublicPath(pathname: string): boolean {
|
||||||
|
return (
|
||||||
|
publicPaths.includes(pathname) ||
|
||||||
|
bridgePaths.includes(pathname) ||
|
||||||
|
pathname.startsWith("/api/auth/") ||
|
||||||
|
pathname.startsWith("/api/netsuite/") ||
|
||||||
|
pathname.startsWith("/api/google/")
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const isWorkOSConfigured =
|
||||||
|
process.env.WORKOS_API_KEY &&
|
||||||
|
process.env.WORKOS_CLIENT_ID &&
|
||||||
|
!process.env.WORKOS_API_KEY.includes("placeholder")
|
||||||
|
|
||||||
|
export default async function middleware(request: NextRequest) {
|
||||||
|
const { pathname } = request.nextUrl
|
||||||
|
|
||||||
|
if (!isWorkOSConfigured) {
|
||||||
|
return NextResponse.next()
|
||||||
|
}
|
||||||
|
|
||||||
|
const { session, headers } = await authkit(request)
|
||||||
|
|
||||||
|
if (isPublicPath(pathname)) {
|
||||||
|
return handleAuthkitHeaders(request, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!session.user) {
|
||||||
|
const loginUrl = new URL("/login", request.url)
|
||||||
|
loginUrl.searchParams.set("from", pathname)
|
||||||
|
return handleAuthkitHeaders(request, headers, { redirect: loginUrl.toString() })
|
||||||
|
}
|
||||||
|
|
||||||
|
return handleAuthkitHeaders(request, headers)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const config = {
|
||||||
|
matcher: [
|
||||||
|
"/((?!_next/static|_next/image|favicon.ico|.*\\.(?:svg|png|jpg|jpeg|gif|webp)$).*)",
|
||||||
|
],
|
||||||
|
}
|
||||||
23
.dev-setup/files/next.config.ts
Normal file
23
.dev-setup/files/next.config.ts
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
import type { NextConfig } from "next";
|
||||||
|
|
||||||
|
const nextConfig: NextConfig = {
|
||||||
|
eslint: {
|
||||||
|
ignoreDuringBuilds: true,
|
||||||
|
},
|
||||||
|
experimental: {
|
||||||
|
optimizePackageImports: [
|
||||||
|
"@tabler/icons-react",
|
||||||
|
"lucide-react",
|
||||||
|
"@radix-ui/react-icons",
|
||||||
|
"recharts",
|
||||||
|
"@workos-inc/node",
|
||||||
|
"date-fns",
|
||||||
|
"remeda",
|
||||||
|
"framer-motion",
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default nextConfig;
|
||||||
|
|
||||||
|
// Cloudflare dev proxy removed for local dev - uses sql.js instead
|
||||||
@ -1,36 +1,39 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -e
|
set -e
|
||||||
|
|
||||||
echo "🔄 Removing local development setup patches..."
|
echo "🔄 Removing local development setup..."
|
||||||
|
|
||||||
# Check if we're in the compass directory
|
|
||||||
if [ ! -f "package.json" ] || [ ! -d "src" ]; then
|
if [ ! -f "package.json" ] || [ ! -d "src" ]; then
|
||||||
echo "❌ Error: Please run this script from the compass directory"
|
echo "❌ Error: Please run this script from the compass directory"
|
||||||
exit 1
|
exit 1
|
||||||
fi
|
fi
|
||||||
|
|
||||||
# Restore modified files
|
# Revert tracked files
|
||||||
echo "📦 Restoring modified files..."
|
echo "📦 Restoring modified files..."
|
||||||
git restore src/middleware.ts
|
git checkout HEAD -- src/middleware.ts next.config.ts package.json bun.lock
|
||||||
git restore src/lib/auth.ts
|
|
||||||
git restore src/db/index.ts
|
|
||||||
git restore next.config.ts
|
|
||||||
git restore .gitignore
|
|
||||||
|
|
||||||
# Remove dev-only new file
|
# Remove dev-only files
|
||||||
echo "📦 Removing dev-only files..."
|
echo "📦 Removing dev-only files..."
|
||||||
if [ -f "src/lib/cloudflare-context.ts" ]; then
|
rm -f src/lib/cloudflare-context.ts
|
||||||
rm src/lib/cloudflare-context.ts
|
rm -f src/lib/db.ts
|
||||||
echo "✓ Removed src/lib/cloudflare-context.ts"
|
rm -f scripts/init-local-db.ts
|
||||||
else
|
echo "✓ Removed dev files"
|
||||||
echo "⚠️ src/lib/cloudflare-context.ts not found, skipping"
|
|
||||||
fi
|
# Restore original imports
|
||||||
|
echo "📦 Restoring original imports..."
|
||||||
|
find src -name "*.ts" -o -name "*.tsx" | xargs sed -i '' 's|from "@/lib/db"|from "@opennextjs/cloudflare"|g'
|
||||||
|
|
||||||
|
# Remove sql.js
|
||||||
|
echo "📦 Removing sql.js..."
|
||||||
|
bun remove sql.js
|
||||||
|
|
||||||
|
# Remove local database
|
||||||
|
rm -f local.db local.db-wal local.db-shm
|
||||||
|
|
||||||
echo ""
|
echo ""
|
||||||
echo "✅ Development setup removed!"
|
echo "✅ Development setup removed!"
|
||||||
echo ""
|
echo ""
|
||||||
echo "📝 Notes:"
|
echo "📝 Notes:"
|
||||||
echo " - Original code has been restored from git"
|
echo " - Original code restored from git"
|
||||||
echo " - Dev mode is now disabled"
|
echo " - WorkOS/Cloudflare auth will be required"
|
||||||
echo " - WorkOS authentication will be required (if configured)"
|
echo " - To re-apply: .dev-setup/apply-dev.sh"
|
||||||
echo " - To re-apply dev setup, run: .dev-setup/apply-dev.sh"
|
|
||||||
|
|||||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -41,3 +41,10 @@ android/app/build/
|
|||||||
# Local auth bypass (dev only)
|
# Local auth bypass (dev only)
|
||||||
src/lib/auth-bypass.ts
|
src/lib/auth-bypass.ts
|
||||||
src/lib/cloudflare-context.ts
|
src/lib/cloudflare-context.ts
|
||||||
|
src/lib/db.ts
|
||||||
|
scripts/init-local-db.ts
|
||||||
|
|
||||||
|
# local dev database
|
||||||
|
local.db
|
||||||
|
local.db-wal
|
||||||
|
local.db-shm
|
||||||
|
|||||||
@ -31,6 +31,7 @@ export function NavSecondary({
|
|||||||
<SidebarMenuButton
|
<SidebarMenuButton
|
||||||
asChild={!item.onClick}
|
asChild={!item.onClick}
|
||||||
onClick={item.onClick}
|
onClick={item.onClick}
|
||||||
|
tooltip={item.title}
|
||||||
>
|
>
|
||||||
{item.onClick ? (
|
{item.onClick ? (
|
||||||
<>
|
<>
|
||||||
|
|||||||
@ -469,7 +469,7 @@ function SidebarMenuItem({ className, ...props }: React.ComponentProps<"li">) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const sidebarMenuButtonVariants = cva(
|
const sidebarMenuButtonVariants = cva(
|
||||||
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:size-9! group-data-[collapsible=icon]:justify-center! group-data-[collapsible=icon]:p-2.5! [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
"peer/menu-button flex w-full items-center gap-2 overflow-hidden rounded-md p-2 text-left text-sm outline-hidden ring-sidebar-ring transition-[width,height,padding] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground focus-visible:ring-2 active:bg-sidebar-accent active:text-sidebar-accent-foreground disabled:pointer-events-none disabled:opacity-50 group-has-data-[sidebar=menu-action]/menu-item:pr-8 aria-disabled:pointer-events-none aria-disabled:opacity-50 data-[active=true]:bg-sidebar-accent data-[active=true]:font-medium data-[active=true]:text-sidebar-accent-foreground data-[state=open]:hover:bg-sidebar-accent data-[state=open]:hover:text-sidebar-accent-foreground group-data-[collapsible=icon]:justify-center! group-data-[collapsible=icon]:gap-0! group-data-[collapsible=icon]:[&>*:nth-child(n+2)]:translate-x-10 [&>span:last-child]:truncate [&>svg]:size-4 [&>svg]:shrink-0",
|
||||||
{
|
{
|
||||||
variants: {
|
variants: {
|
||||||
variant: {
|
variant: {
|
||||||
@ -478,9 +478,9 @@ const sidebarMenuButtonVariants = cva(
|
|||||||
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
|
"bg-background shadow-[0_0_0_1px_hsl(var(--sidebar-border))] hover:bg-sidebar-accent hover:text-sidebar-accent-foreground hover:shadow-[0_0_0_1px_hsl(var(--sidebar-accent))]",
|
||||||
},
|
},
|
||||||
size: {
|
size: {
|
||||||
default: "h-8 text-sm",
|
default: "h-8 text-sm group-data-[collapsible=icon]:size-9! group-data-[collapsible=icon]:p-2!",
|
||||||
sm: "h-7 text-xs",
|
sm: "h-7 text-xs group-data-[collapsible=icon]:size-8! group-data-[collapsible=icon]:p-1.5!",
|
||||||
lg: "h-12 text-sm group-data-[collapsible=icon]:p-0!",
|
lg: "h-12 text-sm group-data-[collapsible=icon]:size-11! group-data-[collapsible=icon]:p-1.5! group-data-[collapsible=icon]:-translate-x-[0.3rem] group-data-[collapsible=icon]:[&>*:first-child]:translate-x-[0.5rem]",
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
defaultVariants: {
|
defaultVariants: {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user