create-tina-app 0.0.0-f1cec43-20251216232909 → 0.0.0-f2dd173-20251229042232

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,7 +239,7 @@ 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",
@@ -405,7 +408,7 @@ async function downloadTemplate(template, root, spinner) {
405
408
  )}`;
406
409
  await downloadAndExtractRepo(root, repoInfo);
407
410
  } else {
408
- const templateFile = path3.join(__dirname, "..", "examples", template.value);
411
+ const templateFile = path2.join(__dirname, "..", "examples", template.value);
409
412
  await copy(`${templateFile}/`, "./");
410
413
  }
411
414
  }
@@ -455,7 +458,7 @@ import { Command } from "commander";
455
458
 
456
459
  // package.json
457
460
  var name = "create-tina-app";
458
- var version = "2.0.0";
461
+ var version = "2.1.0";
459
462
 
460
463
  // src/util/packageManagers.ts
461
464
  var PKG_MANAGERS = ["npm", "yarn", "pnpm", "bun"];
@@ -631,6 +634,260 @@ var THEMES = [
631
634
  ];
632
635
 
633
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, disableGeoip) {
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
+ disableGeoip: disableGeoip ?? true
889
+ });
890
+ }
634
891
  function formatTemplateChoice(template) {
635
892
  let description = template.description || "";
636
893
  if (template.features && template.features.length > 0) {
@@ -649,6 +906,8 @@ ${featuresText}`;
649
906
  async function run() {
650
907
  const ora = (await import("ora")).default;
651
908
  let packageManagerInstallationHadError = false;
909
+ const sessionId = generateSessionId();
910
+ const userId = await getAnonymousUserId();
652
911
  if (process.stdout.columns >= 60) {
653
912
  console.log(TextStyles.tinaOrange(`${llama}`));
654
913
  console.log(TextStyles.tinaOrange(`${tinaCms}`));
@@ -658,10 +917,44 @@ async function run() {
658
917
  const require2 = createRequire(import.meta.url);
659
918
  const version2 = require2("../package.json").version;
660
919
  console.log(`Create Tina App v${version2}`);
920
+ const opts = extractOptions(process.argv);
921
+ const installedPkgManagers = [];
922
+ for (const pkg_manager of PKG_MANAGERS) {
923
+ if (await checkPackageExists(pkg_manager)) {
924
+ installedPkgManagers.push(pkg_manager);
925
+ }
926
+ }
927
+ const telemetryData = {};
928
+ if (!opts.noTelemetry) {
929
+ console.log(`
930
+ ${TextStylesBold.bold("Telemetry Notice")}`);
931
+ console.log(
932
+ `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.
933
+ No personal or project-specific code is ever collected. You can opt out at any time by passing the --noTelemetry flag.
934
+ `
935
+ );
936
+ posthogClient = await initializePostHog(
937
+ "https://identity-v2.tinajs.io/v2/posthog-token",
938
+ false
939
+ );
940
+ const osInfo = await getOsSystemInfo();
941
+ telemetryData["os-platform"] = osInfo.platform;
942
+ telemetryData["os-distro"] = osInfo.distro;
943
+ telemetryData["os-release"] = osInfo.release;
944
+ telemetryData["node-version"] = process.version;
945
+ for (const pkgManager2 of PKG_MANAGERS) {
946
+ telemetryData[`${pkgManager2}-installed`] = installedPkgManagers.includes(pkgManager2);
947
+ }
948
+ }
661
949
  const spinner = ora();
662
950
  preRunChecks(spinner);
663
- const opts = extractOptions(process.argv);
664
- const telemetry = new Telemetry({ disabled: opts?.noTelemetry });
951
+ postHogCapture(
952
+ posthogClient,
953
+ userId,
954
+ sessionId,
955
+ CreateTinaAppStartedEvent,
956
+ telemetryData
957
+ );
665
958
  let template = null;
666
959
  if (opts.template) {
667
960
  template = TEMPLATES.find((_template) => _template.value === opts.template);
@@ -671,6 +964,23 @@ async function run() {
671
964
  (x2) => x2.value
672
965
  )}`
673
966
  );
967
+ postHogCaptureError(
968
+ posthogClient,
969
+ userId,
970
+ sessionId,
971
+ new Error(`Invalid template: ${opts.template}`),
972
+ {
973
+ errorCode: ERROR_CODES.ERR_VAL_INVALID_TEMPLATE,
974
+ errorCategory: "validation",
975
+ step: TRACKING_STEPS.TEMPLATE_SELECT,
976
+ fatal: true,
977
+ additionalProperties: {
978
+ ...telemetryData,
979
+ provided_template: opts.template
980
+ }
981
+ }
982
+ );
983
+ if (posthogClient) await posthogClient.shutdown();
674
984
  exit(1);
675
985
  }
676
986
  }
@@ -680,20 +990,45 @@ async function run() {
680
990
  spinner.fail(
681
991
  `The provided package manager '${opts.pkgManager}' is not supported. Please provide one of the following: ${PKG_MANAGERS}`
682
992
  );
993
+ postHogCaptureError(
994
+ posthogClient,
995
+ userId,
996
+ sessionId,
997
+ new Error(`Invalid package manager: ${opts.pkgManager}`),
998
+ {
999
+ errorCode: ERROR_CODES.ERR_VAL_INVALID_PKG_MANAGER,
1000
+ errorCategory: "validation",
1001
+ step: TRACKING_STEPS.PKG_MANAGER_SELECT,
1002
+ fatal: true,
1003
+ additionalProperties: {
1004
+ ...telemetryData,
1005
+ provided_pkg_manager: opts.pkgManager
1006
+ }
1007
+ }
1008
+ );
1009
+ if (posthogClient) await posthogClient.shutdown();
683
1010
  exit(1);
684
1011
  }
685
1012
  }
686
1013
  if (!pkgManager) {
687
- const installedPkgManagers = [];
688
- for (const pkg_manager of PKG_MANAGERS) {
689
- if (await checkPackageExists(pkg_manager)) {
690
- installedPkgManagers.push(pkg_manager);
691
- }
692
- }
693
1014
  if (installedPkgManagers.length === 0) {
694
1015
  spinner.fail(
695
1016
  `You have no supported package managers installed. Please install one of the following: ${PKG_MANAGERS}`
696
1017
  );
1018
+ postHogCaptureError(
1019
+ posthogClient,
1020
+ userId,
1021
+ sessionId,
1022
+ new Error("No supported package managers installed"),
1023
+ {
1024
+ errorCode: ERROR_CODES.ERR_VAL_NO_PKG_MANAGERS,
1025
+ errorCategory: "validation",
1026
+ step: TRACKING_STEPS.PRE_RUN_CHECKS,
1027
+ fatal: true,
1028
+ additionalProperties: telemetryData
1029
+ }
1030
+ );
1031
+ if (posthogClient) await posthogClient.shutdown();
697
1032
  exit(1);
698
1033
  }
699
1034
  const res = await prompts({
@@ -704,8 +1039,25 @@ async function run() {
704
1039
  return { title: manager, value: manager };
705
1040
  })
706
1041
  });
707
- if (!Object.hasOwn(res, "packageManager")) exit(1);
1042
+ if (!Object.hasOwn(res, "packageManager")) {
1043
+ postHogCaptureError(
1044
+ posthogClient,
1045
+ userId,
1046
+ sessionId,
1047
+ new Error("User cancelled package manager selection"),
1048
+ {
1049
+ errorCode: ERROR_CODES.ERR_CANCEL_PKG_MANAGER_PROMPT,
1050
+ errorCategory: "user-cancellation",
1051
+ step: TRACKING_STEPS.PKG_MANAGER_SELECT,
1052
+ fatal: true,
1053
+ additionalProperties: telemetryData
1054
+ }
1055
+ );
1056
+ if (posthogClient) await posthogClient.shutdown();
1057
+ exit(1);
1058
+ }
708
1059
  pkgManager = res.packageManager;
1060
+ telemetryData["package-manager"] = pkgManager;
709
1061
  }
710
1062
  let projectName = opts.projectName;
711
1063
  if (!projectName) {
@@ -716,13 +1068,29 @@ async function run() {
716
1068
  initial: "my-tina-app",
717
1069
  validate: (name2) => {
718
1070
  const { message, isError } = validate(
719
- path4.basename(path4.resolve(name2))
1071
+ path3.basename(path3.resolve(name2))
720
1072
  );
721
1073
  if (isError) return `Invalid project name: ${message}`;
722
1074
  return true;
723
1075
  }
724
1076
  });
725
- if (!Object.hasOwn(res, "name")) exit(1);
1077
+ if (!Object.hasOwn(res, "name")) {
1078
+ postHogCaptureError(
1079
+ posthogClient,
1080
+ userId,
1081
+ sessionId,
1082
+ new Error("User cancelled project name input"),
1083
+ {
1084
+ errorCode: ERROR_CODES.ERR_CANCEL_PROJECT_NAME_PROMPT,
1085
+ errorCategory: "user-cancellation",
1086
+ step: TRACKING_STEPS.PROJECT_NAME_INPUT,
1087
+ fatal: true,
1088
+ additionalProperties: telemetryData
1089
+ }
1090
+ );
1091
+ if (posthogClient) await posthogClient.shutdown();
1092
+ exit(1);
1093
+ }
726
1094
  projectName = res.name;
727
1095
  }
728
1096
  if (!template) {
@@ -732,9 +1100,26 @@ async function run() {
732
1100
  message: "What starter code would you like to use?",
733
1101
  choices: TEMPLATES.map(formatTemplateChoice)
734
1102
  });
735
- if (!Object.hasOwn(res, "template")) exit(1);
1103
+ if (!Object.hasOwn(res, "template")) {
1104
+ postHogCaptureError(
1105
+ posthogClient,
1106
+ userId,
1107
+ sessionId,
1108
+ new Error("User cancelled template selection"),
1109
+ {
1110
+ errorCode: ERROR_CODES.ERR_CANCEL_TEMPLATE_PROMPT,
1111
+ errorCategory: "user-cancellation",
1112
+ step: TRACKING_STEPS.TEMPLATE_SELECT,
1113
+ fatal: true,
1114
+ additionalProperties: telemetryData
1115
+ }
1116
+ );
1117
+ if (posthogClient) await posthogClient.shutdown();
1118
+ exit(1);
1119
+ }
736
1120
  template = TEMPLATES.find((_template) => _template.value === res.template);
737
1121
  }
1122
+ telemetryData["template"] = template.value;
738
1123
  let themeChoice;
739
1124
  if (template.value === "tina-docs") {
740
1125
  const res = await prompts({
@@ -743,32 +1128,73 @@ async function run() {
743
1128
  message: "What theme would you like to use?",
744
1129
  choices: THEMES
745
1130
  });
746
- if (!Object.hasOwn(res, "theme")) exit(1);
1131
+ if (!Object.hasOwn(res, "theme")) {
1132
+ postHogCaptureError(
1133
+ posthogClient,
1134
+ userId,
1135
+ sessionId,
1136
+ new Error("User cancelled theme selection"),
1137
+ {
1138
+ errorCode: ERROR_CODES.ERR_CANCEL_THEME_PROMPT,
1139
+ errorCategory: "user-cancellation",
1140
+ step: TRACKING_STEPS.THEME_SELECT,
1141
+ fatal: true,
1142
+ additionalProperties: {
1143
+ ...telemetryData,
1144
+ template: template.value
1145
+ }
1146
+ }
1147
+ );
1148
+ if (posthogClient) await posthogClient.shutdown();
1149
+ exit(1);
1150
+ }
747
1151
  themeChoice = res.theme;
748
1152
  }
749
- await telemetry.submitRecord({
750
- event: {
751
- name: "create-tina-app:invoke",
752
- template: template.value,
753
- pkgManager
754
- }
755
- });
756
- const rootDir = path4.join(process.cwd(), projectName);
757
- if (!await isWriteable(path4.dirname(rootDir))) {
1153
+ const rootDir = path3.join(process.cwd(), projectName);
1154
+ if (!await isWriteable(path3.dirname(rootDir))) {
758
1155
  spinner.fail(
759
1156
  "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."
760
1157
  );
1158
+ postHogCaptureError(
1159
+ posthogClient,
1160
+ userId,
1161
+ sessionId,
1162
+ new Error("Directory not writable"),
1163
+ {
1164
+ errorCode: ERROR_CODES.ERR_FS_NOT_WRITABLE,
1165
+ errorCategory: "filesystem",
1166
+ step: TRACKING_STEPS.DIRECTORY_SETUP,
1167
+ fatal: true,
1168
+ additionalProperties: {
1169
+ ...telemetryData,
1170
+ template: template.value
1171
+ }
1172
+ }
1173
+ );
1174
+ if (posthogClient) await posthogClient.shutdown();
761
1175
  process.exit(1);
762
1176
  }
763
1177
  let appName;
764
1178
  try {
765
1179
  appName = await setupProjectDirectory(rootDir);
1180
+ telemetryData["app-name"] = appName;
766
1181
  } catch (err) {
767
- spinner.fail(err.message);
1182
+ const error = err;
1183
+ spinner.fail(error.message);
1184
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1185
+ errorCode: ERROR_CODES.ERR_FS_MKDIR_FAILED,
1186
+ errorCategory: "filesystem",
1187
+ step: TRACKING_STEPS.DIRECTORY_SETUP,
1188
+ fatal: true,
1189
+ additionalProperties: {
1190
+ ...telemetryData,
1191
+ template: template.value
1192
+ }
1193
+ });
1194
+ if (posthogClient) await posthogClient.shutdown();
768
1195
  exit(1);
769
1196
  }
770
1197
  try {
771
- await downloadTemplate(template, rootDir, spinner);
772
1198
  if (themeChoice) {
773
1199
  await updateThemeSettings(rootDir, themeChoice);
774
1200
  }
@@ -780,7 +1206,20 @@ async function run() {
780
1206
  updateProjectPackageVersion(rootDir, "0.0.1");
781
1207
  spinner.succeed();
782
1208
  } catch (err) {
783
- spinner.fail(`Failed to download template: ${err.message}`);
1209
+ const error = err;
1210
+ spinner.fail(`Failed to download template: ${error.message}`);
1211
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1212
+ errorCode: ERROR_CODES.ERR_TPL_DOWNLOAD_FAILED,
1213
+ errorCategory: "template",
1214
+ step: TRACKING_STEPS.DOWNLOADING_TEMPLATE,
1215
+ fatal: true,
1216
+ additionalProperties: {
1217
+ ...telemetryData,
1218
+ template: template.value,
1219
+ theme: themeChoice
1220
+ }
1221
+ });
1222
+ if (posthogClient) await posthogClient.shutdown();
784
1223
  exit(1);
785
1224
  }
786
1225
  spinner.start("Installing packages.");
@@ -788,8 +1227,20 @@ async function run() {
788
1227
  await install(pkgManager, opts.verbose);
789
1228
  spinner.succeed();
790
1229
  } catch (err) {
791
- spinner.fail(`Failed to install packages: ${err.message}`);
1230
+ const error = err;
1231
+ spinner.fail(`Failed to install packages: ${error.message}`);
792
1232
  packageManagerInstallationHadError = true;
1233
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1234
+ errorCode: ERROR_CODES.ERR_INSTALL_PKG_MANAGER_FAILED,
1235
+ errorCategory: "installation",
1236
+ step: TRACKING_STEPS.INSTALLING_PACKAGES,
1237
+ fatal: false,
1238
+ additionalProperties: {
1239
+ ...telemetryData,
1240
+ template: template.value,
1241
+ package_manager: pkgManager
1242
+ }
1243
+ });
793
1244
  }
794
1245
  spinner.start("Initializing git repository.");
795
1246
  try {
@@ -798,8 +1249,26 @@ async function run() {
798
1249
  spinner.succeed();
799
1250
  }
800
1251
  } catch (err) {
1252
+ const error = err;
801
1253
  spinner.fail("Failed to initialize Git repository, skipping.");
1254
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1255
+ errorCode: ERROR_CODES.ERR_GIT_INIT_FAILED,
1256
+ errorCategory: "git",
1257
+ step: TRACKING_STEPS.GIT_INIT,
1258
+ fatal: false,
1259
+ additionalProperties: {
1260
+ ...telemetryData,
1261
+ template: template.value
1262
+ }
1263
+ });
802
1264
  }
1265
+ postHogCapture(
1266
+ posthogClient,
1267
+ userId,
1268
+ sessionId,
1269
+ CreateTinaAppFinishedEvent,
1270
+ telemetryData
1271
+ );
803
1272
  spinner.succeed(`Created ${TextStyles.tinaOrange(appName)}
804
1273
  `);
805
1274
  if (template.value === "tina-hugo-starter") {
@@ -842,12 +1311,28 @@ async function run() {
842
1311
  )}`
843
1312
  );
844
1313
  }
845
- run().catch((error) => {
1314
+ run().catch(async (error) => {
846
1315
  if (process.stdout.columns >= 60) {
847
1316
  console.log(TextStyles.tinaOrange(`${errorArt}`));
848
1317
  }
849
- console.error("Error running create-tina-app: \n", error);
1318
+ console.error("Error running create-tina-app:", error);
1319
+ const sessionId = generateSessionId();
1320
+ const userId = await getAnonymousUserId();
1321
+ postHogCaptureError(posthogClient, userId, sessionId, error, {
1322
+ errorCode: ERROR_CODES.ERR_UNCAUGHT,
1323
+ errorCategory: "uncategorized",
1324
+ step: "unknown",
1325
+ fatal: true,
1326
+ additionalProperties: {}
1327
+ });
1328
+ if (posthogClient) {
1329
+ await posthogClient.shutdown();
1330
+ }
850
1331
  process.exit(1);
1332
+ }).then(async () => {
1333
+ if (posthogClient) {
1334
+ await posthogClient.shutdown();
1335
+ }
851
1336
  });
852
1337
  export {
853
1338
  run
@@ -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,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": "0.0.0-f1cec43-20251216232909",
3
+ "version": "0.0.0-f2dd173-20251229042232",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "files": [
@@ -44,9 +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
- "@tinacms/metrics": "2.0.1"
49
+ "systeminformation": "^5.27.13",
50
+ "tar": "7.4.0"
50
51
  },
51
52
  "scripts": {
52
53
  "types": "pnpm tsc",