<commit_before>(ns figwheel-sidecar.components.cljs-autobuild
  (:require
   [figwheel-sidecar.config :refer [prep-build prepped?]]
   [figwheel-sidecar.build-utils :as butils]
   [figwheel-sidecar.watching :as watching]
   [figwheel-sidecar.utils :as utils]
   [figwheel-sidecar.cljs-utils.exception-parsing :as cljs-ex]
   [strictly-specking-standalone.ansi-util :refer [with-color with-color-when color-text]]

      ;; build hooks
   [figwheel-sidecar.build-middleware.injection :as injection]
   [figwheel-sidecar.build-middleware.notifications :as notifications]
   [figwheel-sidecar.build-middleware.clj-reloading :as clj-reloading]
   [figwheel-sidecar.build-middleware.javascript-reloading :as javascript-reloading]
   [figwheel-sidecar.build-middleware.stamp-and-clean :as stamp-and-clean]

   [com.stuartsierra.component :as component]
   [cljs.closure]
   [cljs.build.api :as bapi]
   [clojure.java.io :as io]
   [clojure.java.shell :refer [sh]]
   [clojure.java.browse]))

;; TODO can I run this without a figwheel server??
;; that would make this component much more useful

(defn cljs-build [{:keys [build-config]}]
  (bapi/build
   (apply bapi/inputs (:compile-paths build-config))
   (:build-options build-config)
   (:compiler-env build-config)))

(defn time-elapsed [started-at]
  (let [elapsed-us (- (System/currentTimeMillis) started-at)]
    (with-precision 2
      (str (/ (double elapsed-us) 1000) " seconds"))))

(defn figwheel-start-and-end-messages [build-fn]
  (fn [{:keys [figwheel-server build-config changed-files] :as build-state}]
    (let [started-at (System/currentTimeMillis)
          {:keys [build-options compile-paths]} build-config
            {:keys [output-to]} build-options]
      ;; print start message
      (println (color-text (str "Compiling \"" output-to
                       "\" from " (pr-str compile-paths) "...")
                :none))
      (try
        (build-fn build-state)
        (println (color-text (str "Successfully compiled \"" output-to
                               "\" in " (time-elapsed started-at) ".")
                              :green))
        (catch Throwable e
          (println (color-text (str
                                "Failed to compile \""
                                output-to
                                "\" in " (time-elapsed started-at) ".")
                               :red))
          (throw e))
        (finally (flush))))))

(defn handle-notify-command [build-state s]
  (when-let [notify-command (-> build-state :build-config :notify-command)]
    (apply sh (concat notify-command [s]))))

(defn notify-command-hook [build-fn]
  (fn [build-state]
    (try
      (build-fn build-state)
      (handle-notify-command build-state (str "Successfully compiled ClojureScript build: "
                                              (name (-> build-state :build-config :id))))
      (catch Throwable e
        (handle-notify-command build-state (str "Failed compiling ClojureScript build: "
                                                (name (-> build-state :build-config :id))))
        (throw e)))))

(defn color-output [build-fn]
  (fn [{:keys [figwheel-server] :as build-state}]
    (with-color-when (-> figwheel-server :ansi-color-output)
      (build-fn build-state))))

;; !! assume nothing about the order of middleware !!
;; if the order changes re-verify the function of each part
(def figwheel-build
  (-> cljs-build
      injection/hook
      notify-command-hook
      figwheel-start-and-end-messages
      notifications/hook
      ;; the following two hooks have to be called before the notifications
      ;; a they modify the message on the way down
      clj-reloading/hook
      javascript-reloading/hook
      color-output))

(def figwheel-build-without-javascript-reloading
  (-> cljs-build
      injection/hook
      notify-command-hook
      figwheel-start-and-end-messages
      notifications/hook
      clj-reloading/hook
      color-output))

(def figwheel-build-without-clj-reloading
  (-> cljs-build
      injection/hook
      notify-command-hook
      figwheel-start-and-end-messages
      notifications/hook
      javascript-reloading/hook
      color-output))

(defn source-paths-that-affect-build [{:keys [build-options watch-paths]}]
  (let [{:keys [libs foreign-libs]} build-options]
    (concat
     watch-paths
     libs
     (not-empty (mapv :file foreign-libs)))))

;; open url build middleware

(def open-urls-once
  (memoize
   (fn [urls]
     (future
       (Thread/sleep 1000)
       (doseq [url urls]
         (clojure.java.browse/browse-url url))))))

(defn open-urls [build-state]
  (when-let [urls (not-empty
                   (-> build-state
                       :build-config
                       :figwheel
                       :open-urls
                       set))]
    (open-urls-once urls)))

(defn open-urls-hook [build-fn]
  (fn [build-state]
    (build-fn build-state)
    (open-urls build-state)))

(defn extract-data-for-deadman-app [build-config e]
  (-> build-config
      :figwheel
      (select-keys [:websocket-host :websocket-url :build-id])
      (assoc
       :autoload false
       :initial-messages [{:msg-name :compile-failed
                           :exception-data (cljs-ex/parse-exception e)}])))

(defn deadman-header-comment []
  "/* FIGWHEEL BAD COMPILE RECOVERY APPLICATION */
/* NOT YOUR COMPILED CLOJURESCRIPT - TEMPORARY FIGWHEEL GENERATED PROGRAM
 * This is only created in the case where the compile fails and you don't have a
 * generated output-to file.
 */")

(defn create-deadman-app-js [build-config output-to-filepath exception]
  (println (color-text (str "Figwheel: initial compile failed - outputting temporary helper application to "
                            output-to-filepath)
                       :magenta))
  (let [data (pr-str (pr-str (extract-data-for-deadman-app build-config exception)))]
    (spit (io/file output-to-filepath)
          (str
           (deadman-header-comment)
           "FIGWHEEL_CLIENT_CONFIGURATION="
           data
           ";\n"
           (slurp #_(io/file (str "../sidecar/resources/compiled-utils/figwheel-helper-deploy.js"))
                  (io/resource "compiled-utils/figwheel-helper-deploy.js"))))))

(defn deadman-output-to-file? [output-to-file]
  (when (.exists (io/file output-to-file))
    (when-let [first-line (first (line-seq (io/reader output-to-file)))]
      (re-matches #".*FIGWHEEL BAD COMPILE.*"  (str first-line)))))

;; this is just used for the initial build
(defn catch-print-hook
  "Build middleware hook that catches and prints errors."
  [build-fn]
  (fn [build-state]
    (try
      (build-fn build-state)
      (catch Throwable e
        (cljs-ex/print-exception e)
        (flush)
        (let [build-config (:build-config build-state)
              output-to-filepath (get-in build-config [:build-options :output-to])]
          ;; I am assuming for now that if the output file exists
          ;; the user is in an interactive development environment
          ;; if not we need to stop and let them know
          ;; this only applies if :output-to doesn't exist
          (when (or (not (.exists (io/file output-to-filepath)))
                    (deadman-output-to-file? output-to-filepath))
            ;; must be a fighweel build
            ;; and not a node build
            (if (and (:figwheel build-config)
                     (-> build-config :build-options :main)
                     (not (= :nodejs (:target build-config))))
              (create-deadman-app-js build-config output-to-filepath e)
              (throw
               (ex-info (str "---- Initial Figwheel ClojureScript Compilation Failed ---- \n"
                             "We need a successful initial build for Figwheel to connect correctly.\n")
                        {:reason :initial-cljs-build-exception
                         :escape-system-exceptions true}
                        e)))))))))

(defn extract-cljs-build-fn
  [{:keys [figwheel-server] :as cljs-autobuild}]
  (or (:cljs-build-fn cljs-autobuild)
      (:cljs-build-fn figwheel-server)
      ;; if no figwheel server
      ;; default build should be standard
      ;; cljs build<commit_msg>compile messaging and other accommodations for code splitting<commit_after>(ns figwheel-sidecar.components.cljs-autobuild
  (:require
   [figwheel-sidecar.config :refer [prep-build prepped?]]
   [figwheel-sidecar.build-utils :as butils]
   [figwheel-sidecar.watching :as watching]
   [figwheel-sidecar.utils :as utils]
   [figwheel-sidecar.cljs-utils.exception-parsing :as cljs-ex]
   [strictly-specking-standalone.ansi-util :refer [with-color with-color-when color-text]]

      ;; build hooks
   [figwheel-sidecar.build-middleware.injection :as injection]
   [figwheel-sidecar.build-middleware.notifications :as notifications]
   [figwheel-sidecar.build-middleware.clj-reloading :as clj-reloading]
   [figwheel-sidecar.build-middleware.javascript-reloading :as javascript-reloading]
   [figwheel-sidecar.build-middleware.stamp-and-clean :as stamp-and-clean]

   [com.stuartsierra.component :as component]
   [cljs.closure]
   [cljs.build.api :as bapi]
   [clojure.java.io :as io]
   [clojure.java.shell :refer [sh]]
   [clojure.java.browse]))

;; TODO can I run this without a figwheel server??
;; that would make this component much more useful

(defn cljs-build [{:keys [build-config]}]
  (bapi/build
   (apply bapi/inputs (:compile-paths build-config))
   (:build-options build-config)
   (:compiler-env build-config)))

(defn time-elapsed [started-at]
  (let [elapsed-us (- (System/currentTimeMillis) started-at)]
    (with-precision 2
      (str (/ (double elapsed-us) 1000) " seconds"))))

(defn figwheel-start-and-end-messages [build-fn]
  (fn [{:keys [figwheel-server build-config changed-files] :as build-state}]
    (let [started-at (System/currentTimeMillis)
          {:keys [build-options compile-paths id]} build-config
            {:keys [output-to output-dir]} build-options]
      ;; print start message
      (println (color-text (str "Compiling build :" id " to \""
                                (or output-to output-dir)
                                "\" from " (pr-str compile-paths) "...")
                           :none))
      (try
        (build-fn build-state)
        (println (color-text (str "Successfully compiled build :" id " to \""
                                  (or output-to output-dir)
                                  "\" in " (time-elapsed started-at) ".")
                             :green))
        (catch Throwable e
          (println (color-text (str
                                "Failed to compile build :" id " from "
                                (pr-str compile-paths)
                                " in " (time-elapsed started-at) ".")
                               :red))
          (throw e))
        (finally (flush))))))

(defn handle-notify-command [build-state s]
  (when-let [notify-command (-> build-state :build-config :notify-command)]
    (apply sh (concat notify-command [s]))))

(defn notify-command-hook [build-fn]
  (fn [build-state]
    (try
      (build-fn build-state)
      (handle-notify-command build-state (str "Successfully compiled ClojureScript build: "
                                              (name (-> build-state :build-config :id))))
      (catch Throwable e
        (handle-notify-command build-state (str "Failed compiling ClojureScript build: "
                                                (name (-> build-state :build-config :id))))
        (throw e)))))

(defn color-output [build-fn]
  (fn [{:keys [figwheel-server] :as build-state}]
    (with-color-when (-> figwheel-server :ansi-color-output)
      (build-fn build-state))))

;; !! assume nothing about the order of middleware !!
;; if the order changes re-verify the function of each part
(def figwheel-build
  (-> cljs-build
      injection/hook
      notify-command-hook
      figwheel-start-and-end-messages
      notifications/hook
      ;; the following two hooks have to be called before the notifications
      ;; a they modify the message on the way down
      clj-reloading/hook
      javascript-reloading/hook
      color-output))

(def figwheel-build-without-javascript-reloading
  (-> cljs-build
      injection/hook
      notify-command-hook
      figwheel-start-and-end-messages
      notifications/hook
      clj-reloading/hook
      color-output))

(def figwheel-build-without-clj-reloading
  (-> cljs-build
      injection/hook
      notify-command-hook
      figwheel-start-and-end-messages
      notifications/hook
      javascript-reloading/hook
      color-output))

(defn source-paths-that-affect-build [{:keys [build-options watch-paths]}]
  (let [{:keys [libs foreign-libs]} build-options]
    (concat
     watch-paths
     libs
     (not-empty (mapv :file foreign-libs)))))

;; open url build middleware

(def open-urls-once
  (memoize
   (fn [urls]
     (future
       (Thread/sleep 1000)
       (doseq [url urls]
         (clojure.java.browse/browse-url url))))))

(defn open-urls [build-state]
  (when-let [urls (not-empty
                   (-> build-state
                       :build-config
                       :figwheel
                       :open-urls
                       set))]
    (open-urls-once urls)))

(defn open-urls-hook [build-fn]
  (fn [build-state]
    (build-fn build-state)
    (open-urls build-state)))

(defn extract-data-for-deadman-app [build-config e]
  (-> build-config
      :figwheel
      (select-keys [:websocket-host :websocket-url :build-id])
      (assoc
       :autoload false
       :initial-messages [{:msg-name :compile-failed
                           :exception-data (cljs-ex/parse-exception e)}])))

(defn deadman-header-comment []
  "/* FIGWHEEL BAD COMPILE RECOVERY APPLICATION */
/* NOT YOUR COMPILED CLOJURESCRIPT - TEMPORARY FIGWHEEL GENERATED PROGRAM
 * This is only created in the case where the compile fails and you don't have a
 * generated output-to file.
 */")

(defn create-deadman-app-js [build-config output-to-filepath exception]
  (println (color-text (str "Figwheel: initial compile failed - outputting temporary helper application to "
                            output-to-filepath)
                       :magenta))
  (let [data (pr-str (pr-str (extract-data-for-deadman-app build-config exception)))]
    (spit (io/file output-to-filepath)
          (str
           (deadman-header-comment)
           "FIGWHEEL_CLIENT_CONFIGURATION="
           data
           ";\n"
           (slurp #_(io/file (str "../sidecar/resources/compiled-utils/figwheel-helper-deploy.js"))
                  (io/resource "compiled-utils/figwheel-helper-deploy.js"))))))

(defn deadman-output-to-file? [output-to-file]
  (when (.exists (io/file output-to-file))
    (when-let [first-line (first (line-seq (io/reader output-to-file)))]
      (re-matches #".*FIGWHEEL BAD COMPILE.*"  (str first-line)))))

;; this is just used for the initial build
(defn catch-print-hook
  "Build middleware hook that catches and prints errors."
  [build-fn]
  (fn [build-state]
    (try
      (build-fn build-state)
      (catch Throwable e
        (cljs-ex/print-exception e)
        (flush)
        (let [build-config (:build-config build-state)
              output-to-filepath (get-in build-config [:build-options :output-to])]
          ;; I am assuming for now that if the output file exists
          ;; the user is in an interactive development environment
          ;; if not we need to stop and let them know
          ;; this only applies if :output-to doesn't exist
          (when (or (not (.exists (io/file output-to-filepath)))
                    (deadman-output-to-file? output-to-filepath))
            ;; must be a fighweel build
            ;; and not a node build
            ;; and not a modules based build
            (if (and (:figwheel build-config)
                     (-> build-config :build-options :main)
                     (not (= :nodejs (:target build-config)))
                     (not (:modules build-config)))
              (create-deadman-app-js build-config output-to-filepath e)
              (throw
               (ex-info (str "---- Initial Figwheel ClojureScript Compilation Failed ---- \n"
                             "We need a successful initial build for Figwheel to connect correctly.\n")
                        {:reason :initial-cljs-build-exception
                         :escape-system-exceptions true}
                        e)))))))))

(defn extract-cljs-build-fn
  [{:keys [figwheel-server] :as cljs-autobuild}]
  (or (:cljs-build-fn cljs-autobuild)
      (:cljs-build-fn figwheel-server)
      ;; if no figwheel server
      ;; default build should be standard
      ;; cljs build
{'fraction_non_alphanumeric': 0.1264292520247737, 'fraction_numerical': 0.0010719390185802763, 'mean_word_length': 2.157766077472734, 'pattern_counts': {'":': 0, '<': 3, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 20, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '3399716', 'n_tokens_mistral': 5207, 'n_tokens_neox': 5035, 'n_words': 1255}