@clubedge/create-clubedge-app 0.2.1 → 0.4.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 +19 -0
- package/README.md +67 -36
- package/bin/create.mjs +2 -469
- package/dist/cli/args.js +54 -0
- package/dist/cli/ui.js +87 -0
- package/dist/core/customize.js +95 -0
- package/dist/core/execute.js +60 -0
- package/dist/core/manifest.js +119 -0
- package/dist/core/plan.js +132 -0
- package/dist/core/source.js +107 -0
- package/dist/index.js +160 -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 +100 -0
- package/dist/utils/names.js +14 -0
- package/dist/utils/process.js +49 -0
- package/package.json +19 -7
- package/template/.dockerignore +11 -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 +96 -0
- package/template/.prettierignore +2 -0
- package/template/.prettierrc.json +1 -0
- package/template/AGENTS.md +32 -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 +170 -0
- package/template/SECURITY.md +15 -0
- package/template/SETUP.md +196 -0
- package/template/SUPPORT.md +8 -0
- package/template/_gitignore +17 -0
- package/template/apps/start/.env.example +27 -0
- package/template/apps/start/AGENTS.md +11 -0
- package/template/apps/start/Dockerfile +38 -0
- package/template/apps/start/package.json +37 -0
- package/template/apps/start/public/.well-known/logo.svg +1 -0
- package/template/apps/start/public/favicon.ico +0 -0
- package/template/apps/start/src/components/auth/auth-form-controls.tsx +108 -0
- package/template/apps/start/src/components/branding/clubedge-mark.tsx +24 -0
- package/template/apps/start/src/components/dashboard/account-button.tsx +33 -0
- package/template/apps/start/src/components/dashboard/app-sidebar.tsx +90 -0
- package/template/apps/start/src/components/landing/copy-command.tsx +46 -0
- package/template/apps/start/src/components/theme/theme-provider.tsx +41 -0
- package/template/apps/start/src/components/theme/theme-toggle.tsx +21 -0
- package/template/apps/start/src/config/site.json +11 -0
- package/template/apps/start/src/config/site.ts +20 -0
- package/template/apps/start/src/env/server.test.ts +48 -0
- package/template/apps/start/src/env/server.ts +67 -0
- package/template/apps/start/src/lib/login-url.test.ts +16 -0
- package/template/apps/start/src/lib/login-url.ts +16 -0
- package/template/apps/start/src/routeTree.gen.ts +220 -0
- package/template/apps/start/src/router.tsx +27 -0
- package/template/apps/start/src/routes/__root.tsx +59 -0
- package/template/apps/start/src/routes/api/health.ts +15 -0
- package/template/apps/start/src/routes/auth/callback.ts +10 -0
- package/template/apps/start/src/routes/auth/sign-in.ts +11 -0
- package/template/apps/start/src/routes/auth/sign-out.ts +11 -0
- package/template/apps/start/src/routes/auth/sign-up.ts +11 -0
- package/template/apps/start/src/routes/dashboard.tsx +351 -0
- package/template/apps/start/src/routes/index.tsx +233 -0
- package/template/apps/start/src/routes/login.tsx +164 -0
- package/template/apps/start/src/server/auth-forms.test.ts +169 -0
- package/template/apps/start/src/server/auth-forms.ts +118 -0
- package/template/apps/start/src/server/auth.functions.ts +27 -0
- package/template/apps/start/src/server/auth.ts +51 -0
- package/template/apps/start/src/server/cache.ts +38 -0
- package/template/apps/start/src/server/cookies.test.ts +41 -0
- package/template/apps/start/src/server/cookies.ts +31 -0
- package/template/apps/start/src/server/db.ts +16 -0
- package/template/apps/start/src/server/storage.ts +39 -0
- package/template/apps/start/src/server/users.ts +17 -0
- package/template/apps/start/src/start.ts +40 -0
- package/template/apps/start/src/styles.css +2 -0
- package/template/apps/start/tsconfig.json +19 -0
- package/template/apps/start/vite.config.ts +27 -0
- package/template/apps/web/AGENTS.md +9 -0
- package/template/apps/web/components.json +21 -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/scripts/start-standalone.mjs +36 -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 +29 -0
- package/template/docker-compose.yml +8 -0
- package/template/e2e/app.ts +12 -0
- package/template/e2e/auth.spec.ts +37 -0
- package/template/e2e/home.spec.ts +15 -0
- package/template/eslint.config.mjs +67 -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 +23 -0
- package/template/pnpm-lock.yaml +4987 -0
- package/template/pnpm-workspace.yaml +3 -0
- package/template/tsconfig.base.json +16 -0
- package/template/turbo.json +17 -0
- package/template/vitest.config.ts +10 -0
- package/template.lock.json +187 -0
package/bin/create.mjs
CHANGED
|
@@ -1,471 +1,4 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
|
+
import { main } from "../dist/index.js";
|
|
2
3
|
|
|
3
|
-
|
|
4
|
-
import { downloadTemplate } from "giget";
|
|
5
|
-
import { access, cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
6
|
-
import { spawn } from "node:child_process";
|
|
7
|
-
import { basename, dirname, join, resolve } from "node:path";
|
|
8
|
-
import process from "node:process";
|
|
9
|
-
import { pathToFileURL } from "node:url";
|
|
10
|
-
|
|
11
|
-
// package.json is the single source for the version and the tested Starter pin. It is
|
|
12
|
-
// published with the package, so the npm registry exposes exactly what each release scaffolds.
|
|
13
|
-
const packageManifest = JSON.parse(await readFile(new URL("../package.json", import.meta.url), "utf8"));
|
|
14
|
-
const packageVersion = packageManifest.version;
|
|
15
|
-
const {
|
|
16
|
-
starterRepository,
|
|
17
|
-
starterRef: defaultStarterRef,
|
|
18
|
-
starterCommit: defaultStarterCommit,
|
|
19
|
-
} = packageManifest.clubedge;
|
|
20
|
-
const siteUrl = "https://clubedge.live";
|
|
21
|
-
const cliUrl = "https://starter.clubedge.live";
|
|
22
|
-
|
|
23
|
-
/* ------------------------------------------------------------------ */
|
|
24
|
-
/* Branding: big block-letter banner */
|
|
25
|
-
/* ------------------------------------------------------------------ */
|
|
26
|
-
|
|
27
|
-
// Each glyph is 6 rows tall. Rows are padded to a common width when rendered.
|
|
28
|
-
const glyphs = {
|
|
29
|
-
C: [" ██████╗", "██╔════╝", "██║ ", "██║ ", "╚██████╗", " ╚═════╝"],
|
|
30
|
-
L: ["██╗ ", "██║ ", "██║ ", "██║ ", "███████╗", "╚══════╝"],
|
|
31
|
-
U: ["██╗ ██╗", "██║ ██║", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
|
|
32
|
-
B: ["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██████╔╝", "╚═════╝ "],
|
|
33
|
-
E: ["███████╗", "██╔════╝", "█████╗ ", "██╔══╝ ", "███████╗", "╚══════╝"],
|
|
34
|
-
D: ["██████╗ ", "██╔══██╗", "██║ ██║", "██║ ██║", "██████╔╝", "╚═════╝ "],
|
|
35
|
-
G: [" ██████╗ ", "██╔════╝ ", "██║ ███╗", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
|
|
36
|
-
S: ["███████╗", "██╔════╝", "███████╗", "╚════██║", "███████║", "╚══════╝"],
|
|
37
|
-
T: ["████████╗", "╚══██╔══╝", " ██║ ", " ██║ ", " ██║ ", " ╚═╝ "],
|
|
38
|
-
A: [" █████╗ ", "██╔══██╗", "███████║", "██╔══██║", "██║ ██║", "╚═╝ ╚═╝"],
|
|
39
|
-
R: ["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██║ ██║", "╚═╝ ╚═╝"],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
function renderWord(word) {
|
|
43
|
-
const letters = [...word].map((letter) => {
|
|
44
|
-
const rows = glyphs[letter];
|
|
45
|
-
const width = Math.max(...rows.map((row) => row.length));
|
|
46
|
-
return rows.map((row) => row.padEnd(width, " "));
|
|
47
|
-
});
|
|
48
|
-
return letters[0].map((_, rowIndex) => letters.map((letter) => letter[rowIndex]).join(""));
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
const colorEnabled = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
52
|
-
const paint = (rgb, value) => (colorEnabled ? `\x1b[38;2;${rgb.join(";")}m${value}\x1b[0m` : value);
|
|
53
|
-
const dim = (value) => (colorEnabled ? `\x1b[2m${value}\x1b[0m` : value);
|
|
54
|
-
const bold = (value) => (colorEnabled ? `\x1b[1m${value}\x1b[0m` : value);
|
|
55
|
-
|
|
56
|
-
// Light violet -> Clubedge brand violet, top to bottom across the banner.
|
|
57
|
-
const gradientFrom = [165, 140, 255];
|
|
58
|
-
const gradientTo = [91, 61, 245];
|
|
59
|
-
const mix = (from, to, t) => from.map((channel, index) => Math.round(channel + (to[index] - channel) * t));
|
|
60
|
-
|
|
61
|
-
function showBanner() {
|
|
62
|
-
// Stay quiet when output is piped (CI, logs, scripts).
|
|
63
|
-
if (!process.stdout.isTTY) return;
|
|
64
|
-
|
|
65
|
-
const columns = process.stdout.columns ?? 80;
|
|
66
|
-
const lines = [...renderWord("CLUBEDGE"), ...renderWord("STARTER")];
|
|
67
|
-
const widest = Math.max(...lines.map((line) => line.length));
|
|
68
|
-
|
|
69
|
-
console.log("");
|
|
70
|
-
if (columns >= widest + 2) {
|
|
71
|
-
lines.forEach((line, index) => {
|
|
72
|
-
console.log(paint(mix(gradientFrom, gradientTo, index / (lines.length - 1)), line));
|
|
73
|
-
});
|
|
74
|
-
} else {
|
|
75
|
-
// Narrow terminal: compact wordmark instead of wrapping the big letters.
|
|
76
|
-
console.log(paint(gradientTo, bold("CLUBEDGE STARTER")));
|
|
77
|
-
}
|
|
78
|
-
console.log("");
|
|
79
|
-
console.log(` ${bold("create-clubedge-app")} ${dim(`v${packageVersion}`)} ${dim("·")} ${paint(gradientFrom, cliUrl)}`);
|
|
80
|
-
console.log(` ${dim("By Clubedge")} ${dim("·")} ${paint(gradientFrom, siteUrl)}`);
|
|
81
|
-
console.log("");
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/* ------------------------------------------------------------------ */
|
|
85
|
-
|
|
86
|
-
function showHelp() {
|
|
87
|
-
console.log(`
|
|
88
|
-
create-clubedge-app ${packageVersion}
|
|
89
|
-
|
|
90
|
-
Create a Next.js project from the Clubedge Starter reference repository.
|
|
91
|
-
This CLI release uses Starter ${defaultStarterRef} by default.
|
|
92
|
-
Expected Starter commit: ${defaultStarterCommit}
|
|
93
|
-
|
|
94
|
-
Usage:
|
|
95
|
-
pnpm dlx @clubedge/create-clubedge-app [project-directory] [options]
|
|
96
|
-
|
|
97
|
-
Options:
|
|
98
|
-
--ref <ref> Starter tag, branch, or commit to scaffold (default: ${defaultStarterRef})
|
|
99
|
-
--no-install Skip dependency installation
|
|
100
|
-
--no-git Skip Git repository initialization
|
|
101
|
-
-h, --help Show this help
|
|
102
|
-
-v, --version Show the CLI version
|
|
103
|
-
|
|
104
|
-
Examples:
|
|
105
|
-
pnpm dlx @clubedge/create-clubedge-app my-app
|
|
106
|
-
pnpm dlx @clubedge/create-clubedge-app my-app --ref <starter-tag-or-commit>
|
|
107
|
-
pnpm dlx @clubedge/create-clubedge-app my-app --no-install
|
|
108
|
-
|
|
109
|
-
Release model:
|
|
110
|
-
CLI ${packageVersion} -> Starter ${defaultStarterRef}
|
|
111
|
-
|
|
112
|
-
Learn more:
|
|
113
|
-
CLI ${cliUrl}
|
|
114
|
-
Clubedge ${siteUrl}
|
|
115
|
-
`);
|
|
116
|
-
}
|
|
117
|
-
|
|
118
|
-
function parseArguments(args) {
|
|
119
|
-
const options = { install: true, git: true, ref: defaultStarterRef, projectDirectory: undefined };
|
|
120
|
-
|
|
121
|
-
for (let index = 0; index < args.length; index += 1) {
|
|
122
|
-
const argument = args[index];
|
|
123
|
-
|
|
124
|
-
if (argument === "--help" || argument === "-h") {
|
|
125
|
-
return { ...options, help: true };
|
|
126
|
-
}
|
|
127
|
-
if (argument === "--version" || argument === "-v") {
|
|
128
|
-
return { ...options, version: true };
|
|
129
|
-
}
|
|
130
|
-
if (argument === "--no-install") {
|
|
131
|
-
options.install = false;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
if (argument === "--no-git") {
|
|
135
|
-
options.git = false;
|
|
136
|
-
continue;
|
|
137
|
-
}
|
|
138
|
-
if (argument === "--ref" || argument.startsWith("--ref=")) {
|
|
139
|
-
const value = argument === "--ref" ? args[++index] : argument.slice("--ref=".length);
|
|
140
|
-
if (!value || value.startsWith("-")) {
|
|
141
|
-
throw new Error("--ref requires a branch or tag name.");
|
|
142
|
-
}
|
|
143
|
-
options.ref = value;
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
if (argument.startsWith("-")) {
|
|
147
|
-
throw new Error(`Unknown option: ${argument}. Run with --help to see available options.`);
|
|
148
|
-
}
|
|
149
|
-
if (options.projectDirectory) {
|
|
150
|
-
throw new Error("Only one project directory can be provided.");
|
|
151
|
-
}
|
|
152
|
-
options.projectDirectory = argument;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
return options;
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
function isCancelled(value) {
|
|
159
|
-
if (isCancel(value)) {
|
|
160
|
-
cancel("Operation cancelled.");
|
|
161
|
-
process.exit(0);
|
|
162
|
-
}
|
|
163
|
-
return value;
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
function packageNameFromDirectory(directory) {
|
|
167
|
-
return (
|
|
168
|
-
basename(directory)
|
|
169
|
-
.toLowerCase()
|
|
170
|
-
.replace(/[^a-z0-9._-]+/g, "-")
|
|
171
|
-
.replace(/^[._-]+|[._-]+$/g, "") || "my-app"
|
|
172
|
-
);
|
|
173
|
-
}
|
|
174
|
-
|
|
175
|
-
function displayNameFromPackageName(packageName) {
|
|
176
|
-
return packageName
|
|
177
|
-
.split(/[-._]+/)
|
|
178
|
-
.filter(Boolean)
|
|
179
|
-
.map((part) => part[0].toUpperCase() + part.slice(1))
|
|
180
|
-
.join(" ");
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
async function run(command, args, cwd) {
|
|
184
|
-
await new Promise((resolvePromise, rejectPromise) => {
|
|
185
|
-
const child = spawn(command, args, {
|
|
186
|
-
cwd,
|
|
187
|
-
stdio: "inherit",
|
|
188
|
-
shell: process.platform === "win32",
|
|
189
|
-
});
|
|
190
|
-
|
|
191
|
-
child.once("error", rejectPromise);
|
|
192
|
-
child.once("exit", (code, signal) => {
|
|
193
|
-
if (code === 0) {
|
|
194
|
-
resolvePromise();
|
|
195
|
-
return;
|
|
196
|
-
}
|
|
197
|
-
rejectPromise(new Error(`${command} exited with ${signal ? `signal ${signal}` : `code ${code}`}.`));
|
|
198
|
-
});
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
async function runCapture(command, args, cwd) {
|
|
203
|
-
return await new Promise((resolvePromise, rejectPromise) => {
|
|
204
|
-
const child = spawn(command, args, {
|
|
205
|
-
cwd,
|
|
206
|
-
shell: false,
|
|
207
|
-
stdio: ["ignore", "pipe", "pipe"],
|
|
208
|
-
});
|
|
209
|
-
let stdout = "";
|
|
210
|
-
let stderr = "";
|
|
211
|
-
child.stdout.on("data", (chunk) => {
|
|
212
|
-
stdout += chunk;
|
|
213
|
-
});
|
|
214
|
-
child.stderr.on("data", (chunk) => {
|
|
215
|
-
stderr += chunk;
|
|
216
|
-
});
|
|
217
|
-
child.once("error", rejectPromise);
|
|
218
|
-
child.once("exit", (code, signal) => {
|
|
219
|
-
if (code === 0) {
|
|
220
|
-
resolvePromise(stdout.trim());
|
|
221
|
-
return;
|
|
222
|
-
}
|
|
223
|
-
rejectPromise(
|
|
224
|
-
new Error(
|
|
225
|
-
`${command} exited with ${signal ? `signal ${signal}` : `code ${code}`}${
|
|
226
|
-
stderr.trim() ? `: ${stderr.trim()}` : "."
|
|
227
|
-
}`,
|
|
228
|
-
),
|
|
229
|
-
);
|
|
230
|
-
});
|
|
231
|
-
});
|
|
232
|
-
}
|
|
233
|
-
|
|
234
|
-
async function verifyDefaultStarterReference(starterRef) {
|
|
235
|
-
if (starterRef !== defaultStarterRef) {
|
|
236
|
-
return;
|
|
237
|
-
}
|
|
238
|
-
|
|
239
|
-
const remote = `https://github.com/${starterRepository}.git`;
|
|
240
|
-
const resolvedReference = await runCapture("git", [
|
|
241
|
-
"ls-remote",
|
|
242
|
-
remote,
|
|
243
|
-
`refs/tags/${starterRef}^{}`,
|
|
244
|
-
]);
|
|
245
|
-
const resolvedCommit = resolvedReference.split(/\s+/)[0];
|
|
246
|
-
if (resolvedCommit !== defaultStarterCommit) {
|
|
247
|
-
throw new Error(
|
|
248
|
-
`Starter tag ${starterRef} resolved to ${resolvedCommit || "no commit"}, expected ${defaultStarterCommit}.`,
|
|
249
|
-
);
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
async function ensureTargetIsSafe(directory) {
|
|
254
|
-
await mkdir(dirname(directory), { recursive: true });
|
|
255
|
-
|
|
256
|
-
try {
|
|
257
|
-
const entries = await readdir(directory);
|
|
258
|
-
if (entries.length > 0) {
|
|
259
|
-
throw new Error(`The target directory is not empty: ${directory}`);
|
|
260
|
-
}
|
|
261
|
-
} catch (error) {
|
|
262
|
-
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
263
|
-
return;
|
|
264
|
-
}
|
|
265
|
-
throw error;
|
|
266
|
-
}
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
async function fileExists(path) {
|
|
270
|
-
try {
|
|
271
|
-
await access(path);
|
|
272
|
-
return true;
|
|
273
|
-
} catch {
|
|
274
|
-
return false;
|
|
275
|
-
}
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
/** Starter v0.2+ keeps project identity in apps/web/src/config/site.json. */
|
|
279
|
-
async function customizeSiteConfig(siteConfigPath, packageName, displayName) {
|
|
280
|
-
const siteConfig = JSON.parse(await readFile(siteConfigPath, "utf8"));
|
|
281
|
-
siteConfig.name = displayName;
|
|
282
|
-
siteConfig.shortName = displayName;
|
|
283
|
-
siteConfig.description = `${displayName} application foundation.`;
|
|
284
|
-
siteConfig.serviceId = packageName;
|
|
285
|
-
siteConfig.workspaceLabel = "Application workspace";
|
|
286
|
-
await writeFile(siteConfigPath, `${JSON.stringify(siteConfig, null, 2)}\n`);
|
|
287
|
-
}
|
|
288
|
-
|
|
289
|
-
/** Starter v0.1.x has no site.json, so names are replaced in known source files. */
|
|
290
|
-
async function customizeLegacySources(directory, displayName) {
|
|
291
|
-
const layoutPath = join(directory, "apps", "web", "src", "app", "layout.tsx");
|
|
292
|
-
const layout = await readFile(layoutPath, "utf8");
|
|
293
|
-
const customizedLayout = layout
|
|
294
|
-
.replaceAll('"Clubedge Starter"', JSON.stringify(displayName))
|
|
295
|
-
.replace(`%s \u00b7 Clubedge Starter`, `%s \u00b7 ${displayName}`)
|
|
296
|
-
.replace(
|
|
297
|
-
"A production-minded foundation for Clubedge applications.",
|
|
298
|
-
`${displayName} application foundation.`,
|
|
299
|
-
);
|
|
300
|
-
await writeFile(layoutPath, customizedLayout);
|
|
301
|
-
|
|
302
|
-
const appFiles = [
|
|
303
|
-
join(directory, "apps", "web", "src", "app", "page.tsx"),
|
|
304
|
-
join(directory, "apps", "web", "src", "app", "dashboard", "_components", "app-sidebar.tsx"),
|
|
305
|
-
join(directory, "apps", "web", "src", "app", "dashboard", "page.tsx"),
|
|
306
|
-
join(directory, "apps", "web", "src", "app", "login", "page.tsx"),
|
|
307
|
-
];
|
|
308
|
-
for (const appFile of appFiles) {
|
|
309
|
-
let source;
|
|
310
|
-
try {
|
|
311
|
-
source = await readFile(appFile, "utf8");
|
|
312
|
-
} catch (error) {
|
|
313
|
-
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
314
|
-
continue;
|
|
315
|
-
}
|
|
316
|
-
throw error;
|
|
317
|
-
}
|
|
318
|
-
const customizedSource = source
|
|
319
|
-
.replaceAll("Clubedge Starter", displayName)
|
|
320
|
-
.replaceAll('>Clubedge<', `>${displayName}<`)
|
|
321
|
-
.replaceAll("Starter workspace", "Application workspace");
|
|
322
|
-
await writeFile(appFile, customizedSource);
|
|
323
|
-
}
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
async function customizeProject(
|
|
327
|
-
directory,
|
|
328
|
-
packageName,
|
|
329
|
-
starterRef = defaultStarterRef,
|
|
330
|
-
starterCommit = starterRef === defaultStarterRef ? defaultStarterCommit : undefined,
|
|
331
|
-
) {
|
|
332
|
-
const rootManifestPath = join(directory, "package.json");
|
|
333
|
-
const rootManifest = JSON.parse(await readFile(rootManifestPath, "utf8"));
|
|
334
|
-
rootManifest.name = packageName;
|
|
335
|
-
rootManifest.clubedge = {
|
|
336
|
-
cliVersion: packageVersion,
|
|
337
|
-
starterRepository,
|
|
338
|
-
starterRef,
|
|
339
|
-
...(starterCommit ? { starterCommit } : {}),
|
|
340
|
-
};
|
|
341
|
-
|
|
342
|
-
for (const scriptName of ["docker:build", "docker:start"]) {
|
|
343
|
-
if (rootManifest.scripts?.[scriptName]) {
|
|
344
|
-
rootManifest.scripts[scriptName] = rootManifest.scripts[scriptName].replace(
|
|
345
|
-
/clubedge-starter/g,
|
|
346
|
-
packageName,
|
|
347
|
-
);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
await writeFile(rootManifestPath, `${JSON.stringify(rootManifest, null, 2)}\n`);
|
|
351
|
-
|
|
352
|
-
const readmePath = join(directory, "README.md");
|
|
353
|
-
const readme = await readFile(readmePath, "utf8");
|
|
354
|
-
const displayName = displayNameFromPackageName(packageName);
|
|
355
|
-
const starterCommitLine = starterCommit ? `\n- Starter commit: \`${starterCommit}\`` : "";
|
|
356
|
-
const customizedReadme = readme
|
|
357
|
-
.replace(/^# Clubedge Starter$/m, `# ${displayName}`)
|
|
358
|
-
.replace(
|
|
359
|
-
/^# (.+)$/m,
|
|
360
|
-
`$&\n\n## Generated from\n\n- Starter repository: \`${starterRepository}\`\n- Starter ref: \`${starterRef}\`${starterCommitLine}\n- CLI version: \`${packageVersion}\``,
|
|
361
|
-
);
|
|
362
|
-
await writeFile(readmePath, customizedReadme);
|
|
363
|
-
|
|
364
|
-
const siteConfigPath = join(directory, "apps", "web", "src", "config", "site.json");
|
|
365
|
-
if (await fileExists(siteConfigPath)) {
|
|
366
|
-
await customizeSiteConfig(siteConfigPath, packageName, displayName);
|
|
367
|
-
} else {
|
|
368
|
-
await customizeLegacySources(directory, displayName);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
const exampleEnvironment = join(directory, ".env.example");
|
|
372
|
-
const localEnvironment = join(directory, "apps", "web", ".env.local");
|
|
373
|
-
await cp(exampleEnvironment, localEnvironment, { errorOnExist: true });
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
async function initializeGit(directory) {
|
|
377
|
-
try {
|
|
378
|
-
await run("git", ["init", "-b", "main"], directory);
|
|
379
|
-
} catch {
|
|
380
|
-
await run("git", ["init"], directory);
|
|
381
|
-
await run("git", ["branch", "-M", "main"], directory);
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
async function createProject(options) {
|
|
386
|
-
let requestedDirectory = options.projectDirectory;
|
|
387
|
-
if (!requestedDirectory) {
|
|
388
|
-
requestedDirectory = isCancelled(
|
|
389
|
-
await text({
|
|
390
|
-
message: "Where should the new project be created?",
|
|
391
|
-
placeholder: "my-app",
|
|
392
|
-
validate(value) {
|
|
393
|
-
if (!value.trim()) return "Enter a project directory name.";
|
|
394
|
-
},
|
|
395
|
-
}),
|
|
396
|
-
);
|
|
397
|
-
}
|
|
398
|
-
|
|
399
|
-
const projectDirectory = resolve(process.cwd(), requestedDirectory);
|
|
400
|
-
if (projectDirectory === resolve(dirname(projectDirectory))) {
|
|
401
|
-
throw new Error("Choose a project subdirectory rather than a filesystem root.");
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
await ensureTargetIsSafe(projectDirectory);
|
|
405
|
-
|
|
406
|
-
const projectName = packageNameFromDirectory(projectDirectory);
|
|
407
|
-
await verifyDefaultStarterReference(options.ref);
|
|
408
|
-
const source = `gh:${starterRepository}#${options.ref}`;
|
|
409
|
-
const downloadSpinner = spinner();
|
|
410
|
-
downloadSpinner.start(`Downloading Clubedge Starter (${options.ref})`);
|
|
411
|
-
try {
|
|
412
|
-
await downloadTemplate(source, { dir: projectDirectory, force: true });
|
|
413
|
-
await customizeProject(projectDirectory, projectName, options.ref);
|
|
414
|
-
downloadSpinner.stop("Starter files downloaded and configured");
|
|
415
|
-
} catch (error) {
|
|
416
|
-
downloadSpinner.stop("Project scaffold failed");
|
|
417
|
-
throw error;
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
if (options.git) {
|
|
421
|
-
console.log("\nInitializing Git...");
|
|
422
|
-
await initializeGit(projectDirectory);
|
|
423
|
-
console.log("Git repository initialized on main");
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
if (options.install) {
|
|
427
|
-
console.log("\nInstalling dependencies with pnpm...\n");
|
|
428
|
-
try {
|
|
429
|
-
await run("pnpm", ["install"], projectDirectory);
|
|
430
|
-
console.log("\nDependencies installed");
|
|
431
|
-
} catch (error) {
|
|
432
|
-
console.error("\nProject created; dependency installation did not complete.");
|
|
433
|
-
console.error(error instanceof Error ? error.message : error);
|
|
434
|
-
console.error("Install pnpm with Corepack, then run `pnpm install` in the new project.");
|
|
435
|
-
}
|
|
436
|
-
}
|
|
437
|
-
|
|
438
|
-
const relativeDirectory = process.cwd() === projectDirectory ? "." : requestedDirectory;
|
|
439
|
-
outro(
|
|
440
|
-
`Created ${projectName}.\n\nNext steps:\n cd ${relativeDirectory}\n pnpm dev\n\nSee SETUP.md for database and provider configuration.\n\nDocs: ${cliUrl}\nClubedge: ${siteUrl}`,
|
|
441
|
-
);
|
|
442
|
-
}
|
|
443
|
-
|
|
444
|
-
async function main() {
|
|
445
|
-
try {
|
|
446
|
-
const options = parseArguments(process.argv.slice(2));
|
|
447
|
-
if (options.version) {
|
|
448
|
-
console.log(packageVersion);
|
|
449
|
-
return;
|
|
450
|
-
}
|
|
451
|
-
|
|
452
|
-
showBanner();
|
|
453
|
-
|
|
454
|
-
if (options.help) {
|
|
455
|
-
showHelp();
|
|
456
|
-
return;
|
|
457
|
-
}
|
|
458
|
-
|
|
459
|
-
intro("Create a project with Clubedge Starter");
|
|
460
|
-
await createProject(options);
|
|
461
|
-
} catch (error) {
|
|
462
|
-
console.error(`\n${error instanceof Error ? error.message : String(error)}`);
|
|
463
|
-
process.exitCode = 1;
|
|
464
|
-
}
|
|
465
|
-
}
|
|
466
|
-
|
|
467
|
-
export { customizeProject, displayNameFromPackageName, packageNameFromDirectory };
|
|
468
|
-
|
|
469
|
-
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
470
|
-
await main();
|
|
471
|
-
}
|
|
4
|
+
await main();
|
package/dist/cli/args.js
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { parseArgs } from "node:util";
|
|
2
|
+
export class UsageError extends Error {
|
|
3
|
+
name = "UsageError";
|
|
4
|
+
}
|
|
5
|
+
export function parseCliArgs(argv) {
|
|
6
|
+
let parsed;
|
|
7
|
+
try {
|
|
8
|
+
parsed = parseArgs({
|
|
9
|
+
args: argv,
|
|
10
|
+
allowPositionals: true,
|
|
11
|
+
allowNegative: true,
|
|
12
|
+
strict: true,
|
|
13
|
+
options: {
|
|
14
|
+
framework: { type: "string" },
|
|
15
|
+
ref: { type: "string" },
|
|
16
|
+
"template-dir": { type: "string" },
|
|
17
|
+
install: { type: "boolean", default: true },
|
|
18
|
+
git: { type: "boolean", default: true },
|
|
19
|
+
yes: { type: "boolean", short: "y", default: false },
|
|
20
|
+
"dry-run": { type: "boolean", default: false },
|
|
21
|
+
help: { type: "boolean", short: "h", default: false },
|
|
22
|
+
version: { type: "boolean", short: "v", default: false },
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
const message = error instanceof Error ? error.message : String(error);
|
|
28
|
+
throw new UsageError(`${message.split("\n")[0]} Run with --help to see available options.`);
|
|
29
|
+
}
|
|
30
|
+
const { values, positionals } = parsed;
|
|
31
|
+
if (positionals.length > 1)
|
|
32
|
+
throw new UsageError("Only one project directory can be provided.");
|
|
33
|
+
if (values.framework !== undefined && !values.framework.trim()) {
|
|
34
|
+
throw new UsageError("--framework requires a framework id, such as next or tanstack-start.");
|
|
35
|
+
}
|
|
36
|
+
if (values.ref !== undefined && !values.ref.trim()) {
|
|
37
|
+
throw new UsageError("--ref requires a tag, branch, or commit.");
|
|
38
|
+
}
|
|
39
|
+
if (values.ref && values["template-dir"]) {
|
|
40
|
+
throw new UsageError("Use either --ref or --template-dir, not both.");
|
|
41
|
+
}
|
|
42
|
+
return {
|
|
43
|
+
projectDirectory: positionals[0],
|
|
44
|
+
framework: values.framework?.trim(),
|
|
45
|
+
ref: values.ref,
|
|
46
|
+
templateDir: values["template-dir"],
|
|
47
|
+
install: values.install,
|
|
48
|
+
git: values.git,
|
|
49
|
+
yes: values.yes,
|
|
50
|
+
dryRun: values["dry-run"],
|
|
51
|
+
help: values.help,
|
|
52
|
+
version: values.version,
|
|
53
|
+
};
|
|
54
|
+
}
|
package/dist/cli/ui.js
ADDED
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import process from "node:process";
|
|
2
|
+
import { cliUrl, cliVersion, siteUrl, starterPin } from "../package-info.js";
|
|
3
|
+
// Each glyph is 6 rows tall. Rows are padded to a common width when rendered.
|
|
4
|
+
const glyphs = {
|
|
5
|
+
C: [" ██████╗", "██╔════╝", "██║ ", "██║ ", "╚██████╗", " ╚═════╝"],
|
|
6
|
+
L: ["██╗ ", "██║ ", "██║ ", "██║ ", "███████╗", "╚══════╝"],
|
|
7
|
+
U: ["██╗ ██╗", "██║ ██║", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
|
|
8
|
+
B: ["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██████╔╝", "╚═════╝ "],
|
|
9
|
+
E: ["███████╗", "██╔════╝", "█████╗ ", "██╔══╝ ", "███████╗", "╚══════╝"],
|
|
10
|
+
D: ["██████╗ ", "██╔══██╗", "██║ ██║", "██║ ██║", "██████╔╝", "╚═════╝ "],
|
|
11
|
+
G: [" ██████╗ ", "██╔════╝ ", "██║ ███╗", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
|
|
12
|
+
S: ["███████╗", "██╔════╝", "███████╗", "╚════██║", "███████║", "╚══════╝"],
|
|
13
|
+
T: ["████████╗", "╚══██╔══╝", " ██║ ", " ██║ ", " ██║ ", " ╚═╝ "],
|
|
14
|
+
A: [" █████╗ ", "██╔══██╗", "███████║", "██╔══██║", "██║ ██║", "╚═╝ ╚═╝"],
|
|
15
|
+
R: ["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██║ ██║", "╚═╝ ╚═╝"],
|
|
16
|
+
};
|
|
17
|
+
function renderWord(word) {
|
|
18
|
+
const letters = [...word].map((letter) => {
|
|
19
|
+
const rows = glyphs[letter];
|
|
20
|
+
const width = Math.max(...rows.map((row) => row.length));
|
|
21
|
+
return rows.map((row) => row.padEnd(width, " "));
|
|
22
|
+
});
|
|
23
|
+
return letters[0].map((_, rowIndex) => letters.map((letter) => letter[rowIndex]).join(""));
|
|
24
|
+
}
|
|
25
|
+
const colorEnabled = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
26
|
+
const paint = (rgb, value) => colorEnabled ? `\x1b[38;2;${rgb.join(";")}m${value}\x1b[0m` : value;
|
|
27
|
+
export const dim = (value) => (colorEnabled ? `\x1b[2m${value}\x1b[0m` : value);
|
|
28
|
+
export const bold = (value) => (colorEnabled ? `\x1b[1m${value}\x1b[0m` : value);
|
|
29
|
+
// Light violet -> Clubedge brand violet, top to bottom across the banner.
|
|
30
|
+
const gradientFrom = [165, 140, 255];
|
|
31
|
+
const gradientTo = [91, 61, 245];
|
|
32
|
+
const mix = (from, to, t) => from.map((channel, index) => Math.round(channel + (to[index] - channel) * t));
|
|
33
|
+
export function showBanner() {
|
|
34
|
+
// Stay quiet when output is piped (CI, logs, scripts).
|
|
35
|
+
if (!process.stdout.isTTY)
|
|
36
|
+
return;
|
|
37
|
+
const columns = process.stdout.columns ?? 80;
|
|
38
|
+
const lines = [...renderWord("CLUBEDGE"), ...renderWord("STARTER")];
|
|
39
|
+
const widest = Math.max(...lines.map((line) => line.length));
|
|
40
|
+
console.log("");
|
|
41
|
+
if (columns >= widest + 2) {
|
|
42
|
+
lines.forEach((line, index) => {
|
|
43
|
+
console.log(paint(mix(gradientFrom, gradientTo, index / (lines.length - 1)), line));
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
else {
|
|
47
|
+
// Narrow terminal: compact wordmark instead of wrapping the big letters.
|
|
48
|
+
console.log(paint(gradientTo, bold("CLUBEDGE STARTER")));
|
|
49
|
+
}
|
|
50
|
+
console.log("");
|
|
51
|
+
console.log(` ${bold("create-clubedge-app")} ${dim(`v${cliVersion}`)} ${dim("·")} ${paint(gradientFrom, cliUrl)}`);
|
|
52
|
+
console.log(` ${dim("By Clubedge")} ${dim("·")} ${paint(gradientFrom, siteUrl)}`);
|
|
53
|
+
console.log("");
|
|
54
|
+
}
|
|
55
|
+
export function helpText() {
|
|
56
|
+
return `
|
|
57
|
+
create-clubedge-app ${cliVersion}
|
|
58
|
+
|
|
59
|
+
Create a project from the Clubedge Starter reference repository.
|
|
60
|
+
This CLI release scaffolds Starter ${starterPin.starterRef} (commit ${starterPin.starterCommit}).
|
|
61
|
+
|
|
62
|
+
Usage:
|
|
63
|
+
pnpm dlx @clubedge/create-clubedge-app [project-directory] [options]
|
|
64
|
+
|
|
65
|
+
Options:
|
|
66
|
+
--framework <id> App framework: next (default) or tanstack-start
|
|
67
|
+
--ref <ref> Download this Starter tag, branch, or commit from GitHub instead
|
|
68
|
+
--template-dir <path> Scaffold from a local Starter checkout (for Starter development)
|
|
69
|
+
--dry-run Show what would be created without writing anything
|
|
70
|
+
-y, --yes Accept defaults and never prompt (default directory: my-app)
|
|
71
|
+
--no-install Skip dependency installation
|
|
72
|
+
--no-git Skip Git repository initialization
|
|
73
|
+
-h, --help Show this help
|
|
74
|
+
-v, --version Show the CLI version
|
|
75
|
+
|
|
76
|
+
Examples:
|
|
77
|
+
pnpm dlx @clubedge/create-clubedge-app my-app
|
|
78
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --framework tanstack-start
|
|
79
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --dry-run
|
|
80
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --ref main
|
|
81
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --template-dir ../clubedge-starter
|
|
82
|
+
|
|
83
|
+
Learn more:
|
|
84
|
+
CLI ${cliUrl}
|
|
85
|
+
Clubedge ${siteUrl}
|
|
86
|
+
`;
|
|
87
|
+
}
|