mirror of
https://github.com/bretzel-app/crumbs.git
synced 2026-10-14 12:28:18 +02:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
1809020501 | ||
|
|
bd4016c998 | ||
|
|
4ef5dbf20b | ||
|
|
00c748a17b | ||
|
|
f63cb6db1d | ||
|
|
329061fe7e | ||
|
|
c638dce753 | ||
|
|
7918c1ecc2 | ||
|
|
5a32563e3b | ||
|
|
1eb10b41bb | ||
|
|
d6eb6d3ad2 | ||
|
|
f1e18ab968 |
@@ -46,7 +46,18 @@ jobs:
|
|||||||
cache: pnpm
|
cache: pnpm
|
||||||
|
|
||||||
- run: pnpm install --frozen-lockfile
|
- run: pnpm install --frozen-lockfile
|
||||||
- run: pnpm test:unit
|
- run: pnpm test:unit:coverage
|
||||||
|
|
||||||
|
- name: Coverage summary
|
||||||
|
if: always()
|
||||||
|
run: bash scripts/coverage-summary.sh "Unit Test Coverage" coverage/unit/coverage-summary.json
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: unit-coverage
|
||||||
|
path: coverage/unit/
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
e2e-tests:
|
e2e-tests:
|
||||||
name: E2E Tests
|
name: E2E Tests
|
||||||
@@ -80,9 +91,25 @@ jobs:
|
|||||||
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
if: steps.playwright-cache.outputs.cache-hit == 'true'
|
||||||
run: npx playwright install-deps chromium
|
run: npx playwright install-deps chromium
|
||||||
|
|
||||||
|
- name: Build (with coverage instrumentation)
|
||||||
|
run: pnpm build
|
||||||
|
env:
|
||||||
|
DATABASE_URL: ./data/test-crumbs.db
|
||||||
|
NODE_ENV: test
|
||||||
|
VITE_COVERAGE: 'true'
|
||||||
|
|
||||||
- run: pnpm test:e2e
|
- run: pnpm test:e2e
|
||||||
env:
|
env:
|
||||||
CI: true
|
CI: true
|
||||||
|
VITE_COVERAGE: 'true'
|
||||||
|
|
||||||
|
- name: Merge E2E coverage
|
||||||
|
if: always()
|
||||||
|
run: npx nyc report
|
||||||
|
|
||||||
|
- name: Coverage summary
|
||||||
|
if: always()
|
||||||
|
run: bash scripts/coverage-summary.sh "E2E Test Coverage" coverage/e2e/coverage-summary.json
|
||||||
|
|
||||||
- uses: actions/upload-artifact@v4
|
- uses: actions/upload-artifact@v4
|
||||||
if: always()
|
if: always()
|
||||||
@@ -91,7 +118,14 @@ jobs:
|
|||||||
path: |
|
path: |
|
||||||
playwright-report/
|
playwright-report/
|
||||||
test-results/
|
test-results/
|
||||||
retention-days: 30
|
retention-days: 7
|
||||||
|
|
||||||
|
- uses: actions/upload-artifact@v4
|
||||||
|
if: always()
|
||||||
|
with:
|
||||||
|
name: e2e-coverage
|
||||||
|
path: coverage/e2e/
|
||||||
|
retention-days: 14
|
||||||
|
|
||||||
docker-build:
|
docker-build:
|
||||||
name: Docker Build
|
name: Docker Build
|
||||||
|
|||||||
@@ -32,6 +32,10 @@ vite.config.ts.timestamp-*
|
|||||||
/test-results
|
/test-results
|
||||||
/playwright-report
|
/playwright-report
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
/coverage
|
||||||
|
.nyc_output
|
||||||
|
|
||||||
# Claude Code
|
# Claude Code
|
||||||
/.claude/
|
/.claude/
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,7 @@
|
|||||||
|
{
|
||||||
|
"report-dir": "coverage/e2e",
|
||||||
|
"temp-dir": "coverage/e2e/raw",
|
||||||
|
"reporter": ["text", "html", "lcov", "json-summary"],
|
||||||
|
"include": ["src/**"],
|
||||||
|
"exclude": ["node_modules", "tests"]
|
||||||
|
}
|
||||||
@@ -98,6 +98,10 @@ tests/
|
|||||||
unit/ # Vitest unit tests
|
unit/ # Vitest unit tests
|
||||||
e2e/ # Playwright e2e tests (Gherkin-style)
|
e2e/ # Playwright e2e tests (Gherkin-style)
|
||||||
helpers/fixtures.ts # Shared authenticatedPage fixture
|
helpers/fixtures.ts # Shared authenticatedPage fixture
|
||||||
|
website/ # Static landing page (crumbs.bretzel.app)
|
||||||
|
index.html # Landing page with feature cards, screenshots, deploy section
|
||||||
|
styles.css # Landing page styles (retro parchment theme)
|
||||||
|
assets/ # Favicon, screenshots
|
||||||
```
|
```
|
||||||
|
|
||||||
## Commands
|
## Commands
|
||||||
|
|||||||
@@ -1,4 +1,5 @@
|
|||||||
.PHONY: help dev build preview check test test-unit test-e2e lint \
|
.PHONY: help dev build preview check test test-unit test-e2e \
|
||||||
|
test-unit-coverage test-e2e-coverage lint \
|
||||||
db-push db-generate db-migrate db-studio docs-api \
|
db-push db-generate db-migrate db-studio docs-api \
|
||||||
install docker-build docker-up docker-down docker-logs clean \
|
install docker-build docker-up docker-down docker-logs clean \
|
||||||
release release-patch release-minor release-major
|
release release-patch release-minor release-major
|
||||||
@@ -27,6 +28,12 @@ test-unit: ## Run unit tests only
|
|||||||
test-e2e: ## Run e2e tests only
|
test-e2e: ## Run e2e tests only
|
||||||
pnpm test:e2e
|
pnpm test:e2e
|
||||||
|
|
||||||
|
test-unit-coverage: ## Run unit tests with coverage
|
||||||
|
pnpm test:unit:coverage
|
||||||
|
|
||||||
|
test-e2e-coverage: ## Run e2e tests with coverage
|
||||||
|
VITE_COVERAGE=true pnpm build && pnpm test:e2e && npx nyc report
|
||||||
|
|
||||||
lint: ## Run linter
|
lint: ## Run linter
|
||||||
pnpm lint
|
pnpm lint
|
||||||
|
|
||||||
|
|||||||
+1
-1
@@ -1,6 +1,6 @@
|
|||||||
services:
|
services:
|
||||||
crumbs:
|
crumbs:
|
||||||
image: ghcr.io/bretzel-app/crumbs:0.14.0
|
image: ghcr.io/bretzel-app/crumbs:0.16.1
|
||||||
container_name: crumbs
|
container_name: crumbs
|
||||||
ports:
|
ports:
|
||||||
- "3000:3000"
|
- "3000:3000"
|
||||||
|
|||||||
+7
-1
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "crumbs",
|
"name": "crumbs",
|
||||||
"private": true,
|
"private": true,
|
||||||
"version": "0.15.0",
|
"version": "0.17.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Bretzel (https://bretzel.app)",
|
"author": "Bretzel (https://bretzel.app)",
|
||||||
@@ -19,7 +19,10 @@
|
|||||||
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
|
||||||
"test:unit": "vitest run",
|
"test:unit": "vitest run",
|
||||||
"test:unit:watch": "vitest",
|
"test:unit:watch": "vitest",
|
||||||
|
"test:unit:coverage": "vitest run --coverage",
|
||||||
"test:e2e": "playwright test",
|
"test:e2e": "playwright test",
|
||||||
|
"test:e2e:coverage": "VITE_COVERAGE=true pnpm build && pnpm test:e2e && nyc report",
|
||||||
|
"coverage:merge-e2e": "nyc merge coverage/e2e/raw coverage/e2e/merged.json && nyc report",
|
||||||
"test": "pnpm test:unit && pnpm test:e2e",
|
"test": "pnpm test:unit && pnpm test:e2e",
|
||||||
"db:push": "drizzle-kit push",
|
"db:push": "drizzle-kit push",
|
||||||
"db:generate": "drizzle-kit generate",
|
"db:generate": "drizzle-kit generate",
|
||||||
@@ -68,13 +71,16 @@
|
|||||||
"@types/uuid": "^10.0.0",
|
"@types/uuid": "^10.0.0",
|
||||||
"@types/zxcvbn": "^4.4.5",
|
"@types/zxcvbn": "^4.4.5",
|
||||||
"@vite-pwa/sveltekit": "^1.1.0",
|
"@vite-pwa/sveltekit": "^1.1.0",
|
||||||
|
"@vitest/coverage-v8": "^3.2.4",
|
||||||
"drizzle-kit": "^0.31.9",
|
"drizzle-kit": "^0.31.9",
|
||||||
|
"nyc": "^18.0.0",
|
||||||
"svelte": "^5.53.7",
|
"svelte": "^5.53.7",
|
||||||
"svelte-check": "^4.4.4",
|
"svelte-check": "^4.4.4",
|
||||||
"tailwindcss": "^4.2.1",
|
"tailwindcss": "^4.2.1",
|
||||||
"tsx": "^4.21.0",
|
"tsx": "^4.21.0",
|
||||||
"typescript": "^5.9.3",
|
"typescript": "^5.9.3",
|
||||||
"vite": "^7.3.1",
|
"vite": "^7.3.1",
|
||||||
|
"vite-plugin-istanbul": "^7.2.1",
|
||||||
"vitest": "^3.2.4",
|
"vitest": "^3.2.4",
|
||||||
"yaml": "^2.8.2"
|
"yaml": "^2.8.2"
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -4,7 +4,8 @@ export default defineConfig({
|
|||||||
testDir: './tests/e2e',
|
testDir: './tests/e2e',
|
||||||
fullyParallel: true,
|
fullyParallel: true,
|
||||||
forbidOnly: !!process.env.CI,
|
forbidOnly: !!process.env.CI,
|
||||||
retries: process.env.CI ? 2 : 0,
|
retries: 0,
|
||||||
|
workers: process.env.CI ? 4 : undefined,
|
||||||
reporter: 'html',
|
reporter: 'html',
|
||||||
globalSetup: './tests/e2e/global-setup.ts',
|
globalSetup: './tests/e2e/global-setup.ts',
|
||||||
use: {
|
use: {
|
||||||
@@ -27,7 +28,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
],
|
],
|
||||||
webServer: {
|
webServer: {
|
||||||
command: 'pnpm build && pnpm preview --port 4173',
|
command: 'pnpm preview --port 4173',
|
||||||
port: 4173,
|
port: 4173,
|
||||||
reuseExistingServer: !process.env.CI,
|
reuseExistingServer: !process.env.CI,
|
||||||
timeout: 120_000,
|
timeout: 120_000,
|
||||||
|
|||||||
Generated
+925
File diff suppressed because it is too large
Load Diff
Executable
+37
@@ -0,0 +1,37 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
# Writes a coverage summary table to $GITHUB_STEP_SUMMARY (or stdout if not in CI).
|
||||||
|
# Usage: scripts/coverage-summary.sh <title> <json-summary-path>
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
TITLE="${1:?Usage: coverage-summary.sh <title> <json-summary-path>}"
|
||||||
|
JSON="${2:?Usage: coverage-summary.sh <title> <json-summary-path>}"
|
||||||
|
|
||||||
|
if [ ! -f "$JSON" ]; then
|
||||||
|
echo "Coverage summary not found: $JSON" >&2
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Resolve to absolute path for require()
|
||||||
|
ABS_JSON="$(cd "$(dirname "$JSON")" && pwd)/$(basename "$JSON")"
|
||||||
|
|
||||||
|
# Extract totals with node (available in CI, no jq dependency)
|
||||||
|
read -r stmts branches funcs lines < <(node -e "
|
||||||
|
const s = require('$ABS_JSON').total;
|
||||||
|
console.log(s.statements.pct, s.branches.pct, s.functions.pct, s.lines.pct);
|
||||||
|
")
|
||||||
|
|
||||||
|
TABLE="## $TITLE
|
||||||
|
|
||||||
|
| Metric | Coverage |
|
||||||
|
|--------|----------|
|
||||||
|
| Statements | ${stmts}% |
|
||||||
|
| Branches | ${branches}% |
|
||||||
|
| Functions | ${funcs}% |
|
||||||
|
| Lines | ${lines}% |
|
||||||
|
"
|
||||||
|
|
||||||
|
if [ -n "${GITHUB_STEP_SUMMARY:-}" ]; then
|
||||||
|
echo "$TABLE" >> "$GITHUB_STEP_SUMMARY"
|
||||||
|
else
|
||||||
|
echo "$TABLE"
|
||||||
|
fi
|
||||||
+19
-24
@@ -22,36 +22,31 @@ export const handle: Handle = async ({ event, resolve }) => {
|
|||||||
return resolve(event);
|
return resolve(event);
|
||||||
}
|
}
|
||||||
|
|
||||||
// MCP endpoint: authenticate via Bearer token (API key)
|
// API routes: authenticate via Bearer token if present
|
||||||
if (pathname.startsWith('/api/mcp')) {
|
if (pathname.startsWith('/api/')) {
|
||||||
const authHeader = event.request.headers.get('authorization');
|
const authHeader = event.request.headers.get('authorization');
|
||||||
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;
|
const token = authHeader?.startsWith('Bearer ') ? authHeader.slice(7) : null;
|
||||||
|
|
||||||
if (!token) {
|
if (token) {
|
||||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
const { valid, userId } = validateApiKey(token);
|
||||||
status: 401,
|
if (!valid || !userId) {
|
||||||
headers: { 'Content-Type': 'application/json' }
|
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||||
});
|
status: 401,
|
||||||
}
|
headers: { 'Content-Type': 'application/json' }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const { valid, userId } = validateApiKey(token);
|
const user = getUserForApiKey(userId);
|
||||||
if (!valid || !userId) {
|
if (!user) {
|
||||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
||||||
status: 401,
|
status: 401,
|
||||||
headers: { 'Content-Type': 'application/json' }
|
headers: { 'Content-Type': 'application/json' }
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const user = getUserForApiKey(userId);
|
event.locals.user = user;
|
||||||
if (!user) {
|
return resolve(event);
|
||||||
return new Response(JSON.stringify({ error: 'Unauthorized' }), {
|
|
||||||
status: 401,
|
|
||||||
headers: { 'Content-Type': 'application/json' }
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
event.locals.user = user;
|
|
||||||
return resolve(event);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if setup is complete
|
// Check if setup is complete
|
||||||
|
|||||||
@@ -32,7 +32,7 @@
|
|||||||
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/mcp' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
|
class="flex items-center gap-2 rounded-sm px-3 py-2 text-sm whitespace-nowrap transition-colors {pathname === '/settings/mcp' ? 'bg-[var(--primary)]/15 text-[var(--primary)] font-medium' : 'text-[var(--text)] hover:bg-[var(--bg-base)]'}"
|
||||||
>
|
>
|
||||||
<Cpu size={16} />
|
<Cpu size={16} />
|
||||||
MCP Server
|
API
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
{#if data.user?.role === 'admin'}
|
{#if data.user?.role === 'admin'}
|
||||||
|
|||||||
@@ -122,7 +122,7 @@ ${mcpJsonConfig}`);
|
|||||||
<h2 class="text-lg font-semibold text-[var(--text)]">API Keys</h2>
|
<h2 class="text-lg font-semibold text-[var(--text)]">API Keys</h2>
|
||||||
</div>
|
</div>
|
||||||
<p class="mb-4 text-xs text-[var(--text-muted)]">
|
<p class="mb-4 text-xs text-[var(--text-muted)]">
|
||||||
API keys allow external tools (like Claude Code) to access your notes via the MCP protocol.
|
API keys allow external tools to access your notes via the MCP protocol and the REST API.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<!-- Create Key Form -->
|
<!-- Create Key Form -->
|
||||||
@@ -229,9 +229,19 @@ ${mcpJsonConfig}`);
|
|||||||
{/if}
|
{/if}
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
|
<!-- REST API Example -->
|
||||||
|
<section class="mb-6 rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
|
||||||
|
<h2 class="mb-4 text-lg font-semibold text-[var(--text)]">REST API</h2>
|
||||||
|
<p class="mb-3 text-xs text-[var(--text-muted)]">
|
||||||
|
Use your API key to authenticate requests to any <code class="rounded-sm bg-[var(--bg-base)] px-1 py-0.5">/api/*</code> endpoint:
|
||||||
|
</p>
|
||||||
|
<pre class="overflow-x-auto rounded-sm bg-[var(--bg-base)] p-3 text-xs text-[var(--text)]">curl -H "Authorization: Bearer {activeApiKey}" \
|
||||||
|
{baseUrl}/api/notes</pre>
|
||||||
|
</section>
|
||||||
|
|
||||||
<!-- MCP Config Help -->
|
<!-- MCP Config Help -->
|
||||||
<section class="rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
|
<section class="rounded-sm border border-[var(--border-subtle)] bg-[var(--bg-surface)] p-6 shadow-[var(--card-shadow)]">
|
||||||
<h2 class="mb-4 text-lg font-semibold text-[var(--text)]">Client Configuration</h2>
|
<h2 class="mb-4 text-lg font-semibold text-[var(--text)]">MCP Client Configuration</h2>
|
||||||
<p class="mb-3 text-xs text-[var(--text-muted)]">
|
<p class="mb-3 text-xs text-[var(--text-muted)]">
|
||||||
Add this to your <code class="rounded-sm bg-[var(--bg-base)] px-1 py-0.5">.claude/settings.json</code> (Claude Code):
|
Add this to your <code class="rounded-sm bg-[var(--bg-base)] px-1 py-0.5">.claude/settings.json</code> (Claude Code):
|
||||||
</p>
|
</p>
|
||||||
|
|||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import { writeFileSync, mkdirSync } from 'fs';
|
||||||
|
import { join } from 'path';
|
||||||
|
import type { Page } from '@playwright/test';
|
||||||
|
|
||||||
|
const COVERAGE_DIR = join(process.cwd(), 'coverage', 'e2e', 'raw');
|
||||||
|
|
||||||
|
let counter = 0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collect Istanbul coverage data from the browser page.
|
||||||
|
* The vite-plugin-istanbul injects `window.__coverage__` when COVERAGE=true.
|
||||||
|
*/
|
||||||
|
export async function collectCoverage(page: Page, testTitle: string) {
|
||||||
|
if (process.env.VITE_COVERAGE !== 'true') return;
|
||||||
|
|
||||||
|
const coverage = await page.evaluate(() => (window as any).__coverage__);
|
||||||
|
if (!coverage) return;
|
||||||
|
|
||||||
|
mkdirSync(COVERAGE_DIR, { recursive: true });
|
||||||
|
|
||||||
|
const safeName = testTitle.replace(/[^a-zA-Z0-9-_]/g, '_');
|
||||||
|
const filename = `coverage-${safeName}-${counter++}-${Date.now()}.json`;
|
||||||
|
writeFileSync(join(COVERAGE_DIR, filename), JSON.stringify(coverage));
|
||||||
|
}
|
||||||
@@ -1,4 +1,5 @@
|
|||||||
import { test as base, expect, type Page, type Locator } from '@playwright/test';
|
import { test as base, expect, type Page, type Locator } from '@playwright/test';
|
||||||
|
import { collectCoverage } from './coverage';
|
||||||
|
|
||||||
const TEST_EMAIL = 'admin@test.com';
|
const TEST_EMAIL = 'admin@test.com';
|
||||||
const TEST_PASSWORD = 'testpassword123';
|
const TEST_PASSWORD = 'testpassword123';
|
||||||
@@ -13,10 +14,12 @@ export function noteCard(page: Page, title: string): Locator {
|
|||||||
* Extended test fixture that handles setup/auth.
|
* Extended test fixture that handles setup/auth.
|
||||||
*/
|
*/
|
||||||
export const test = base.extend<{ authenticatedPage: Page }>({
|
export const test = base.extend<{ authenticatedPage: Page }>({
|
||||||
authenticatedPage: async ({ page }, use) => {
|
authenticatedPage: async ({ page }, use, testInfo) => {
|
||||||
// Setup: create user and login
|
// Setup: create user and login
|
||||||
await setupAndLogin(page);
|
await setupAndLogin(page);
|
||||||
await use(page);
|
await use(page);
|
||||||
|
// Teardown: collect coverage data from the browser
|
||||||
|
await collectCoverage(page, testInfo.title);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -57,10 +57,10 @@ test.describe('Settings — API Key Management', () => {
|
|||||||
// Given the user is on the settings page
|
// Given the user is on the settings page
|
||||||
await page.goto('/settings');
|
await page.goto('/settings');
|
||||||
|
|
||||||
// When the user clicks the MCP Server nav link
|
// When the user clicks the API nav link
|
||||||
await page.getByRole('link', { name: 'MCP Server' }).click();
|
await page.getByRole('link', { name: 'API' }).click();
|
||||||
|
|
||||||
// Then the MCP settings page is shown
|
// Then the API settings page is shown
|
||||||
await expect(page).toHaveURL('/settings/mcp');
|
await expect(page).toHaveURL('/settings/mcp');
|
||||||
await expect(page.getByRole('heading', { name: 'API Keys' })).toBeVisible();
|
await expect(page.getByRole('heading', { name: 'API Keys' })).toBeVisible();
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import { sveltekit } from '@sveltejs/kit/vite';
|
import { sveltekit } from '@sveltejs/kit/vite';
|
||||||
import { SvelteKitPWA } from '@vite-pwa/sveltekit';
|
import { SvelteKitPWA } from '@vite-pwa/sveltekit';
|
||||||
import tailwindcss from '@tailwindcss/vite';
|
import tailwindcss from '@tailwindcss/vite';
|
||||||
|
import istanbul from 'vite-plugin-istanbul';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
|
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
@@ -10,6 +11,17 @@ export default defineConfig({
|
|||||||
plugins: [
|
plugins: [
|
||||||
tailwindcss(),
|
tailwindcss(),
|
||||||
sveltekit(),
|
sveltekit(),
|
||||||
|
...(process.env.VITE_COVERAGE === 'true'
|
||||||
|
? [
|
||||||
|
istanbul({
|
||||||
|
include: 'src/**/*',
|
||||||
|
exclude: ['node_modules', 'tests/', '**/*.test.ts'],
|
||||||
|
extension: ['.ts', '.svelte'],
|
||||||
|
requireEnv: true,
|
||||||
|
forceBuildInstrument: true
|
||||||
|
})
|
||||||
|
]
|
||||||
|
: []),
|
||||||
SvelteKitPWA({
|
SvelteKitPWA({
|
||||||
registerType: 'autoUpdate',
|
registerType: 'autoUpdate',
|
||||||
manifest: {
|
manifest: {
|
||||||
|
|||||||
+8
-1
@@ -9,6 +9,13 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
test: {
|
test: {
|
||||||
include: ['src/**/*.test.ts'],
|
include: ['src/**/*.test.ts'],
|
||||||
environment: 'node'
|
environment: 'node',
|
||||||
|
coverage: {
|
||||||
|
provider: 'v8',
|
||||||
|
reporter: ['text', 'html', 'lcov', 'json-summary'],
|
||||||
|
reportsDirectory: './coverage/unit',
|
||||||
|
include: ['src/lib/**/*.ts'],
|
||||||
|
exclude: ['src/lib/**/*.test.ts', 'src/lib/types/**']
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|||||||
+3
-3
@@ -145,8 +145,8 @@
|
|||||||
<div class="feature-icon">
|
<div class="feature-icon">
|
||||||
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/></svg>
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><rect x="4" y="4" width="16" height="16" rx="2"/><path d="M9 9h6v6H9z"/><path d="M15 2v2"/><path d="M15 20v2"/><path d="M2 15h2"/><path d="M2 9h2"/><path d="M20 15h2"/><path d="M20 9h2"/><path d="M9 2v2"/><path d="M9 20v2"/></svg>
|
||||||
</div>
|
</div>
|
||||||
<h3>MCP Server</h3>
|
<h3>API & MCP</h3>
|
||||||
<p>Built-in AI integration. Connect Claude, Cursor, or any MCP client to read and write your notes.</p>
|
<p>Full REST API with token auth. Integrate with n8n, Zapier, or scripts. Built-in MCP server for AI tools like Claude and Cursor.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Multi-User -->
|
<!-- Multi-User -->
|
||||||
@@ -253,7 +253,7 @@
|
|||||||
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/docs/DEPLOYMENT.md" target="_blank" rel="noopener">Docs</a></li>
|
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/docs/DEPLOYMENT.md" target="_blank" rel="noopener">Docs</a></li>
|
||||||
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/LICENSE" target="_blank" rel="noopener">License</a></li>
|
<li><a href="https://github.com/bretzel-app/crumbs/blob/main/LICENSE" target="_blank" rel="noopener">License</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<span class="footer-version">v0.15.0</span>
|
<span class="footer-version">v0.17.0</span>
|
||||||
</div>
|
</div>
|
||||||
</footer>
|
</footer>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user