1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
{ depot, pkgs, ... }:

let
  # Upstream firefox-profiler frontend source, pinned to a specific commit.
  profilerSrc = pkgs.fetchFromGitHub {
    owner = "firefox-devtools";
    repo = "profiler";
    rev = "cab6f4aa17406f55c290a3a3bcdd05b6ff8bd901";
    hash = "sha256-6ZqfxkeUUq3C1V6m18CFJElJJzoatibLgJjvfSi7rqU=";
  };

  # Build the profiler frontend (sandboxed, runs as nixbld).
  # We patch PROFILER_SERVER_ORIGIN to '' so all API calls are relative —
  # the Go server serves both the frontend and the API from the same origin.
  profilerFrontend = pkgs.stdenv.mkDerivation {
    pname = "firefox-profiler";
    version = "0.0.0";
    src = profilerSrc;

    # Upstream's lockfile is yarn v1, so the offline cache is built by
    # `fetchYarnDeps` and unpacked by `yarnConfigHook`.
    yarnOfflineCache = pkgs.fetchYarnDeps {
      yarnLock = profilerSrc + "/yarn.lock";
      hash = "sha256-9Cbb+YWWnMRgxV1t/Yn84J1oTP1w9V++IzvTSfaqvkY=";
    };

    nativeBuildInputs = [
      pkgs.yarnConfigHook
      # Needed for executing package.json scripts.
      pkgs.nodejs_24
    ];

    # Empty string → relative URLs → frontend calls the same-origin Go server.
    postPatch = ''
      substituteInPlace src/app-logic/constants.ts \
        --replace-fail \
          "export const PROFILER_SERVER_ORIGIN = 'https://api.profiler.firefox.com';" \
          "export const PROFILER_SERVER_ORIGIN = ''';"
    '';

    buildPhase = ''
      runHook preBuild
      export HOME=$NIX_BUILD_TOP
      yarn --offline build-prod
      runHook postBuild
    '';

    # `yarnInstallHook` is meant for installing a node package; here we only
    # want the built static assets, so the install step is spelled out.
    installPhase = ''
      runHook preInstall
      cp -r dist $out
      runHook postInstall
    '';
  };

  # Assemble the Go source tree with the built frontend dist/ embedded inside.
  goSrc = pkgs.runCommand "firefox-profiler-server-src" { } ''
    mkdir -p "$out"
    cp ${./main.go} "$out/main.go"
    cp ${./go.mod} "$out/go.mod"
    cp -r ${profilerFrontend} "$out/dist"
  '';

  # Build the Go server binary. No external Go dependencies — stdlib only.
  server = pkgs.buildGoModule {
    pname = "firefox-profiler";
    version = "0";
    src = goSrc;
    # No external Go modules — stdlib + embed only.
    vendorHash = null;
  };

in
pkgs.symlinkJoin {
  name = "firefox-profiler";
  outputs = [ "out" "man" ];
  paths = [ server ];
  postBuild = ''
    mkdir -p $man/share/man/man1
    cp ${./firefox-profiler.1} $man/share/man/man1/firefox-profiler.1
  '';
}