@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 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.1.12
15
+ create-clubedge-app v0.2.0
16
16
  |
17
17
  v
18
- Clubedge Starter v0.1.1
19
- commit 0be0c18d41f01cd011c576fb65c25711081b8ad4
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.1.12` | `clubedge-starter@v0.1.1` | The CLI's required, tested default |
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.1.1` tag is expected to resolve to commit
31
- `0be0c18d41f01cd011c576fb65c25711081b8ad4`. The CLI verifies this before
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.1.1)
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.1.1` Starter release, so the same CLI version produces the same Starter revision. Use `--ref` to explicitly select a tag, branch, or commit.
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.1.1`. Every generated project records its CLI version, Starter repository, exact Starter ref, and resolved Starter commit in `package.json` and `README.md`.
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.1.1
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.1.1";
13
- const defaultStarterCommit = "0be0c18d41f01cd011c576fb65c25711081b8ad4";
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 layoutPath = join(directory, "apps", "web", "src", "app", "layout.tsx");
236
- const layout = await readFile(layoutPath, "utf8");
237
- const customizedLayout = layout
238
- .replaceAll('"Clubedge Starter"', JSON.stringify(displayName))
239
- .replace(`%s \u00b7 Clubedge Starter`, `%s \u00b7 ${displayName}`)
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@clubedge/create-clubedge-app",
3
- "version": "0.1.12",
3
+ "version": "0.2.0",
4
4
  "description": "Create a Next.js application from the Clubedge Starter reference implementation.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.9.0",