@techstream/quark-create-app 1.5.3 → 1.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (48) hide show
  1. package/package.json +4 -2
  2. package/src/index.js +52 -9
  3. package/templates/base-project/.github/dependabot.yml +12 -0
  4. package/templates/base-project/.github/workflows/ci.yml +97 -0
  5. package/templates/base-project/.github/workflows/dependabot-auto-merge.yml +22 -0
  6. package/templates/base-project/.github/workflows/release.yml +38 -0
  7. package/templates/base-project/apps/web/biome.json +7 -0
  8. package/templates/base-project/apps/web/jsconfig.json +5 -5
  9. package/templates/base-project/apps/web/next.config.js +86 -1
  10. package/templates/base-project/apps/web/package.json +4 -4
  11. package/templates/base-project/apps/web/src/app/api/auth/register/route.js +6 -7
  12. package/templates/base-project/apps/web/src/app/layout.js +3 -4
  13. package/templates/base-project/apps/web/src/app/manifest.js +12 -0
  14. package/templates/base-project/apps/web/src/app/robots.js +21 -0
  15. package/templates/base-project/apps/web/src/app/sitemap.js +20 -0
  16. package/templates/base-project/apps/web/src/lib/seo/indexing.js +23 -0
  17. package/templates/base-project/apps/web/src/lib/seo/site-metadata.js +33 -0
  18. package/templates/base-project/apps/web/src/proxy.js +1 -2
  19. package/templates/base-project/apps/worker/package.json +4 -4
  20. package/templates/base-project/apps/worker/src/index.js +26 -12
  21. package/templates/base-project/apps/worker/src/index.test.js +296 -15
  22. package/templates/base-project/biome.json +44 -0
  23. package/templates/base-project/docker-compose.yml +7 -4
  24. package/templates/base-project/package.json +1 -1
  25. package/templates/base-project/packages/db/package.json +1 -1
  26. package/templates/base-project/packages/db/prisma/schema.prisma +1 -17
  27. package/templates/base-project/packages/db/prisma.config.ts +3 -2
  28. package/templates/base-project/packages/db/scripts/seed.js +117 -30
  29. package/templates/base-project/packages/db/src/queries.js +52 -118
  30. package/templates/base-project/packages/db/src/queries.test.js +0 -29
  31. package/templates/base-project/packages/db/src/schemas.js +0 -12
  32. package/templates/base-project/pnpm-workspace.yaml +4 -0
  33. package/templates/base-project/turbo.json +5 -3
  34. package/templates/config/package.json +2 -0
  35. package/templates/config/src/environment.js +270 -0
  36. package/templates/config/src/index.js +10 -18
  37. package/templates/config/src/load-config.js +135 -0
  38. package/templates/config/src/validate-env.js +60 -2
  39. package/templates/jobs/package.json +2 -2
  40. package/templates/jobs/src/definitions.test.js +34 -0
  41. package/templates/jobs/src/index.js +1 -1
  42. package/templates/ui/package.json +4 -4
  43. package/templates/ui/src/button.test.js +23 -0
  44. package/templates/ui/src/index.js +1 -3
  45. package/templates/base-project/apps/web/src/app/api/posts/[id]/route.js +0 -65
  46. package/templates/base-project/apps/web/src/app/api/posts/route.js +0 -95
  47. package/templates/ui/src/card.js +0 -14
  48. package/templates/ui/src/input.js +0 -11
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@techstream/quark-create-app",
3
- "version": "1.5.3",
3
+ "version": "1.6.0",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "quark-create-app": "src/index.js",
@@ -14,7 +14,7 @@
14
14
  ],
15
15
  "dependencies": {
16
16
  "chalk": "^5.6.2",
17
- "commander": "^12.1.0",
17
+ "commander": "^14.0.3",
18
18
  "execa": "^9.6.1",
19
19
  "fs-extra": "^11.3.3",
20
20
  "prompts": "^2.4.2"
@@ -28,6 +28,8 @@
28
28
  },
29
29
  "license": "ISC",
30
30
  "scripts": {
31
+ "sync-templates": "node scripts/sync-templates.js",
32
+ "sync-templates:check": "node scripts/sync-templates.js --check",
31
33
  "test": "node test-cli.js",
32
34
  "test:build": "node test-build.js",
33
35
  "test:e2e": "node test-e2e.js",
package/src/index.js CHANGED
@@ -474,7 +474,11 @@ program
474
474
 
475
475
  // Step 8: Create .env.example file
476
476
  console.log(chalk.cyan("\n 📋 Creating environment configuration..."));
477
- const envExampleTemplate = `# --- Database Configuration ---
477
+ const envExampleTemplate = `# --- Environment ---
478
+ # Supported: development, test, staging, production (default: development)
479
+ # NODE_ENV=development
480
+
481
+ # --- Database Configuration ---
478
482
  # These map to the service names in docker-compose.yml
479
483
  # ⚠️ SECURITY WARNING: Change these default passwords in production!
480
484
  # Generate strong passwords with: openssl rand -base64 32
@@ -492,13 +496,30 @@ REDIS_PORT=6379
492
496
  # Optional: Set REDIS_URL to override the dynamic construction above
493
497
  # REDIS_URL="redis://localhost:6379"
494
498
 
495
- # --- Mail Configuration (Mailpit in development) ---
499
+ # --- Mail Configuration ---
500
+ # Development: Mailpit local SMTP (defaults below work with docker-compose)
496
501
  MAIL_HOST=localhost
497
502
  MAIL_SMTP_PORT=1025
498
503
  MAIL_UI_PORT=8025
499
504
  # Optional: Set MAIL_SMTP_URL to override the dynamic construction above
500
505
  # MAIL_SMTP_URL="smtp://localhost:1025"
501
506
 
507
+ # Production SMTP: Set these instead of MAIL_* when using a real SMTP relay
508
+ # SMTP_HOST=smtp.example.com
509
+ # SMTP_PORT=587
510
+ # SMTP_SECURE=true
511
+ # SMTP_USER=your_smtp_user
512
+ # SMTP_PASSWORD=your_smtp_password
513
+
514
+ # --- Email Provider ---
515
+ # Provider: "smtp" (default) or "resend"
516
+ # EMAIL_PROVIDER=smtp
517
+ # EMAIL_FROM=App Name <noreply@yourdomain.com>
518
+
519
+ # Resend (only when EMAIL_PROVIDER=resend)
520
+ # Get your API key at: https://resend.com/api-keys
521
+ # RESEND_API_KEY=re_xxxxxxxxxxxxx
522
+
502
523
  # --- Application URL ---
503
524
  # In development, APP_URL is derived automatically from PORT — no need to set it.
504
525
  # In production, set this to your real domain:
@@ -524,23 +545,36 @@ PORT=3000
524
545
  # --- Worker Configuration ---
525
546
  WORKER_CONCURRENCY=5
526
547
 
527
- # --- File Storage Configuration ---
528
- # Storage provider: "local" (default) or "s3" (S3-compatible, e.g. Cloudflare R2)
548
+ # --- File Storage ---
549
+ # Provider: "local" (default) or "s3" (S3-compatible: AWS S3, Cloudflare R2, MinIO)
529
550
  STORAGE_PROVIDER=local
530
- # Local storage directory (only used when STORAGE_PROVIDER=local)
551
+ # Local storage directory (only when STORAGE_PROVIDER=local)
531
552
  # STORAGE_LOCAL_DIR=./uploads
532
553
 
533
- # S3 / Cloudflare R2 Configuration (only used when STORAGE_PROVIDER=s3)
554
+ # S3 / Cloudflare R2 (only when STORAGE_PROVIDER=s3)
534
555
  # S3_BUCKET=your-bucket-name
535
- # S3_REGION=auto
556
+ # S3_REGION=auto # Use "auto" for Cloudflare R2
536
557
  # S3_ENDPOINT=https://<account-id>.r2.cloudflarestorage.com
537
558
  # S3_ACCESS_KEY_ID=your-access-key
538
559
  # S3_SECRET_ACCESS_KEY=your-secret-key
539
560
  # S3_PUBLIC_URL=https://your-public-bucket-domain.com
540
561
 
541
562
  # --- Upload Limits ---
542
- # UPLOAD_MAX_SIZE=10485760
563
+ # UPLOAD_MAX_SIZE=10485760 # Max file size in bytes (default: 10MB)
543
564
  # UPLOAD_ALLOWED_TYPES=image/jpeg,image/png,image/gif,image/webp,image/avif,image/svg+xml,application/pdf
565
+
566
+ # --- Rate Limiting & Security ---
567
+ # RATE_LIMIT_MAX=100 # Max requests per window (default: 1000 dev, 100 prod)
568
+ # RATE_LIMIT_WINDOW_MS=900000 # Window in ms (default: 15 minutes)
569
+ # API_BODY_SIZE_LIMIT=2097152 # 2MB (default)
570
+ # UPLOAD_SIZE_LIMIT=10485760 # 10MB proxy-level limit (default)
571
+
572
+ # --- Logging & Cache ---
573
+ # LOG_LEVEL=debug # debug, info, warn, error (default: debug dev, info prod)
574
+ # CACHE_TTL=60 # Default cache TTL in seconds (default: 60 dev, 600 prod)
575
+
576
+ # --- Database Seeding ---
577
+ # SEED_PROFILE=dev # Options: dev (default, includes audit logs + sample job), minimal (users only)
544
578
  `;
545
579
  await fs.writeFile(
546
580
  path.join(targetDir, ".env.example"),
@@ -609,6 +643,9 @@ WORKER_CONCURRENCY=5
609
643
 
610
644
  # --- File Storage ---
611
645
  STORAGE_PROVIDER=local
646
+
647
+ # --- Database Seeding ---
648
+ # SEED_PROFILE=dev # Options: dev (default), minimal (users only — use for production initial seed)
612
649
  `;
613
650
  await fs.writeFile(path.join(targetDir, ".env"), envContent);
614
651
  console.log(
@@ -725,7 +762,13 @@ STORAGE_PROVIDER=local
725
762
  console.log(chalk.white(` 1. cd ${projectName}`));
726
763
  console.log(chalk.white(` 2. docker compose up -d`));
727
764
  console.log(chalk.white(` 3. pnpm db:migrate`));
728
- console.log(chalk.white(` 4. pnpm dev\n`));
765
+ console.log(chalk.white(` 4. pnpm db:seed`));
766
+ console.log(chalk.white(` 5. pnpm dev\n`));
767
+ console.log(
768
+ chalk.dim(
769
+ ` Tip: set SEED_PROFILE=minimal in .env for a lean seed (admin user only)\n`,
770
+ ),
771
+ );
729
772
 
730
773
  console.log(chalk.cyan("Important:"));
731
774
  console.log(
@@ -0,0 +1,12 @@
1
+ version: 2
2
+ updates:
3
+ - package-ecosystem: npm
4
+ directory: /
5
+ schedule:
6
+ interval: weekly
7
+ groups:
8
+ production:
9
+ dependency-type: production
10
+ development:
11
+ dependency-type: development
12
+ open-pull-requests-limit: 10
@@ -0,0 +1,97 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ branches: [main]
8
+
9
+ jobs:
10
+ lint:
11
+ name: Lint
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: actions/checkout@v4
15
+
16
+ - uses: pnpm/action-setup@v4
17
+
18
+ - uses: actions/setup-node@v4
19
+ with:
20
+ node-version: 24
21
+ cache: pnpm
22
+
23
+ - run: pnpm install --frozen-lockfile
24
+
25
+ - run: pnpm lint
26
+
27
+ test:
28
+ runs-on: ubuntu-latest
29
+ name: Test
30
+ services:
31
+ postgres:
32
+ image: postgres:16-alpine
33
+ env:
34
+ POSTGRES_USER: postgres
35
+ POSTGRES_PASSWORD: postgres
36
+ POSTGRES_DB: app_test
37
+ ports:
38
+ - 5432:5432
39
+ options: >-
40
+ --health-cmd="pg_isready -U postgres"
41
+ --health-interval=5s
42
+ --health-timeout=5s
43
+ --health-retries=5
44
+ redis:
45
+ image: redis:7-alpine
46
+ ports:
47
+ - 6379:6379
48
+ options: >-
49
+ --health-cmd="redis-cli ping"
50
+ --health-interval=5s
51
+ --health-timeout=5s
52
+ --health-retries=5
53
+ env:
54
+ POSTGRES_USER: postgres
55
+ POSTGRES_PASSWORD: postgres
56
+ POSTGRES_HOST: localhost
57
+ POSTGRES_PORT: "5432"
58
+ POSTGRES_DB: app_test
59
+ REDIS_HOST: localhost
60
+ REDIS_PORT: "6379"
61
+ NEXTAUTH_SECRET: ci-test-secret-must-be-at-least-32-characters-long
62
+ NODE_ENV: test
63
+ steps:
64
+ - uses: actions/checkout@v4
65
+
66
+ - uses: pnpm/action-setup@v4
67
+
68
+ - uses: actions/setup-node@v4
69
+ with:
70
+ node-version: 24
71
+ cache: pnpm
72
+
73
+ - run: pnpm install --frozen-lockfile
74
+
75
+ - run: pnpm db:generate
76
+
77
+ - run: pnpm test
78
+
79
+ build:
80
+ name: Build
81
+ needs: [lint, test]
82
+ runs-on: ubuntu-latest
83
+ steps:
84
+ - uses: actions/checkout@v4
85
+
86
+ - uses: pnpm/action-setup@v4
87
+
88
+ - uses: actions/setup-node@v4
89
+ with:
90
+ node-version: 24
91
+ cache: pnpm
92
+
93
+ - run: pnpm install --frozen-lockfile
94
+
95
+ - run: pnpm db:generate
96
+
97
+ - run: pnpm build
@@ -0,0 +1,22 @@
1
+ name: Auto-merge Dependabot
2
+
3
+ on: pull_request
4
+
5
+ permissions:
6
+ contents: write
7
+ pull-requests: write
8
+
9
+ jobs:
10
+ auto-merge:
11
+ if: github.actor == 'dependabot[bot]'
12
+ runs-on: ubuntu-latest
13
+ steps:
14
+ - uses: dependabot/fetch-metadata@v2
15
+ id: meta
16
+
17
+ - name: Auto-merge patch updates
18
+ if: steps.meta.outputs.update-type == 'version-update:semver-patch'
19
+ run: gh pr merge "$PR_URL" --auto --squash
20
+ env:
21
+ PR_URL: ${{ github.event.pull_request.html_url }}
22
+ GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -0,0 +1,38 @@
1
+ name: Release
2
+
3
+ on:
4
+ workflow_dispatch:
5
+ inputs:
6
+ notes:
7
+ description: "Release notes (optional — auto-generated if blank)"
8
+ required: false
9
+
10
+ permissions:
11
+ contents: write
12
+
13
+ jobs:
14
+ release:
15
+ name: Tag & Release
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+ with:
20
+ fetch-depth: 0
21
+
22
+ - name: Generate date-based tag
23
+ id: tag
24
+ run: |
25
+ BASE="v$(date +%Y.%m.%d)"
26
+ EXISTING=$(git tag -l "${BASE}*" | wc -l | tr -d ' ')
27
+ if [ "$EXISTING" -eq "0" ]; then
28
+ echo "tag=${BASE}" >> "$GITHUB_OUTPUT"
29
+ else
30
+ echo "tag=${BASE}.${EXISTING}" >> "$GITHUB_OUTPUT"
31
+ fi
32
+
33
+ - name: Create GitHub Release
34
+ uses: softprops/action-gh-release@v2
35
+ with:
36
+ tag_name: ${{ steps.tag.outputs.tag }}
37
+ generate_release_notes: ${{ github.event.inputs.notes == '' }}
38
+ body: ${{ github.event.inputs.notes }}
@@ -0,0 +1,7 @@
1
+ {
2
+ "$schema": "https://biomejs.dev/schemas/2.4.0/schema.json",
3
+ "extends": ["../../biome.json"],
4
+ "files": {
5
+ "includes": ["*.{js,ts,mjs,cjs,json}", "src/**", "scripts/**"]
6
+ }
7
+ }
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "compilerOptions": {
3
- "baseUrl": ".",
3
+ "baseUrl": "./src",
4
4
  "paths": {
5
- "@/*": ["./src/*"]
6
- }
5
+ "@/*": ["./*"]
6
+ },
7
+ "jsx": "preserve"
7
8
  },
8
- "include": ["next.env.d.ts", "**/*.js", "**/*.jsx"],
9
- "exclude": ["node_modules"]
9
+ "include": ["next.config.js"]
10
10
  }
@@ -1,6 +1,91 @@
1
1
  /** @type {import('next').NextConfig} */
2
2
  const nextConfig = {
3
- // No TypeScript configuration needed for JS-only projects
3
+ // Support workspace package resolution (including @techstream/quark-db which uses
4
+ // the Prisma driver-adapter pattern — pure JS, no native engine binary)
5
+ transpilePackages: [
6
+ "@techstream/quark-core",
7
+ "@techstream/quark-db",
8
+ "@techstream/quark-ui",
9
+ "@techstream/quark-jobs",
10
+ ],
11
+
12
+ // Security headers
13
+ // NOTE: These are also applied by proxy.js for proxy-matched routes.
14
+ // Keeping them here as a fallback for routes the proxy doesn't match.
15
+ async headers() {
16
+ return [
17
+ {
18
+ source: "/:path*",
19
+ headers: [
20
+ {
21
+ key: "X-DNS-Prefetch-Control",
22
+ value: "on",
23
+ },
24
+ {
25
+ key: "X-Frame-Options",
26
+ value: "SAMEORIGIN",
27
+ },
28
+ {
29
+ key: "X-Content-Type-Options",
30
+ value: "nosniff",
31
+ },
32
+ {
33
+ key: "Referrer-Policy",
34
+ value: "strict-origin-when-cross-origin",
35
+ },
36
+ {
37
+ key: "Permissions-Policy",
38
+ value: "camera=(), microphone=(), geolocation=()",
39
+ },
40
+ {
41
+ key: "Content-Security-Policy",
42
+ value:
43
+ "default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data: https:; font-src 'self';",
44
+ },
45
+ ],
46
+ },
47
+ ];
48
+ },
49
+
50
+ // Environment variables validation
51
+ env: {
52
+ APP_URL: process.env.APP_URL,
53
+ NEXTAUTH_URL: process.env.NEXTAUTH_URL || process.env.APP_URL,
54
+ NEXTAUTH_SECRET: process.env.NEXTAUTH_SECRET,
55
+ },
56
+
57
+ // Request body size limits (security)
58
+ experimental: {
59
+ // Limit request body size to prevent DoS attacks
60
+ // Default is 4MB, we're being explicit here
61
+ // Adjust based on your needs (e.g., larger for file uploads)
62
+ serverActions: {
63
+ bodySizeLimit: "2mb", // For Server Actions
64
+ },
65
+ },
66
+
67
+ // API route configuration
68
+ async rewrites() {
69
+ return [];
70
+ },
71
+
72
+ // Compiler options for production optimization
73
+ compiler: {
74
+ removeConsole:
75
+ process.env.NODE_ENV === "production"
76
+ ? { exclude: ["error", "warn"] }
77
+ : false,
78
+ },
79
+
80
+ // Image optimization configuration
81
+ images: {
82
+ domains: [],
83
+ formats: ["image/avif", "image/webp"],
84
+ },
85
+
86
+ // Production-only settings
87
+ poweredByHeader: false,
88
+ compress: true,
4
89
  };
5
90
 
6
91
  export default nextConfig;
@@ -25,9 +25,9 @@
25
25
  "zod": "^4.3.6"
26
26
  },
27
27
  "devDependencies": {
28
- "@techstream/quark-config": "workspace:*",
29
- "@tailwindcss/postcss": "^4",
30
- "@types/node": "^20",
31
- "tailwindcss": "^4"
28
+ "@tailwindcss/postcss": "^4.1.18",
29
+ "@types/node": "^20.19.33",
30
+ "tailwindcss": "^4.1.18",
31
+ "@techstream/quark-config": "workspace:*"
32
32
  }
33
33
  }
@@ -35,20 +35,19 @@ export const POST = withCsrfProtection(async (request) => {
35
35
  password: hashedPassword,
36
36
  });
37
37
 
38
- // Enqueue welcome email (fire-and-forget)
38
+ // Don't return the password
39
+ const { password: _, ...safeUser } = newUser;
40
+
41
+ // Enqueue welcome email (fire-and-forget — don't block the response)
39
42
  try {
40
43
  const emailQueue = createQueue(JOB_QUEUES.EMAIL);
41
44
  await emailQueue.add(JOB_NAMES.SEND_WELCOME_EMAIL, {
42
45
  userId: newUser.id,
43
46
  });
44
- } catch (emailError) {
45
- // Don't fail registration if email enqueue fails
46
- console.error("Failed to enqueue welcome email:", emailError);
47
+ } catch {
48
+ // Non-critical user is created even if email fails to enqueue
47
49
  }
48
50
 
49
- // Don't return the password
50
- const { password: _, ...safeUser } = newUser;
51
-
52
51
  return NextResponse.json(safeUser, { status: 201 });
53
52
  } catch (error) {
54
53
  return handleError(error);
@@ -1,7 +1,6 @@
1
- export const metadata = {
2
- title: "Quark",
3
- description: "A modern monorepo with Next.js, React, and Prisma",
4
- };
1
+ import { getSiteMetadata } from "../lib/seo/site-metadata.js";
2
+
3
+ export const metadata = getSiteMetadata();
5
4
 
6
5
  export default function RootLayout({ children }) {
7
6
  return (
@@ -0,0 +1,12 @@
1
+ import { config, getAppUrl } from "@techstream/quark-config";
2
+
3
+ export default function manifest() {
4
+ return {
5
+ name: config.appName,
6
+ short_name: config.appName,
7
+ description: config.appDescription,
8
+ start_url: "/",
9
+ display: "standalone",
10
+ id: getAppUrl(),
11
+ };
12
+ }
@@ -0,0 +1,21 @@
1
+ import { getAppUrl } from "@techstream/quark-config";
2
+ import { isWebsiteIndexable } from "../lib/seo/indexing.js";
3
+
4
+ export default function robots() {
5
+ const appUrl = getAppUrl();
6
+ const indexable = isWebsiteIndexable();
7
+
8
+ return {
9
+ rules: indexable
10
+ ? {
11
+ userAgent: "*",
12
+ allow: "/",
13
+ disallow: ["/api/"],
14
+ }
15
+ : {
16
+ userAgent: "*",
17
+ disallow: "/",
18
+ },
19
+ sitemap: indexable ? `${appUrl}/sitemap.xml` : undefined,
20
+ };
21
+ }
@@ -0,0 +1,20 @@
1
+ import { getAppUrl } from "@techstream/quark-config";
2
+ import { isWebsiteIndexable } from "../lib/seo/indexing.js";
3
+
4
+ const STATIC_ROUTES = [{ path: "/", changeFrequency: "daily", priority: 1 }];
5
+
6
+ export default function sitemap() {
7
+ if (!isWebsiteIndexable()) {
8
+ return [];
9
+ }
10
+
11
+ const appUrl = getAppUrl();
12
+ const lastModified = new Date();
13
+
14
+ return STATIC_ROUTES.map((route) => ({
15
+ url: `${appUrl}${route.path}`,
16
+ lastModified,
17
+ changeFrequency: route.changeFrequency,
18
+ priority: route.priority,
19
+ }));
20
+ }
@@ -0,0 +1,23 @@
1
+ export function isWebsiteIndexable(env = process.env) {
2
+ return (env.NODE_ENV || "").toLowerCase() === "production";
3
+ }
4
+
5
+ export function getMetadataRobots(env = process.env) {
6
+ if (isWebsiteIndexable(env)) {
7
+ return {
8
+ index: true,
9
+ follow: true,
10
+ };
11
+ }
12
+
13
+ return {
14
+ index: false,
15
+ follow: false,
16
+ nocache: true,
17
+ googleBot: {
18
+ index: false,
19
+ follow: false,
20
+ noimageindex: true,
21
+ },
22
+ };
23
+ }
@@ -0,0 +1,33 @@
1
+ import { config, getAppUrl } from "@techstream/quark-config";
2
+ import { getMetadataRobots } from "./indexing.js";
3
+
4
+ const appUrl = getAppUrl();
5
+ const { appName, appDescription } = config;
6
+
7
+ export function getSiteMetadata() {
8
+ return {
9
+ metadataBase: new URL(appUrl),
10
+ title: {
11
+ default: appName,
12
+ template: `%s | ${appName}`,
13
+ },
14
+ description: appDescription,
15
+ applicationName: appName,
16
+ alternates: {
17
+ canonical: "/",
18
+ },
19
+ openGraph: {
20
+ type: "website",
21
+ url: appUrl,
22
+ title: appName,
23
+ description: appDescription,
24
+ siteName: appName,
25
+ },
26
+ twitter: {
27
+ card: "summary",
28
+ title: appName,
29
+ description: appDescription,
30
+ },
31
+ robots: getMetadataRobots(),
32
+ };
33
+ }
@@ -151,8 +151,7 @@ export function proxy(request) {
151
151
 
152
152
  // Apply rate limiting to API routes only
153
153
  if (pathname.startsWith("/api/")) {
154
- const ip =
155
- request.ip || request.headers.get("x-forwarded-for") || "unknown";
154
+ const ip = request.ip || "unknown";
156
155
  const rateLimitResult = checkRateLimit(ip, pathname);
157
156
 
158
157
  if (rateLimitResult.limited) {
@@ -1,8 +1,8 @@
1
1
  {
2
2
  "name": "@techstream/quark-worker",
3
3
  "version": "1.0.0",
4
- "type": "module",
5
4
  "private": true,
5
+ "type": "module",
6
6
  "description": "",
7
7
  "main": "index.js",
8
8
  "scripts": {
@@ -18,11 +18,11 @@
18
18
  "@techstream/quark-core": "^1.0.0",
19
19
  "@techstream/quark-db": "workspace:*",
20
20
  "@techstream/quark-jobs": "workspace:*",
21
- "bullmq": "^5.64.1"
21
+ "bullmq": "^5.67.3"
22
22
  },
23
23
  "devDependencies": {
24
24
  "@techstream/quark-config": "workspace:*",
25
- "@types/node": "^24.10.1",
26
- "tsx": "^4.20.6"
25
+ "@types/node": "^24.10.12",
26
+ "tsx": "^4.21.0"
27
27
  }
28
28
  }