create-tina-app 2.0.0 → 2.1.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/dist/index.js CHANGED
@@ -1,7 +1,6 @@
1
1
  // src/index.ts
2
- import { Telemetry } from "@tinacms/metrics";
3
2
  import prompts from "prompts";
4
- import path4 from "node:path";
3
+ import path3 from "node:path";
5
4
  import { createRequire } from "node:module";
6
5
 
7
6
  // src/util/fileUtil.ts
@@ -20,6 +19,16 @@ var TextStyles = {
20
19
  err: chalk.red,
21
20
  bold: chalk.bold
22
21
  };
22
+ var TextStylesBold = {
23
+ tinaOrange: chalk.hex("#EC4816").bold,
24
+ link: (url) => `\x1B]8;;${url}\x07${chalk.cyan.underline(url)}\x1B]8;;\x07`,
25
+ cmd: chalk.bgBlackBright.bold.white,
26
+ info: chalk.blue,
27
+ success: chalk.green,
28
+ warn: chalk.yellow,
29
+ err: chalk.red,
30
+ bold: chalk.bold
31
+ };
23
32
 
24
33
  // src/util/fileUtil.ts
25
34
  async function isWriteable(directory) {
@@ -125,7 +134,6 @@ function install(packageManager, verboseOutput) {
125
134
 
126
135
  // src/util/git.ts
127
136
  import { execSync } from "child_process";
128
- import path2 from "path";
129
137
  import fs2 from "fs-extra";
130
138
  function isInGitRepository() {
131
139
  try {
@@ -144,16 +152,11 @@ function isInMercurialRepository() {
144
152
  return false;
145
153
  }
146
154
  function makeFirstCommit(root) {
147
- try {
148
- execSync("git checkout -b main", { stdio: "ignore" });
149
- execSync("git add -A", { stdio: "ignore" });
150
- execSync('git commit -m "Initial commit from Create Tina App"', {
151
- stdio: "ignore"
152
- });
153
- } catch (err) {
154
- fs2.removeSync(path2.join(root, ".git"));
155
- throw err;
156
- }
155
+ execSync("git checkout -b main", { stdio: "ignore" });
156
+ execSync("git add -A", { stdio: "ignore" });
157
+ execSync('git commit -m "Initial commit from Create Tina App"', {
158
+ stdio: "ignore"
159
+ });
157
160
  }
158
161
  function initializeGit(spinner) {
159
162
  execSync("git --version", { stdio: "ignore" });
@@ -236,13 +239,27 @@ async function downloadAndExtractRepo(root, { username, name: name2, branch, fil
236
239
 
237
240
  // src/templates.ts
238
241
  import { copy } from "fs-extra";
239
- import path3 from "path";
242
+ import path2 from "path";
240
243
  var TEMPLATES = [
241
244
  {
242
245
  title: "\u2B50 NextJS starter",
243
246
  description: "Kickstart your project with Next.js \u2013 our top recommendation for a seamless, performant, and versatile web experience.",
244
247
  value: "tina-nextjs-starter",
245
248
  isInternal: false,
249
+ features: [
250
+ {
251
+ name: "Visual Editing",
252
+ description: "\u2705"
253
+ },
254
+ {
255
+ name: "ISR",
256
+ description: "\u2705"
257
+ },
258
+ {
259
+ name: "SSG",
260
+ description: "\u2705"
261
+ }
262
+ ],
246
263
  gitURL: "https://github.com/tinacms/tina-nextjs-starter",
247
264
  devUrl: "http://localhost:3000"
248
265
  },
@@ -251,6 +268,20 @@ var TEMPLATES = [
251
268
  description: "Get your documentation site up and running with TinaCMS and Next.js in minutes.",
252
269
  value: "tina-docs",
253
270
  isInternal: false,
271
+ features: [
272
+ {
273
+ name: "Visual Editing",
274
+ description: "\u2705"
275
+ },
276
+ {
277
+ name: "ISR",
278
+ description: "\u2705"
279
+ },
280
+ {
281
+ name: "SSG",
282
+ description: "\u2705"
283
+ }
284
+ ],
254
285
  gitURL: "https://github.com/tinacms/tina-docs",
255
286
  devUrl: "http://localhost:3000"
256
287
  },
@@ -259,6 +290,20 @@ var TEMPLATES = [
259
290
  description: "Get started with Astro - a modern static site generator designed for fast, lightweight, and flexible web projects.",
260
291
  value: "tina-astro-starter",
261
292
  isInternal: false,
293
+ features: [
294
+ {
295
+ name: "Visual Editing",
296
+ description: "\u274C"
297
+ },
298
+ {
299
+ name: "ISR",
300
+ description: "\u274C"
301
+ },
302
+ {
303
+ name: "SSG",
304
+ description: "\u2705"
305
+ }
306
+ ],
262
307
  gitURL: "https://github.com/tinacms/tina-astro-starter",
263
308
  devUrl: "http://localhost:4321"
264
309
  },
@@ -267,6 +312,20 @@ var TEMPLATES = [
267
312
  description: "With Hugo, you wield the power of lightning-fast site generation, crafting web experiences at the speed of thought.",
268
313
  value: "tina-hugo-starter",
269
314
  isInternal: false,
315
+ features: [
316
+ {
317
+ name: "Visual Editing",
318
+ description: "\u274C"
319
+ },
320
+ {
321
+ name: "ISR",
322
+ description: "\u274C"
323
+ },
324
+ {
325
+ name: "SSG",
326
+ description: "\u2705"
327
+ }
328
+ ],
270
329
  gitURL: "https://github.com/tinacms/tina-hugo-starter",
271
330
  devUrl: "http://localhost:1313"
272
331
  },
@@ -275,6 +334,20 @@ var TEMPLATES = [
275
334
  description: "Dive into Remix to orchestrate seamless, interactive user journeys like a maestro of the web.",
276
335
  value: "tina-remix-starter",
277
336
  isInternal: false,
337
+ features: [
338
+ {
339
+ name: "Visual Editing",
340
+ description: "\u274C"
341
+ },
342
+ {
343
+ name: "ISR",
344
+ description: "\u274C"
345
+ },
346
+ {
347
+ name: "SSG",
348
+ description: "\u26A0\uFE0F Requires adapter"
349
+ }
350
+ ],
278
351
  gitURL: "https://github.com/tinacms/tina-remix-starter",
279
352
  devUrl: "http://localhost:3000"
280
353
  },
@@ -283,14 +356,42 @@ var TEMPLATES = [
283
356
  description: "Docusaurus empowers you to build and evolve documentation like crafting a living, breathing knowledge repository.",
284
357
  value: "tinasaurus",
285
358
  isInternal: false,
359
+ features: [
360
+ {
361
+ name: "Visual Editing",
362
+ description: "\u274C"
363
+ },
364
+ {
365
+ name: "ISR",
366
+ description: "\u274C"
367
+ },
368
+ {
369
+ name: "SSR",
370
+ description: "\u2705"
371
+ }
372
+ ],
286
373
  gitURL: "https://github.com/tinacms/tinasaurus",
287
374
  devUrl: "http://localhost:3000"
288
375
  },
289
376
  {
290
377
  title: "Bare bones starter",
291
- description: "Stripped down to essentials, this starter is the canvas for pure, unadulterated code creativity.",
378
+ description: "Stripped down to essentials, this starter is the canvas for pure, unadulterated code creativity. Built with Next.js.",
292
379
  value: "basic",
293
380
  isInternal: false,
381
+ features: [
382
+ {
383
+ name: "Visual Editing",
384
+ description: "\u2705"
385
+ },
386
+ {
387
+ name: "ISR",
388
+ description: "\u2705"
389
+ },
390
+ {
391
+ name: "SSG",
392
+ description: "\u2705"
393
+ }
394
+ ],
294
395
  gitURL: "https://github.com/tinacms/tina-barebones-starter",
295
396
  devUrl: "http://localhost:3000"
296
397
  }
@@ -307,7 +408,7 @@ async function downloadTemplate(template, root, spinner) {
307
408
  )}`;
308
409
  await downloadAndExtractRepo(root, repoInfo);
309
410
  } else {
310
- const templateFile = path3.join(__dirname, "..", "examples", template.value);
411
+ const templateFile = path2.join(__dirname, "..", "examples", template.value);
311
412
  await copy(`${templateFile}/`, "./");
312
413
  }
313
414
  }
@@ -357,7 +458,7 @@ import { Command } from "commander";
357
458
 
358
459
  // package.json
359
460
  var name = "create-tina-app";
360
- var version = "2.0.0";
461
+ var version = "2.1.0";
361
462
 
362
463
  // src/util/packageManagers.ts
363
464
  var PKG_MANAGERS = ["npm", "yarn", "pnpm", "bun"];
@@ -391,11 +492,111 @@ function extractOptions(args) {
391
492
  return opts;
392
493
  }
393
494
 
394
- // src/index.ts
395
- import validate from "validate-npm-package-name";
495
+ // src/util/isNpm.js
496
+ import { builtinModules as builtins } from "node:module";
497
+ function validate(name2) {
498
+ if (name2 === null) {
499
+ return {
500
+ message: "name cannot be null",
501
+ isError: true
502
+ };
503
+ }
504
+ if (name2 === void 0) {
505
+ return {
506
+ message: "name cannot be undefined",
507
+ isError: true
508
+ };
509
+ }
510
+ if (typeof name2 !== "string") {
511
+ return {
512
+ message: "name must be a string",
513
+ isError: true
514
+ };
515
+ }
516
+ if (!name2.length) {
517
+ return {
518
+ message: "name length must be greater than zero",
519
+ isError: true
520
+ };
521
+ }
522
+ if (name2.startsWith(".")) {
523
+ return {
524
+ message: "name cannot start with a period",
525
+ isError: true
526
+ };
527
+ }
528
+ if (name2.match(/^_/)) {
529
+ return {
530
+ message: "name cannot start with an underscore",
531
+ isError: true
532
+ };
533
+ }
534
+ if (name2.trim() !== name2) {
535
+ return {
536
+ message: "name cannot contain leading or trailing spaces",
537
+ isError: true
538
+ };
539
+ }
540
+ const exclusionList = ["node_modules", "favicon.ico"];
541
+ exclusionList.forEach(function(excludedName) {
542
+ if (name2.toLowerCase() === excludedName) {
543
+ return {
544
+ message: excludedName + " is not a valid package name",
545
+ isError: true
546
+ };
547
+ }
548
+ });
549
+ if (builtins.includes(name2.toLowerCase())) {
550
+ return {
551
+ message: name2 + " is a core module name",
552
+ isError: true
553
+ };
554
+ }
555
+ if (name2.length > 214) {
556
+ return {
557
+ message: "name can no longer contain more than 214 characters",
558
+ isError: true
559
+ };
560
+ }
561
+ if (name2.toLowerCase() !== name2) {
562
+ return {
563
+ message: "name can no longer contain capital letters",
564
+ isError: true
565
+ };
566
+ }
567
+ if (/[~'!()*]/.test(name2.split("/").slice(-1)[0])) {
568
+ return {
569
+ message: `name can no longer contain special characters ("~'!()*")`,
570
+ isError: true
571
+ };
572
+ }
573
+ if (encodeURIComponent(name2) !== name2) {
574
+ const scopedPackagePattern = new RegExp("^(?:@([^/]+?)[/])?([^/]+?)$");
575
+ const nameMatch = name2.match(scopedPackagePattern);
576
+ if (nameMatch) {
577
+ const user = nameMatch[1];
578
+ const pkg = nameMatch[2];
579
+ if (pkg.startsWith(".")) {
580
+ return {
581
+ message: "name cannot start with a period",
582
+ isError: true
583
+ };
584
+ }
585
+ if (encodeURIComponent(user) === user && encodeURIComponent(pkg) === pkg) {
586
+ return { message: null, isError: false };
587
+ }
588
+ }
589
+ return {
590
+ message: "name can only contain URL-friendly characters",
591
+ isError: true
592
+ };
593
+ }
594
+ return { message: null, isError: false };
595
+ }
396
596
 
397
597
  // src/util/asciiArt.ts
398
598
  var llama = " :--=: \n :-===- \n -=====- \n -=======. \n .=========-. \n :===========--:\n -=============.\n .==========-:. \n :=========-. \n -=========- \n .==========- \n -==========- \n :===========- \n -=============. \n :==============: \n :===============- \n .:-================- \n ..::---==================== \n ....::::::::::-------============================. \n .---=================================================: \n .-=====================================================- \n:=======================================================. \n .-====================================================. \n .-=================================================. \n :=============================================- \n -============================================. \n .============-:. -==========- \n :=========-: .. -==========. \n -========: :-=- -=========- \n .========. .-==== :=========: \n -=======: :=====. -========: \n -======- -====- -=======: \n -=====: -====: :======. \n .=====. -====. .-====- \n :==== -===- -====: \n -==- :===- :====. ";
599
+ var errorArt = " ++++++++++++++\u2260 \n +++++++++\u2248 \u03C0+++++++++\u2260 \n ++++ ++++ \n +++ +++\u2248 \n +++ +++ \n \u03C0++ +++ \n ++ ++ \n +- +\u03C0 \n ++\xD7 +++++++++ \u2260+++++++++ \u221A+++++++++ ++ \n + ++ \u2248+++ ++ ++ ++++ +\u221E \n ++ ++ ++ ++ ++ ++ + \n ++ ++ ++ ++ ++ ++ ++ \n ++ ++-+++++++- ++ ++ +++++++- + \n \u221A+ ++\xF7+++++ ++ ++++++++ ++ \n ++ ++ \u221E++ ++ ++ ++ \n ++ ++ +++ ++ ++ ++ \n ++ ++ +++ \u2260+++++++++ ++ \u2260+ \n ++ ++ ++ +\u2260\u221E\u221A\u221A\u2260\xD7+\u03C0 ++ \u2260+ \n ++ \u2260+ \n ++ \u2260+ \n ++ +++ \u2260+ \n ++ ++++ \u2260+ \n ++ ++++++++ \u2260+ \n ++ ++++++++++ =+ \n ++ ++++++ =+ \n ++ \u2248++++++ =+ \n ++ +++++++ \u2260+ \n ++ +++++++ \u2260+ \n ++ +++++++ =+ \n ++ ++++++++ =+ \n ++ \u221E++++++++++++ \u2260+ \n ++ \u221E++++++++++++++++++++++++++++++- =+ \n ++ +++++++++++++++++++++++++++++++++ \u2260+ \n ++ +++++++++++++++++++++++++++++++++ \u2260+ \n ++ ++++++++++++++++++++++++++++++ \u2260+ \n ++ +++++++++++++++++++++++++++++ \u2260+ \n ++ ++++++++++++++++++++++++++++ \u2260+ \n ++ ++++++++\u2260\u2260++++++=\u221A ++++++++ \u2260+ \n ++ ++++++ + +++++++ \u2260+ \n ++ +++++ \u221E+++ \xF7++++++ \u2260+ \n ++ ++++ +++ +++++\u221A \u2260+ \n ++ +++= +++\u221A ++++ \u2260+ \n ++ ++\u221E =++ +++ \u2260+ \n ++ ++\u2248 +++ ++\xF7 =+ \n ++ +++ +++\u2260 +++ =+ \n ++ \u2260+ \n ++ + \n \u2248+++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ \n ++ +\u2260 \n ++ +++++++++ ++++++++ +++++++ ++++++ ++++++++ +\u2260 \n ++ ++ ++ ++ + ++ ++ ++ ++ ++ +\u2260 \n ++ ++ ++ ++ + ++ ++ ++ ++ ++ +\u2260 \n ++ ++++++++ ++++++++ ++++++++ ++ ++ ++++++++ +\u2260 \n ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\u2260 \n ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ +\u2260 \n ++ +++++++++ ++ ++ ++ ++ +++++++ ++ ++ +\u2260 \n ++ +\u2260 \n ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ \n \n ";
399
600
  var tinaCms = "\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2557\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n\u255A\u2550\u2550\u2588\u2588\u2554\u2550\u2550\u255D\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2557\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\u2588\u2588\u2588\u2588\u2557 \u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2550\u2550\u255D\n \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2554\u2588\u2588\u2557 \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2554\u2588\u2588\u2588\u2588\u2554\u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2557\n \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551\u255A\u2588\u2588\u2557\u2588\u2588\u2551\u2588\u2588\u2554\u2550\u2550\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2554\u255D\u2588\u2588\u2551\u255A\u2550\u2550\u2550\u2550\u2588\u2588\u2551\n \u2588\u2588\u2551 \u2588\u2588\u2551\u2588\u2588\u2551 \u255A\u2588\u2588\u2588\u2588\u2551\u2588\u2588\u2551 \u2588\u2588\u2551\u255A\u2588\u2588\u2588\u2588\u2588\u2588\u2557\u2588\u2588\u2551 \u255A\u2550\u255D \u2588\u2588\u2551\u2588\u2588\u2588\u2588\u2588\u2588\u2588\u2551\n \u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D \u255A\u2550\u2550\u2550\u2550\u2550\u255D\u255A\u2550\u255D \u255A\u2550\u255D\u255A\u2550\u2550\u2550\u2550\u2550\u2550\u255D";
400
601
 
401
602
  // src/themes.ts
@@ -433,9 +634,279 @@ var THEMES = [
433
634
  ];
434
635
 
435
636
  // src/index.ts
637
+ import { PostHog } from "posthog-node";
638
+
639
+ // src/util/posthog.ts
640
+ import { createHash, randomUUID } from "node:crypto";
641
+ import { system as getSystemInfo } from "systeminformation";
642
+ var CreateTinaAppStartedEvent = "create-tina-app-started";
643
+ var CreateTinaAppFinishedEvent = "create-tina-app-finished";
644
+ var TRACKING_STEPS = {
645
+ INIT: "initializing",
646
+ PRE_RUN_CHECKS: "pre_run_checks",
647
+ TELEMETRY_SETUP: "telemetry_setup",
648
+ PKG_MANAGER_SELECT: "package_manager_selection",
649
+ PROJECT_NAME_INPUT: "project_name_input",
650
+ TEMPLATE_SELECT: "template_selection",
651
+ THEME_SELECT: "theme_selection",
652
+ DIRECTORY_SETUP: "directory_setup",
653
+ DOWNLOADING_TEMPLATE: "downloading_template",
654
+ UPDATING_METADATA: "updating_metadata",
655
+ INSTALLING_PACKAGES: "installing_packages",
656
+ GIT_INIT: "git_initialization",
657
+ COMPLETE: "complete"
658
+ };
659
+ function generateSessionId() {
660
+ return randomUUID();
661
+ }
662
+ async function getAnonymousUserId() {
663
+ try {
664
+ const sysInfo = await getSystemInfo();
665
+ const systemUuid = sysInfo.uuid || "unknown";
666
+ if (systemUuid === "unknown" || !systemUuid) {
667
+ return `fallback-${randomUUID()}`;
668
+ }
669
+ const hash = createHash("sha256");
670
+ hash.update(systemUuid);
671
+ return hash.digest("hex").substring(0, 32);
672
+ } catch (error) {
673
+ return `fallback-${randomUUID()}`;
674
+ }
675
+ }
676
+ var ERROR_CODES = {
677
+ // Validation Errors (VAL_*)
678
+ ERR_VAL_INVALID_TEMPLATE: "ERR_VAL_INVALID_TEMPLATE",
679
+ ERR_VAL_INVALID_PKG_MANAGER: "ERR_VAL_INVALID_PKG_MANAGER",
680
+ ERR_VAL_INVALID_PROJECT_NAME: "ERR_VAL_INVALID_PROJECT_NAME",
681
+ ERR_VAL_UNSUPPORTED_NODE: "ERR_VAL_UNSUPPORTED_NODE",
682
+ ERR_VAL_NO_PKG_MANAGERS: "ERR_VAL_NO_PKG_MANAGERS",
683
+ // File System Errors (FS_*)
684
+ ERR_FS_NOT_WRITABLE: "ERR_FS_NOT_WRITABLE",
685
+ ERR_FS_HAS_CONFLICTS: "ERR_FS_HAS_CONFLICTS",
686
+ ERR_FS_MKDIR_FAILED: "ERR_FS_MKDIR_FAILED",
687
+ ERR_FS_CHDIR_FAILED: "ERR_FS_CHDIR_FAILED",
688
+ ERR_FS_READ_PACKAGE_JSON: "ERR_FS_READ_PACKAGE_JSON",
689
+ ERR_FS_WRITE_PACKAGE_JSON: "ERR_FS_WRITE_PACKAGE_JSON",
690
+ ERR_FS_UPDATE_THEME_FAILED: "ERR_FS_UPDATE_THEME_FAILED",
691
+ ERR_FS_COPY_TEMPLATE_FAILED: "ERR_FS_COPY_TEMPLATE_FAILED",
692
+ // Network Errors (NET_*)
693
+ ERR_NET_POSTHOG_CONFIG_FETCH: "ERR_NET_POSTHOG_CONFIG_FETCH",
694
+ ERR_NET_TARBALL_DOWNLOAD: "ERR_NET_TARBALL_DOWNLOAD",
695
+ ERR_NET_GITHUB_API_FAILED: "ERR_NET_GITHUB_API_FAILED",
696
+ ERR_NET_REPO_INFO_NOT_FOUND: "ERR_NET_REPO_INFO_NOT_FOUND",
697
+ ERR_NET_REPO_INVALID_URL: "ERR_NET_REPO_INVALID_URL",
698
+ ERR_NET_TARBALL_EXTRACT: "ERR_NET_TARBALL_EXTRACT",
699
+ // Installation Errors (INSTALL_*)
700
+ ERR_INSTALL_PKG_MANAGER_FAILED: "ERR_INSTALL_PKG_MANAGER_FAILED",
701
+ ERR_INSTALL_PKG_MANAGER_NOT_FOUND: "ERR_INSTALL_PKG_MANAGER_NOT_FOUND",
702
+ ERR_INSTALL_SPAWN_ERROR: "ERR_INSTALL_SPAWN_ERROR",
703
+ ERR_INSTALL_TIMEOUT: "ERR_INSTALL_TIMEOUT",
704
+ // Git Errors (GIT_*)
705
+ ERR_GIT_NOT_INSTALLED: "ERR_GIT_NOT_INSTALLED",
706
+ ERR_GIT_INIT_FAILED: "ERR_GIT_INIT_FAILED",
707
+ ERR_GIT_ADD_FAILED: "ERR_GIT_ADD_FAILED",
708
+ ERR_GIT_COMMIT_FAILED: "ERR_GIT_COMMIT_FAILED",
709
+ ERR_GIT_CHECKOUT_FAILED: "ERR_GIT_CHECKOUT_FAILED",
710
+ ERR_GIT_ALREADY_INITIALIZED: "ERR_GIT_ALREADY_INITIALIZED",
711
+ // User Cancellation (CANCEL_*)
712
+ ERR_CANCEL_PKG_MANAGER_PROMPT: "ERR_CANCEL_PKG_MANAGER_PROMPT",
713
+ ERR_CANCEL_PROJECT_NAME_PROMPT: "ERR_CANCEL_PROJECT_NAME_PROMPT",
714
+ ERR_CANCEL_TEMPLATE_PROMPT: "ERR_CANCEL_TEMPLATE_PROMPT",
715
+ ERR_CANCEL_THEME_PROMPT: "ERR_CANCEL_THEME_PROMPT",
716
+ ERR_CANCEL_SIGINT: "ERR_CANCEL_SIGINT",
717
+ // Configuration Errors (CFG_*)
718
+ ERR_CFG_POSTHOG_INIT_FAILED: "ERR_CFG_POSTHOG_INIT_FAILED",
719
+ ERR_CFG_OSINFO_FETCH_FAILED: "ERR_CFG_OSINFO_FETCH_FAILED",
720
+ ERR_CFG_TELEMETRY_SETUP_FAILED: "ERR_CFG_TELEMETRY_SETUP_FAILED",
721
+ // Template Errors (TPL_*)
722
+ ERR_TPL_DOWNLOAD_FAILED: "ERR_TPL_DOWNLOAD_FAILED",
723
+ ERR_TPL_EXTRACT_FAILED: "ERR_TPL_EXTRACT_FAILED",
724
+ ERR_TPL_METADATA_UPDATE_FAILED: "ERR_TPL_METADATA_UPDATE_FAILED",
725
+ ERR_TPL_INTERNAL_COPY_FAILED: "ERR_TPL_INTERNAL_COPY_FAILED",
726
+ ERR_TPL_THEME_UPDATE_FAILED: "ERR_TPL_THEME_UPDATE_FAILED",
727
+ // Uncategorized
728
+ ERR_UNCAUGHT: "ERR_UNCAUGHT"
729
+ };
730
+ function sanitizeStackTrace(stack) {
731
+ if (!stack) return "";
732
+ let sanitized = stack;
733
+ sanitized = sanitized.replace(/\/Users\/[^\/]+/g, "<user-home>");
734
+ sanitized = sanitized.replace(/[A-Z]:\\Users\\[^\\]+/gi, "<user-home>");
735
+ sanitized = sanitized.replace(
736
+ /.*\/(packages\/create-tina-app)\//g,
737
+ "<workspace>/$1/"
738
+ );
739
+ sanitized = sanitized.replace(
740
+ /(\/|\\)node_modules(\/|\\)/g,
741
+ "<node_modules>/"
742
+ );
743
+ sanitized = sanitized.replace(/\.npm\/_cacache/g, "<pkg-cache>");
744
+ sanitized = sanitized.replace(/\.yarn\/cache/g, "<pkg-cache>");
745
+ sanitized = sanitized.replace(/\.pnpm-store/g, "<pkg-cache>");
746
+ sanitized = sanitized.replace(/[A-Z]:\\/gi, "<drive>/");
747
+ return sanitized;
748
+ }
749
+ function truncateStackTrace(stack, maxFrames = 5) {
750
+ const lines = stack.split("\n");
751
+ const filtered = lines.filter((line) => {
752
+ if (!line.trim().startsWith("at ")) return true;
753
+ return !line.includes("node:internal") && !line.includes("node_modules/prompts") && !line.includes("node_modules/ora");
754
+ }).slice(0, maxFrames + 1);
755
+ return filtered.join("\n");
756
+ }
757
+ function createSimpleHash(str) {
758
+ let hash = 0;
759
+ for (let i = 0; i < str.length; i++) {
760
+ const char = str.charCodeAt(i);
761
+ hash = (hash << 5) - hash + char;
762
+ hash = hash & hash;
763
+ }
764
+ return Math.abs(hash).toString(36);
765
+ }
766
+ function sanitizeError(error) {
767
+ const message = error.message || "Unknown error";
768
+ const rawStack = error.stack || "";
769
+ const truncated = truncateStackTrace(rawStack);
770
+ const sanitizedStack = sanitizeStackTrace(truncated);
771
+ const stackForHash = rawStack.replace(/:\d+:\d+/g, "").replace(/\/Users\/[^\/]+/g, "").replace(/[A-Z]:\\Users\\[^\\]+/gi, "");
772
+ const originalStackHash = createSimpleHash(stackForHash);
773
+ return {
774
+ message,
775
+ sanitizedStack,
776
+ originalStackHash
777
+ };
778
+ }
779
+ function postHogCapture(client, distinctId, sessionId, event, properties) {
780
+ if (process.env.TINA_DEV === "true") return;
781
+ if (!client) {
782
+ return;
783
+ }
784
+ try {
785
+ client.capture({
786
+ distinctId,
787
+ event,
788
+ properties: {
789
+ ...properties,
790
+ sessionId,
791
+ system: "tinacms/create-tina-app"
792
+ }
793
+ });
794
+ } catch (error) {
795
+ console.error("Error capturing event:", error);
796
+ }
797
+ }
798
+ function postHogCaptureError(client, distinctId, sessionId, error, context) {
799
+ if (process.env.TINA_DEV === "true") return;
800
+ if (!client) return;
801
+ const { message, sanitizedStack, originalStackHash } = sanitizeError(error);
802
+ const {
803
+ errorCode,
804
+ errorCategory,
805
+ step,
806
+ fatal = true,
807
+ additionalProperties = {}
808
+ } = context;
809
+ let eventName;
810
+ if (errorCategory === "user-cancellation") {
811
+ eventName = "create-tina-app-user-cancelled";
812
+ } else if (errorCategory === "validation") {
813
+ eventName = "create-tina-app-validation-error";
814
+ } else {
815
+ eventName = "create-tina-app-error";
816
+ }
817
+ const properties = {
818
+ error_code: errorCode,
819
+ error_category: errorCategory,
820
+ error_message: message.substring(0, 500),
821
+ // Limit message length
822
+ sanitized_stack: sanitizedStack,
823
+ stack_hash: originalStackHash,
824
+ step,
825
+ fatal,
826
+ user_cancelled: errorCategory === "user-cancellation",
827
+ sessionId,
828
+ ...additionalProperties
829
+ };
830
+ try {
831
+ client.capture({
832
+ distinctId,
833
+ event: eventName,
834
+ properties: {
835
+ ...properties,
836
+ system: "tinacms/create-tina-app"
837
+ }
838
+ });
839
+ } catch (captureError) {
840
+ console.error("Error capturing error event:", captureError);
841
+ }
842
+ }
843
+
844
+ // src/util/fetchPosthogConfig.tsx
845
+ async function fetchPostHogConfig(endpointUrl) {
846
+ try {
847
+ const response = await fetch(endpointUrl, {
848
+ method: "GET",
849
+ headers: {
850
+ "Content-Type": "application/json"
851
+ }
852
+ });
853
+ if (!response.ok) {
854
+ throw new Error(`Failed to fetch PostHog config: ${response.statusText}`);
855
+ }
856
+ const config = await response.json();
857
+ return {
858
+ POSTHOG_API_KEY: config.api_key,
859
+ POSTHOG_ENDPOINT: config.host
860
+ };
861
+ } catch (error) {
862
+ console.warn(
863
+ `Failed to fetch PostHog config from endpoint: ${error instanceof Error ? error.message : "Unknown error"}`
864
+ );
865
+ return {};
866
+ }
867
+ }
868
+
869
+ // src/index.ts
870
+ import { osInfo as getOsSystemInfo } from "systeminformation";
871
+ var posthogClient = null;
872
+ async function initializePostHog(configEndpoint) {
873
+ let apiKey;
874
+ let endpoint;
875
+ if (configEndpoint) {
876
+ const config = await fetchPostHogConfig(configEndpoint);
877
+ apiKey = config.POSTHOG_API_KEY;
878
+ endpoint = config.POSTHOG_ENDPOINT;
879
+ }
880
+ if (!apiKey) {
881
+ console.warn(
882
+ "PostHog API key not found. PostHog tracking will be disabled."
883
+ );
884
+ return null;
885
+ }
886
+ return new PostHog(apiKey, {
887
+ host: endpoint
888
+ });
889
+ }
890
+ function formatTemplateChoice(template) {
891
+ let description = template.description || "";
892
+ if (template.features && template.features.length > 0) {
893
+ const featuresText = template.features.map((feature) => ` \u2022 ${feature.name}: ${feature.description}`).join("\n");
894
+ description = `${description}
895
+
896
+ Features:
897
+ ${featuresText}`;
898
+ }
899
+ return {
900
+ title: template.title,
901
+ value: template.value,
902
+ description
903
+ };
904
+ }
436
905
  async function run() {
437
906
  const ora = (await import("ora")).default;
438
907
  let packageManagerInstallationHadError = false;
908
+ const sessionId = generateSessionId();
909
+ const userId = await getAnonymousUserId();
439
910
  if (process.stdout.columns >= 60) {
440
911
  console.log(TextStyles.tinaOrange(`${llama}`));
441
912
  console.log(TextStyles.tinaOrange(`${tinaCms}`));
@@ -445,10 +916,43 @@ async function run() {
445
916
  const require2 = createRequire(import.meta.url);
446
917
  const version2 = require2("../package.json").version;
447
918
  console.log(`Create Tina App v${version2}`);
919
+ const opts = extractOptions(process.argv);
920
+ const installedPkgManagers = [];
921
+ for (const pkg_manager of PKG_MANAGERS) {
922
+ if (await checkPackageExists(pkg_manager)) {
923
+ installedPkgManagers.push(pkg_manager);
924
+ }
925
+ }
926
+ const telemetryData = {};
927
+ if (!opts.noTelemetry) {
928
+ console.log(`
929
+ ${TextStylesBold.bold("Telemetry Notice")}`);
930
+ console.log(
931
+ `To help the TinaCMS team improve the developer experience, create-tina-app collects anonymous usage statistics. This data helps us understand which environments and features are most important to support. Usage analytics may include: Operating system and version, package manager name and version (local only), Node.js version (local only), and the selected TinaCMS starter template.
932
+ No personal or project-specific code is ever collected. You can opt out at any time by passing the --noTelemetry flag.
933
+ `
934
+ );
935
+ posthogClient = await initializePostHog(
936
+ "https://identity-v2.tinajs.io/v2/posthog-token"
937
+ );
938
+ const osInfo = await getOsSystemInfo();
939
+ telemetryData["os-platform"] = osInfo.platform;
940
+ telemetryData["os-distro"] = osInfo.distro;
941
+ telemetryData["os-release"] = osInfo.release;
942
+ telemetryData["node-version"] = process.version;
943
+ for (const pkgManager2 of PKG_MANAGERS) {
944
+ telemetryData[`${pkgManager2}-installed`] = installedPkgManagers.includes(pkgManager2);
945
+ }
946
+ }
448
947
  const spinner = ora();
449
948
  preRunChecks(spinner);
450
- const opts = extractOptions(process.argv);
451
- const telemetry = new Telemetry({ disabled: opts?.noTelemetry });
949
+ postHogCapture(
950
+ posthogClient,
951
+ userId,
952
+ sessionId,
953
+ CreateTinaAppStartedEvent,
954
+ telemetryData
955
+ );
452
956
  let template = null;
453
957
  if (opts.template) {
454
958
  template = TEMPLATES.find((_template) => _template.value === opts.template);
@@ -458,6 +962,23 @@ async function run() {
458
962
  (x2) => x2.value
459
963
  )}`
460
964
  );
965
+ postHogCaptureError(
966
+ posthogClient,
967
+ userId,
968
+ sessionId,
969
+ new Error(`Invalid template: ${opts.template}`),
970
+ {
971
+ errorCode: ERROR_CODES.ERR_VAL_INVALID_TEMPLATE,
972
+ errorCategory: "validation",
973
+ step: TRACKING_STEPS.TEMPLATE_SELECT,
974
+ fatal: true,
975
+ additionalProperties: {
976
+ ...telemetryData,
977
+ provided_template: opts.template
978
+ }
979
+ }
980
+ );
981
+ if (posthogClient) await posthogClient.shutdown();
461
982
  exit(1);
462
983
  }
463
984
  }
@@ -467,20 +988,45 @@ async function run() {
467
988
  spinner.fail(
468
989
  `The provided package manager '${opts.pkgManager}' is not supported. Please provide one of the following: ${PKG_MANAGERS}`
469
990
  );
991
+ postHogCaptureError(
992
+ posthogClient,
993
+ userId,
994
+ sessionId,
995
+ new Error(`Invalid package manager: ${opts.pkgManager}`),
996
+ {
997
+ errorCode: ERROR_CODES.ERR_VAL_INVALID_PKG_MANAGER,
998
+ errorCategory: "validation",
999
+ step: TRACKING_STEPS.PKG_MANAGER_SELECT,
1000
+ fatal: true,
1001
+ additionalProperties: {
1002
+ ...telemetryData,
1003
+ provided_pkg_manager: opts.pkgManager
1004
+ }
1005
+ }
1006
+ );
1007
+ if (posthogClient) await posthogClient.shutdown();
470
1008
  exit(1);
471
1009
  }
472
1010
  }
473
1011
  if (!pkgManager) {
474
- const installedPkgManagers = [];
475
- for (const pkg_manager of PKG_MANAGERS) {
476
- if (await checkPackageExists(pkg_manager)) {
477
- installedPkgManagers.push(pkg_manager);
478
- }
479
- }
480
1012
  if (installedPkgManagers.length === 0) {
481
1013
  spinner.fail(
482
1014
  `You have no supported package managers installed. Please install one of the following: ${PKG_MANAGERS}`
483
1015
  );
1016
+ postHogCaptureError(
1017
+ posthogClient,
1018
+ userId,
1019
+ sessionId,
1020
+ new Error("No supported package managers installed"),
1021
+ {
1022
+ errorCode: ERROR_CODES.ERR_VAL_NO_PKG_MANAGERS,
1023
+ errorCategory: "validation",
1024
+ step: TRACKING_STEPS.PRE_RUN_CHECKS,
1025
+ fatal: true,
1026
+ additionalProperties: telemetryData
1027
+ }
1028
+ );
1029
+ if (posthogClient) await posthogClient.shutdown();
484
1030
  exit(1);
485
1031
  }
486
1032
  const res = await prompts({
@@ -491,8 +1037,25 @@ async function run() {
491
1037
  return { title: manager, value: manager };
492
1038
  })
493
1039
  });
494
- if (!Object.hasOwn(res, "packageManager")) exit(1);
1040
+ if (!Object.hasOwn(res, "packageManager")) {
1041
+ postHogCaptureError(
1042
+ posthogClient,
1043
+ userId,
1044
+ sessionId,
1045
+ new Error("User cancelled package manager selection"),
1046
+ {
1047
+ errorCode: ERROR_CODES.ERR_CANCEL_PKG_MANAGER_PROMPT,
1048
+ errorCategory: "user-cancellation",
1049
+ step: TRACKING_STEPS.PKG_MANAGER_SELECT,
1050
+ fatal: true,
1051
+ additionalProperties: telemetryData
1052
+ }
1053
+ );
1054
+ if (posthogClient) await posthogClient.shutdown();
1055
+ exit(1);
1056
+ }
495
1057
  pkgManager = res.packageManager;
1058
+ telemetryData["package-manager"] = pkgManager;
496
1059
  }
497
1060
  let projectName = opts.projectName;
498
1061
  if (!projectName) {
@@ -502,14 +1065,30 @@ async function run() {
502
1065
  message: "What is your project named?",
503
1066
  initial: "my-tina-app",
504
1067
  validate: (name2) => {
505
- const { validForNewPackages, errors } = validate(
506
- path4.basename(path4.resolve(name2))
1068
+ const { message, isError } = validate(
1069
+ path3.basename(path3.resolve(name2))
507
1070
  );
508
- if (validForNewPackages) return true;
509
- return `Invalid project name: ${errors[0]}`;
1071
+ if (isError) return `Invalid project name: ${message}`;
1072
+ return true;
510
1073
  }
511
1074
  });
512
- if (!Object.hasOwn(res, "name")) exit(1);
1075
+ if (!Object.hasOwn(res, "name")) {
1076
+ postHogCaptureError(
1077
+ posthogClient,
1078
+ userId,
1079
+ sessionId,
1080
+ new Error("User cancelled project name input"),
1081
+ {
1082
+ errorCode: ERROR_CODES.ERR_CANCEL_PROJECT_NAME_PROMPT,
1083
+ errorCategory: "user-cancellation",
1084
+ step: TRACKING_STEPS.PROJECT_NAME_INPUT,
1085
+ fatal: true,
1086
+ additionalProperties: telemetryData
1087
+ }
1088
+ );
1089
+ if (posthogClient) await posthogClient.shutdown();
1090
+ exit(1);
1091
+ }
513
1092
  projectName = res.name;
514
1093
  }
515
1094
  if (!template) {
@@ -517,11 +1096,28 @@ async function run() {
517
1096
  name: "template",
518
1097
  type: "select",
519
1098
  message: "What starter code would you like to use?",
520
- choices: TEMPLATES
1099
+ choices: TEMPLATES.map(formatTemplateChoice)
521
1100
  });
522
- if (!Object.hasOwn(res, "template")) exit(1);
1101
+ if (!Object.hasOwn(res, "template")) {
1102
+ postHogCaptureError(
1103
+ posthogClient,
1104
+ userId,
1105
+ sessionId,
1106
+ new Error("User cancelled template selection"),
1107
+ {
1108
+ errorCode: ERROR_CODES.ERR_CANCEL_TEMPLATE_PROMPT,
1109
+ errorCategory: "user-cancellation",
1110
+ step: TRACKING_STEPS.TEMPLATE_SELECT,
1111
+ fatal: true,
1112
+ additionalProperties: telemetryData
1113
+ }
1114
+ );
1115
+ if (posthogClient) await posthogClient.shutdown();
1116
+ exit(1);
1117
+ }
523
1118
  template = TEMPLATES.find((_template) => _template.value === res.template);
524
1119
  }
1120
+ telemetryData["template"] = template.value;
525
1121
  let themeChoice;
526
1122
  if (template.value === "tina-docs") {
527
1123
  const res = await prompts({
@@ -530,32 +1126,73 @@ async function run() {
530
1126
  message: "What theme would you like to use?",
531
1127
  choices: THEMES
532
1128
  });
533
- if (!Object.hasOwn(res, "theme")) exit(1);
1129
+ if (!Object.hasOwn(res, "theme")) {
1130
+ postHogCaptureError(
1131
+ posthogClient,
1132
+ userId,
1133
+ sessionId,
1134
+ new Error("User cancelled theme selection"),
1135
+ {
1136
+ errorCode: ERROR_CODES.ERR_CANCEL_THEME_PROMPT,
1137
+ errorCategory: "user-cancellation",
1138
+ step: TRACKING_STEPS.THEME_SELECT,
1139
+ fatal: true,
1140
+ additionalProperties: {
1141
+ ...telemetryData,
1142
+ template: template.value
1143
+ }
1144
+ }
1145
+ );
1146
+ if (posthogClient) await posthogClient.shutdown();
1147
+ exit(1);
1148
+ }
534
1149
  themeChoice = res.theme;
535
1150
  }
536
- await telemetry.submitRecord({
537
- event: {
538
- name: "create-tina-app:invoke",
539
- template: template.value,
540
- pkgManager
541
- }
542
- });
543
- const rootDir = path4.join(process.cwd(), projectName);
544
- if (!await isWriteable(path4.dirname(rootDir))) {
1151
+ const rootDir = path3.join(process.cwd(), projectName);
1152
+ if (!await isWriteable(path3.dirname(rootDir))) {
545
1153
  spinner.fail(
546
1154
  "The application path is not writable, please check folder permissions and try again. It is likely you do not have write permissions for this folder."
547
1155
  );
1156
+ postHogCaptureError(
1157
+ posthogClient,
1158
+ userId,
1159
+ sessionId,
1160
+ new Error("Directory not writable"),
1161
+ {
1162
+ errorCode: ERROR_CODES.ERR_FS_NOT_WRITABLE,
1163
+ errorCategory: "filesystem",
1164
+ step: TRACKING_STEPS.DIRECTORY_SETUP,
1165
+ fatal: true,
1166
+ additionalProperties: {
1167
+ ...telemetryData,
1168
+ template: template.value
1169
+ }
1170
+ }
1171
+ );
1172
+ if (posthogClient) await posthogClient.shutdown();
548
1173
  process.exit(1);
549
1174
  }
550
1175
  let appName;
551
1176
  try {
552
1177
  appName = await setupProjectDirectory(rootDir);
1178
+ telemetryData["app-name"] = appName;
553
1179
  } catch (err) {
554
- spinner.fail(err.message);
1180
+ const error = err;
1181
+ spinner.fail(error.message);
1182
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1183
+ errorCode: ERROR_CODES.ERR_FS_MKDIR_FAILED,
1184
+ errorCategory: "filesystem",
1185
+ step: TRACKING_STEPS.DIRECTORY_SETUP,
1186
+ fatal: true,
1187
+ additionalProperties: {
1188
+ ...telemetryData,
1189
+ template: template.value
1190
+ }
1191
+ });
1192
+ if (posthogClient) await posthogClient.shutdown();
555
1193
  exit(1);
556
1194
  }
557
1195
  try {
558
- await downloadTemplate(template, rootDir, spinner);
559
1196
  if (themeChoice) {
560
1197
  await updateThemeSettings(rootDir, themeChoice);
561
1198
  }
@@ -567,7 +1204,20 @@ async function run() {
567
1204
  updateProjectPackageVersion(rootDir, "0.0.1");
568
1205
  spinner.succeed();
569
1206
  } catch (err) {
570
- spinner.fail(`Failed to download template: ${err.message}`);
1207
+ const error = err;
1208
+ spinner.fail(`Failed to download template: ${error.message}`);
1209
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1210
+ errorCode: ERROR_CODES.ERR_TPL_DOWNLOAD_FAILED,
1211
+ errorCategory: "template",
1212
+ step: TRACKING_STEPS.DOWNLOADING_TEMPLATE,
1213
+ fatal: true,
1214
+ additionalProperties: {
1215
+ ...telemetryData,
1216
+ template: template.value,
1217
+ theme: themeChoice
1218
+ }
1219
+ });
1220
+ if (posthogClient) await posthogClient.shutdown();
571
1221
  exit(1);
572
1222
  }
573
1223
  spinner.start("Installing packages.");
@@ -575,8 +1225,20 @@ async function run() {
575
1225
  await install(pkgManager, opts.verbose);
576
1226
  spinner.succeed();
577
1227
  } catch (err) {
578
- spinner.fail(`Failed to install packages: ${err.message}`);
1228
+ const error = err;
1229
+ spinner.fail(`Failed to install packages: ${error.message}`);
579
1230
  packageManagerInstallationHadError = true;
1231
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1232
+ errorCode: ERROR_CODES.ERR_INSTALL_PKG_MANAGER_FAILED,
1233
+ errorCategory: "installation",
1234
+ step: TRACKING_STEPS.INSTALLING_PACKAGES,
1235
+ fatal: false,
1236
+ additionalProperties: {
1237
+ ...telemetryData,
1238
+ template: template.value,
1239
+ package_manager: pkgManager
1240
+ }
1241
+ });
580
1242
  }
581
1243
  spinner.start("Initializing git repository.");
582
1244
  try {
@@ -585,8 +1247,26 @@ async function run() {
585
1247
  spinner.succeed();
586
1248
  }
587
1249
  } catch (err) {
1250
+ const error = err;
588
1251
  spinner.fail("Failed to initialize Git repository, skipping.");
1252
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1253
+ errorCode: ERROR_CODES.ERR_GIT_INIT_FAILED,
1254
+ errorCategory: "git",
1255
+ step: TRACKING_STEPS.GIT_INIT,
1256
+ fatal: false,
1257
+ additionalProperties: {
1258
+ ...telemetryData,
1259
+ template: template.value
1260
+ }
1261
+ });
589
1262
  }
1263
+ postHogCapture(
1264
+ posthogClient,
1265
+ userId,
1266
+ sessionId,
1267
+ CreateTinaAppFinishedEvent,
1268
+ telemetryData
1269
+ );
590
1270
  spinner.succeed(`Created ${TextStyles.tinaOrange(appName)}
591
1271
  `);
592
1272
  if (template.value === "tina-hugo-starter") {
@@ -629,9 +1309,28 @@ async function run() {
629
1309
  )}`
630
1310
  );
631
1311
  }
632
- run().catch((error) => {
1312
+ run().catch(async (error) => {
1313
+ if (process.stdout.columns >= 60) {
1314
+ console.log(TextStyles.tinaOrange(`${errorArt}`));
1315
+ }
633
1316
  console.error("Error running create-tina-app:", error);
1317
+ const sessionId = generateSessionId();
1318
+ const userId = await getAnonymousUserId();
1319
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1320
+ errorCode: ERROR_CODES.ERR_UNCAUGHT,
1321
+ errorCategory: "uncategorized",
1322
+ step: "unknown",
1323
+ fatal: true,
1324
+ additionalProperties: {}
1325
+ });
1326
+ if (posthogClient) {
1327
+ await posthogClient.shutdown();
1328
+ }
634
1329
  process.exit(1);
1330
+ }).then(async () => {
1331
+ if (posthogClient) {
1332
+ await posthogClient.shutdown();
1333
+ }
635
1334
  });
636
1335
  export {
637
1336
  run
@@ -1,7 +1,12 @@
1
1
  import { Ora } from 'ora';
2
+ type Feature = {
3
+ name: string;
4
+ description: string;
5
+ };
2
6
  export type BaseExample = {
3
7
  title: string;
4
8
  description?: string;
9
+ features?: Feature[];
5
10
  value: string;
6
11
  devUrl: string;
7
12
  };
@@ -15,3 +20,4 @@ export type ExternalTemplate = BaseExample & {
15
20
  export type Template = InternalTemplate | ExternalTemplate;
16
21
  export declare const TEMPLATES: Template[];
17
22
  export declare function downloadTemplate(template: Template, root: string, spinner: Ora): Promise<void>;
23
+ export {};
@@ -1,2 +1,3 @@
1
1
  export declare const llama: string;
2
+ export declare const errorArt: string;
2
3
  export declare const tinaCms: string;
@@ -0,0 +1,5 @@
1
+ export interface PostHogConfig {
2
+ POSTHOG_API_KEY?: string;
3
+ POSTHOG_ENDPOINT?: string;
4
+ }
5
+ export default function fetchPostHogConfig(endpointUrl: string): Promise<PostHogConfig>;
@@ -0,0 +1,16 @@
1
+ /**
2
+ * @typedef {Object} ValidationResult
3
+ * @property {string | null} message
4
+ * @property {boolean} isError
5
+ */
6
+ /**
7
+ * Validates whether the provided name is valid on NPM.
8
+ *
9
+ * @param {string} name
10
+ * @returns {ValidationResult}
11
+ */
12
+ export default function validate(name: string): ValidationResult;
13
+ export type ValidationResult = {
14
+ message: string | null;
15
+ isError: boolean;
16
+ };
@@ -0,0 +1,147 @@
1
+ import { PostHog } from 'posthog-node';
2
+ export declare const CreateTinaAppStartedEvent: string;
3
+ export declare const CreateTinaAppFinishedEvent: string;
4
+ /**
5
+ * Step names for tracking progress through the create-tina-app process
6
+ */
7
+ export declare const TRACKING_STEPS: {
8
+ readonly INIT: "initializing";
9
+ readonly PRE_RUN_CHECKS: "pre_run_checks";
10
+ readonly TELEMETRY_SETUP: "telemetry_setup";
11
+ readonly PKG_MANAGER_SELECT: "package_manager_selection";
12
+ readonly PROJECT_NAME_INPUT: "project_name_input";
13
+ readonly TEMPLATE_SELECT: "template_selection";
14
+ readonly THEME_SELECT: "theme_selection";
15
+ readonly DIRECTORY_SETUP: "directory_setup";
16
+ readonly DOWNLOADING_TEMPLATE: "downloading_template";
17
+ readonly UPDATING_METADATA: "updating_metadata";
18
+ readonly INSTALLING_PACKAGES: "installing_packages";
19
+ readonly GIT_INIT: "git_initialization";
20
+ readonly COMPLETE: "complete";
21
+ };
22
+ /**
23
+ * Generate a unique session ID for this run
24
+ */
25
+ export declare function generateSessionId(): string;
26
+ /**
27
+ * Get a hashed user ID based on system UUID
28
+ * Returns a consistent anonymous identifier for the machine
29
+ */
30
+ export declare function getAnonymousUserId(): Promise<string>;
31
+ /**
32
+ * Structured error codes for categorizing failures
33
+ */
34
+ export declare const ERROR_CODES: {
35
+ readonly ERR_VAL_INVALID_TEMPLATE: "ERR_VAL_INVALID_TEMPLATE";
36
+ readonly ERR_VAL_INVALID_PKG_MANAGER: "ERR_VAL_INVALID_PKG_MANAGER";
37
+ readonly ERR_VAL_INVALID_PROJECT_NAME: "ERR_VAL_INVALID_PROJECT_NAME";
38
+ readonly ERR_VAL_UNSUPPORTED_NODE: "ERR_VAL_UNSUPPORTED_NODE";
39
+ readonly ERR_VAL_NO_PKG_MANAGERS: "ERR_VAL_NO_PKG_MANAGERS";
40
+ readonly ERR_FS_NOT_WRITABLE: "ERR_FS_NOT_WRITABLE";
41
+ readonly ERR_FS_HAS_CONFLICTS: "ERR_FS_HAS_CONFLICTS";
42
+ readonly ERR_FS_MKDIR_FAILED: "ERR_FS_MKDIR_FAILED";
43
+ readonly ERR_FS_CHDIR_FAILED: "ERR_FS_CHDIR_FAILED";
44
+ readonly ERR_FS_READ_PACKAGE_JSON: "ERR_FS_READ_PACKAGE_JSON";
45
+ readonly ERR_FS_WRITE_PACKAGE_JSON: "ERR_FS_WRITE_PACKAGE_JSON";
46
+ readonly ERR_FS_UPDATE_THEME_FAILED: "ERR_FS_UPDATE_THEME_FAILED";
47
+ readonly ERR_FS_COPY_TEMPLATE_FAILED: "ERR_FS_COPY_TEMPLATE_FAILED";
48
+ readonly ERR_NET_POSTHOG_CONFIG_FETCH: "ERR_NET_POSTHOG_CONFIG_FETCH";
49
+ readonly ERR_NET_TARBALL_DOWNLOAD: "ERR_NET_TARBALL_DOWNLOAD";
50
+ readonly ERR_NET_GITHUB_API_FAILED: "ERR_NET_GITHUB_API_FAILED";
51
+ readonly ERR_NET_REPO_INFO_NOT_FOUND: "ERR_NET_REPO_INFO_NOT_FOUND";
52
+ readonly ERR_NET_REPO_INVALID_URL: "ERR_NET_REPO_INVALID_URL";
53
+ readonly ERR_NET_TARBALL_EXTRACT: "ERR_NET_TARBALL_EXTRACT";
54
+ readonly ERR_INSTALL_PKG_MANAGER_FAILED: "ERR_INSTALL_PKG_MANAGER_FAILED";
55
+ readonly ERR_INSTALL_PKG_MANAGER_NOT_FOUND: "ERR_INSTALL_PKG_MANAGER_NOT_FOUND";
56
+ readonly ERR_INSTALL_SPAWN_ERROR: "ERR_INSTALL_SPAWN_ERROR";
57
+ readonly ERR_INSTALL_TIMEOUT: "ERR_INSTALL_TIMEOUT";
58
+ readonly ERR_GIT_NOT_INSTALLED: "ERR_GIT_NOT_INSTALLED";
59
+ readonly ERR_GIT_INIT_FAILED: "ERR_GIT_INIT_FAILED";
60
+ readonly ERR_GIT_ADD_FAILED: "ERR_GIT_ADD_FAILED";
61
+ readonly ERR_GIT_COMMIT_FAILED: "ERR_GIT_COMMIT_FAILED";
62
+ readonly ERR_GIT_CHECKOUT_FAILED: "ERR_GIT_CHECKOUT_FAILED";
63
+ readonly ERR_GIT_ALREADY_INITIALIZED: "ERR_GIT_ALREADY_INITIALIZED";
64
+ readonly ERR_CANCEL_PKG_MANAGER_PROMPT: "ERR_CANCEL_PKG_MANAGER_PROMPT";
65
+ readonly ERR_CANCEL_PROJECT_NAME_PROMPT: "ERR_CANCEL_PROJECT_NAME_PROMPT";
66
+ readonly ERR_CANCEL_TEMPLATE_PROMPT: "ERR_CANCEL_TEMPLATE_PROMPT";
67
+ readonly ERR_CANCEL_THEME_PROMPT: "ERR_CANCEL_THEME_PROMPT";
68
+ readonly ERR_CANCEL_SIGINT: "ERR_CANCEL_SIGINT";
69
+ readonly ERR_CFG_POSTHOG_INIT_FAILED: "ERR_CFG_POSTHOG_INIT_FAILED";
70
+ readonly ERR_CFG_OSINFO_FETCH_FAILED: "ERR_CFG_OSINFO_FETCH_FAILED";
71
+ readonly ERR_CFG_TELEMETRY_SETUP_FAILED: "ERR_CFG_TELEMETRY_SETUP_FAILED";
72
+ readonly ERR_TPL_DOWNLOAD_FAILED: "ERR_TPL_DOWNLOAD_FAILED";
73
+ readonly ERR_TPL_EXTRACT_FAILED: "ERR_TPL_EXTRACT_FAILED";
74
+ readonly ERR_TPL_METADATA_UPDATE_FAILED: "ERR_TPL_METADATA_UPDATE_FAILED";
75
+ readonly ERR_TPL_INTERNAL_COPY_FAILED: "ERR_TPL_INTERNAL_COPY_FAILED";
76
+ readonly ERR_TPL_THEME_UPDATE_FAILED: "ERR_TPL_THEME_UPDATE_FAILED";
77
+ readonly ERR_UNCAUGHT: "ERR_UNCAUGHT";
78
+ };
79
+ /**
80
+ * Sends an event to PostHog for analytics tracking.
81
+ *
82
+ * @param client - The PostHog client instance used to send the event
83
+ * @param distinctId - A unique identifier for the user (hashed system UUID)
84
+ * @param sessionId - A unique identifier for this run/session
85
+ * @param event - The name of the event to track (e.g., 'create-tina-app-started')
86
+ * @param properties - Additional properties to include with the event
87
+ *
88
+ * @remarks
89
+ * - Returns early if the PostHog client is not provided
90
+ * - Skips sending data when `TINA_DEV` environment variable is set to 'true'
91
+ * - Automatically adds a 'system' property with value 'tinacms/create-tina-app'
92
+ * - Includes sessionId in properties to track individual runs
93
+ * - Uses hashed system UUID as distinctId to track unique users anonymously
94
+ * - Logs errors to console if event capture fails
95
+ *
96
+ * @example
97
+ * ```typescript
98
+ * const client = new PostHog('api-key');
99
+ * const userId = await getAnonymousUserId();
100
+ * const sessionId = generateSessionId();
101
+ * postHogCapture(client, userId, sessionId, 'create-tina-app-started', {
102
+ * template: 'basic',
103
+ * typescript: true
104
+ * });
105
+ * ```
106
+ */
107
+ export declare function postHogCapture(client: PostHog, distinctId: string, sessionId: string, event: string, properties: Record<string, any>): void;
108
+ /**
109
+ * Capture an error event in PostHog with categorized tracking and sanitized stack traces
110
+ *
111
+ * @param client - The PostHog client instance
112
+ * @param distinctId - A unique identifier for the user (hashed system UUID)
113
+ * @param sessionId - A unique identifier for this run/session
114
+ * @param error - The error object that was thrown
115
+ * @param context - Context about the error including code, category, step, and additional properties
116
+ *
117
+ * @remarks
118
+ * - Sanitizes stack traces to remove local file paths
119
+ * - Maps error categories to three event types:
120
+ * - 'create-tina-app-error' for technical failures (filesystem, network, installation, git, etc.)
121
+ * - 'create-tina-app-validation-error' for user input validation issues
122
+ * - 'create-tina-app-user-cancelled' for user cancellations (Ctrl+C)
123
+ * - Includes error code, sanitized stack, step name, and telemetry data in properties
124
+ * - Non-fatal errors are tracked but allow the process to continue
125
+ *
126
+ * @example
127
+ * ```typescript
128
+ * try {
129
+ * await downloadTemplate();
130
+ * } catch (err) {
131
+ * postHogCaptureError(client, userId, sessionId, err as Error, {
132
+ * errorCode: ERROR_CODES.ERR_TPL_DOWNLOAD_FAILED,
133
+ * errorCategory: 'template',
134
+ * step: TRACKING_STEPS.DOWNLOADING_TEMPLATE,
135
+ * fatal: true,
136
+ * additionalProperties: { template: 'basic' }
137
+ * });
138
+ * }
139
+ * ```
140
+ */
141
+ export declare function postHogCaptureError(client: PostHog | null, distinctId: string, sessionId: string, error: Error, context: {
142
+ errorCode: string;
143
+ errorCategory: string;
144
+ step: string;
145
+ fatal?: boolean;
146
+ additionalProperties?: Record<string, any>;
147
+ }): void;
@@ -8,3 +8,13 @@ export declare const TextStyles: {
8
8
  err: import("chalk").ChalkInstance;
9
9
  bold: import("chalk").ChalkInstance;
10
10
  };
11
+ export declare const TextStylesBold: {
12
+ tinaOrange: import("chalk").ChalkInstance;
13
+ link: (url: string) => string;
14
+ cmd: import("chalk").ChalkInstance;
15
+ info: import("chalk").ChalkInstance;
16
+ success: import("chalk").ChalkInstance;
17
+ warn: import("chalk").ChalkInstance;
18
+ err: import("chalk").ChalkInstance;
19
+ bold: import("chalk").ChalkInstance;
20
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-tina-app",
3
- "version": "2.0.0",
3
+ "version": "2.1.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -44,10 +44,10 @@
44
44
  "cross-spawn": "^7.0.6",
45
45
  "fs-extra": "^11.3.0",
46
46
  "ora": "^8.2.0",
47
+ "posthog-node": "^5.17.2",
47
48
  "prompts": "^2.4.2",
48
- "tar": "7.4.0",
49
- "validate-npm-package-name": "^5.0.1",
50
- "@tinacms/metrics": "2.0.1"
49
+ "systeminformation": "^5.27.13",
50
+ "tar": "7.4.0"
51
51
  },
52
52
  "scripts": {
53
53
  "types": "pnpm tsc",