@clubedge/create-clubedge-app 0.1.12 → 0.2.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 +7 -0
- package/README.md +10 -10
- package/bin/create.mjs +143 -41
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,13 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to `create-clubedge-app` are documented here.
|
|
4
4
|
|
|
5
|
+
## 0.2.0
|
|
6
|
+
|
|
7
|
+
- Default to Starter `v0.2.0`, which adds project identity in `site.json`, dashboard protection, rate-limited sign-in and sign-up, and lazy environment validation.
|
|
8
|
+
- Write the project name to `apps/web/src/config/site.json` when the Starter provides it, instead of replacing strings in source files. Starter `v0.1.x` refs keep the previous behavior.
|
|
9
|
+
|
|
10
|
+
## 0.1.13
|
|
11
|
+
|
|
5
12
|
## 0.1.12
|
|
6
13
|
|
|
7
14
|
- Install the published CLI's production dependencies before invoking it in the npm smoke test.
|
package/README.md
CHANGED
|
@@ -12,23 +12,23 @@ The CLI downloads a tested Starter release, names the generated project, creates
|
|
|
12
12
|
Each CLI release is tested against one specific Starter release:
|
|
13
13
|
|
|
14
14
|
```text
|
|
15
|
-
create-clubedge-app v0.
|
|
15
|
+
create-clubedge-app v0.2.0
|
|
16
16
|
|
|
|
17
17
|
v
|
|
18
|
-
Clubedge Starter v0.
|
|
19
|
-
commit
|
|
18
|
+
Clubedge Starter v0.2.0
|
|
19
|
+
commit db15901da90d8d8a190d158c895dfe76c172b415
|
|
20
20
|
```
|
|
21
21
|
|
|
22
22
|
The current relationship is:
|
|
23
23
|
|
|
24
24
|
| CLI release | Default Starter release | Meaning |
|
|
25
25
|
| --- | --- | --- |
|
|
26
|
-
| `create-clubedge-app@0.
|
|
26
|
+
| `create-clubedge-app@0.2.0` | `clubedge-starter@v0.2.0` | The CLI's required, tested default |
|
|
27
27
|
|
|
28
28
|
This means the same CLI version always scaffolds the same Starter revision by default. The CLI does not silently follow Starter `main`.
|
|
29
29
|
|
|
30
|
-
The `v0.
|
|
31
|
-
`
|
|
30
|
+
The `v0.2.0` tag is expected to resolve to commit
|
|
31
|
+
`db15901da90d8d8a190d158c895dfe76c172b415`. The CLI verifies this before
|
|
32
32
|
scaffolding with the default reference and fails rather than silently using a
|
|
33
33
|
moved tag.
|
|
34
34
|
|
|
@@ -73,7 +73,7 @@ pnpm dlx @clubedge/create-clubedge-app
|
|
|
73
73
|
```text
|
|
74
74
|
Usage: pnpm dlx @clubedge/create-clubedge-app [project-directory] [options]
|
|
75
75
|
|
|
76
|
-
--ref <ref> Select a starter tag, branch, or commit (default: v0.
|
|
76
|
+
--ref <ref> Select a starter tag, branch, or commit (default: v0.2.0)
|
|
77
77
|
--no-install Skip pnpm install
|
|
78
78
|
--no-git Skip Git initialization
|
|
79
79
|
-h, --help Show help
|
|
@@ -87,9 +87,9 @@ pnpm dlx @clubedge/create-clubedge-app my-app --ref <starter-tag-or-commit>
|
|
|
87
87
|
pnpm dlx @clubedge/create-clubedge-app my-app --no-install
|
|
88
88
|
```
|
|
89
89
|
|
|
90
|
-
The target directory must be empty or not exist. The CLI will not delete or overwrite files in a non-empty target directory. The default is the tested `v0.
|
|
90
|
+
The target directory must be empty or not exist. The CLI will not delete or overwrite files in a non-empty target directory. The default is the tested `v0.2.0` Starter release, so the same CLI version produces the same Starter revision. Use `--ref` to explicitly select a tag, branch, or commit.
|
|
91
91
|
|
|
92
|
-
The default is the tested Starter release tag `v0.
|
|
92
|
+
The default is the tested Starter release tag `v0.2.0`. Every generated project records its CLI version, Starter repository, exact Starter ref, and resolved Starter commit in `package.json` and `README.md`.
|
|
93
93
|
|
|
94
94
|
Use `--ref` only when you intentionally want a different Starter revision:
|
|
95
95
|
|
|
@@ -101,7 +101,7 @@ pnpm dlx @clubedge/create-clubedge-app my-app
|
|
|
101
101
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref main
|
|
102
102
|
|
|
103
103
|
# Reproduce a specific Starter release or commit
|
|
104
|
-
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.
|
|
104
|
+
pnpm dlx @clubedge/create-clubedge-app my-app --ref v0.2.0
|
|
105
105
|
pnpm dlx @clubedge/create-clubedge-app my-app --ref <commit-sha>
|
|
106
106
|
```
|
|
107
107
|
|
package/bin/create.mjs
CHANGED
|
@@ -2,19 +2,84 @@
|
|
|
2
2
|
|
|
3
3
|
import { cancel, intro, isCancel, outro, spinner, text } from "@clack/prompts";
|
|
4
4
|
import { downloadTemplate } from "giget";
|
|
5
|
-
import { cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
5
|
+
import { access, cp, mkdir, readdir, readFile, writeFile } from "node:fs/promises";
|
|
6
6
|
import { spawn } from "node:child_process";
|
|
7
7
|
import { basename, dirname, join, resolve } from "node:path";
|
|
8
8
|
import process from "node:process";
|
|
9
9
|
import { pathToFileURL } from "node:url";
|
|
10
10
|
|
|
11
11
|
const starterRepository = "Clubedge/clubedge-starter";
|
|
12
|
-
const defaultStarterRef = "v0.
|
|
13
|
-
const defaultStarterCommit = "
|
|
12
|
+
const defaultStarterRef = "v0.2.0";
|
|
13
|
+
const defaultStarterCommit = "db15901da90d8d8a190d158c895dfe76c172b415";
|
|
14
|
+
const siteUrl = "https://clubedge.live";
|
|
15
|
+
const cliUrl = "https://starter.clubedge.live";
|
|
14
16
|
const packageVersion = JSON.parse(
|
|
15
17
|
await readFile(new URL("../package.json", import.meta.url), "utf8"),
|
|
16
18
|
).version;
|
|
17
19
|
|
|
20
|
+
/* ------------------------------------------------------------------ */
|
|
21
|
+
/* Branding: big block-letter banner */
|
|
22
|
+
/* ------------------------------------------------------------------ */
|
|
23
|
+
|
|
24
|
+
// Each glyph is 6 rows tall. Rows are padded to a common width when rendered.
|
|
25
|
+
const glyphs = {
|
|
26
|
+
C: [" ██████╗", "██╔════╝", "██║ ", "██║ ", "╚██████╗", " ╚═════╝"],
|
|
27
|
+
L: ["██╗ ", "██║ ", "██║ ", "██║ ", "███████╗", "╚══════╝"],
|
|
28
|
+
U: ["██╗ ██╗", "██║ ██║", "██║ ██║", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
|
|
29
|
+
B: ["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██████╔╝", "╚═════╝ "],
|
|
30
|
+
E: ["███████╗", "██╔════╝", "█████╗ ", "██╔══╝ ", "███████╗", "╚══════╝"],
|
|
31
|
+
D: ["██████╗ ", "██╔══██╗", "██║ ██║", "██║ ██║", "██████╔╝", "╚═════╝ "],
|
|
32
|
+
G: [" ██████╗ ", "██╔════╝ ", "██║ ███╗", "██║ ██║", "╚██████╔╝", " ╚═════╝ "],
|
|
33
|
+
S: ["███████╗", "██╔════╝", "███████╗", "╚════██║", "███████║", "╚══════╝"],
|
|
34
|
+
T: ["████████╗", "╚══██╔══╝", " ██║ ", " ██║ ", " ██║ ", " ╚═╝ "],
|
|
35
|
+
A: [" █████╗ ", "██╔══██╗", "███████║", "██╔══██║", "██║ ██║", "╚═╝ ╚═╝"],
|
|
36
|
+
R: ["██████╗ ", "██╔══██╗", "██████╔╝", "██╔══██╗", "██║ ██║", "╚═╝ ╚═╝"],
|
|
37
|
+
};
|
|
38
|
+
|
|
39
|
+
function renderWord(word) {
|
|
40
|
+
const letters = [...word].map((letter) => {
|
|
41
|
+
const rows = glyphs[letter];
|
|
42
|
+
const width = Math.max(...rows.map((row) => row.length));
|
|
43
|
+
return rows.map((row) => row.padEnd(width, " "));
|
|
44
|
+
});
|
|
45
|
+
return letters[0].map((_, rowIndex) => letters.map((letter) => letter[rowIndex]).join(""));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
const colorEnabled = Boolean(process.stdout.isTTY) && !process.env.NO_COLOR && process.env.TERM !== "dumb";
|
|
49
|
+
const paint = (rgb, value) => (colorEnabled ? `\x1b[38;2;${rgb.join(";")}m${value}\x1b[0m` : value);
|
|
50
|
+
const dim = (value) => (colorEnabled ? `\x1b[2m${value}\x1b[0m` : value);
|
|
51
|
+
const bold = (value) => (colorEnabled ? `\x1b[1m${value}\x1b[0m` : value);
|
|
52
|
+
|
|
53
|
+
// Light violet -> Clubedge brand violet, top to bottom across the banner.
|
|
54
|
+
const gradientFrom = [165, 140, 255];
|
|
55
|
+
const gradientTo = [91, 61, 245];
|
|
56
|
+
const mix = (from, to, t) => from.map((channel, index) => Math.round(channel + (to[index] - channel) * t));
|
|
57
|
+
|
|
58
|
+
function showBanner() {
|
|
59
|
+
// Stay quiet when output is piped (CI, logs, scripts).
|
|
60
|
+
if (!process.stdout.isTTY) return;
|
|
61
|
+
|
|
62
|
+
const columns = process.stdout.columns ?? 80;
|
|
63
|
+
const lines = [...renderWord("CLUBEDGE"), ...renderWord("STARTER")];
|
|
64
|
+
const widest = Math.max(...lines.map((line) => line.length));
|
|
65
|
+
|
|
66
|
+
console.log("");
|
|
67
|
+
if (columns >= widest + 2) {
|
|
68
|
+
lines.forEach((line, index) => {
|
|
69
|
+
console.log(paint(mix(gradientFrom, gradientTo, index / (lines.length - 1)), line));
|
|
70
|
+
});
|
|
71
|
+
} else {
|
|
72
|
+
// Narrow terminal: compact wordmark instead of wrapping the big letters.
|
|
73
|
+
console.log(paint(gradientTo, bold("CLUBEDGE STARTER")));
|
|
74
|
+
}
|
|
75
|
+
console.log("");
|
|
76
|
+
console.log(` ${bold("create-clubedge-app")} ${dim(`v${packageVersion}`)} ${dim("·")} ${paint(gradientFrom, cliUrl)}`);
|
|
77
|
+
console.log(` ${dim("By Clubedge")} ${dim("·")} ${paint(gradientFrom, siteUrl)}`);
|
|
78
|
+
console.log("");
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/* ------------------------------------------------------------------ */
|
|
82
|
+
|
|
18
83
|
function showHelp() {
|
|
19
84
|
console.log(`
|
|
20
85
|
create-clubedge-app ${packageVersion}
|
|
@@ -40,6 +105,10 @@ Examples:
|
|
|
40
105
|
|
|
41
106
|
Release model:
|
|
42
107
|
CLI ${packageVersion} -> Starter ${defaultStarterRef}
|
|
108
|
+
|
|
109
|
+
Learn more:
|
|
110
|
+
CLI ${cliUrl}
|
|
111
|
+
Clubedge ${siteUrl}
|
|
43
112
|
`);
|
|
44
113
|
}
|
|
45
114
|
|
|
@@ -194,6 +263,63 @@ async function ensureTargetIsSafe(directory) {
|
|
|
194
263
|
}
|
|
195
264
|
}
|
|
196
265
|
|
|
266
|
+
async function fileExists(path) {
|
|
267
|
+
try {
|
|
268
|
+
await access(path);
|
|
269
|
+
return true;
|
|
270
|
+
} catch {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
/** Starter v0.2+ keeps project identity in apps/web/src/config/site.json. */
|
|
276
|
+
async function customizeSiteConfig(siteConfigPath, packageName, displayName) {
|
|
277
|
+
const siteConfig = JSON.parse(await readFile(siteConfigPath, "utf8"));
|
|
278
|
+
siteConfig.name = displayName;
|
|
279
|
+
siteConfig.shortName = displayName;
|
|
280
|
+
siteConfig.description = `${displayName} application foundation.`;
|
|
281
|
+
siteConfig.serviceId = packageName;
|
|
282
|
+
siteConfig.workspaceLabel = "Application workspace";
|
|
283
|
+
await writeFile(siteConfigPath, `${JSON.stringify(siteConfig, null, 2)}\n`);
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
/** Starter v0.1.x has no site.json, so names are replaced in known source files. */
|
|
287
|
+
async function customizeLegacySources(directory, displayName) {
|
|
288
|
+
const layoutPath = join(directory, "apps", "web", "src", "app", "layout.tsx");
|
|
289
|
+
const layout = await readFile(layoutPath, "utf8");
|
|
290
|
+
const customizedLayout = layout
|
|
291
|
+
.replaceAll('"Clubedge Starter"', JSON.stringify(displayName))
|
|
292
|
+
.replace(`%s \u00b7 Clubedge Starter`, `%s \u00b7 ${displayName}`)
|
|
293
|
+
.replace(
|
|
294
|
+
"A production-minded foundation for Clubedge applications.",
|
|
295
|
+
`${displayName} application foundation.`,
|
|
296
|
+
);
|
|
297
|
+
await writeFile(layoutPath, customizedLayout);
|
|
298
|
+
|
|
299
|
+
const appFiles = [
|
|
300
|
+
join(directory, "apps", "web", "src", "app", "page.tsx"),
|
|
301
|
+
join(directory, "apps", "web", "src", "app", "dashboard", "_components", "app-sidebar.tsx"),
|
|
302
|
+
join(directory, "apps", "web", "src", "app", "dashboard", "page.tsx"),
|
|
303
|
+
join(directory, "apps", "web", "src", "app", "login", "page.tsx"),
|
|
304
|
+
];
|
|
305
|
+
for (const appFile of appFiles) {
|
|
306
|
+
let source;
|
|
307
|
+
try {
|
|
308
|
+
source = await readFile(appFile, "utf8");
|
|
309
|
+
} catch (error) {
|
|
310
|
+
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
311
|
+
continue;
|
|
312
|
+
}
|
|
313
|
+
throw error;
|
|
314
|
+
}
|
|
315
|
+
const customizedSource = source
|
|
316
|
+
.replaceAll("Clubedge Starter", displayName)
|
|
317
|
+
.replaceAll('>Clubedge<', `>${displayName}<`)
|
|
318
|
+
.replaceAll("Starter workspace", "Application workspace");
|
|
319
|
+
await writeFile(appFile, customizedSource);
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
|
|
197
323
|
async function customizeProject(
|
|
198
324
|
directory,
|
|
199
325
|
packageName,
|
|
@@ -232,38 +358,11 @@ async function customizeProject(
|
|
|
232
358
|
);
|
|
233
359
|
await writeFile(readmePath, customizedReadme);
|
|
234
360
|
|
|
235
|
-
const
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
.replace(
|
|
241
|
-
"A production-minded foundation for Clubedge applications.",
|
|
242
|
-
`${displayName} application foundation.`,
|
|
243
|
-
);
|
|
244
|
-
await writeFile(layoutPath, customizedLayout);
|
|
245
|
-
|
|
246
|
-
const appFiles = [
|
|
247
|
-
join(directory, "apps", "web", "src", "app", "page.tsx"),
|
|
248
|
-
join(directory, "apps", "web", "src", "app", "dashboard", "_components", "app-sidebar.tsx"),
|
|
249
|
-
join(directory, "apps", "web", "src", "app", "dashboard", "page.tsx"),
|
|
250
|
-
join(directory, "apps", "web", "src", "app", "login", "page.tsx"),
|
|
251
|
-
];
|
|
252
|
-
for (const appFile of appFiles) {
|
|
253
|
-
let source;
|
|
254
|
-
try {
|
|
255
|
-
source = await readFile(appFile, "utf8");
|
|
256
|
-
} catch (error) {
|
|
257
|
-
if (error && typeof error === "object" && "code" in error && error.code === "ENOENT") {
|
|
258
|
-
continue;
|
|
259
|
-
}
|
|
260
|
-
throw error;
|
|
261
|
-
}
|
|
262
|
-
const customizedSource = source
|
|
263
|
-
.replaceAll("Clubedge Starter", displayName)
|
|
264
|
-
.replaceAll('>Clubedge<', `>${displayName}<`)
|
|
265
|
-
.replaceAll("Starter workspace", "Application workspace");
|
|
266
|
-
await writeFile(appFile, customizedSource);
|
|
361
|
+
const siteConfigPath = join(directory, "apps", "web", "src", "config", "site.json");
|
|
362
|
+
if (await fileExists(siteConfigPath)) {
|
|
363
|
+
await customizeSiteConfig(siteConfigPath, packageName, displayName);
|
|
364
|
+
} else {
|
|
365
|
+
await customizeLegacySources(directory, displayName);
|
|
267
366
|
}
|
|
268
367
|
|
|
269
368
|
const exampleEnvironment = join(directory, ".env.example");
|
|
@@ -335,22 +434,25 @@ async function createProject(options) {
|
|
|
335
434
|
|
|
336
435
|
const relativeDirectory = process.cwd() === projectDirectory ? "." : requestedDirectory;
|
|
337
436
|
outro(
|
|
338
|
-
`Created ${projectName}.\n\nNext steps:\n cd ${relativeDirectory}\n pnpm dev\n\nSee SETUP.md for database and provider configuration
|
|
437
|
+
`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}`,
|
|
339
438
|
);
|
|
340
439
|
}
|
|
341
440
|
|
|
342
441
|
async function main() {
|
|
343
442
|
try {
|
|
344
443
|
const options = parseArguments(process.argv.slice(2));
|
|
345
|
-
if (options.help) {
|
|
346
|
-
showHelp();
|
|
347
|
-
return;
|
|
348
|
-
}
|
|
349
444
|
if (options.version) {
|
|
350
445
|
console.log(packageVersion);
|
|
351
446
|
return;
|
|
352
447
|
}
|
|
353
448
|
|
|
449
|
+
showBanner();
|
|
450
|
+
|
|
451
|
+
if (options.help) {
|
|
452
|
+
showHelp();
|
|
453
|
+
return;
|
|
454
|
+
}
|
|
455
|
+
|
|
354
456
|
intro("Create a project with Clubedge Starter");
|
|
355
457
|
await createProject(options);
|
|
356
458
|
} catch (error) {
|
|
@@ -363,4 +465,4 @@ export { customizeProject, displayNameFromPackageName, packageNameFromDirectory
|
|
|
363
465
|
|
|
364
466
|
if (process.argv[1] && import.meta.url === pathToFileURL(resolve(process.argv[1])).href) {
|
|
365
467
|
await main();
|
|
366
|
-
}
|
|
468
|
+
}
|
package/package.json
CHANGED