@clubedge/create-clubedge-app 0.2.0 → 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.
- package/CHANGELOG.md +16 -0
- package/README.md +50 -35
- package/bin/create.mjs +2 -466
- package/dist/cli/args.js +49 -0
- package/dist/cli/ui.js +85 -0
- package/dist/core/customize.js +56 -0
- package/dist/core/execute.js +60 -0
- package/dist/core/manifest.js +81 -0
- package/dist/core/plan.js +67 -0
- package/dist/core/source.js +107 -0
- package/dist/index.js +125 -0
- package/dist/package-info.js +8 -0
- package/dist/steps/git.js +11 -0
- package/dist/steps/install.js +19 -0
- package/dist/utils/fs.js +97 -0
- package/dist/utils/names.js +14 -0
- package/dist/utils/process.js +49 -0
- package/package.json +21 -5
- package/template/.dockerignore +8 -0
- package/template/.env.example +25 -0
- package/template/.gitattributes +6 -0
- package/template/.github/ISSUE_TEMPLATE/bug_report.yml +35 -0
- package/template/.github/ISSUE_TEMPLATE/config.yml +8 -0
- package/template/.github/ISSUE_TEMPLATE/feature_request.yml +24 -0
- package/template/.github/pull_request_template.md +20 -0
- package/template/.github/workflows/ci.yml +49 -0
- package/template/.prettierignore +1 -0
- package/template/.prettierrc.json +1 -0
- package/template/AGENTS.md +31 -0
- package/template/CODE_OF_CONDUCT.md +22 -0
- package/template/CONTRIBUTING.md +53 -0
- package/template/Dockerfile +32 -0
- package/template/LICENSE +201 -0
- package/template/NOTICE +6 -0
- package/template/PUBLISHING.md +27 -0
- package/template/README.md +157 -0
- package/template/SECURITY.md +15 -0
- package/template/SETUP.md +174 -0
- package/template/SUPPORT.md +8 -0
- package/template/_gitignore +13 -0
- package/template/apps/web/AGENTS.md +9 -0
- package/template/apps/web/components.json +21 -0
- package/template/apps/web/e2e/auth.spec.ts +37 -0
- package/template/apps/web/e2e/home.spec.ts +15 -0
- package/template/apps/web/next-env.d.ts +7 -0
- package/template/apps/web/next.config.ts +32 -0
- package/template/apps/web/package.json +33 -0
- package/template/apps/web/postcss.config.mjs +3 -0
- package/template/apps/web/public/.gitkeep +0 -0
- package/template/apps/web/public/.well-known/logo.svg +1 -0
- package/template/apps/web/src/app/actions/auth.ts +85 -0
- package/template/apps/web/src/app/api/health/route.ts +11 -0
- package/template/apps/web/src/app/auth/callback/route.ts +21 -0
- package/template/apps/web/src/app/dashboard/_components/account-button.tsx +34 -0
- package/template/apps/web/src/app/dashboard/_components/app-sidebar.tsx +91 -0
- package/template/apps/web/src/app/dashboard/page.tsx +352 -0
- package/template/apps/web/src/app/favicon.ico +0 -0
- package/template/apps/web/src/app/layout.tsx +36 -0
- package/template/apps/web/src/app/login/_components/auth-form-controls.tsx +88 -0
- package/template/apps/web/src/app/login/page.tsx +149 -0
- package/template/apps/web/src/app/page.tsx +229 -0
- package/template/apps/web/src/app/theme-provider.tsx +43 -0
- package/template/apps/web/src/app/theme-toggle.tsx +23 -0
- package/template/apps/web/src/components/branding/clubedge-mark.tsx +25 -0
- package/template/apps/web/src/components/landing/copy-command.tsx +48 -0
- package/template/apps/web/src/config/site.json +11 -0
- package/template/apps/web/src/config/site.ts +20 -0
- package/template/apps/web/src/env/client.ts +22 -0
- package/template/apps/web/src/env/server.test.ts +35 -0
- package/template/apps/web/src/env/server.ts +45 -0
- package/template/apps/web/src/lib/login-url.test.ts +16 -0
- package/template/apps/web/src/lib/login-url.ts +16 -0
- package/template/apps/web/src/lib/utils.ts +1 -0
- package/template/apps/web/src/proxy.ts +31 -0
- package/template/apps/web/src/server/auth.ts +69 -0
- package/template/apps/web/src/server/cache.ts +38 -0
- package/template/apps/web/src/server/db.ts +16 -0
- package/template/apps/web/src/server/storage.ts +39 -0
- package/template/apps/web/src/server/users.ts +17 -0
- package/template/apps/web/tsconfig.json +36 -0
- package/template/clubedge.template.json +13 -0
- package/template/docker-compose.yml +8 -0
- package/template/eslint.config.mjs +63 -0
- package/template/package.json +47 -0
- package/template/packages/auth/package.json +23 -0
- package/template/packages/auth/src/index.ts +9 -0
- package/template/packages/auth/src/supabase.test.ts +126 -0
- package/template/packages/auth/src/supabase.ts +65 -0
- package/template/packages/auth/src/types.ts +59 -0
- package/template/packages/auth/tsconfig.json +4 -0
- package/template/packages/cache/package.json +20 -0
- package/template/packages/cache/src/index.test.ts +67 -0
- package/template/packages/cache/src/index.ts +19 -0
- package/template/packages/cache/src/rate-limit.test.ts +52 -0
- package/template/packages/cache/src/rate-limit.ts +74 -0
- package/template/packages/cache/src/redis.ts +106 -0
- package/template/packages/cache/tsconfig.json +4 -0
- package/template/packages/core/package.json +17 -0
- package/template/packages/core/src/client-ip.test.ts +19 -0
- package/template/packages/core/src/client-ip.ts +11 -0
- package/template/packages/core/src/errors.test.ts +20 -0
- package/template/packages/core/src/errors.ts +20 -0
- package/template/packages/core/src/index.ts +4 -0
- package/template/packages/core/src/redirect.test.ts +27 -0
- package/template/packages/core/src/redirect.ts +20 -0
- package/template/packages/core/src/result.ts +10 -0
- package/template/packages/core/tsconfig.json +4 -0
- package/template/packages/db/drizzle/0000_initial.sql +10 -0
- package/template/packages/db/drizzle/meta/_journal.json +13 -0
- package/template/packages/db/drizzle.config.ts +9 -0
- package/template/packages/db/package.json +30 -0
- package/template/packages/db/scripts/load-env.ts +14 -0
- package/template/packages/db/scripts/migrate.ts +11 -0
- package/template/packages/db/scripts/seed.ts +14 -0
- package/template/packages/db/src/client.ts +22 -0
- package/template/packages/db/src/index.ts +4 -0
- package/template/packages/db/src/schema/index.ts +1 -0
- package/template/packages/db/src/schema/users.ts +14 -0
- package/template/packages/db/src/users.ts +16 -0
- package/template/packages/db/tsconfig.json +4 -0
- package/template/packages/storage/package.json +24 -0
- package/template/packages/storage/src/index.ts +1 -0
- package/template/packages/storage/src/s3.test.ts +86 -0
- package/template/packages/storage/src/s3.ts +86 -0
- package/template/packages/storage/src/supabase.ts +69 -0
- package/template/packages/storage/src/types.ts +18 -0
- package/template/packages/storage/tsconfig.json +4 -0
- package/template/packages/ui/components.json +21 -0
- package/template/packages/ui/package.json +32 -0
- package/template/packages/ui/src/components/badge.tsx +48 -0
- package/template/packages/ui/src/components/breadcrumb.tsx +102 -0
- package/template/packages/ui/src/components/button.tsx +57 -0
- package/template/packages/ui/src/components/card.tsx +87 -0
- package/template/packages/ui/src/components/input.tsx +19 -0
- package/template/packages/ui/src/components/label.tsx +19 -0
- package/template/packages/ui/src/components/separator.tsx +20 -0
- package/template/packages/ui/src/components/sheet.tsx +125 -0
- package/template/packages/ui/src/components/sidebar.tsx +692 -0
- package/template/packages/ui/src/components/skeleton.tsx +13 -0
- package/template/packages/ui/src/components/switch.tsx +31 -0
- package/template/packages/ui/src/components/table.tsx +88 -0
- package/template/packages/ui/src/components/tooltip.tsx +53 -0
- package/template/packages/ui/src/hooks/use-mobile.ts +19 -0
- package/template/packages/ui/src/lib/utils.ts +1 -0
- package/template/packages/ui/src/styles/globals.css +126 -0
- package/template/packages/ui/tsconfig.json +19 -0
- package/template/playwright.config.ts +19 -0
- package/template/pnpm-lock.yaml +3624 -0
- package/template/pnpm-workspace.yaml +3 -0
- package/template/scripts/start-standalone.mjs +37 -0
- package/template/tsconfig.base.json +16 -0
- package/template/turbo.json +17 -0
- package/template/vitest.config.ts +8 -0
- 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
|
+
}
|
package/dist/utils/fs.js
ADDED
|
@@ -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.
|
|
4
|
-
"description": "Create
|
|
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",
|
|
@@ -17,6 +20,11 @@
|
|
|
17
20
|
"engines": {
|
|
18
21
|
"node": ">=22.12.0"
|
|
19
22
|
},
|
|
23
|
+
"clubedge": {
|
|
24
|
+
"starterRepository": "Clubedge/clubedge-starter",
|
|
25
|
+
"starterRef": "v0.3.0",
|
|
26
|
+
"starterCommit": "60f1691920c91a5cf83e480a3ce98c3ddefdef55"
|
|
27
|
+
},
|
|
20
28
|
"license": "Apache-2.0",
|
|
21
29
|
"repository": {
|
|
22
30
|
"type": "git",
|
|
@@ -34,12 +42,20 @@
|
|
|
34
42
|
"scaffolding"
|
|
35
43
|
],
|
|
36
44
|
"scripts": {
|
|
37
|
-
"
|
|
38
|
-
"check": "
|
|
39
|
-
"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"
|
|
40
51
|
},
|
|
41
52
|
"dependencies": {
|
|
42
53
|
"@clack/prompts": "^1.8.1",
|
|
43
54
|
"giget": "^2.0.0"
|
|
55
|
+
},
|
|
56
|
+
"devDependencies": {
|
|
57
|
+
"@types/node": "^22.20.5",
|
|
58
|
+
"typescript": "^5.9.3",
|
|
59
|
+
"vitest": "^5.0.3"
|
|
44
60
|
}
|
|
45
61
|
}
|
|
@@ -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,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"]
|