@farm.js/create-app 0.1.0-beta.36 → 0.1.0-beta.38

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.mjs CHANGED
@@ -4,8 +4,10 @@ import path from "path";
4
4
  import fs from "fs/promises";
5
5
  import { spawn } from "node:child_process";
6
6
  import { existsSync } from "node:fs";
7
- import { mkdir, readFile, writeFile } from "node:fs/promises";
7
+ import { chmod, mkdir, readFile, rename, unlink, writeFile } from "node:fs/promises";
8
8
  import path$1 from "node:path";
9
+ import { randomUUID } from "node:crypto";
10
+ import os from "node:os";
9
11
  //#region ../farm-cli/src/ui-feature-registry.ts
10
12
  async function installUIFeature(input) {
11
13
  const feature = input.definition.ui;
@@ -2520,6 +2522,269 @@ function escapeRegExp(input) {
2520
2522
  return input.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
2521
2523
  }
2522
2524
  //#endregion
2525
+ //#region ../farm-cli/src/telemetry.ts
2526
+ const TELEMETRY_SCHEMA_VERSION = 1;
2527
+ const DEFAULT_TELEMETRY_ENDPOINT = "https://farmjs.dev/api/telemetry/v1/events";
2528
+ const TELEMETRY_NOTICE_URL = "https://farmjs.dev/docs/telemetry";
2529
+ const REQUEST_TIMEOUT_MS = 750;
2530
+ const FARM_TEMPLATES = [
2531
+ "basic",
2532
+ "react-compiler",
2533
+ "auth",
2534
+ "better-auth",
2535
+ "ai",
2536
+ "auth0",
2537
+ "authjs",
2538
+ "autumn",
2539
+ "clerk",
2540
+ "jobs-inngest",
2541
+ "jobs-trigger",
2542
+ "polar",
2543
+ "resend",
2544
+ "stripe",
2545
+ "supabase",
2546
+ "unkey",
2547
+ "workos"
2548
+ ];
2549
+ const RENDERERS = [
2550
+ "react",
2551
+ "preact",
2552
+ "solid",
2553
+ "vue",
2554
+ "svelte"
2555
+ ];
2556
+ const PACKAGE_MANAGERS = [
2557
+ "npm",
2558
+ "pnpm",
2559
+ "yarn",
2560
+ "bun"
2561
+ ];
2562
+ function defaultConfig() {
2563
+ return {
2564
+ version: TELEMETRY_SCHEMA_VERSION,
2565
+ enabled: false,
2566
+ noticeShown: false
2567
+ };
2568
+ }
2569
+ function configDirectory() {
2570
+ if (process.env.FARM_TELEMETRY_CONFIG_DIR) return path$1.resolve(process.env.FARM_TELEMETRY_CONFIG_DIR);
2571
+ if (process.platform === "win32") return path$1.join(process.env.APPDATA || path$1.join(os.homedir(), "AppData", "Roaming"), "farmjs");
2572
+ if (process.platform === "darwin") return path$1.join(os.homedir(), "Library", "Application Support", "farmjs");
2573
+ return path$1.join(process.env.XDG_CONFIG_HOME || path$1.join(os.homedir(), ".config"), "farmjs");
2574
+ }
2575
+ function getFarmTelemetryConfigFile() {
2576
+ return path$1.join(configDirectory(), "telemetry.json");
2577
+ }
2578
+ async function readConfig() {
2579
+ try {
2580
+ const parsed = JSON.parse(await readFile(getFarmTelemetryConfigFile(), "utf8"));
2581
+ if (parsed.version !== TELEMETRY_SCHEMA_VERSION) return defaultConfig();
2582
+ return {
2583
+ version: TELEMETRY_SCHEMA_VERSION,
2584
+ enabled: parsed.enabled === true,
2585
+ noticeShown: parsed.noticeShown === true,
2586
+ anonymousId: isUuid(parsed.anonymousId) ? parsed.anonymousId : void 0
2587
+ };
2588
+ } catch {
2589
+ return defaultConfig();
2590
+ }
2591
+ }
2592
+ async function writeConfig(config) {
2593
+ const file = getFarmTelemetryConfigFile();
2594
+ const directory = path$1.dirname(file);
2595
+ const temporaryFile = `${file}.${process.pid}.${randomUUID()}.tmp`;
2596
+ try {
2597
+ await mkdir(directory, {
2598
+ recursive: true,
2599
+ mode: 448
2600
+ });
2601
+ await writeFile(temporaryFile, `${JSON.stringify(config, null, 2)}\n`, { mode: 384 });
2602
+ await rename(temporaryFile, file);
2603
+ await chmod(file, 384).catch(() => void 0);
2604
+ } catch {
2605
+ await unlink(temporaryFile).catch(() => void 0);
2606
+ }
2607
+ }
2608
+ function isUuid(value) {
2609
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
2610
+ }
2611
+ function isTrue(value) {
2612
+ return value !== void 0 && [
2613
+ "1",
2614
+ "true",
2615
+ "yes",
2616
+ "on"
2617
+ ].includes(value.toLowerCase());
2618
+ }
2619
+ function isFalse(value) {
2620
+ return value !== void 0 && [
2621
+ "0",
2622
+ "false",
2623
+ "no",
2624
+ "off"
2625
+ ].includes(value.toLowerCase());
2626
+ }
2627
+ function environmentDecision() {
2628
+ if (process.env.DO_NOT_TRACK !== void 0 && !isFalse(process.env.DO_NOT_TRACK)) return {
2629
+ enabled: false,
2630
+ reason: "DO_NOT_TRACK is set"
2631
+ };
2632
+ if (isTrue(process.env.FARM_TELEMETRY_DISABLED)) return {
2633
+ enabled: false,
2634
+ reason: "FARM_TELEMETRY_DISABLED is set"
2635
+ };
2636
+ if (isTrue(process.env.FARM_TELEMETRY)) return { enabled: true };
2637
+ if (isFalse(process.env.FARM_TELEMETRY)) return {
2638
+ enabled: false,
2639
+ reason: "FARM_TELEMETRY disables collection"
2640
+ };
2641
+ return {};
2642
+ }
2643
+ function isContinuousIntegration() {
2644
+ return isTrue(process.env.CI) || isTrue(process.env.GITHUB_ACTIONS) || isTrue(process.env.BUILDKITE) || isTrue(process.env.CIRCLECI);
2645
+ }
2646
+ function isInteractive() {
2647
+ return process.stdin.isTTY === true && process.stdout.isTTY === true;
2648
+ }
2649
+ function getEndpoint() {
2650
+ const candidate = process.env.FARM_TELEMETRY_ENDPOINT || DEFAULT_TELEMETRY_ENDPOINT;
2651
+ try {
2652
+ const url = new URL(candidate);
2653
+ const isLocal = [
2654
+ "localhost",
2655
+ "127.0.0.1",
2656
+ "::1"
2657
+ ].includes(url.hostname);
2658
+ if (url.protocol !== "https:" && !(url.protocol === "http:" && isLocal)) return DEFAULT_TELEMETRY_ENDPOINT;
2659
+ return url.toString();
2660
+ } catch {
2661
+ return DEFAULT_TELEMETRY_ENDPOINT;
2662
+ }
2663
+ }
2664
+ async function resolveState() {
2665
+ const config = await readConfig();
2666
+ const environment = environmentDecision();
2667
+ const enabled = environment.enabled ?? config.enabled;
2668
+ const source = environment.enabled !== void 0 ? "environment" : config.enabled ? "configuration" : "default";
2669
+ if (!enabled) return {
2670
+ config,
2671
+ enabled,
2672
+ active: false,
2673
+ source,
2674
+ reason: environment.reason
2675
+ };
2676
+ if (environment.enabled === true) return {
2677
+ config,
2678
+ enabled,
2679
+ active: true,
2680
+ source
2681
+ };
2682
+ if (process.env.NODE_ENV === "test") return {
2683
+ config,
2684
+ enabled,
2685
+ active: false,
2686
+ source,
2687
+ reason: "test environments are skipped"
2688
+ };
2689
+ if (isContinuousIntegration()) return {
2690
+ config,
2691
+ enabled,
2692
+ active: false,
2693
+ source,
2694
+ reason: "CI environments are skipped"
2695
+ };
2696
+ if (!isInteractive()) return {
2697
+ config,
2698
+ enabled,
2699
+ active: false,
2700
+ source,
2701
+ reason: "non-interactive commands are skipped"
2702
+ };
2703
+ return {
2704
+ config,
2705
+ enabled,
2706
+ active: true,
2707
+ source
2708
+ };
2709
+ }
2710
+ async function showFarmTelemetryNotice() {
2711
+ if (!isInteractive() || isContinuousIntegration() || process.env.NODE_ENV === "test") return;
2712
+ if (environmentDecision().enabled !== void 0) return;
2713
+ const config = await readConfig();
2714
+ if (config.noticeShown) return;
2715
+ process.stderr.write(`Farm.js anonymous telemetry is off during beta. Run "farm telemetry enable" to opt in.\nLearn more: ${TELEMETRY_NOTICE_URL}\n`);
2716
+ await writeConfig({
2717
+ ...config,
2718
+ noticeShown: true
2719
+ });
2720
+ }
2721
+ async function trackFarmProjectCreated(input) {
2722
+ const template = allowlisted(input.template, FARM_TEMPLATES);
2723
+ const renderer = allowlisted(input.renderer, RENDERERS);
2724
+ const packageManager = allowlisted(input.packageManager, PACKAGE_MANAGERS);
2725
+ await track({
2726
+ eventType: "project_created",
2727
+ source: "create-app",
2728
+ packageName: "@farm.js/create-app",
2729
+ packageVersion: sanitizeVersion(input.packageVersion),
2730
+ ...template ? { template } : {},
2731
+ ...renderer ? { renderer } : {},
2732
+ ...packageManager ? { packageManager } : {},
2733
+ ...typeof input.typescript === "boolean" ? { typescript: input.typescript } : {},
2734
+ ...typeof input.installedDependencies === "boolean" ? { installedDependencies: input.installedDependencies } : {}
2735
+ });
2736
+ }
2737
+ async function track(event) {
2738
+ try {
2739
+ const state = await resolveState();
2740
+ if (!state.active) return;
2741
+ const anonymousId = state.config.anonymousId || randomUUID();
2742
+ if (!state.config.anonymousId) await writeConfig({
2743
+ ...state.config,
2744
+ anonymousId
2745
+ });
2746
+ await send({
2747
+ schemaVersion: TELEMETRY_SCHEMA_VERSION,
2748
+ eventId: randomUUID(),
2749
+ anonymousId,
2750
+ nodeMajor: Number.parseInt(process.versions.node.split(".")[0] || "0", 10),
2751
+ platform: normalizePlatform(process.platform),
2752
+ architecture: normalizeArchitecture(process.arch),
2753
+ ...event
2754
+ });
2755
+ } catch {}
2756
+ }
2757
+ async function send(payload) {
2758
+ const controller = new AbortController();
2759
+ const timeout = setTimeout(() => controller.abort(), REQUEST_TIMEOUT_MS);
2760
+ timeout.unref?.();
2761
+ try {
2762
+ await fetch(getEndpoint(), {
2763
+ method: "POST",
2764
+ headers: { "content-type": "application/json" },
2765
+ body: JSON.stringify(payload),
2766
+ signal: controller.signal,
2767
+ keepalive: true
2768
+ });
2769
+ } catch {} finally {
2770
+ clearTimeout(timeout);
2771
+ }
2772
+ }
2773
+ function sanitizeVersion(value) {
2774
+ return /^[0-9A-Za-z.+_-]{1,64}$/.test(value) ? value : "unknown";
2775
+ }
2776
+ function allowlisted(value, values) {
2777
+ return value && values.includes(value) ? value : void 0;
2778
+ }
2779
+ function normalizePlatform(value) {
2780
+ if (value === "darwin" || value === "linux") return value;
2781
+ if (value === "win32") return "windows";
2782
+ return "other";
2783
+ }
2784
+ function normalizeArchitecture(value) {
2785
+ return value === "arm64" || value === "x64" ? value : "other";
2786
+ }
2787
+ //#endregion
2523
2788
  //#region src/index.ts
2524
2789
  const templateDetails = {
2525
2790
  basic: {
@@ -2662,6 +2927,7 @@ function integrationTemplate(input) {
2662
2927
  }
2663
2928
  async function createApp(projectName, options = {}) {
2664
2929
  showBanner();
2930
+ await showFarmTelemetryNotice();
2665
2931
  const templates = await getAvailableTemplates();
2666
2932
  if (templates.length === 0) {
2667
2933
  logger.error("No templates are available in this package.");
@@ -2820,6 +3086,14 @@ async function createApp(projectName, options = {}) {
2820
3086
  if (integrationResult.env.length) logger.info("Copy .env.example to .env.local and add your provider credentials.");
2821
3087
  for (const note of integrationResult.notes) logger.info(note);
2822
3088
  }
3089
+ await trackFarmProjectCreated({
3090
+ packageVersion: options.telemetryPackageVersion ?? "unknown",
3091
+ template,
3092
+ renderer,
3093
+ packageManager: packageManager.name,
3094
+ typescript: useTypeScript,
3095
+ installedDependencies: !options.skipInstall
3096
+ });
2823
3097
  }
2824
3098
  async function copyTemplate(template, projectPath, useTypeScript, renderer) {
2825
3099
  const details = templateDetails[template];