create-skit 0.1.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 (195) hide show
  1. package/README.md +36 -0
  2. package/bin/create-skit.mjs +1064 -0
  3. package/lib/module-application.mjs +281 -0
  4. package/lib/module-resolver.mjs +179 -0
  5. package/modules/README.md +22 -0
  6. package/modules/ai-dx/files/AGENTS.md +116 -0
  7. package/modules/ai-dx/files/ARCHITECTURE.md +103 -0
  8. package/modules/ai-dx/module.json +14 -0
  9. package/modules/ai-dx-claude/files/CLAUDE.md +8 -0
  10. package/modules/ai-dx-claude/module.json +13 -0
  11. package/modules/ai-dx-cursor/files/.cursor/rules/auth.mdc +53 -0
  12. package/modules/ai-dx-cursor/files/.cursor/rules/database.mdc +48 -0
  13. package/modules/ai-dx-cursor/files/.cursor/rules/env.mdc +43 -0
  14. package/modules/ai-dx-cursor/files/.cursor/rules/nextjs.mdc +58 -0
  15. package/modules/ai-dx-cursor/files/.cursor/rules/project.mdc +33 -0
  16. package/modules/ai-dx-cursor/files/.cursor/rules/testing.mdc +55 -0
  17. package/modules/ai-dx-cursor/module.json +18 -0
  18. package/modules/ai-dx-gemini/files/.gemini/GEMINI.md +5 -0
  19. package/modules/ai-dx-gemini/module.json +13 -0
  20. package/modules/auth-core/module.json +8 -0
  21. package/modules/auth-github/module.json +20 -0
  22. package/modules/billing-polar/module.json +20 -0
  23. package/modules/billing-stripe/module.json +23 -0
  24. package/modules/dashboard-shell/files/src/app/globals.css +756 -0
  25. package/modules/dashboard-shell/files/src/app/settings/page.tsx +67 -0
  26. package/modules/dashboard-shell/module.json +11 -0
  27. package/modules/db-pg/module.json +21 -0
  28. package/modules/db-postgresjs/module.json +21 -0
  29. package/modules/deploy-docker/files/.dockerignore +19 -0
  30. package/modules/deploy-docker/files/Dockerfile +25 -0
  31. package/modules/deploy-docker/module.json +11 -0
  32. package/modules/email-resend/module.json +21 -0
  33. package/modules/quality-baseline/module.json +8 -0
  34. package/modules/testing-baseline/module.json +8 -0
  35. package/package.json +40 -0
  36. package/presets/README.md +12 -0
  37. package/presets/blank.json +67 -0
  38. package/presets/dashboard.json +67 -0
  39. package/templates/base-web/.env.example +17 -0
  40. package/templates/base-web/.github/workflows/ci.yml +34 -0
  41. package/templates/base-web/.husky/pre-commit +3 -0
  42. package/templates/base-web/.husky/pre-push +3 -0
  43. package/templates/base-web/.prettierignore +3 -0
  44. package/templates/base-web/README.md +42 -0
  45. package/templates/base-web/drizzle.config.ts +16 -0
  46. package/templates/base-web/eslint.config.mjs +127 -0
  47. package/templates/base-web/manifest.json +5 -0
  48. package/templates/base-web/next-env.d.ts +4 -0
  49. package/templates/base-web/next.config.ts +5 -0
  50. package/templates/base-web/package.json +75 -0
  51. package/templates/base-web/playwright.config.ts +21 -0
  52. package/templates/base-web/prettier.config.mjs +9 -0
  53. package/templates/base-web/proxy.ts +23 -0
  54. package/templates/base-web/src/app/api/auth/[...all]/route.ts +5 -0
  55. package/templates/base-web/src/app/api/billing/checkout/route.ts +26 -0
  56. package/templates/base-web/src/app/api/billing/portal/route.ts +25 -0
  57. package/templates/base-web/src/app/api/email/test/route.ts +28 -0
  58. package/templates/base-web/src/app/api/webhooks/polar/route.ts +5 -0
  59. package/templates/base-web/src/app/api/webhooks/stripe/route.ts +5 -0
  60. package/templates/base-web/src/app/billing/page.tsx +55 -0
  61. package/templates/base-web/src/app/dashboard/page.tsx +15 -0
  62. package/templates/base-web/src/app/email/page.tsx +46 -0
  63. package/templates/base-web/src/app/error.tsx +27 -0
  64. package/templates/base-web/src/app/globals.css +534 -0
  65. package/templates/base-web/src/app/layout.tsx +19 -0
  66. package/templates/base-web/src/app/llms-full.txt/route.ts +158 -0
  67. package/templates/base-web/src/app/llms.txt/route.ts +59 -0
  68. package/templates/base-web/src/app/loading.tsx +24 -0
  69. package/templates/base-web/src/app/not-found.tsx +16 -0
  70. package/templates/base-web/src/app/page.tsx +5 -0
  71. package/templates/base-web/src/app/sign-in/page.tsx +14 -0
  72. package/templates/base-web/src/app/sign-up/page.tsx +14 -0
  73. package/templates/base-web/src/components/auth/email-auth-form.test.tsx +40 -0
  74. package/templates/base-web/src/components/auth/email-auth-form.tsx +128 -0
  75. package/templates/base-web/src/components/auth/sign-out-button.tsx +29 -0
  76. package/templates/base-web/src/db/index.ts +16 -0
  77. package/templates/base-web/src/db/schema/auth.ts +4 -0
  78. package/templates/base-web/src/db/schema/index.ts +2 -0
  79. package/templates/base-web/src/db/schema/projects.ts +17 -0
  80. package/templates/base-web/src/db/seeds/index.ts +32 -0
  81. package/templates/base-web/src/lib/auth-client.ts +5 -0
  82. package/templates/base-web/src/lib/auth-session.ts +21 -0
  83. package/templates/base-web/src/lib/auth.ts +23 -0
  84. package/templates/base-web/src/lib/billing/index.ts +37 -0
  85. package/templates/base-web/src/lib/billing/providers/polar.ts +80 -0
  86. package/templates/base-web/src/lib/billing/providers/stripe.ts +77 -0
  87. package/templates/base-web/src/lib/billing/types.ts +25 -0
  88. package/templates/base-web/src/lib/email/index.ts +19 -0
  89. package/templates/base-web/src/lib/email/templates.test.ts +12 -0
  90. package/templates/base-web/src/lib/email/templates.ts +40 -0
  91. package/templates/base-web/src/lib/env.ts +83 -0
  92. package/templates/base-web/tests/e2e/home.spec.ts +8 -0
  93. package/templates/base-web/tsconfig.json +34 -0
  94. package/templates/base-web/vitest.config.ts +19 -0
  95. package/templates/blank/.env.example +16 -0
  96. package/templates/blank/.github/workflows/ci.yml +34 -0
  97. package/templates/blank/.husky/pre-commit +3 -0
  98. package/templates/blank/.husky/pre-push +3 -0
  99. package/templates/blank/.prettierignore +3 -0
  100. package/templates/blank/drizzle.config.ts +16 -0
  101. package/templates/blank/eslint.config.mjs +127 -0
  102. package/templates/blank/next-env.d.ts +4 -0
  103. package/templates/blank/next.config.ts +5 -0
  104. package/templates/blank/package.json +75 -0
  105. package/templates/blank/playwright.config.ts +21 -0
  106. package/templates/blank/prettier.config.mjs +9 -0
  107. package/templates/blank/proxy.ts +28 -0
  108. package/templates/blank/src/app/api/auth/[...all]/route.ts +5 -0
  109. package/templates/blank/src/app/api/billing/checkout/route.ts +26 -0
  110. package/templates/blank/src/app/api/billing/portal/route.ts +25 -0
  111. package/templates/blank/src/app/api/email/test/route.ts +28 -0
  112. package/templates/blank/src/app/api/webhooks/polar/route.ts +5 -0
  113. package/templates/blank/src/app/api/webhooks/stripe/route.ts +5 -0
  114. package/templates/blank/src/app/billing/page.tsx +70 -0
  115. package/templates/blank/src/app/email/page.tsx +46 -0
  116. package/templates/blank/src/app/globals.css +394 -0
  117. package/templates/blank/src/app/layout.tsx +19 -0
  118. package/templates/blank/src/app/page.tsx +23 -0
  119. package/templates/blank/src/app/sign-in/page.tsx +18 -0
  120. package/templates/blank/src/app/sign-up/page.tsx +18 -0
  121. package/templates/blank/src/components/auth/email-auth-form.test.tsx +39 -0
  122. package/templates/blank/src/components/auth/email-auth-form.tsx +109 -0
  123. package/templates/blank/src/components/auth/sign-out-button.tsx +29 -0
  124. package/templates/blank/src/db/index.ts +16 -0
  125. package/templates/blank/src/db/schema/auth.ts +4 -0
  126. package/templates/blank/src/db/schema/index.ts +2 -0
  127. package/templates/blank/src/db/schema/projects.ts +17 -0
  128. package/templates/blank/src/db/seeds/index.ts +28 -0
  129. package/templates/blank/src/lib/auth-client.ts +5 -0
  130. package/templates/blank/src/lib/auth-session.ts +11 -0
  131. package/templates/blank/src/lib/auth.ts +23 -0
  132. package/templates/blank/src/lib/billing/index.ts +37 -0
  133. package/templates/blank/src/lib/billing/providers/polar.ts +80 -0
  134. package/templates/blank/src/lib/billing/providers/stripe.ts +77 -0
  135. package/templates/blank/src/lib/billing/types.ts +25 -0
  136. package/templates/blank/src/lib/email/index.ts +19 -0
  137. package/templates/blank/src/lib/email/templates.test.ts +15 -0
  138. package/templates/blank/src/lib/email/templates.ts +40 -0
  139. package/templates/blank/src/lib/env.ts +80 -0
  140. package/templates/blank/tsconfig.json +34 -0
  141. package/templates/blank/vitest.config.ts +19 -0
  142. package/templates/dashboard/.env.example +16 -0
  143. package/templates/dashboard/.github/workflows/ci.yml +34 -0
  144. package/templates/dashboard/.husky/pre-commit +3 -0
  145. package/templates/dashboard/.husky/pre-push +3 -0
  146. package/templates/dashboard/.prettierignore +3 -0
  147. package/templates/dashboard/drizzle.config.ts +16 -0
  148. package/templates/dashboard/eslint.config.mjs +127 -0
  149. package/templates/dashboard/next-env.d.ts +4 -0
  150. package/templates/dashboard/next.config.ts +5 -0
  151. package/templates/dashboard/package.json +75 -0
  152. package/templates/dashboard/playwright.config.ts +21 -0
  153. package/templates/dashboard/prettier.config.mjs +9 -0
  154. package/templates/dashboard/proxy.ts +36 -0
  155. package/templates/dashboard/src/app/api/auth/[...all]/route.ts +5 -0
  156. package/templates/dashboard/src/app/api/billing/checkout/route.ts +26 -0
  157. package/templates/dashboard/src/app/api/billing/portal/route.ts +25 -0
  158. package/templates/dashboard/src/app/api/email/test/route.ts +28 -0
  159. package/templates/dashboard/src/app/api/webhooks/polar/route.ts +5 -0
  160. package/templates/dashboard/src/app/api/webhooks/stripe/route.ts +5 -0
  161. package/templates/dashboard/src/app/billing/layout.tsx +22 -0
  162. package/templates/dashboard/src/app/billing/page.tsx +73 -0
  163. package/templates/dashboard/src/app/dashboard/layout.tsx +22 -0
  164. package/templates/dashboard/src/app/dashboard/page.tsx +104 -0
  165. package/templates/dashboard/src/app/email/layout.tsx +22 -0
  166. package/templates/dashboard/src/app/email/page.tsx +54 -0
  167. package/templates/dashboard/src/app/globals.css +1357 -0
  168. package/templates/dashboard/src/app/layout.tsx +25 -0
  169. package/templates/dashboard/src/app/page.tsx +154 -0
  170. package/templates/dashboard/src/app/settings/layout.tsx +22 -0
  171. package/templates/dashboard/src/app/settings/page.tsx +85 -0
  172. package/templates/dashboard/src/app/sign-in/page.tsx +47 -0
  173. package/templates/dashboard/src/app/sign-up/page.tsx +47 -0
  174. package/templates/dashboard/src/components/auth/email-auth-form.test.tsx +39 -0
  175. package/templates/dashboard/src/components/auth/email-auth-form.tsx +160 -0
  176. package/templates/dashboard/src/components/auth/sign-out-button.tsx +29 -0
  177. package/templates/dashboard/src/components/dashboard/shell.tsx +158 -0
  178. package/templates/dashboard/src/db/index.ts +16 -0
  179. package/templates/dashboard/src/db/schema/auth.ts +4 -0
  180. package/templates/dashboard/src/db/schema/index.ts +2 -0
  181. package/templates/dashboard/src/db/schema/projects.ts +17 -0
  182. package/templates/dashboard/src/db/seeds/index.ts +28 -0
  183. package/templates/dashboard/src/lib/auth-client.ts +5 -0
  184. package/templates/dashboard/src/lib/auth-session.ts +11 -0
  185. package/templates/dashboard/src/lib/auth.ts +41 -0
  186. package/templates/dashboard/src/lib/billing/index.ts +37 -0
  187. package/templates/dashboard/src/lib/billing/providers/polar.ts +80 -0
  188. package/templates/dashboard/src/lib/billing/providers/stripe.ts +77 -0
  189. package/templates/dashboard/src/lib/billing/types.ts +25 -0
  190. package/templates/dashboard/src/lib/email/index.ts +19 -0
  191. package/templates/dashboard/src/lib/email/templates.test.ts +15 -0
  192. package/templates/dashboard/src/lib/email/templates.ts +40 -0
  193. package/templates/dashboard/src/lib/env.ts +88 -0
  194. package/templates/dashboard/tsconfig.json +34 -0
  195. package/templates/dashboard/vitest.config.ts +19 -0
@@ -0,0 +1,19 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { defineConfig } from "vitest/config";
5
+
6
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
7
+
8
+ export default defineConfig({
9
+ resolve: {
10
+ alias: {
11
+ "@": path.resolve(dirname, "./src")
12
+ }
13
+ },
14
+ test: {
15
+ environment: "jsdom",
16
+ globals: true,
17
+ include: ["src/**/*.test.{ts,tsx}"]
18
+ }
19
+ });
@@ -0,0 +1,16 @@
1
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
2
+ BETTER_AUTH_URL=http://localhost:3000
3
+ DATABASE_URL=postgresql://postgres:postgres@localhost:5432/__PROJECT_NAME__
4
+ BETTER_AUTH_SECRET=replace-me
5
+ RESEND_API_KEY=re_xxx
6
+ EMAIL_FROM=Panda Starter <onboarding@example.com>
7
+ STRIPE_SECRET_KEY=sk_test_xxx
8
+ STRIPE_PRICE_ID=price_xxx
9
+ STRIPE_WEBHOOK_SECRET=whsec_xxx
10
+ POLAR_ACCESS_TOKEN=polar_pat_xxx
11
+ POLAR_ORGANIZATION_ID=org_xxx
12
+ POLAR_PRODUCT_ID=prod_xxx
13
+ POLAR_SERVER=sandbox
14
+ POLAR_WEBHOOK_SECRET=polar_whsec_xxx
15
+ BILLING_PROVIDER=__BILLING_PROVIDER__
16
+ EMAIL_PROVIDER=__EMAIL_PROVIDER__
@@ -0,0 +1,34 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: ["main"]
6
+ pull_request:
7
+
8
+ jobs:
9
+ quality:
10
+ runs-on: ubuntu-latest
11
+ steps:
12
+ - name: Checkout
13
+ uses: actions/checkout@v4
14
+
15
+ - name: Setup Node
16
+ uses: actions/setup-node@v4
17
+ with:
18
+ node-version: 22
19
+ cache: __CI_CACHE__
20
+
21
+ - name: Install
22
+ run: __CI_INSTALL_COMMAND__
23
+
24
+ - name: Lint
25
+ run: __CI_RUN_LINT__
26
+
27
+ - name: Typecheck
28
+ run: __CI_RUN_TYPECHECK__
29
+
30
+ - name: Unit tests
31
+ run: __CI_RUN_TEST__
32
+
33
+ - name: Build
34
+ run: __CI_RUN_BUILD__
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env sh
2
+
3
+ npm run lint-staged && npm run typecheck
@@ -0,0 +1,3 @@
1
+ #!/usr/bin/env sh
2
+
3
+ npm run test
@@ -0,0 +1,3 @@
1
+ .next
2
+ coverage
3
+ node_modules
@@ -0,0 +1,16 @@
1
+ import { defineConfig } from "drizzle-kit";
2
+
3
+ if (!process.env.DATABASE_URL) {
4
+ throw new Error("DATABASE_URL is required to use Drizzle.");
5
+ }
6
+
7
+ export default defineConfig({
8
+ schema: "./src/db/schema/index.ts",
9
+ out: "./src/db/migrations",
10
+ dialect: "postgresql",
11
+ dbCredentials: {
12
+ url: process.env.DATABASE_URL
13
+ },
14
+ verbose: true,
15
+ strict: true
16
+ });
@@ -0,0 +1,127 @@
1
+ import path from "node:path";
2
+ import { fileURLToPath } from "node:url";
3
+
4
+ import { defineConfig, globalIgnores } from "eslint/config";
5
+ import prettier from "eslint-config-prettier/flat";
6
+ import nextTs from "eslint-config-next/typescript";
7
+ import nextVitals from "eslint-config-next/core-web-vitals";
8
+ import importPlugin from "eslint-plugin-import";
9
+ import unusedImports from "eslint-plugin-unused-imports";
10
+ import globals from "globals";
11
+
12
+ const SOURCE_FILES = ["**/*.{js,mjs,cjs,ts,mts,cts,jsx,tsx}"];
13
+ const dirname = path.dirname(fileURLToPath(import.meta.url));
14
+
15
+ export default defineConfig([
16
+ ...nextVitals,
17
+ ...nextTs,
18
+ {
19
+ files: SOURCE_FILES,
20
+ languageOptions: {
21
+ parserOptions: {
22
+ projectService: true,
23
+ tsconfigRootDir: dirname
24
+ },
25
+ globals: {
26
+ ...globals.browser,
27
+ ...globals.nodeBuiltin
28
+ }
29
+ },
30
+ plugins: {
31
+ import: importPlugin,
32
+ "unused-imports": unusedImports
33
+ },
34
+ settings: {
35
+ "import/resolver": {
36
+ typescript: true
37
+ }
38
+ },
39
+ rules: {
40
+ "@typescript-eslint/consistent-type-imports": [
41
+ "error",
42
+ {
43
+ prefer: "type-imports",
44
+ fixStyle: "inline-type-imports"
45
+ }
46
+ ],
47
+ "@typescript-eslint/no-floating-promises": "error",
48
+ "@typescript-eslint/no-misused-promises": [
49
+ "error",
50
+ {
51
+ checksVoidReturn: {
52
+ attributes: false
53
+ }
54
+ }
55
+ ],
56
+ "@typescript-eslint/no-unused-vars": "off",
57
+ "import/first": "error",
58
+ "import/newline-after-import": "error",
59
+ "import/no-duplicates": "error",
60
+ "import/order": [
61
+ "error",
62
+ {
63
+ groups: [
64
+ "builtin",
65
+ "external",
66
+ "internal",
67
+ ["parent", "sibling", "index"],
68
+ "object",
69
+ "type"
70
+ ],
71
+ alphabetize: {
72
+ order: "asc",
73
+ caseInsensitive: true
74
+ },
75
+ "newlines-between": "always",
76
+ pathGroups: [
77
+ {
78
+ pattern: "@/**",
79
+ group: "internal",
80
+ position: "before"
81
+ }
82
+ ],
83
+ pathGroupsExcludedImportTypes: ["builtin"]
84
+ }
85
+ ],
86
+ "unused-imports/no-unused-imports": "error",
87
+ "unused-imports/no-unused-vars": [
88
+ "warn",
89
+ {
90
+ vars: "all",
91
+ varsIgnorePattern: "^_",
92
+ args: "after-used",
93
+ argsIgnorePattern: "^_"
94
+ }
95
+ ]
96
+ }
97
+ },
98
+ {
99
+ files: SOURCE_FILES,
100
+ ignores: ["drizzle.config.ts", "src/lib/env.ts"],
101
+ rules: {
102
+ "no-restricted-properties": [
103
+ "error",
104
+ {
105
+ object: "process",
106
+ property: "env",
107
+ message: "Use the validated env object from src/lib/env.ts."
108
+ }
109
+ ]
110
+ }
111
+ },
112
+ prettier,
113
+ globalIgnores([
114
+ ".next/**",
115
+ "out/**",
116
+ "build/**",
117
+ "coverage/**",
118
+ "drizzle.config.ts",
119
+ "eslint.config.mjs",
120
+ "next.config.ts",
121
+ "playwright.config.ts",
122
+ "prettier.config.mjs",
123
+ "proxy.ts",
124
+ "next-env.d.ts",
125
+ "vitest.config.ts"
126
+ ])
127
+ ]);
@@ -0,0 +1,4 @@
1
+ /// <reference types="next" />
2
+ /// <reference types="next/image-types/global" />
3
+
4
+ // This file is managed by Next.js.
@@ -0,0 +1,5 @@
1
+ import type { NextConfig } from "next";
2
+
3
+ const nextConfig: NextConfig = {};
4
+
5
+ export default nextConfig;
@@ -0,0 +1,75 @@
1
+ {
2
+ "name": "__PROJECT_NAME__",
3
+ "version": "0.1.0",
4
+ "private": true,
5
+ "packageManager": "__PACKAGE_MANAGER__",
6
+ "type": "module",
7
+ "scripts": {
8
+ "dev": "next dev",
9
+ "build": "next build",
10
+ "start": "next start",
11
+ "lint": "eslint . --max-warnings=0",
12
+ "lint:fix": "eslint . --fix",
13
+ "typecheck": "tsc --noEmit",
14
+ "format": "prettier . --write",
15
+ "format:check": "prettier . --check",
16
+ "check": "__CHECK_COMMAND__",
17
+ "prepare": "husky",
18
+ "lint-staged": "lint-staged",
19
+ "test": "vitest run",
20
+ "test:watch": "vitest",
21
+ "test:e2e": "playwright test",
22
+ "test:e2e:headed": "playwright test --headed",
23
+ "db:generate": "drizzle-kit generate",
24
+ "db:migrate": "drizzle-kit migrate",
25
+ "db:push": "drizzle-kit push",
26
+ "db:studio": "drizzle-kit studio",
27
+ "db:seed": "tsx src/db/seeds/index.ts",
28
+ "auth:generate": "npx @better-auth/cli@latest generate --config src/lib/auth.ts --output src/db/schema/auth.ts",
29
+ "auth:secret": "npx @better-auth/cli@latest secret"
30
+ },
31
+ "dependencies": {
32
+ "better-auth": "1.3.8",
33
+ "drizzle-orm": "0.44.x",
34
+ "next": "16.2.x",
35
+ __DATABASE_DEPENDENCIES__
36
+ "react": "19.2.x",
37
+ "react-dom": "19.2.x",
38
+ __PROVIDER_PACKAGE_DEPENDENCIES__
39
+ "server-only": "^0.0.1",
40
+ "zod": "4.1.5"
41
+ },
42
+ "devDependencies": {
43
+ "@better-auth/cli": "1.3.8",
44
+ "@playwright/test": "1.55.0",
45
+ "@testing-library/react": "16.3.0",
46
+ "@types/node": "^24.0.0",
47
+ __DATABASE_DEV_DEPENDENCIES__
48
+ "@types/react": "^19.0.0",
49
+ "@types/react-dom": "^19.0.0",
50
+ "drizzle-kit": "0.31.x",
51
+ "eslint": "9.35.0",
52
+ "eslint-config-next": "16.2.x",
53
+ "eslint-config-prettier": "10.1.8",
54
+ "eslint-import-resolver-typescript": "^3.10.1",
55
+ "eslint-plugin-import": "2.32.0",
56
+ "eslint-plugin-unused-imports": "4.2.0",
57
+ "globals": "16.3.0",
58
+ "husky": "^9.1.7",
59
+ "jsdom": "26.1.0",
60
+ "lint-staged": "^16.1.6",
61
+ "playwright": "1.55.0",
62
+ "prettier": "3.6.2",
63
+ "tsx": "4.20.x",
64
+ "typescript": "5.9.x",
65
+ "vitest": "3.2.4"
66
+ },
67
+ "lint-staged": {
68
+ "*.{js,jsx,ts,tsx,mjs,cjs}": [
69
+ "eslint --fix"
70
+ ],
71
+ "*.{json,md,css,yml,yaml,html}": [
72
+ "prettier --write"
73
+ ]
74
+ }
75
+ }
@@ -0,0 +1,21 @@
1
+ import { defineConfig, devices } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ testDir: "./tests/e2e",
5
+ fullyParallel: true,
6
+ use: {
7
+ baseURL: "http://127.0.0.1:3000",
8
+ trace: "on-first-retry"
9
+ },
10
+ webServer: {
11
+ command: "npm run dev",
12
+ url: "http://127.0.0.1:3000",
13
+ reuseExistingServer: !process.env.CI
14
+ },
15
+ projects: [
16
+ {
17
+ name: "chromium",
18
+ use: { ...devices["Desktop Chrome"] }
19
+ }
20
+ ]
21
+ });
@@ -0,0 +1,9 @@
1
+ /** @type {import("prettier").Config} */
2
+ const config = {
3
+ semi: true,
4
+ singleQuote: false,
5
+ trailingComma: "none",
6
+ printWidth: 88
7
+ };
8
+
9
+ export default config;
@@ -0,0 +1,36 @@
1
+ import { NextResponse } from "next/server";
2
+ import type { NextRequest } from "next/server";
3
+
4
+ import { getSessionCookie } from "better-auth/cookies";
5
+
6
+ export function proxy(request: NextRequest) {
7
+ const sessionCookie = getSessionCookie(request);
8
+ const { pathname } = request.nextUrl;
9
+
10
+ if (
11
+ !sessionCookie &&
12
+ (pathname.startsWith("/dashboard") ||
13
+ pathname.startsWith("/settings") ||
14
+ pathname.startsWith("/billing") ||
15
+ pathname.startsWith("/email"))
16
+ ) {
17
+ return NextResponse.redirect(new URL("/sign-in", request.url));
18
+ }
19
+
20
+ if (sessionCookie && (pathname === "/sign-in" || pathname === "/sign-up")) {
21
+ return NextResponse.redirect(new URL("/dashboard", request.url));
22
+ }
23
+
24
+ return NextResponse.next();
25
+ }
26
+
27
+ export const config = {
28
+ matcher: [
29
+ "/dashboard/:path*",
30
+ "/settings/:path*",
31
+ "/billing/:path*",
32
+ "/email/:path*",
33
+ "/sign-in",
34
+ "/sign-up"
35
+ ]
36
+ };
@@ -0,0 +1,5 @@
1
+ import { toNextJsHandler } from "better-auth/next-js";
2
+
3
+ import { auth } from "@/lib/auth";
4
+
5
+ export const { GET, POST } = toNextJsHandler(auth);
@@ -0,0 +1,26 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ import { getSession } from "@/lib/auth-session";
4
+ import { getBillingProvider, getBillingProviderName } from "@/lib/billing";
5
+ import { env } from "@/lib/env";
6
+
7
+ export async function POST(request: Request) {
8
+ const session = await getSession();
9
+
10
+ if (!session) {
11
+ redirect("/sign-in");
12
+ }
13
+
14
+ const formData = await request.formData();
15
+ const selected = String(formData.get("provider") ?? "");
16
+ const providerName = getBillingProviderName(selected);
17
+ const provider = getBillingProvider(providerName);
18
+ const checkout = await provider.createCheckoutSession({
19
+ userId: session.user.id,
20
+ customerEmail: session.user.email,
21
+ successUrl: `${env.NEXT_PUBLIC_APP_URL}/billing?success=1&provider=${providerName}`,
22
+ cancelUrl: `${env.NEXT_PUBLIC_APP_URL}/billing?canceled=1&provider=${providerName}`
23
+ });
24
+
25
+ redirect(checkout.url);
26
+ }
@@ -0,0 +1,25 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ import { getSession } from "@/lib/auth-session";
4
+ import { getBillingProvider, getBillingProviderName } from "@/lib/billing";
5
+ import { env } from "@/lib/env";
6
+
7
+ export async function POST(request: Request) {
8
+ const session = await getSession();
9
+
10
+ if (!session) {
11
+ redirect("/sign-in");
12
+ }
13
+
14
+ const formData = await request.formData();
15
+ const selected = String(formData.get("provider") ?? "");
16
+ const providerName = getBillingProviderName(selected);
17
+ const provider = getBillingProvider(providerName);
18
+ const portal = await provider.createCustomerPortalSession({
19
+ userId: session.user.id,
20
+ customerEmail: session.user.email,
21
+ returnUrl: `${env.NEXT_PUBLIC_APP_URL}/billing?provider=${providerName}`
22
+ });
23
+
24
+ redirect(portal.url);
25
+ }
@@ -0,0 +1,28 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ import { getSession } from "@/lib/auth-session";
4
+ import { sendEmail } from "@/lib/email";
5
+ import { createWelcomeEmailTemplate } from "@/lib/email/templates";
6
+ import { env } from "@/lib/env";
7
+
8
+ export async function POST() {
9
+ const session = await getSession();
10
+
11
+ if (!session) {
12
+ redirect("/sign-in");
13
+ }
14
+
15
+ if (env.EMAIL_PROVIDER === "none") {
16
+ redirect("/email");
17
+ }
18
+
19
+ await sendEmail({
20
+ to: session.user.email,
21
+ template: createWelcomeEmailTemplate({
22
+ appName: "Skit",
23
+ recipientName: session.user.name
24
+ })
25
+ });
26
+
27
+ redirect("/email?sent=1");
28
+ }
@@ -0,0 +1,5 @@
1
+ __POLAR_WEBHOOK_ROUTE_IMPORT__
2
+
3
+ export async function POST(__POLAR_WEBHOOK_ROUTE_SIGNATURE__) {
4
+ __POLAR_WEBHOOK_ROUTE_BODY__
5
+ }
@@ -0,0 +1,5 @@
1
+ __STRIPE_WEBHOOK_ROUTE_IMPORT__
2
+
3
+ export async function POST(__STRIPE_WEBHOOK_ROUTE_SIGNATURE__) {
4
+ __STRIPE_WEBHOOK_ROUTE_BODY__
5
+ }
@@ -0,0 +1,22 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ import { getSession } from "@/lib/auth-session";
4
+ import { DashboardShell } from "@/components/dashboard/shell";
5
+
6
+ export default async function BillingLayout({
7
+ children
8
+ }: {
9
+ children: React.ReactNode;
10
+ }) {
11
+ const session = await getSession();
12
+
13
+ if (!session) {
14
+ redirect("/sign-in");
15
+ }
16
+
17
+ return (
18
+ <DashboardShell user={session.user}>
19
+ {children}
20
+ </DashboardShell>
21
+ );
22
+ }
@@ -0,0 +1,73 @@
1
+ import { getSession } from "@/lib/auth-session";
2
+ import { env } from "@/lib/env";
3
+
4
+ export default async function BillingPage({
5
+ searchParams
6
+ }: {
7
+ searchParams: Promise<Record<string, string | string[] | undefined>>;
8
+ }) {
9
+ const session = await getSession();
10
+ const params = await searchParams;
11
+ const success = params.success === "1";
12
+ const canceled = params.canceled === "1";
13
+ const providers =
14
+ env.BILLING_PROVIDER === "both"
15
+ ? ["stripe", "polar"]
16
+ : env.BILLING_PROVIDER === "none"
17
+ ? []
18
+ : [env.BILLING_PROVIDER];
19
+
20
+ return (
21
+ <>
22
+ <header className="dashboard-header">
23
+ <div>
24
+ <p className="eyebrow">Billing</p>
25
+ <h1>Manage your subscription</h1>
26
+ <p className="lede" style={{ marginTop: 4, fontSize: "0.9375rem" }}>
27
+ Current provider: <strong>{env.BILLING_PROVIDER}</strong>
28
+ </p>
29
+ </div>
30
+ </header>
31
+
32
+ {success ? <p className="form-success">Checkout completed or redirected successfully.</p> : null}
33
+ {canceled ? <p className="form-error">Checkout was canceled.</p> : null}
34
+
35
+ {providers.length === 0 ? (
36
+ <div className="empty-state">
37
+ <div className="empty-state-icon">💳</div>
38
+ <h3>Billing is disabled</h3>
39
+ <p>
40
+ Re-run the starter with a billing provider to enable checkout flows.
41
+ </p>
42
+ </div>
43
+ ) : (
44
+ <div className="billing-grid">
45
+ {providers.map((provider) => (
46
+ <form
47
+ key={provider}
48
+ action="/api/billing/checkout"
49
+ method="post"
50
+ className="billing-card"
51
+ >
52
+ <input type="hidden" name="provider" value={provider} />
53
+ <h2>
54
+ <span className="bento-card-icon" style={{ width: 32, height: 32, fontSize: "1rem" }}>
55
+ {provider === "stripe" ? "💜" : "🧊"}
56
+ </span>
57
+ {provider === "stripe" ? "Stripe" : "Polar"}
58
+ </h2>
59
+ <p>
60
+ {provider === "stripe"
61
+ ? "Create a subscription checkout session using the configured Stripe price."
62
+ : "Create a Polar checkout session using the configured product identifier."}
63
+ </p>
64
+ <button type="submit" className="btn-primary" style={{ width: "100%" }}>
65
+ {provider === "stripe" ? "Start Stripe checkout" : "Start Polar checkout"}
66
+ </button>
67
+ </form>
68
+ ))}
69
+ </div>
70
+ )}
71
+ </>
72
+ );
73
+ }
@@ -0,0 +1,22 @@
1
+ import { redirect } from "next/navigation";
2
+
3
+ import { getSession } from "@/lib/auth-session";
4
+ import { DashboardShell } from "@/components/dashboard/shell";
5
+
6
+ export default async function DashboardLayout({
7
+ children
8
+ }: {
9
+ children: React.ReactNode;
10
+ }) {
11
+ const session = await getSession();
12
+
13
+ if (!session) {
14
+ redirect("/sign-in");
15
+ }
16
+
17
+ return (
18
+ <DashboardShell user={session.user}>
19
+ {children}
20
+ </DashboardShell>
21
+ );
22
+ }