@nuvio/cli 0.5.3 → 0.5.4

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/README.md CHANGED
@@ -8,3 +8,15 @@ pnpm dev
8
8
  ```
9
9
 
10
10
  See [Nuvio docs](https://github.com/ehah/Nuvio/blob/main/docs/nuvioUser.md).
11
+
12
+ ## Telemetry
13
+
14
+ Nuvio collects anonymous usage metrics to improve onboarding and reliability. No source code, file contents, file paths, project names, emails, or personal data are sent.
15
+
16
+ Disable anytime with:
17
+
18
+ ```bash
19
+ NUVIO_TELEMETRY=0
20
+ ```
21
+
22
+ See [PostHog_telemetry.md](https://github.com/ehah/Nuvio/blob/main/docs/PostHog_telemetry.md).
package/dist/cli-entry.js CHANGED
@@ -20,7 +20,12 @@ var MSG = {
20
20
  monorepoRoot: "This looks like the Nuvio monorepo. Run init in your app folder, not the tooling repo.",
21
21
  cliPackage: "Cannot init inside @nuvio/cli package.",
22
22
  partialHelp: "Nuvio set up what it could safely. Finish the steps in nuvio/SETUP_TODO.md, then run your dev server.",
23
- noHeading: 'Nuvio is wired, but I could not find a heading to mark editable. Add data-nuvio-id="page.title" to one visible element (see nuvio/START_HERE.md).'
23
+ noHeading: 'Nuvio is wired, but I could not find a heading to mark editable. Add data-nuvio-id="page.title" to one visible element (see nuvio/START_HERE.md).',
24
+ telemetryNotice: `Nuvio collects anonymous usage metrics to improve onboarding and reliability.
25
+ No source code, file contents, file paths, project names, emails, or personal data are sent.
26
+
27
+ Disable anytime with:
28
+ NUVIO_TELEMETRY=0`
24
29
  };
25
30
 
26
31
  // src/detect-project.ts
@@ -692,6 +697,165 @@ function printVerification(v) {
692
697
  console.log(` Starter id page.title \u2014 ${v.starterId}`);
693
698
  }
694
699
 
700
+ // src/telemetry.ts
701
+ import { mkdirSync as mkdirSync2, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
702
+ import { homedir } from "os";
703
+ import { join as join7 } from "path";
704
+ import { randomUUID } from "crypto";
705
+ import os from "os";
706
+ import { PostHog } from "posthog-node";
707
+
708
+ // src/nuvio-posthog-token.ts
709
+ var NUVIO_POSTHOG_TOKEN = "phc_CJnWrLU4hB4aA88DJrPnma2WBMQqVHxUMVvrsye3R6x2";
710
+
711
+ // src/telemetry.ts
712
+ var POSTHOG_HOST = "https://us.i.posthog.com";
713
+ function telemetryFilePath() {
714
+ return join7(homedir(), ".nuvio", "telemetry.json");
715
+ }
716
+ var FORBIDDEN_PROP_KEYS = /* @__PURE__ */ new Set([
717
+ "cwd",
718
+ "root",
719
+ "file",
720
+ "path",
721
+ "name",
722
+ "message",
723
+ "stack"
724
+ ]);
725
+ var client = null;
726
+ var sessionAnonymousId = null;
727
+ function telemetryDebug(message, detail) {
728
+ if (process.env.NUVIO_TELEMETRY_DEBUG !== "1") return;
729
+ if (detail !== void 0) {
730
+ console.error(`[nuvio telemetry] ${message}`, detail);
731
+ return;
732
+ }
733
+ console.error(`[nuvio telemetry] ${message}`);
734
+ }
735
+ function isTelemetryEnabled() {
736
+ const flag = process.env.NUVIO_TELEMETRY;
737
+ if (flag === "0") return false;
738
+ if (flag?.toLowerCase() === "false") return false;
739
+ return true;
740
+ }
741
+ function posthogToken() {
742
+ return process.env.NUVIO_POSTHOG_TOKEN ?? NUVIO_POSTHOG_TOKEN;
743
+ }
744
+ function tokenIsConfigured(token) {
745
+ return Boolean(token && token.startsWith("phc_"));
746
+ }
747
+ function readOrCreateAnonymousId() {
748
+ if (sessionAnonymousId) return sessionAnonymousId;
749
+ try {
750
+ const raw = readFileSync10(telemetryFilePath(), "utf8");
751
+ const parsed = JSON.parse(raw);
752
+ if (parsed.anonymousId) {
753
+ sessionAnonymousId = parsed.anonymousId;
754
+ return parsed.anonymousId;
755
+ }
756
+ } catch {
757
+ }
758
+ const id = randomUUID();
759
+ sessionAnonymousId = id;
760
+ try {
761
+ mkdirSync2(join7(homedir(), ".nuvio"), { recursive: true, mode: 448 });
762
+ writeFileSync6(
763
+ telemetryFilePath(),
764
+ JSON.stringify({ anonymousId: id }, null, 2),
765
+ { mode: 384 }
766
+ );
767
+ } catch {
768
+ }
769
+ return id;
770
+ }
771
+ function getClient() {
772
+ if (!isTelemetryEnabled()) return null;
773
+ const token = posthogToken();
774
+ if (!tokenIsConfigured(token)) return null;
775
+ if (!client) {
776
+ client = new PostHog(token, {
777
+ host: POSTHOG_HOST,
778
+ flushAt: 1,
779
+ flushInterval: 0
780
+ });
781
+ telemetryDebug("PostHog client initialized", {
782
+ host: POSTHOG_HOST,
783
+ tokenPrefix: `${token.slice(0, 8)}\u2026`
784
+ });
785
+ }
786
+ return client;
787
+ }
788
+ function sanitizeProps(props) {
789
+ if (!props) return void 0;
790
+ const out = {};
791
+ for (const [key, value] of Object.entries(props)) {
792
+ if (FORBIDDEN_PROP_KEYS.has(key)) continue;
793
+ if (value === void 0) continue;
794
+ if (typeof value === "string" && /[/\\]/.test(value)) continue;
795
+ out[key] = value;
796
+ }
797
+ return Object.keys(out).length > 0 ? out : void 0;
798
+ }
799
+ function buildCliTelemetryProps(pm, project) {
800
+ const props = {
801
+ nuvio_version: NUVIO_VERSION,
802
+ os: process.platform,
803
+ arch: os.arch(),
804
+ node: process.version
805
+ };
806
+ if (pm) props.package_manager = pm;
807
+ if (project) {
808
+ props.has_react = true;
809
+ props.has_vite = true;
810
+ props.has_tailwind = project.tailwindOk;
811
+ }
812
+ return props;
813
+ }
814
+ function preflightErrorCode(message) {
815
+ if (message === MSG.noPackageJson) return "preflight_no_package_json";
816
+ if (message === MSG.noVite) return "preflight_no_vite";
817
+ if (message === MSG.noReact) return "preflight_no_react";
818
+ if (message === MSG.noViteDep) return "preflight_no_vite_dep";
819
+ if (message === MSG.monorepoRoot || message === MSG.cliPackage) {
820
+ return "preflight_monorepo";
821
+ }
822
+ return "preflight_unknown";
823
+ }
824
+ function captureCliEvent(event, props) {
825
+ try {
826
+ if (!isTelemetryEnabled()) {
827
+ telemetryDebug(`skipped ${event} (telemetry disabled)`);
828
+ return;
829
+ }
830
+ const ph = getClient();
831
+ if (!ph) {
832
+ telemetryDebug(`skipped ${event} (no PostHog client \u2014 check token)`);
833
+ return;
834
+ }
835
+ const distinctId = readOrCreateAnonymousId();
836
+ ph.capture({
837
+ distinctId,
838
+ event,
839
+ properties: sanitizeProps(props)
840
+ });
841
+ telemetryDebug(`captured ${event}`, { distinctId });
842
+ } catch (error) {
843
+ telemetryDebug(`capture failed for ${event}`, error);
844
+ }
845
+ }
846
+ async function shutdownTelemetry() {
847
+ try {
848
+ if (client) {
849
+ await client.flush();
850
+ await client.shutdown();
851
+ client = null;
852
+ telemetryDebug("flush + shutdown complete");
853
+ }
854
+ } catch (error) {
855
+ telemetryDebug("shutdown failed", error);
856
+ }
857
+ }
858
+
695
859
  // src/init.ts
696
860
  function isAutoYes(opts) {
697
861
  if (opts.yes) return true;
@@ -756,28 +920,53 @@ ${MSG.partialHelp}`);
756
920
  "\nNuvio helped you as far as it safely could. See warnings above."
757
921
  );
758
922
  }
923
+ console.log(`
924
+ ${MSG.telemetryNotice}`);
759
925
  }
760
926
  async function runInit(opts) {
761
927
  const root = opts.cwd;
928
+ const pm = detectPackageManager(root, opts.pm);
929
+ if (!opts.dryRun) {
930
+ captureCliEvent(
931
+ "nuvio_init_started",
932
+ buildCliTelemetryProps(pm)
933
+ );
934
+ }
762
935
  let project;
763
936
  try {
764
937
  project = detectProject(root);
765
938
  } catch (e) {
766
939
  if (e instanceof PreflightError) {
767
940
  console.error(e.message);
941
+ if (!opts.dryRun) {
942
+ captureCliEvent("nuvio_init_failed", {
943
+ ...buildCliTelemetryProps(pm),
944
+ error_code: preflightErrorCode(e.message)
945
+ });
946
+ }
768
947
  return 1;
769
948
  }
770
949
  if (opts.verbose) console.error(e);
771
950
  console.error("Something went wrong. Run with --verbose for details.");
951
+ if (!opts.dryRun) {
952
+ captureCliEvent("nuvio_init_failed", {
953
+ ...buildCliTelemetryProps(pm),
954
+ error_code: "unexpected_error"
955
+ });
956
+ }
772
957
  return 2;
773
958
  }
774
- const pm = detectPackageManager(root, opts.pm);
959
+ const projectProps = buildCliTelemetryProps(pm, project);
775
960
  const plan = createPlan(root, pm);
776
961
  plan.installCommand = opts.noInstall ? "(skipped \u2014 --no-install)" : installCommand(pm, NUVIO_VERSION);
777
962
  if (project.tailwindWarn && !opts.skipTailwindCheck) {
778
963
  const msg = "Tailwind CSS not detected. Class/style edits may not work until Tailwind is installed.";
779
964
  if (opts.strict) {
780
965
  console.error(MSG.strictTailwind);
966
+ captureCliEvent("nuvio_init_failed", {
967
+ ...projectProps,
968
+ error_code: "strict_tailwind"
969
+ });
781
970
  return 1;
782
971
  }
783
972
  plan.warnings.push(msg);
@@ -808,6 +997,10 @@ async function runInit(opts) {
808
997
  const ok = await confirm(plan);
809
998
  if (!ok) {
810
999
  console.log("Cancelled.");
1000
+ captureCliEvent("nuvio_init_failed", {
1001
+ ...projectProps,
1002
+ error_code: "user_cancelled"
1003
+ });
811
1004
  return 1;
812
1005
  }
813
1006
  }
@@ -817,6 +1010,10 @@ async function runInit(opts) {
817
1010
  const result = runInstall(root, pm, NUVIO_VERSION);
818
1011
  if (!result.ok) {
819
1012
  console.error(result.message ?? "Install failed.");
1013
+ captureCliEvent("nuvio_init_failed", {
1014
+ ...projectProps,
1015
+ error_code: "install_failed"
1016
+ });
820
1017
  return 1;
821
1018
  }
822
1019
  } else {
@@ -889,7 +1086,18 @@ async function runInit(opts) {
889
1086
  project.viteConfigPath
890
1087
  );
891
1088
  printVerification(verification);
892
- if (plan.tier === "failed") return 1;
1089
+ if (plan.tier === "failed") {
1090
+ captureCliEvent("nuvio_init_failed", {
1091
+ ...projectProps,
1092
+ error_code: "init_tier_failed",
1093
+ result_tier: "failed"
1094
+ });
1095
+ return 1;
1096
+ }
1097
+ captureCliEvent("nuvio_init_completed", {
1098
+ ...projectProps,
1099
+ result_tier: plan.tier
1100
+ });
893
1101
  return plan.tier === "partial" || plan.tier === "full" ? 0 : 1;
894
1102
  }
895
1103
 
@@ -968,9 +1176,16 @@ async function runCli(argv) {
968
1176
  try {
969
1177
  return await runInit(opts);
970
1178
  } catch (e) {
1179
+ const pm = detectPackageManager(opts.cwd, opts.pm);
1180
+ captureCliEvent("nuvio_init_failed", {
1181
+ ...buildCliTelemetryProps(pm),
1182
+ error_code: "unexpected_error"
1183
+ });
971
1184
  if (opts.verbose) console.error(e);
972
1185
  else console.error("Something went wrong. Run with --verbose for details.");
973
1186
  return 2;
1187
+ } finally {
1188
+ await shutdownTelemetry();
974
1189
  }
975
1190
  }
976
1191
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nuvio/cli",
3
- "version": "0.5.3",
3
+ "version": "0.5.4",
4
4
  "description": "Nuvio CLI: one-command Vite + React onboarding (nuvio init).",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -36,7 +36,8 @@
36
36
  "@babel/parser": "^7.26.9",
37
37
  "@babel/traverse": "^7.26.9",
38
38
  "@babel/types": "^7.26.9",
39
- "fast-glob": "^3.3.3"
39
+ "fast-glob": "^3.3.3",
40
+ "posthog-node": "^5.36.2"
40
41
  },
41
42
  "devDependencies": {
42
43
  "@types/babel__generator": "^7.6.8",