create-better-t-stack 3.19.4 → 3.19.5

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/dist/cli.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
- import { l as createBtsCli } from "./src-mMd-eO_p.mjs";
2
+ import { l as createBtsCli } from "./src-C8vduSK8.mjs";
3
3
 
4
4
  //#region src/cli.ts
5
5
  createBtsCli().run();
package/dist/index.mjs CHANGED
@@ -1,4 +1,4 @@
1
1
  #!/usr/bin/env node
2
- import { _ as DatabaseSetupError, a as VirtualFileSystem, b as UserCancelledError, c as create, d as docs, f as generate, g as CompatibilityError, h as CLIError, i as TEMPLATE_COUNT, l as createBtsCli, m as sponsors, n as GeneratorError, o as add, p as router, r as Result, s as builder, t as EMBEDDED_TEMPLATES, u as createVirtual, v as DirectoryConflictError, x as ValidationError, y as ProjectCreationError } from "./src-mMd-eO_p.mjs";
2
+ import { _ as DatabaseSetupError, a as VirtualFileSystem, b as UserCancelledError, c as create, d as docs, f as generate, g as CompatibilityError, h as CLIError, i as TEMPLATE_COUNT, l as createBtsCli, m as sponsors, n as GeneratorError, o as add, p as router, r as Result, s as builder, t as EMBEDDED_TEMPLATES, u as createVirtual, v as DirectoryConflictError, x as ValidationError, y as ProjectCreationError } from "./src-C8vduSK8.mjs";
3
3
 
4
4
  export { CLIError, CompatibilityError, DatabaseSetupError, DirectoryConflictError, EMBEDDED_TEMPLATES, GeneratorError, ProjectCreationError, Result, TEMPLATE_COUNT, UserCancelledError, ValidationError, VirtualFileSystem, add, builder, create, createBtsCli, createVirtual, docs, generate, router, sponsors };
@@ -1,23 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import { t as __reExport } from "./chunk-CHc3S52W.mjs";
3
- import { autocompleteMultiselect, cancel, confirm, group, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
4
3
  import { createRouterClient, os } from "@orpc/server";
5
4
  import { Result, Result as Result$1, TaggedError } from "better-result";
6
- import pc from "picocolors";
7
5
  import { createCli } from "trpc-cli";
8
6
  import z from "zod";
9
- import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps } from "@better-t-stack/template-generator";
10
- import { writeTree } from "@better-t-stack/template-generator/fs-writer";
7
+ import { autocompleteMultiselect, cancel, confirm, group, intro, isCancel, log, multiselect, outro, select, spinner, text } from "@clack/prompts";
8
+ import pc from "picocolors";
9
+ import envPaths from "env-paths";
11
10
  import fs from "fs-extra";
12
11
  import path from "node:path";
13
12
  import { fileURLToPath } from "node:url";
13
+ import { EMBEDDED_TEMPLATES, EMBEDDED_TEMPLATES as EMBEDDED_TEMPLATES$1, GeneratorError, GeneratorError as GeneratorError$1, TEMPLATE_COUNT, VirtualFileSystem, VirtualFileSystem as VirtualFileSystem$1, dependencyVersionMap, generate, generate as generate$1, generateReproducibleCommand, processAddonTemplates, processAddonsDeps } from "@better-t-stack/template-generator";
14
14
  import consola, { consola as consola$1 } from "consola";
15
+ import gradient from "gradient-string";
16
+ import { $, execa } from "execa";
17
+ import { writeTree } from "@better-t-stack/template-generator/fs-writer";
15
18
  import { ConfirmPrompt, GroupMultiSelectPrompt, MultiSelectPrompt, SelectPrompt, isCancel as isCancel$1 } from "@clack/core";
16
19
  import { AsyncLocalStorage } from "node:async_hooks";
17
20
  import { applyEdits, modify, parse } from "jsonc-parser";
18
- import gradient from "gradient-string";
19
- import { $, execa } from "execa";
20
- import envPaths from "env-paths";
21
21
  import { format } from "oxfmt";
22
22
  import os$1 from "node:os";
23
23
 
@@ -94,25 +94,6 @@ const ADDON_COMPATIBILITY = {
94
94
  none: []
95
95
  };
96
96
 
97
- //#endregion
98
- //#region src/types.ts
99
- var types_exports = {};
100
- import * as import__better_t_stack_types from "@better-t-stack/types";
101
- __reExport(types_exports, import__better_t_stack_types);
102
-
103
- //#endregion
104
- //#region src/utils/compatibility.ts
105
- const WEB_FRAMEWORKS = [
106
- "tanstack-router",
107
- "react-router",
108
- "tanstack-start",
109
- "next",
110
- "nuxt",
111
- "svelte",
112
- "solid",
113
- "astro"
114
- ];
115
-
116
97
  //#endregion
117
98
  //#region src/utils/errors.ts
118
99
  /**
@@ -202,6 +183,344 @@ function displayError(error) {
202
183
  else consola.error(pc.red(error.message));
203
184
  }
204
185
 
186
+ //#endregion
187
+ //#region src/utils/get-latest-cli-version.ts
188
+ function getLatestCLIVersionResult() {
189
+ const packageJsonPath = path.join(PKG_ROOT, "package.json");
190
+ return Result.try({
191
+ try: () => {
192
+ return fs.readJSONSync(packageJsonPath).version;
193
+ },
194
+ catch: (e) => new CLIError({
195
+ message: `Failed to read CLI version from package.json: ${e instanceof Error ? e.message : String(e)}`,
196
+ cause: e
197
+ })
198
+ });
199
+ }
200
+ function getLatestCLIVersion() {
201
+ return getLatestCLIVersionResult().unwrapOr("1.0.0");
202
+ }
203
+
204
+ //#endregion
205
+ //#region src/utils/project-history.ts
206
+ const paths = envPaths("better-t-stack", { suffix: "" });
207
+ const HISTORY_FILE = "history.json";
208
+ var HistoryError = class extends TaggedError("HistoryError")() {};
209
+ function getHistoryDir() {
210
+ return paths.data;
211
+ }
212
+ function getHistoryPath() {
213
+ return path.join(paths.data, HISTORY_FILE);
214
+ }
215
+ function generateId() {
216
+ return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
217
+ }
218
+ function emptyHistory() {
219
+ return {
220
+ version: 1,
221
+ entries: []
222
+ };
223
+ }
224
+ async function ensureHistoryDir() {
225
+ return Result.tryPromise({
226
+ try: async () => {
227
+ await fs.ensureDir(getHistoryDir());
228
+ },
229
+ catch: (e) => new HistoryError({
230
+ message: `Failed to create history directory: ${e instanceof Error ? e.message : String(e)}`,
231
+ cause: e
232
+ })
233
+ });
234
+ }
235
+ async function readHistory() {
236
+ const historyPath = getHistoryPath();
237
+ const existsResult = await Result.tryPromise({
238
+ try: async () => await fs.pathExists(historyPath),
239
+ catch: (e) => new HistoryError({
240
+ message: `Failed to check history file: ${e instanceof Error ? e.message : String(e)}`,
241
+ cause: e
242
+ })
243
+ });
244
+ if (existsResult.isErr()) return existsResult;
245
+ if (!existsResult.value) return Result.ok(emptyHistory());
246
+ const readResult = await Result.tryPromise({
247
+ try: async () => await fs.readJson(historyPath),
248
+ catch: (e) => new HistoryError({
249
+ message: `Failed to read history file: ${e instanceof Error ? e.message : String(e)}`,
250
+ cause: e
251
+ })
252
+ });
253
+ if (readResult.isErr()) return Result.ok(emptyHistory());
254
+ return Result.ok(readResult.value);
255
+ }
256
+ async function writeHistory(history) {
257
+ const ensureDirResult = await ensureHistoryDir();
258
+ if (ensureDirResult.isErr()) return ensureDirResult;
259
+ return Result.tryPromise({
260
+ try: async () => {
261
+ await fs.writeJson(getHistoryPath(), history, { spaces: 2 });
262
+ },
263
+ catch: (e) => new HistoryError({
264
+ message: `Failed to write history file: ${e instanceof Error ? e.message : String(e)}`,
265
+ cause: e
266
+ })
267
+ });
268
+ }
269
+ async function addToHistory(config, reproducibleCommand) {
270
+ const historyResult = await readHistory();
271
+ if (historyResult.isErr()) return historyResult;
272
+ const history = historyResult.value;
273
+ const entry = {
274
+ id: generateId(),
275
+ projectName: config.projectName,
276
+ projectDir: config.projectDir,
277
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
278
+ stack: {
279
+ frontend: config.frontend,
280
+ backend: config.backend,
281
+ database: config.database,
282
+ orm: config.orm,
283
+ runtime: config.runtime,
284
+ auth: config.auth,
285
+ payments: config.payments,
286
+ api: config.api,
287
+ addons: config.addons,
288
+ examples: config.examples,
289
+ dbSetup: config.dbSetup,
290
+ packageManager: config.packageManager
291
+ },
292
+ cliVersion: getLatestCLIVersion(),
293
+ reproducibleCommand
294
+ };
295
+ history.entries.unshift(entry);
296
+ if (history.entries.length > 100) history.entries = history.entries.slice(0, 100);
297
+ return await writeHistory(history);
298
+ }
299
+ async function getHistory(limit = 10) {
300
+ const historyResult = await readHistory();
301
+ if (historyResult.isErr()) return historyResult;
302
+ return Result.ok(historyResult.value.entries.slice(0, limit));
303
+ }
304
+ async function clearHistory() {
305
+ const historyPath = getHistoryPath();
306
+ return Result.tryPromise({
307
+ try: async () => {
308
+ if (await fs.pathExists(historyPath)) await fs.remove(historyPath);
309
+ },
310
+ catch: (e) => new HistoryError({
311
+ message: `Failed to clear history: ${e instanceof Error ? e.message : String(e)}`,
312
+ cause: e
313
+ })
314
+ });
315
+ }
316
+
317
+ //#endregion
318
+ //#region src/utils/render-title.ts
319
+ const TITLE_TEXT = `
320
+ ██████╗ ███████╗████████╗████████╗███████╗██████╗
321
+ ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
322
+ ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
323
+ ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
324
+ ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
325
+ ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
326
+
327
+ ████████╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
328
+ ╚══██╔══╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
329
+ ██║ ███████╗ ██║ ███████║██║ █████╔╝
330
+ ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
331
+ ██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
332
+ ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
333
+ `;
334
+ const catppuccinTheme = {
335
+ pink: "#F5C2E7",
336
+ mauve: "#CBA6F7",
337
+ red: "#F38BA8",
338
+ maroon: "#E78284",
339
+ peach: "#FAB387",
340
+ yellow: "#F9E2AF",
341
+ green: "#A6E3A1",
342
+ teal: "#94E2D5",
343
+ sky: "#89DCEB",
344
+ sapphire: "#74C7EC",
345
+ lavender: "#B4BEFE"
346
+ };
347
+ const renderTitle = () => {
348
+ const terminalWidth = process.stdout.columns || 80;
349
+ const titleLines = TITLE_TEXT.split("\n");
350
+ if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) console.log(gradient(Object.values(catppuccinTheme)).multiline(`Better T Stack`));
351
+ else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
352
+ };
353
+
354
+ //#endregion
355
+ //#region src/commands/history.ts
356
+ function formatStackSummary(entry) {
357
+ const parts = [];
358
+ if (entry.stack.frontend.length > 0 && !entry.stack.frontend.includes("none")) parts.push(entry.stack.frontend.join(", "));
359
+ if (entry.stack.backend && entry.stack.backend !== "none") parts.push(entry.stack.backend);
360
+ if (entry.stack.database && entry.stack.database !== "none") parts.push(entry.stack.database);
361
+ if (entry.stack.orm && entry.stack.orm !== "none") parts.push(entry.stack.orm);
362
+ return parts.length > 0 ? parts.join(" + ") : "minimal";
363
+ }
364
+ function formatDate(isoString) {
365
+ return new Date(isoString).toLocaleDateString("en-US", {
366
+ year: "numeric",
367
+ month: "short",
368
+ day: "numeric",
369
+ hour: "2-digit",
370
+ minute: "2-digit"
371
+ });
372
+ }
373
+ async function historyHandler(input) {
374
+ if (input.clear) {
375
+ const clearResult = await clearHistory();
376
+ if (clearResult.isErr()) {
377
+ log.warn(pc.yellow(clearResult.error.message));
378
+ return;
379
+ }
380
+ log.success(pc.green("Project history cleared."));
381
+ return;
382
+ }
383
+ const historyResult = await getHistory(input.limit);
384
+ if (historyResult.isErr()) {
385
+ log.warn(pc.yellow(historyResult.error.message));
386
+ return;
387
+ }
388
+ const entries = historyResult.value;
389
+ if (entries.length === 0) {
390
+ log.info(pc.dim("No projects in history yet."));
391
+ log.info(pc.dim("Create a project with: create-better-t-stack my-app"));
392
+ return;
393
+ }
394
+ if (input.json) {
395
+ console.log(JSON.stringify(entries, null, 2));
396
+ return;
397
+ }
398
+ renderTitle();
399
+ intro(pc.magenta(`Project History (${entries.length} entries)`));
400
+ for (const [index, entry] of entries.entries()) {
401
+ const num = pc.dim(`${index + 1}.`);
402
+ const name = pc.cyan(pc.bold(entry.projectName));
403
+ const stack = pc.dim(formatStackSummary(entry));
404
+ log.message(`${num} ${name}`);
405
+ log.message(` ${pc.dim("Created:")} ${formatDate(entry.createdAt)}`);
406
+ log.message(` ${pc.dim("Path:")} ${entry.projectDir}`);
407
+ log.message(` ${pc.dim("Stack:")} ${stack}`);
408
+ log.message(` ${pc.dim("Command:")} ${pc.dim(entry.reproducibleCommand)}`);
409
+ log.message("");
410
+ }
411
+ }
412
+
413
+ //#endregion
414
+ //#region src/utils/open-url.ts
415
+ async function openUrl(url) {
416
+ const platform = process.platform;
417
+ if (platform === "darwin") {
418
+ await $({ stdio: "ignore" })`open ${url}`;
419
+ return;
420
+ }
421
+ if (platform === "win32") {
422
+ const escapedUrl = url.replace(/&/g, "^&");
423
+ await $({ stdio: "ignore" })`cmd /c start "" ${escapedUrl}`;
424
+ return;
425
+ }
426
+ await $({ stdio: "ignore" })`xdg-open ${url}`;
427
+ }
428
+
429
+ //#endregion
430
+ //#region src/utils/sponsors.ts
431
+ const SPONSORS_JSON_URL = "https://sponsors.better-t-stack.dev/sponsors.json";
432
+ async function fetchSponsors(url = SPONSORS_JSON_URL) {
433
+ const s = spinner();
434
+ s.start("Fetching sponsors…");
435
+ const response = await fetch(url);
436
+ if (!response.ok) {
437
+ s.stop(pc.red(`Failed to fetch sponsors: ${response.statusText}`));
438
+ throw new Error(`Failed to fetch sponsors: ${response.statusText}`);
439
+ }
440
+ const sponsors = await response.json();
441
+ s.stop("Sponsors fetched successfully!");
442
+ return sponsors;
443
+ }
444
+ function displaySponsors(sponsors) {
445
+ const { total_sponsors } = sponsors.summary;
446
+ if (total_sponsors === 0) {
447
+ log.info("No sponsors found. You can be the first one! ✨");
448
+ outro(pc.cyan("Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor."));
449
+ return;
450
+ }
451
+ displaySponsorsBox(sponsors);
452
+ if (total_sponsors - sponsors.specialSponsors.length > 0) log.message(pc.blue(`+${total_sponsors - sponsors.specialSponsors.length} more amazing sponsors.\n`));
453
+ outro(pc.magenta("Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor."));
454
+ }
455
+ function displaySponsorsBox(sponsors) {
456
+ if (sponsors.specialSponsors.length === 0) return;
457
+ let output = `${pc.bold(pc.cyan("-> Special Sponsors"))}\n\n`;
458
+ sponsors.specialSponsors.forEach((sponsor, idx) => {
459
+ const displayName = sponsor.name ?? sponsor.githubId;
460
+ const tier = sponsor.tierName ? ` ${pc.yellow(`(${sponsor.tierName})`)}` : "";
461
+ output += `${pc.green(`• ${displayName}`)}${tier}\n`;
462
+ output += ` ${pc.dim("GitHub:")} https://github.com/${sponsor.githubId}\n`;
463
+ const website = sponsor.websiteUrl ?? sponsor.githubUrl;
464
+ if (website) output += ` ${pc.dim("Website:")} ${website}\n`;
465
+ if (idx < sponsors.specialSponsors.length - 1) output += "\n";
466
+ });
467
+ consola$1.box(output);
468
+ }
469
+
470
+ //#endregion
471
+ //#region src/commands/meta.ts
472
+ const DOCS_URL = "https://better-t-stack.dev/docs";
473
+ const BUILDER_URL = "https://better-t-stack.dev/new";
474
+ async function openExternalUrl(url, successMessage) {
475
+ if ((await Result.tryPromise({
476
+ try: () => openUrl(url),
477
+ catch: () => null
478
+ })).isOk()) log.success(pc.blue(successMessage));
479
+ else log.message(`Please visit ${url}`);
480
+ }
481
+ async function showSponsorsCommand() {
482
+ const result = await Result.tryPromise({
483
+ try: async () => {
484
+ renderTitle();
485
+ intro(pc.magenta("Better-T-Stack Sponsors"));
486
+ displaySponsors(await fetchSponsors());
487
+ },
488
+ catch: (error) => new CLIError({
489
+ message: error instanceof Error ? error.message : "Failed to display sponsors",
490
+ cause: error
491
+ })
492
+ });
493
+ if (result.isErr()) {
494
+ displayError(result.error);
495
+ process.exit(1);
496
+ }
497
+ }
498
+ async function openDocsCommand() {
499
+ await openExternalUrl(DOCS_URL, "Opened docs in your default browser.");
500
+ }
501
+ async function openBuilderCommand() {
502
+ await openExternalUrl(BUILDER_URL, "Opened builder in your default browser.");
503
+ }
504
+
505
+ //#endregion
506
+ //#region src/types.ts
507
+ var types_exports = {};
508
+ import * as import__better_t_stack_types from "@better-t-stack/types";
509
+ __reExport(types_exports, import__better_t_stack_types);
510
+
511
+ //#endregion
512
+ //#region src/utils/compatibility.ts
513
+ const WEB_FRAMEWORKS = [
514
+ "tanstack-router",
515
+ "react-router",
516
+ "tanstack-start",
517
+ "next",
518
+ "nuxt",
519
+ "svelte",
520
+ "solid",
521
+ "astro"
522
+ ];
523
+
205
524
  //#endregion
206
525
  //#region src/utils/compatibility-rules.ts
207
526
  function validationErr$1(message) {
@@ -845,43 +1164,6 @@ async function updateBtsConfig(projectDir, updates) {
845
1164
  } catch {}
846
1165
  }
847
1166
 
848
- //#endregion
849
- //#region src/utils/render-title.ts
850
- const TITLE_TEXT = `
851
- ██████╗ ███████╗████████╗████████╗███████╗██████╗
852
- ██╔══██╗██╔════╝╚══██╔══╝╚══██╔══╝██╔════╝██╔══██╗
853
- ██████╔╝█████╗ ██║ ██║ █████╗ ██████╔╝
854
- ██╔══██╗██╔══╝ ██║ ██║ ██╔══╝ ██╔══██╗
855
- ██████╔╝███████╗ ██║ ██║ ███████╗██║ ██║
856
- ╚═════╝ ╚══════╝ ╚═╝ ╚═╝ ╚══════╝╚═╝ ╚═╝
857
-
858
- ████████╗ ███████╗████████╗ █████╗ ██████╗██╗ ██╗
859
- ╚══██╔══╝ ██╔════╝╚══██╔══╝██╔══██╗██╔════╝██║ ██╔╝
860
- ██║ ███████╗ ██║ ███████║██║ █████╔╝
861
- ██║ ╚════██║ ██║ ██╔══██║██║ ██╔═██╗
862
- ██║ ███████║ ██║ ██║ ██║╚██████╗██║ ██╗
863
- ╚═╝ ╚══════╝ ╚═╝ ╚═╝ ╚═╝ ╚═════╝╚═╝ ╚═╝
864
- `;
865
- const catppuccinTheme = {
866
- pink: "#F5C2E7",
867
- mauve: "#CBA6F7",
868
- red: "#F38BA8",
869
- maroon: "#E78284",
870
- peach: "#FAB387",
871
- yellow: "#F9E2AF",
872
- green: "#A6E3A1",
873
- teal: "#94E2D5",
874
- sky: "#89DCEB",
875
- sapphire: "#74C7EC",
876
- lavender: "#B4BEFE"
877
- };
878
- const renderTitle = () => {
879
- const terminalWidth = process.stdout.columns || 80;
880
- const titleLines = TITLE_TEXT.split("\n");
881
- if (terminalWidth < Math.max(...titleLines.map((line) => line.length))) console.log(gradient(Object.values(catppuccinTheme)).multiline(`Better T Stack`));
882
- else console.log(gradient(Object.values(catppuccinTheme)).multiline(TITLE_TEXT));
883
- };
884
-
885
1167
  //#endregion
886
1168
  //#region src/utils/add-package-deps.ts
887
1169
  const addPackageDependency = async (opts) => {
@@ -3041,24 +3323,6 @@ async function getProjectName(initialName) {
3041
3323
  return projectPath;
3042
3324
  }
3043
3325
 
3044
- //#endregion
3045
- //#region src/utils/get-latest-cli-version.ts
3046
- function getLatestCLIVersionResult() {
3047
- const packageJsonPath = path.join(PKG_ROOT, "package.json");
3048
- return Result.try({
3049
- try: () => {
3050
- return fs.readJSONSync(packageJsonPath).version;
3051
- },
3052
- catch: (e) => new CLIError({
3053
- message: `Failed to read CLI version from package.json: ${e instanceof Error ? e.message : String(e)}`,
3054
- cause: e
3055
- })
3056
- });
3057
- }
3058
- function getLatestCLIVersion() {
3059
- return getLatestCLIVersionResult().unwrapOr("1.0.0");
3060
- }
3061
-
3062
3326
  //#endregion
3063
3327
  //#region src/utils/telemetry.ts
3064
3328
  /**
@@ -3233,119 +3497,6 @@ async function setupProjectDirectory(finalPathInput, shouldClearDirectory) {
3233
3497
  };
3234
3498
  }
3235
3499
 
3236
- //#endregion
3237
- //#region src/utils/project-history.ts
3238
- const paths = envPaths("better-t-stack", { suffix: "" });
3239
- const HISTORY_FILE = "history.json";
3240
- var HistoryError = class extends TaggedError("HistoryError")() {};
3241
- function getHistoryDir() {
3242
- return paths.data;
3243
- }
3244
- function getHistoryPath() {
3245
- return path.join(paths.data, HISTORY_FILE);
3246
- }
3247
- function generateId() {
3248
- return `${Date.now()}-${Math.random().toString(36).substring(2, 9)}`;
3249
- }
3250
- function emptyHistory() {
3251
- return {
3252
- version: 1,
3253
- entries: []
3254
- };
3255
- }
3256
- async function ensureHistoryDir() {
3257
- return Result.tryPromise({
3258
- try: async () => {
3259
- await fs.ensureDir(getHistoryDir());
3260
- },
3261
- catch: (e) => new HistoryError({
3262
- message: `Failed to create history directory: ${e instanceof Error ? e.message : String(e)}`,
3263
- cause: e
3264
- })
3265
- });
3266
- }
3267
- async function readHistory() {
3268
- const historyPath = getHistoryPath();
3269
- const existsResult = await Result.tryPromise({
3270
- try: async () => await fs.pathExists(historyPath),
3271
- catch: (e) => new HistoryError({
3272
- message: `Failed to check history file: ${e instanceof Error ? e.message : String(e)}`,
3273
- cause: e
3274
- })
3275
- });
3276
- if (existsResult.isErr()) return existsResult;
3277
- if (!existsResult.value) return Result.ok(emptyHistory());
3278
- const readResult = await Result.tryPromise({
3279
- try: async () => await fs.readJson(historyPath),
3280
- catch: (e) => new HistoryError({
3281
- message: `Failed to read history file: ${e instanceof Error ? e.message : String(e)}`,
3282
- cause: e
3283
- })
3284
- });
3285
- if (readResult.isErr()) return Result.ok(emptyHistory());
3286
- return Result.ok(readResult.value);
3287
- }
3288
- async function writeHistory(history) {
3289
- const ensureDirResult = await ensureHistoryDir();
3290
- if (ensureDirResult.isErr()) return ensureDirResult;
3291
- return Result.tryPromise({
3292
- try: async () => {
3293
- await fs.writeJson(getHistoryPath(), history, { spaces: 2 });
3294
- },
3295
- catch: (e) => new HistoryError({
3296
- message: `Failed to write history file: ${e instanceof Error ? e.message : String(e)}`,
3297
- cause: e
3298
- })
3299
- });
3300
- }
3301
- async function addToHistory(config, reproducibleCommand) {
3302
- const historyResult = await readHistory();
3303
- if (historyResult.isErr()) return historyResult;
3304
- const history = historyResult.value;
3305
- const entry = {
3306
- id: generateId(),
3307
- projectName: config.projectName,
3308
- projectDir: config.projectDir,
3309
- createdAt: (/* @__PURE__ */ new Date()).toISOString(),
3310
- stack: {
3311
- frontend: config.frontend,
3312
- backend: config.backend,
3313
- database: config.database,
3314
- orm: config.orm,
3315
- runtime: config.runtime,
3316
- auth: config.auth,
3317
- payments: config.payments,
3318
- api: config.api,
3319
- addons: config.addons,
3320
- examples: config.examples,
3321
- dbSetup: config.dbSetup,
3322
- packageManager: config.packageManager
3323
- },
3324
- cliVersion: getLatestCLIVersion(),
3325
- reproducibleCommand
3326
- };
3327
- history.entries.unshift(entry);
3328
- if (history.entries.length > 100) history.entries = history.entries.slice(0, 100);
3329
- return await writeHistory(history);
3330
- }
3331
- async function getHistory(limit = 10) {
3332
- const historyResult = await readHistory();
3333
- if (historyResult.isErr()) return historyResult;
3334
- return Result.ok(historyResult.value.entries.slice(0, limit));
3335
- }
3336
- async function clearHistory() {
3337
- const historyPath = getHistoryPath();
3338
- return Result.tryPromise({
3339
- try: async () => {
3340
- if (await fs.pathExists(historyPath)) await fs.remove(historyPath);
3341
- },
3342
- catch: (e) => new HistoryError({
3343
- message: `Failed to clear history: ${e instanceof Error ? e.message : String(e)}`,
3344
- cause: e
3345
- })
3346
- });
3347
- }
3348
-
3349
3500
  //#endregion
3350
3501
  //#region src/utils/project-name-validation.ts
3351
3502
  function validateProjectName(name) {
@@ -5072,11 +5223,12 @@ async function displayPostInstallInstructions(config) {
5072
5223
  "solid"
5073
5224
  ].includes(f));
5074
5225
  const hasNative = frontend?.includes("native-bare") || frontend?.includes("native-uniwind") || frontend?.includes("native-unistyles");
5075
- const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
5076
- const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
5077
5226
  const hasReactRouter = frontend?.includes("react-router");
5078
5227
  const hasSvelte = frontend?.includes("svelte");
5079
5228
  const webPort = hasReactRouter || hasSvelte ? "5173" : "3001";
5229
+ const betterAuthConvexInstructions = isConvex && config.auth === "better-auth" ? getBetterAuthConvexInstructions(hasWeb ?? false, webPort, packageManager) : "";
5230
+ const bunWebNativeWarning = packageManager === "bun" && hasNative && hasWeb ? getBunWebNativeWarning() : "";
5231
+ const noOrmWarning = !isConvex && database !== "none" && orm === "none" ? getNoOrmWarning() : "";
5080
5232
  let output = `${pc.bold("Next steps")}\n${pc.cyan("1.")} ${cdCmd}\n`;
5081
5233
  let stepCounter = 2;
5082
5234
  if (!depsInstalled) output += `${pc.cyan(`${stepCounter++}.`)} ${packageManager} install\n`;
@@ -5116,6 +5268,7 @@ async function displayPostInstallInstructions(config) {
5116
5268
  if (alchemyDeployInstructions) output += `\n${alchemyDeployInstructions.trim()}\n`;
5117
5269
  if (starlightInstructions) output += `\n${starlightInstructions.trim()}\n`;
5118
5270
  if (clerkInstructions) output += `\n${clerkInstructions.trim()}\n`;
5271
+ if (betterAuthConvexInstructions) output += `\n${betterAuthConvexInstructions.trim()}\n`;
5119
5272
  if (polarInstructions) output += `\n${polarInstructions.trim()}\n`;
5120
5273
  if (noOrmWarning) output += `\n${noOrmWarning.trim()}\n`;
5121
5274
  if (bunWebNativeWarning) output += `\n${bunWebNativeWarning.trim()}\n`;
@@ -5199,6 +5352,10 @@ function getBunWebNativeWarning() {
5199
5352
  function getClerkInstructions() {
5200
5353
  return `${pc.bold("Clerk Authentication Setup:")}\n${pc.cyan("•")} Follow the guide: ${pc.underline("https://docs.convex.dev/auth/clerk")}\n${pc.cyan("•")} Set CLERK_JWT_ISSUER_DOMAIN in Convex Dashboard\n${pc.cyan("•")} Set CLERK_PUBLISHABLE_KEY in apps/*/.env`;
5201
5354
  }
5355
+ function getBetterAuthConvexInstructions(hasWeb, webPort, packageManager) {
5356
+ const cmd = packageManager === "npm" ? "npx" : packageManager;
5357
+ return `${pc.bold("Better Auth + Convex Setup:")}\n${pc.cyan("•")} Set environment variables from ${pc.white("packages/backend")}:\n${pc.white(" cd packages/backend")}\n${pc.white(` ${cmd} convex env set BETTER_AUTH_SECRET=$(openssl rand -base64 32)`)}\n` + (hasWeb ? `${pc.white(` ${cmd} convex env set SITE_URL http://localhost:${webPort}`)}\n` : "");
5358
+ }
5202
5359
  function getPolarInstructions(backend) {
5203
5360
  const envPath = backend === "self" ? "apps/web/.env" : "apps/server/.env";
5204
5361
  return `${pc.bold("Polar Payments Setup:")}\n${pc.cyan("•")} Get access token & product ID from ${pc.underline("https://sandbox.polar.sh/")}\n${pc.cyan("•")} Set POLAR_ACCESS_TOKEN in ${envPath}`;
@@ -5560,121 +5717,8 @@ async function handleDirectoryConflictProgrammatically(currentPathInput, strateg
5560
5717
  }
5561
5718
  }
5562
5719
 
5563
- //#endregion
5564
- //#region src/utils/open-url.ts
5565
- async function openUrl(url) {
5566
- const platform = process.platform;
5567
- if ((await Result.tryPromise({
5568
- try: async () => {
5569
- if (platform === "darwin") await $({ stdio: "ignore" })`open ${url}`;
5570
- else if (platform === "win32") {
5571
- const escapedUrl = url.replace(/&/g, "^&");
5572
- await $({ stdio: "ignore" })`cmd /c start "" ${escapedUrl}`;
5573
- } else await $({ stdio: "ignore" })`xdg-open ${url}`;
5574
- },
5575
- catch: () => void 0
5576
- })).isErr()) log.message(`Please open ${url} in your browser.`);
5577
- }
5578
-
5579
- //#endregion
5580
- //#region src/utils/sponsors.ts
5581
- const SPONSORS_JSON_URL = "https://sponsors.better-t-stack.dev/sponsors.json";
5582
- async function fetchSponsors(url = SPONSORS_JSON_URL) {
5583
- const s = spinner();
5584
- s.start("Fetching sponsors…");
5585
- const response = await fetch(url);
5586
- if (!response.ok) {
5587
- s.stop(pc.red(`Failed to fetch sponsors: ${response.statusText}`));
5588
- throw new Error(`Failed to fetch sponsors: ${response.statusText}`);
5589
- }
5590
- const sponsors = await response.json();
5591
- s.stop("Sponsors fetched successfully!");
5592
- return sponsors;
5593
- }
5594
- function displaySponsors(sponsors) {
5595
- const { total_sponsors } = sponsors.summary;
5596
- if (total_sponsors === 0) {
5597
- log.info("No sponsors found. You can be the first one! ✨");
5598
- outro(pc.cyan("Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor."));
5599
- return;
5600
- }
5601
- displaySponsorsBox(sponsors);
5602
- if (total_sponsors - sponsors.specialSponsors.length > 0) log.message(pc.blue(`+${total_sponsors - sponsors.specialSponsors.length} more amazing sponsors.\n`));
5603
- outro(pc.magenta("Visit https://github.com/sponsors/AmanVarshney01 to become a sponsor."));
5604
- }
5605
- function displaySponsorsBox(sponsors) {
5606
- if (sponsors.specialSponsors.length === 0) return;
5607
- let output = `${pc.bold(pc.cyan("-> Special Sponsors"))}\n\n`;
5608
- sponsors.specialSponsors.forEach((sponsor, idx) => {
5609
- const displayName = sponsor.name ?? sponsor.githubId;
5610
- const tier = sponsor.tierName ? ` ${pc.yellow(`(${sponsor.tierName})`)}` : "";
5611
- output += `${pc.green(`• ${displayName}`)}${tier}\n`;
5612
- output += ` ${pc.dim("GitHub:")} https://github.com/${sponsor.githubId}\n`;
5613
- const website = sponsor.websiteUrl ?? sponsor.githubUrl;
5614
- if (website) output += ` ${pc.dim("Website:")} ${website}\n`;
5615
- if (idx < sponsors.specialSponsors.length - 1) output += "\n";
5616
- });
5617
- consola$1.box(output);
5618
- }
5619
-
5620
5720
  //#endregion
5621
5721
  //#region src/index.ts
5622
- function formatStackSummary(entry) {
5623
- const parts = [];
5624
- if (entry.stack.frontend.length > 0 && !entry.stack.frontend.includes("none")) parts.push(entry.stack.frontend.join(", "));
5625
- if (entry.stack.backend && entry.stack.backend !== "none") parts.push(entry.stack.backend);
5626
- if (entry.stack.database && entry.stack.database !== "none") parts.push(entry.stack.database);
5627
- if (entry.stack.orm && entry.stack.orm !== "none") parts.push(entry.stack.orm);
5628
- return parts.length > 0 ? parts.join(" + ") : "minimal";
5629
- }
5630
- function formatDate(isoString) {
5631
- return new Date(isoString).toLocaleDateString("en-US", {
5632
- year: "numeric",
5633
- month: "short",
5634
- day: "numeric",
5635
- hour: "2-digit",
5636
- minute: "2-digit"
5637
- });
5638
- }
5639
- async function historyHandler(input) {
5640
- if (input.clear) {
5641
- const clearResult = await clearHistory();
5642
- if (clearResult.isErr()) {
5643
- log.warn(pc.yellow(clearResult.error.message));
5644
- return;
5645
- }
5646
- log.success(pc.green("Project history cleared."));
5647
- return;
5648
- }
5649
- const historyResult = await getHistory(input.limit);
5650
- if (historyResult.isErr()) {
5651
- log.warn(pc.yellow(historyResult.error.message));
5652
- return;
5653
- }
5654
- const entries = historyResult.value;
5655
- if (entries.length === 0) {
5656
- log.info(pc.dim("No projects in history yet."));
5657
- log.info(pc.dim("Create a project with: create-better-t-stack my-app"));
5658
- return;
5659
- }
5660
- if (input.json) {
5661
- console.log(JSON.stringify(entries, null, 2));
5662
- return;
5663
- }
5664
- renderTitle();
5665
- intro(pc.magenta(`Project History (${entries.length} entries)`));
5666
- for (const [index, entry] of entries.entries()) {
5667
- const num = pc.dim(`${index + 1}.`);
5668
- const name = pc.cyan(pc.bold(entry.projectName));
5669
- const stack = pc.dim(formatStackSummary(entry));
5670
- log.message(`${num} ${name}`);
5671
- log.message(` ${pc.dim("Created:")} ${formatDate(entry.createdAt)}`);
5672
- log.message(` ${pc.dim("Path:")} ${entry.projectDir}`);
5673
- log.message(` ${pc.dim("Stack:")} ${stack}`);
5674
- log.message(` ${pc.dim("Command:")} ${pc.dim(entry.reproducibleCommand)}`);
5675
- log.message("");
5676
- }
5677
- }
5678
5722
  const router = os.router({
5679
5723
  create: os.meta({
5680
5724
  description: "Create a new Better-T-Stack project",
@@ -5713,39 +5757,9 @@ const router = os.router({
5713
5757
  });
5714
5758
  if (options.verbose) return result;
5715
5759
  }),
5716
- sponsors: os.meta({ description: "Show Better-T-Stack sponsors" }).handler(async () => {
5717
- const result = await Result.tryPromise({
5718
- try: async () => {
5719
- renderTitle();
5720
- intro(pc.magenta("Better-T-Stack Sponsors"));
5721
- displaySponsors(await fetchSponsors());
5722
- },
5723
- catch: (e) => new CLIError({
5724
- message: e instanceof Error ? e.message : "Failed to display sponsors",
5725
- cause: e
5726
- })
5727
- });
5728
- if (result.isErr()) {
5729
- displayError(result.error);
5730
- process.exit(1);
5731
- }
5732
- }),
5733
- docs: os.meta({ description: "Open Better-T-Stack documentation" }).handler(async () => {
5734
- const DOCS_URL = "https://better-t-stack.dev/docs";
5735
- if ((await Result.tryPromise({
5736
- try: () => openUrl(DOCS_URL),
5737
- catch: () => null
5738
- })).isOk()) log.success(pc.blue("Opened docs in your default browser."));
5739
- else log.message(`Please visit ${DOCS_URL}`);
5740
- }),
5741
- builder: os.meta({ description: "Open the web-based stack builder" }).handler(async () => {
5742
- const BUILDER_URL = "https://better-t-stack.dev/new";
5743
- if ((await Result.tryPromise({
5744
- try: () => openUrl(BUILDER_URL),
5745
- catch: () => null
5746
- })).isOk()) log.success(pc.blue("Opened builder in your default browser."));
5747
- else log.message(`Please visit ${BUILDER_URL}`);
5748
- }),
5760
+ sponsors: os.meta({ description: "Show Better-T-Stack sponsors" }).handler(showSponsorsCommand),
5761
+ docs: os.meta({ description: "Open Better-T-Stack documentation" }).handler(openDocsCommand),
5762
+ builder: os.meta({ description: "Open the web-based stack builder" }).handler(openBuilderCommand),
5749
5763
  add: os.meta({ description: "Add addons to an existing Better-T-Stack project" }).input(z.object({
5750
5764
  addons: z.array(types_exports.AddonsSchema).optional().describe("Addons to add"),
5751
5765
  install: z.boolean().optional().default(false).describe("Install dependencies after adding"),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-better-t-stack",
3
- "version": "3.19.4",
3
+ "version": "3.19.5",
4
4
  "description": "A modern CLI tool for scaffolding end-to-end type-safe TypeScript projects with best practices and customizable configurations",
5
5
  "keywords": [
6
6
  "better-auth",
@@ -70,8 +70,8 @@
70
70
  "prepublishOnly": "npm run build"
71
71
  },
72
72
  "dependencies": {
73
- "@better-t-stack/template-generator": "^3.19.4",
74
- "@better-t-stack/types": "^3.19.4",
73
+ "@better-t-stack/template-generator": "^3.19.5",
74
+ "@better-t-stack/types": "^3.19.5",
75
75
  "@clack/core": "^1.0.0",
76
76
  "@clack/prompts": "^1.0.0",
77
77
  "@orpc/server": "^1.13.4",