@clubedge/create-clubedge-app 0.2.1 → 0.3.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 (154) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +50 -35
  3. package/bin/create.mjs +2 -469
  4. package/dist/cli/args.js +49 -0
  5. package/dist/cli/ui.js +85 -0
  6. package/dist/core/customize.js +56 -0
  7. package/dist/core/execute.js +60 -0
  8. package/dist/core/manifest.js +81 -0
  9. package/dist/core/plan.js +67 -0
  10. package/dist/core/source.js +107 -0
  11. package/dist/index.js +125 -0
  12. package/dist/package-info.js +8 -0
  13. package/dist/steps/git.js +11 -0
  14. package/dist/steps/install.js +19 -0
  15. package/dist/utils/fs.js +97 -0
  16. package/dist/utils/names.js +14 -0
  17. package/dist/utils/process.js +49 -0
  18. package/package.json +18 -7
  19. package/template/.dockerignore +8 -0
  20. package/template/.env.example +25 -0
  21. package/template/.gitattributes +6 -0
  22. package/template/.github/ISSUE_TEMPLATE/bug_report.yml +35 -0
  23. package/template/.github/ISSUE_TEMPLATE/config.yml +8 -0
  24. package/template/.github/ISSUE_TEMPLATE/feature_request.yml +24 -0
  25. package/template/.github/pull_request_template.md +20 -0
  26. package/template/.github/workflows/ci.yml +49 -0
  27. package/template/.prettierignore +1 -0
  28. package/template/.prettierrc.json +1 -0
  29. package/template/AGENTS.md +31 -0
  30. package/template/CODE_OF_CONDUCT.md +22 -0
  31. package/template/CONTRIBUTING.md +53 -0
  32. package/template/Dockerfile +32 -0
  33. package/template/LICENSE +201 -0
  34. package/template/NOTICE +6 -0
  35. package/template/PUBLISHING.md +27 -0
  36. package/template/README.md +157 -0
  37. package/template/SECURITY.md +15 -0
  38. package/template/SETUP.md +174 -0
  39. package/template/SUPPORT.md +8 -0
  40. package/template/_gitignore +13 -0
  41. package/template/apps/web/AGENTS.md +9 -0
  42. package/template/apps/web/components.json +21 -0
  43. package/template/apps/web/e2e/auth.spec.ts +37 -0
  44. package/template/apps/web/e2e/home.spec.ts +15 -0
  45. package/template/apps/web/next-env.d.ts +7 -0
  46. package/template/apps/web/next.config.ts +32 -0
  47. package/template/apps/web/package.json +33 -0
  48. package/template/apps/web/postcss.config.mjs +3 -0
  49. package/template/apps/web/public/.gitkeep +0 -0
  50. package/template/apps/web/public/.well-known/logo.svg +1 -0
  51. package/template/apps/web/src/app/actions/auth.ts +85 -0
  52. package/template/apps/web/src/app/api/health/route.ts +11 -0
  53. package/template/apps/web/src/app/auth/callback/route.ts +21 -0
  54. package/template/apps/web/src/app/dashboard/_components/account-button.tsx +34 -0
  55. package/template/apps/web/src/app/dashboard/_components/app-sidebar.tsx +91 -0
  56. package/template/apps/web/src/app/dashboard/page.tsx +352 -0
  57. package/template/apps/web/src/app/favicon.ico +0 -0
  58. package/template/apps/web/src/app/layout.tsx +36 -0
  59. package/template/apps/web/src/app/login/_components/auth-form-controls.tsx +88 -0
  60. package/template/apps/web/src/app/login/page.tsx +149 -0
  61. package/template/apps/web/src/app/page.tsx +229 -0
  62. package/template/apps/web/src/app/theme-provider.tsx +43 -0
  63. package/template/apps/web/src/app/theme-toggle.tsx +23 -0
  64. package/template/apps/web/src/components/branding/clubedge-mark.tsx +25 -0
  65. package/template/apps/web/src/components/landing/copy-command.tsx +48 -0
  66. package/template/apps/web/src/config/site.json +11 -0
  67. package/template/apps/web/src/config/site.ts +20 -0
  68. package/template/apps/web/src/env/client.ts +22 -0
  69. package/template/apps/web/src/env/server.test.ts +35 -0
  70. package/template/apps/web/src/env/server.ts +45 -0
  71. package/template/apps/web/src/lib/login-url.test.ts +16 -0
  72. package/template/apps/web/src/lib/login-url.ts +16 -0
  73. package/template/apps/web/src/lib/utils.ts +1 -0
  74. package/template/apps/web/src/proxy.ts +31 -0
  75. package/template/apps/web/src/server/auth.ts +69 -0
  76. package/template/apps/web/src/server/cache.ts +38 -0
  77. package/template/apps/web/src/server/db.ts +16 -0
  78. package/template/apps/web/src/server/storage.ts +39 -0
  79. package/template/apps/web/src/server/users.ts +17 -0
  80. package/template/apps/web/tsconfig.json +36 -0
  81. package/template/clubedge.template.json +13 -0
  82. package/template/docker-compose.yml +8 -0
  83. package/template/eslint.config.mjs +63 -0
  84. package/template/package.json +47 -0
  85. package/template/packages/auth/package.json +23 -0
  86. package/template/packages/auth/src/index.ts +9 -0
  87. package/template/packages/auth/src/supabase.test.ts +126 -0
  88. package/template/packages/auth/src/supabase.ts +65 -0
  89. package/template/packages/auth/src/types.ts +59 -0
  90. package/template/packages/auth/tsconfig.json +4 -0
  91. package/template/packages/cache/package.json +20 -0
  92. package/template/packages/cache/src/index.test.ts +67 -0
  93. package/template/packages/cache/src/index.ts +19 -0
  94. package/template/packages/cache/src/rate-limit.test.ts +52 -0
  95. package/template/packages/cache/src/rate-limit.ts +74 -0
  96. package/template/packages/cache/src/redis.ts +106 -0
  97. package/template/packages/cache/tsconfig.json +4 -0
  98. package/template/packages/core/package.json +17 -0
  99. package/template/packages/core/src/client-ip.test.ts +19 -0
  100. package/template/packages/core/src/client-ip.ts +11 -0
  101. package/template/packages/core/src/errors.test.ts +20 -0
  102. package/template/packages/core/src/errors.ts +20 -0
  103. package/template/packages/core/src/index.ts +4 -0
  104. package/template/packages/core/src/redirect.test.ts +27 -0
  105. package/template/packages/core/src/redirect.ts +20 -0
  106. package/template/packages/core/src/result.ts +10 -0
  107. package/template/packages/core/tsconfig.json +4 -0
  108. package/template/packages/db/drizzle/0000_initial.sql +10 -0
  109. package/template/packages/db/drizzle/meta/_journal.json +13 -0
  110. package/template/packages/db/drizzle.config.ts +9 -0
  111. package/template/packages/db/package.json +30 -0
  112. package/template/packages/db/scripts/load-env.ts +14 -0
  113. package/template/packages/db/scripts/migrate.ts +11 -0
  114. package/template/packages/db/scripts/seed.ts +14 -0
  115. package/template/packages/db/src/client.ts +22 -0
  116. package/template/packages/db/src/index.ts +4 -0
  117. package/template/packages/db/src/schema/index.ts +1 -0
  118. package/template/packages/db/src/schema/users.ts +14 -0
  119. package/template/packages/db/src/users.ts +16 -0
  120. package/template/packages/db/tsconfig.json +4 -0
  121. package/template/packages/storage/package.json +24 -0
  122. package/template/packages/storage/src/index.ts +1 -0
  123. package/template/packages/storage/src/s3.test.ts +86 -0
  124. package/template/packages/storage/src/s3.ts +86 -0
  125. package/template/packages/storage/src/supabase.ts +69 -0
  126. package/template/packages/storage/src/types.ts +18 -0
  127. package/template/packages/storage/tsconfig.json +4 -0
  128. package/template/packages/ui/components.json +21 -0
  129. package/template/packages/ui/package.json +32 -0
  130. package/template/packages/ui/src/components/badge.tsx +48 -0
  131. package/template/packages/ui/src/components/breadcrumb.tsx +102 -0
  132. package/template/packages/ui/src/components/button.tsx +57 -0
  133. package/template/packages/ui/src/components/card.tsx +87 -0
  134. package/template/packages/ui/src/components/input.tsx +19 -0
  135. package/template/packages/ui/src/components/label.tsx +19 -0
  136. package/template/packages/ui/src/components/separator.tsx +20 -0
  137. package/template/packages/ui/src/components/sheet.tsx +125 -0
  138. package/template/packages/ui/src/components/sidebar.tsx +692 -0
  139. package/template/packages/ui/src/components/skeleton.tsx +13 -0
  140. package/template/packages/ui/src/components/switch.tsx +31 -0
  141. package/template/packages/ui/src/components/table.tsx +88 -0
  142. package/template/packages/ui/src/components/tooltip.tsx +53 -0
  143. package/template/packages/ui/src/hooks/use-mobile.ts +19 -0
  144. package/template/packages/ui/src/lib/utils.ts +1 -0
  145. package/template/packages/ui/src/styles/globals.css +126 -0
  146. package/template/packages/ui/tsconfig.json +19 -0
  147. package/template/playwright.config.ts +19 -0
  148. package/template/pnpm-lock.yaml +3624 -0
  149. package/template/pnpm-workspace.yaml +3 -0
  150. package/template/scripts/start-standalone.mjs +37 -0
  151. package/template/tsconfig.base.json +16 -0
  152. package/template/turbo.json +17 -0
  153. package/template/vitest.config.ts +8 -0
  154. package/template.lock.json +142 -0
@@ -0,0 +1,19 @@
1
+ import process from "node:process";
2
+ import { commandSucceeds, run } from "../utils/process.js";
3
+ /** The generated workspace relies on pnpm features (workspace:* and pnpm-workspace.yaml). */
4
+ export async function resolvePnpm() {
5
+ if (await commandSucceeds("pnpm", ["--version"]))
6
+ return ["pnpm"];
7
+ if (await commandSucceeds("corepack", ["pnpm", "--version"]))
8
+ return ["corepack", "pnpm"];
9
+ return null;
10
+ }
11
+ /** The package manager that launched the CLI, from npm_config_user_agent (e.g. "npm/10.8.2 ..."). */
12
+ export function detectLauncher(userAgent = process.env.npm_config_user_agent) {
13
+ const name = userAgent?.split("/")[0];
14
+ return name && ["npm", "pnpm", "yarn", "bun"].includes(name) ? name : null;
15
+ }
16
+ export async function installDependencies(directory, pnpm) {
17
+ const [command, ...prefix] = pnpm;
18
+ await run(command, [...prefix, "install"], directory);
19
+ }
@@ -0,0 +1,97 @@
1
+ import { access, copyFile, mkdir, readdir, rm } from "node:fs/promises";
2
+ import { dirname, join, relative, sep } from "node:path";
3
+ import { runCapture } from "./process.js";
4
+ export async function pathExists(path) {
5
+ try {
6
+ await access(path);
7
+ return true;
8
+ }
9
+ catch {
10
+ return false;
11
+ }
12
+ }
13
+ /**
14
+ * npm drops `.gitignore` and `.npmrc` from published tarballs, so the bundled template stores
15
+ * them under these names and the CLI restores them when copying.
16
+ */
17
+ export const packedFileNames = {
18
+ ".gitignore": "_gitignore",
19
+ ".npmrc": "_npmrc",
20
+ };
21
+ export function toPackedPath(path) {
22
+ return mapBasename(path, (name) => packedFileNames[name]);
23
+ }
24
+ export function fromPackedPath(path) {
25
+ return mapBasename(path, (name) => Object.entries(packedFileNames).find(([, packed]) => packed === name)?.[0]);
26
+ }
27
+ function mapBasename(path, map) {
28
+ const parts = path.split("/");
29
+ const last = parts.pop();
30
+ return [...parts, map(last) ?? last].join("/");
31
+ }
32
+ /** Never copied from a local template directory that is not a Git work tree. */
33
+ const ignoredDirectories = new Set([
34
+ ".git",
35
+ ".next",
36
+ ".turbo",
37
+ ".pnpm-store",
38
+ "node_modules",
39
+ "coverage",
40
+ "dist",
41
+ "playwright-report",
42
+ "test-results",
43
+ ]);
44
+ /** Local environment files hold secrets; only the committed example is part of a template. */
45
+ function isLocalSecret(name) {
46
+ return /^\.env(\..+)?$/.test(name) && name !== ".env.example";
47
+ }
48
+ /** True for dependencies, build output, and local secrets, which never belong in a template. */
49
+ export function isIgnoredTemplatePath(path) {
50
+ const parts = path.split("/");
51
+ return parts.some((part) => ignoredDirectories.has(part)) || isLocalSecret(parts.at(-1));
52
+ }
53
+ /**
54
+ * Lists template files as forward-slash relative paths. Inside a Git work tree this narrows to
55
+ * tracked and unignored files. The ignore rules always apply as well, because the directory may
56
+ * sit inside an unrelated repository (a home directory under version control, for example).
57
+ */
58
+ export async function listTemplateFiles(root) {
59
+ let files;
60
+ try {
61
+ const output = await runCapture("git", ["-C", root, "ls-files", "-co", "--exclude-standard", "-z"]);
62
+ files = output.split("\0").filter(Boolean);
63
+ }
64
+ catch {
65
+ files = await walk(root, root);
66
+ }
67
+ const kept = await Promise.all(files.map(async (file) => !isIgnoredTemplatePath(file) && (await pathExists(join(root, file))) ? file : null));
68
+ return kept.filter((file) => file !== null).sort();
69
+ }
70
+ async function walk(root, directory) {
71
+ const entries = await readdir(directory, { withFileTypes: true });
72
+ const files = [];
73
+ for (const entry of entries) {
74
+ const path = join(directory, entry.name);
75
+ if (entry.isDirectory()) {
76
+ if (!ignoredDirectories.has(entry.name))
77
+ files.push(...(await walk(root, path)));
78
+ }
79
+ else if (entry.isFile() && !isLocalSecret(entry.name)) {
80
+ files.push(relative(root, path).split(sep).join("/"));
81
+ }
82
+ }
83
+ return files;
84
+ }
85
+ export async function copyTemplateFiles(sourceRoot, targetRoot, files) {
86
+ for (const { from, to } of files) {
87
+ const target = join(targetRoot, to);
88
+ await mkdir(dirname(target), { recursive: true });
89
+ await copyFile(join(sourceRoot, from), target);
90
+ }
91
+ }
92
+ /** Removes everything inside a directory while keeping the directory itself. */
93
+ export async function emptyDirectory(directory) {
94
+ for (const entry of await readdir(directory)) {
95
+ await rm(join(directory, entry), { recursive: true, force: true });
96
+ }
97
+ }
@@ -0,0 +1,14 @@
1
+ import { basename } from "node:path";
2
+ export function packageNameFromDirectory(directory) {
3
+ return (basename(directory)
4
+ .toLowerCase()
5
+ .replace(/[^a-z0-9._-]+/g, "-")
6
+ .replace(/^[._-]+|[._-]+$/g, "") || "my-app");
7
+ }
8
+ export function displayNameFromPackageName(packageName) {
9
+ return packageName
10
+ .split(/[-._]+/)
11
+ .filter(Boolean)
12
+ .map((part) => part[0].toUpperCase() + part.slice(1))
13
+ .join(" ");
14
+ }
@@ -0,0 +1,49 @@
1
+ import { spawn } from "node:child_process";
2
+ import process from "node:process";
3
+ // Package manager shims on Windows are .cmd files, which need a shell to start.
4
+ const useShell = process.platform === "win32";
5
+ /** Runs a command with inherited output. Rejects on a non-zero exit. */
6
+ export async function run(command, args, cwd) {
7
+ await new Promise((resolve, reject) => {
8
+ const child = spawn(command, args, { cwd, stdio: "inherit", shell: useShell });
9
+ child.once("error", reject);
10
+ child.once("exit", (code, signal) => {
11
+ if (code === 0)
12
+ resolve();
13
+ else
14
+ reject(new Error(`${command} exited with ${signal ? `signal ${signal}` : `code ${code}`}.`));
15
+ });
16
+ });
17
+ }
18
+ /** Runs a command and returns trimmed stdout. Rejects with stderr on a non-zero exit. */
19
+ export async function runCapture(command, args, cwd) {
20
+ return await new Promise((resolve, reject) => {
21
+ const child = spawn(command, args, {
22
+ cwd,
23
+ shell: useShell,
24
+ stdio: ["ignore", "pipe", "pipe"],
25
+ });
26
+ let stdout = "";
27
+ let stderr = "";
28
+ child.stdout.on("data", (chunk) => (stdout += chunk.toString()));
29
+ child.stderr.on("data", (chunk) => (stderr += chunk.toString()));
30
+ child.once("error", reject);
31
+ child.once("exit", (code, signal) => {
32
+ if (code === 0) {
33
+ resolve(stdout.trim());
34
+ return;
35
+ }
36
+ const reason = signal ? `signal ${signal}` : `code ${code}`;
37
+ reject(new Error(`${command} exited with ${reason}${stderr.trim() ? `: ${stderr.trim()}` : "."}`));
38
+ });
39
+ });
40
+ }
41
+ export async function commandSucceeds(command, args) {
42
+ try {
43
+ await runCapture(command, args);
44
+ return true;
45
+ }
46
+ catch {
47
+ return false;
48
+ }
49
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@clubedge/create-clubedge-app",
3
- "version": "0.2.1",
4
- "description": "Create a Next.js application from the Clubedge Starter reference implementation.",
3
+ "version": "0.3.0",
4
+ "description": "Create an application from the Clubedge Starter reference implementation.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.9.0",
7
7
  "bin": {
@@ -9,6 +9,9 @@
9
9
  },
10
10
  "files": [
11
11
  "bin/",
12
+ "dist/",
13
+ "template/",
14
+ "template.lock.json",
12
15
  "README.md",
13
16
  "CHANGELOG.md",
14
17
  "LICENSE",
@@ -19,8 +22,8 @@
19
22
  },
20
23
  "clubedge": {
21
24
  "starterRepository": "Clubedge/clubedge-starter",
22
- "starterRef": "v0.2.0",
23
- "starterCommit": "db15901da90d8d8a190d158c895dfe76c172b415"
25
+ "starterRef": "v0.3.0",
26
+ "starterCommit": "60f1691920c91a5cf83e480a3ce98c3ddefdef55"
24
27
  },
25
28
  "license": "Apache-2.0",
26
29
  "repository": {
@@ -39,12 +42,20 @@
39
42
  "scaffolding"
40
43
  ],
41
44
  "scripts": {
42
- "start": "node ./bin/create.mjs",
43
- "check": "node --check ./bin/create.mjs",
44
- "test": "node --test"
45
+ "build": "tsc -p tsconfig.build.json",
46
+ "check": "tsc --noEmit",
47
+ "test": "vitest run",
48
+ "bundle-template": "node scripts/bundle-template.mjs",
49
+ "start": "pnpm build && node ./bin/create.mjs",
50
+ "prepack": "pnpm build"
45
51
  },
46
52
  "dependencies": {
47
53
  "@clack/prompts": "^1.8.1",
48
54
  "giget": "^2.0.0"
55
+ },
56
+ "devDependencies": {
57
+ "@types/node": "^22.20.5",
58
+ "typescript": "^5.9.3",
59
+ "vitest": "^5.0.3"
49
60
  }
50
61
  }
@@ -0,0 +1,8 @@
1
+ node_modules
2
+ .pnpm-store
3
+ .turbo
4
+ .next
5
+ .git
6
+ .env*
7
+ coverage
8
+ playwright-report
@@ -0,0 +1,25 @@
1
+ NODE_ENV=development
2
+ NEXT_PUBLIC_APP_URL=http://localhost:3000
3
+
4
+ # Supabase PostgreSQL: use the transaction pooler with prepare disabled (configured in src/db).
5
+ DATABASE_URL=postgresql://postgres.[project-ref]:[password]@aws-0-[region].pooler.supabase.com:6543/postgres
6
+
7
+ # Supabase Auth (publishable key only; never put a service role key in the browser)
8
+ NEXT_PUBLIC_SUPABASE_URL=
9
+ NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY=
10
+
11
+ # Optional cache and rate limits (Redis protocol; rediss:// enables TLS)
12
+ # Upstash example: rediss://default:YOUR_PASSWORD@your-database.upstash.io:6379
13
+ REDIS_URL=
14
+
15
+ # Storage adapter: s3 (works with AWS S3 and Cloudflare R2) or supabase
16
+ STORAGE_PROVIDER=s3
17
+ STORAGE_BUCKET=
18
+ STORAGE_REGION=auto
19
+ STORAGE_ENDPOINT=
20
+ STORAGE_ACCESS_KEY_ID=
21
+ STORAGE_SECRET_ACCESS_KEY=
22
+ STORAGE_PUBLIC_URL=
23
+
24
+ # Supabase Storage adapter
25
+ SUPABASE_STORAGE_BUCKET=
@@ -0,0 +1,6 @@
1
+ # Keep LF endings on every platform so Prettier and shell scripts behave the same everywhere.
2
+ * text=auto eol=lf
3
+ *.ico binary
4
+ *.png binary
5
+ *.jpg binary
6
+ *.webp binary
@@ -0,0 +1,35 @@
1
+ name: Bug report
2
+ description: Report a reproducible problem in Clubedge Starter.
3
+ title: "[Bug]: "
4
+ labels: [bug]
5
+ body:
6
+ - type: markdown
7
+ attributes:
8
+ value: Before submitting, search existing issues and remove secrets or personal data from logs.
9
+ - type: textarea
10
+ id: description
11
+ attributes:
12
+ label: What happened?
13
+ description: Describe the behavior and what you expected instead.
14
+ validations:
15
+ required: true
16
+ - type: textarea
17
+ id: reproduce
18
+ attributes:
19
+ label: Steps to reproduce
20
+ placeholder: Include the smallest set of steps that shows the problem.
21
+ validations:
22
+ required: true
23
+ - type: input
24
+ id: versions
25
+ attributes:
26
+ label: Environment
27
+ description: Include OS, Node.js, and pnpm versions.
28
+ placeholder: Windows 11, Node 22.12, pnpm 10.9.0
29
+ validations:
30
+ required: true
31
+ - type: textarea
32
+ id: logs
33
+ attributes:
34
+ label: Logs or screenshots
35
+ description: Redact tokens, passwords, URLs with credentials, and other private data.
@@ -0,0 +1,8 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: Setup and troubleshooting
4
+ url: https://github.com/Clubedge/clubedge-starter/blob/main/SETUP.md
5
+ about: Check the setup guide before opening an issue.
6
+ - name: Report a security vulnerability privately
7
+ url: https://github.com/Clubedge/clubedge-starter/security/advisories/new
8
+ about: Do not post vulnerability details in a public issue.
@@ -0,0 +1,24 @@
1
+ name: Feature request
2
+ description: Suggest an improvement or capability.
3
+ title: "[Feature]: "
4
+ labels: [enhancement]
5
+ body:
6
+ - type: textarea
7
+ id: problem
8
+ attributes:
9
+ label: What problem should this solve?
10
+ description: Describe the use case and who benefits.
11
+ validations:
12
+ required: true
13
+ - type: textarea
14
+ id: proposal
15
+ attributes:
16
+ label: Proposed solution
17
+ description: Describe the change you have in mind. Alternatives are welcome.
18
+ validations:
19
+ required: true
20
+ - type: textarea
21
+ id: details
22
+ attributes:
23
+ label: Additional context
24
+ description: Include links, examples, or constraints that would help evaluate the request.
@@ -0,0 +1,20 @@
1
+ ## Summary
2
+
3
+ <!-- What changed and why? Link related issues with "Closes #123" when applicable. -->
4
+
5
+ ## Changes
6
+
7
+ <!-- List the meaningful changes. -->
8
+
9
+ ## Verification
10
+
11
+ - [ ] `pnpm lint`
12
+ - [ ] `pnpm typecheck`
13
+ - [ ] `pnpm test`
14
+ - [ ] `pnpm format:check`
15
+ - [ ] `pnpm build`
16
+ - [ ] `pnpm test:e2e` (when browser behavior changed)
17
+
18
+ ## Notes for reviewers
19
+
20
+ <!-- Include screenshots, migration details, environment changes, or known limitations. -->
@@ -0,0 +1,49 @@
1
+ name: CI
2
+ on:
3
+ pull_request:
4
+ push:
5
+ branches: [main]
6
+ workflow_dispatch:
7
+ permissions:
8
+ contents: read
9
+ jobs:
10
+ verify:
11
+ runs-on: ubuntu-latest
12
+ env:
13
+ DATABASE_URL: postgresql://postgres:postgres@127.0.0.1:5432/postgres
14
+ steps:
15
+ - uses: actions/checkout@v4
16
+ - uses: pnpm/action-setup@v4
17
+ - uses: actions/setup-node@v4
18
+ with:
19
+ node-version: 22
20
+ cache: pnpm
21
+ - run: pnpm install --frozen-lockfile
22
+ - run: pnpm format:check
23
+ - run: pnpm lint
24
+ - run: pnpm typecheck
25
+ - run: pnpm test
26
+ - run: pnpm exec playwright install --with-deps chromium
27
+ - run: pnpm test:e2e
28
+ - run: pnpm build
29
+ - run: pnpm audit --audit-level high
30
+
31
+ docker:
32
+ runs-on: ubuntu-latest
33
+ steps:
34
+ - uses: actions/checkout@v4
35
+ - name: Build the production image
36
+ run: docker build --tag clubedge-starter:ci .
37
+ - name: Start the image and check health
38
+ run: |
39
+ set -euo pipefail
40
+ docker run --detach --name app --publish 3000:3000 --env DATABASE_URL=postgresql://postgres:postgres@127.0.0.1:5432/postgres clubedge-starter:ci
41
+ trap 'docker logs app; docker rm --force app' EXIT
42
+ for attempt in $(seq 1 30); do
43
+ if curl --fail --silent http://127.0.0.1:3000/api/health; then
44
+ exit 0
45
+ fi
46
+ sleep 2
47
+ done
48
+ echo "The container did not become healthy."
49
+ exit 1
@@ -0,0 +1 @@
1
+ pnpm-lock.yaml
@@ -0,0 +1 @@
1
+ { "semi": true, "singleQuote": false, "trailingComma": "all", "printWidth": 100 }
@@ -0,0 +1,31 @@
1
+ # Agent guide
2
+
3
+ ## Architecture rules
4
+
5
+ - `packages/{core,auth,cache,db,storage}` are framework-agnostic. Never import `next`, `react`, `server-only`, `@tanstack/*`, or app code (`@/…`) there. Accept configuration, clients, and cookies as function arguments.
6
+ - `packages/core` depends on no other workspace package.
7
+ - `apps/web/src/server/` is the composition root: the only place that reads environment variables, bridges Next.js cookies to `CookieStore`, and picks providers. Routes, server actions, and components import from `@/server/*`, never from `@supabase/*`, `@aws-sdk/*`, `redis`, or `postgres`.
8
+ - Expected failures (bad credentials, invalid codes) are returned as `Result` values from `@clubedge/core`; exceptions are for unexpected errors.
9
+ - Project identity (name, description, links) lives in `apps/web/src/config/site.json`; `create-clubedge-app` rewrites that file, so do not hard-code the project name elsewhere.
10
+ - `pnpm lint` enforces these boundaries. Run `pnpm lint`, `pnpm typecheck`, and `pnpm test` before finishing a change.
11
+
12
+ <!-- BEGIN:nextjs-agent-rules -->
13
+
14
+ ## This is NOT the Next.js you know
15
+
16
+ This version has breaking changes — APIs, conventions, and file structure may all differ from your training data. Read the relevant guide in `node_modules/next/dist/docs/` (resolved from this file's directory; in monorepos the `next` package may not be visible from the repo root) before writing any code. Heed deprecation notices.
17
+
18
+ This block is written and re-added by `next dev` — verify at `node_modules/next/dist/server/lib/generate-agent-files.js`. Removing it from a diff only re-creates the uncommitted change; committing it with your work keeps the tree clean.
19
+
20
+ <!-- END:nextjs-agent-rules -->
21
+
22
+ <!-- BEGIN:turborepo-agent-rules -->
23
+
24
+ # This is NOT the Turborepo you know
25
+
26
+ Turborepo configuration, task behavior, and CLI commands can vary between installed versions and may differ from your training data. Resolve the `turbo` package from this file's directory or relevant workspace; in monorepos, it may not be visible from the repository root. For example, run `node -p "require.resolve('turbo/package.json')"` from a workspace that depends on `turbo`.
27
+
28
+ Read `docs/README.md` inside that installed package first, then read the relevant pages from its `docs/` directory before changing Turborepo configuration or commands. Heed deprecation notices. These bundled docs match the installed package version and are available without network access.
29
+
30
+ This block is written and re-added by `turbo` before repository-scoped commands when an AI agent is detected. In the Turborepo source repository, its template is defined in `crates/turborepo-cli/src/cli/agent_guidance.rs`. Removing the managed block while updates are enabled means a later qualifying invocation will add it again. Set `"agentGuidance": false` in the root `turbo.json` or `turbo.jsonc` to opt out; this does not remove an existing block. Keep the block committed with your work to avoid an uncommitted change on the next agent invocation.
31
+ <!-- END:turborepo-agent-rules -->
@@ -0,0 +1,22 @@
1
+ # Code of Conduct
2
+
3
+ ## Our commitment
4
+
5
+ We are committed to making participation in Clubedge Starter welcoming and respectful for everyone, regardless of experience, identity, background, or point of view.
6
+
7
+ ## Expected behavior
8
+
9
+ - Be considerate, respectful, and constructive.
10
+ - Critique ideas and code without personal attacks.
11
+ - Accept feedback and focus on what is best for the project and its users.
12
+ - Respect privacy. Do not publish private information or credentials.
13
+
14
+ ## Unacceptable behavior
15
+
16
+ Harassment, discrimination, intimidation, personal attacks, unwanted sexual attention, doxxing, and other conduct that would reasonably make someone feel unsafe are not acceptable in project spaces.
17
+
18
+ ## Enforcement
19
+
20
+ Project maintainers may edit or remove contributions and may restrict participation when behavior violates this code. Report conduct concerns privately to the repository owner through [their GitHub profile](https://github.com/Clubedge). Reports will be handled as confidentially as practical.
21
+
22
+ This code applies in repository discussions, issues, pull requests, and other spaces when someone is representing this project.
@@ -0,0 +1,53 @@
1
+ # Contributing
2
+
3
+ Thanks for helping improve Clubedge Starter. Contributions of code, documentation, bug reports, and focused feature proposals are welcome.
4
+
5
+ ## Before you start
6
+
7
+ - Check existing issues and pull requests for related work.
8
+ - For a large or user-visible change, open an issue first so we can agree on the scope.
9
+ - For security concerns, follow [SECURITY.md](SECURITY.md) instead of opening a public issue.
10
+ - Keep pull requests focused. Explain the problem, the change, and any setup or migration impact.
11
+
12
+ ## Development setup
13
+
14
+ Follow [SETUP.md](SETUP.md) to install dependencies and configure local services. Most changes can be checked with:
15
+
16
+ ```sh
17
+ pnpm lint
18
+ pnpm typecheck
19
+ pnpm test
20
+ pnpm format:check
21
+ pnpm build
22
+ ```
23
+
24
+ Run `pnpm test:e2e` for changes that affect browser behavior. It requires a locally installed Playwright browser. CI runs the full configured verification workflow for pull requests.
25
+
26
+ ## Contribution workflow
27
+
28
+ 1. Fork the repository and create a focused branch from `main`.
29
+ 2. Make the change and add or update coverage and documentation where appropriate.
30
+ 3. Run the checks relevant to the change and review `git diff` for secrets or generated output.
31
+ 4. Open a pull request against `main`, describe the user impact, and link the related issue when there is one.
32
+ 5. Respond to review feedback; maintainers will use the pull request checks and discussion to decide when the change is ready to merge.
33
+
34
+ ## Project conventions
35
+
36
+ - Keep application code in `apps/web`, reusable shadcn/ui code in `packages/ui`, and framework-agnostic infrastructure in `packages/{core,auth,db,storage,cache}`.
37
+ - Reach providers through `apps/web/src/server` rather than scattering provider SDK calls through routes and components. `pnpm lint` enforces these boundaries.
38
+ - Keep server secrets out of client code and out of committed files. Update `.env.example` when adding configuration, using placeholders only.
39
+ - For database changes, update the Drizzle schema and generate, review, and commit the migration. Do not maintain a second migration history for the same application tables.
40
+ - For shared UI changes, preserve compatibility between both `components.json` files and use the established package exports.
41
+ - Update docs when commands, configuration, or behavior change.
42
+ - `clubedge.template.json` tells `create-clubedge-app` how to scaffold this repository. Keep it in step when moving the app, the site config, the environment example, or the Docker image name; `pnpm test` checks it.
43
+ - Follow the existing formatting and TypeScript conventions. Prefer a small dependency footprint and explain new runtime dependencies in the pull request.
44
+
45
+ ## Pull request checklist
46
+
47
+ - [ ] The change addresses an issue or clearly explains its motivation.
48
+ - [ ] Documentation and `.env.example` are updated when needed.
49
+ - [ ] Database migrations are included and reviewed when schema changes.
50
+ - [ ] Relevant lint, typecheck, unit, formatting, and build checks pass locally.
51
+ - [ ] No credentials, generated build output, or unrelated changes are included.
52
+
53
+ By submitting a contribution for inclusion, you agree that it will be distributed under the repository's [Apache License, Version 2.0](LICENSE), consistent with section 5 of that license. No separate contributor license agreement is currently required.
@@ -0,0 +1,32 @@
1
+ FROM node:22-alpine AS base
2
+ RUN corepack enable
3
+ WORKDIR /app
4
+
5
+ # Reduce the workspace to the web app and the packages it depends on, split into manifests
6
+ # (for a cacheable install layer) and full sources. New packages are picked up automatically.
7
+ FROM base AS pruner
8
+ COPY . .
9
+ RUN pnpm dlx turbo@^2.5.0 prune @clubedge/web --docker
10
+
11
+ FROM base AS deps
12
+ COPY --from=pruner /app/out/json/ .
13
+ RUN pnpm install --frozen-lockfile
14
+
15
+ FROM base AS builder
16
+ ENV NEXT_TELEMETRY_DISABLED=1
17
+ COPY --from=deps /app/ .
18
+ COPY --from=pruner /app/out/full/ .
19
+ COPY tsconfig.base.json ./
20
+ RUN pnpm build
21
+
22
+ FROM node:22-alpine AS runner
23
+ ENV NODE_ENV=production NEXT_TELEMETRY_DISABLED=1 HOSTNAME=0.0.0.0 PORT=3000
24
+ WORKDIR /app
25
+ RUN addgroup --system --gid 1001 nodejs && adduser --system --uid 1001 nextjs
26
+ COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/standalone ./
27
+ COPY --from=builder --chown=nextjs:nodejs /app/apps/web/public ./apps/web/public
28
+ COPY --from=builder --chown=nextjs:nodejs /app/apps/web/.next/static ./apps/web/.next/static
29
+ WORKDIR /app/apps/web
30
+ USER nextjs
31
+ EXPOSE 3000
32
+ CMD ["node", "server.js"]