{"id":19942052,"url":"https://github.com/day8/shadow-git-inject","last_synced_at":"2025-05-03T15:32:03.025Z","repository":{"id":42080108,"uuid":"349855396","full_name":"day8/shadow-git-inject","owner":"day8","description":"A shadow-cljs build hook that computes the \"version\" at build-time - from the ambient git context (latest tag?).","archived":false,"fork":false,"pushed_at":"2022-04-13T12:52:54.000Z","size":89,"stargazers_count":28,"open_issues_count":1,"forks_count":0,"subscribers_count":7,"default_branch":"master","last_synced_at":"2025-04-06T02:34:48.693Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Clojure","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"epl-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/day8.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":".github/FUNDING.yml","license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null},"funding":{"github":"mike-thompson-day8"}},"created_at":"2021-03-20T23:15:57.000Z","updated_at":"2024-01-28T02:55:49.000Z","dependencies_parsed_at":"2022-08-12T04:20:34.077Z","dependency_job_id":null,"html_url":"https://github.com/day8/shadow-git-inject","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/day8%2Fshadow-git-inject","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/day8%2Fshadow-git-inject/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/day8%2Fshadow-git-inject/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/day8%2Fshadow-git-inject/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/day8","download_url":"https://codeload.github.com/day8/shadow-git-inject/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252211067,"owners_count":21712327,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-13T00:11:31.182Z","updated_at":"2025-05-03T15:32:02.719Z","avatar_url":"https://github.com/day8.png","language":"Clojure","funding_links":["https://github.com/sponsors/mike-thompson-day8"],"categories":[],"sub_categories":[],"readme":"\u003c!-- [![CI](https://github.com/day8/shadow-git-inject/workflows/ci/badge.svg)](https://github.com/day8/shadow-git-inject/actions?workflow=ci)\n[![CD](https://github.com/day8/shadow-git-inject/workflows/cd/badge.svg)](https://github.com/day8/shadow-git-inject/actions?workflow=cd)\n[![GitHub tag (latest by date)](https://img.shields.io/github/v/tag/day8/shadow-git-inject?style=flat)](https://github.com/day8/shadow-git-inject/tags) \n[![GitHub pull requests](https://img.shields.io/github/issues-pr/day8/shadow-git-inject)](https://github.com/day8/shadow-git-inject/pulls)--\u003e\n[![Clojars Project](https://img.shields.io/clojars/v/day8/shadow-git-inject?style=for-the-badge\u0026logo=clojure\u0026logoColor=fff)](https://clojars.org/day8/shadow-git-inject)\n[![GitHub issues](https://img.shields.io/github/issues-raw/day8/shadow-git-inject?style=for-the-badge)](https://github.com/day8/shadow-git-inject/issues)\n[![License](https://img.shields.io/github/license/day8/shadow-git-inject?style=for-the-badge)](LICENSE)\n\n# shadow-git-inject\n\nThis is a shadow-cljs \"build hook\" which processes your build \"configuration\". \n\nAt build time, it will compute an application's \"version\" from the \"ambient git context\" (think latest tag) \nso that it can be \"injected\" into your application. \n\nThis hook assumes that you tag your releases, and that you'd like an automatic way of injecting the most recent tag into your application.\n\n## Quick Start\n\nYour `shadow-cljs` build configuration will look like this: \n```clj\n:compiler-options {:closure-defines {my-app.config/version :shadow-git-inject/version}}\n```\n\nYou'll notice that it contains a placeholder keyword for the current version: `:shadow-git-inject/version`.\n\nAt build time, this build hook will:\n   1. apply ***a two-rule method*** to compute the \"version\" from ***the ambient git context***. We refer to this as `the computed version`.\n   2. replace the placeholder keyword with `the computed version`\n \nBecause this version is used within the `closure-define` section,\nit allows you to embed `the computed version` into your ClojureScript\napplication (into the var `my-app.config/version` in the example config above),\nmaking it readily available at run-time for purposes like logging.\n\n## The Ambient Git Context\n\nImagine you are at the command line in a git repo, and you execute:\n```sh\n$ git describe --tags --dirty=-dirty\n```\nIf the latest tag in your branch was `v1.0.4`, this command might output something like:\n```sh\nv1.0.4-3-g975b-dirty\n```\nwhich encodes four (hyphen separated) values which we refer to as \"the ambient git context\":\n  - the latest git tag: \"v1.0.4\"\n  - the number of commits the repo is currently \"ahead\" of that latest tag: \"3\" \n  - the short ref (SHA) for the commit referenced by that latest tag: \"g975b\"\n  - an indication that there are uncommitted changes: \"dirty\"  (or absent)\n  \n## The Two-Rule Method\n\nThis build hook creates `the computed version` from these four \"ambient\" values by applying two rules:\n  1. when the \"ahead\" count is 0, and the repo is not dirty, `the computed version` will just be the latest tag (eg: `1.0.4`)\n  2. when the \"ahead\" count is non-zero, or the repo is dirty, `the computed version` will be the tag suffixed with `-\u003cahead-count\u003e-\u003cshort-ref\u003e-SNAPSHOT`, e.g. `1.0.4-3-g975b-SNAPSHOT`\n\n***Note #1:*** there is a configuration option to ignore `dirty` state. See the Configuration section below.\n\n***Note #2:*** only part of the latest tag is used (just `1.0.4`, not the full string `v1.0.4`) but that's explained in the next section. \n\n## The Latest Tag\n\nSo far, we have said `the computed version` is created using the \"latest tag\". While that is often true, it is not the whole story, which is acually as follows:\n  1. what's used is the \"latest version tag\" found in the commit history  (\"latest version tag\" vs \"latest tag\")\n  2. where a \"version tag\" is a tag with a specific textual structure\n  3. by default, that textual structure must match the regex: `#\"^v(\\d+\\.\\d+\\.\\d+)$\"`\n  4. so, one of these \"version tags\" might look like: `v1.2.3`  (the string `\"v\"` followed by a semver, `\"N.N.N\"`)\n  5. tags which do not match the regex are ignored (which means you can use tags for other purposes, not just for nominating versions)\n  6. you can override this default regex with one of your own which will recognise an alternative textual structure (see how below)\n  7. you'll notice that the regex has a capturing group which extracts just the semver part: \"N.N.N\". If you provide your own regex, it must contain a single capturing group which isolates that part of the tag to be used in `the computed version`.\n  \nSo, this build hook will traverse backwards through the history of the current commit looking for a tag which has the right structure (matches the regex), and when it finds one, it is THAT tag which is used to create `the computed version` - it is that tag against which the \"ahead count\" will be calculated, etc.\n\n## Sharp Edges\n  \nPlease be aware of the following: \n  - if no matching tag is found then `the computed version` will be `git-version-tag-not-found`\n  - this build hook obtains the \"ambient git context\" by shelling out to the `git` executable. If this executable is not in the PATH, then you'll see messages on `stderr` and `the computed version` will be `git-command-not-found`\n\n## The Two Steps\n\nThe two-step narrative presented above says this build hook:\n  1. creates `the computed version` \n  2. replaces a placeholder string within build configuration(s) with `the computed version`\n\nWhile that's true, it is a simplification. The real steps are:\n  1. this build hook computes **four** build-time values, of which `the computed version` is just one\n  2. this build hook will perform a search and replace across ***all the `EDN`*** in \nthe current build's configuration block, looking for four special string values and, where they are found, it will replace them with the associated computed value from step 1. \n\nSo, the special keyword :shadow-git-inject/version will be replaced ***anywhere*** it is found within the build configuration EDN.\n\nWhen you consider this second step, keep in mind that this build hook runs \nvery early in the shadow-cljs build pipeline at the `configure` step. \n\nThe four special keywords supported - referred to as `substitution keys` - are: \n\n\n|   substitution key                     |    example replacement       |\n|----------------------------------------|------------------------------|\n| :shadow-git-inject/version             | \"12.4.1-2-453a730-SNAPSHOT\"  |\n| :shadow-git-inject/build-iso-date-time | \"2019-11-18T00:05:02.273361\" |      \n| :shadow-git-inject/build-iso-date-week | \"2019-W47-2\"                 |\n| :shadow-git-inject/user-name           | \"Isaac\"                      |\n\nFor performance, only certain parts of the shadow-cljs configuration are processed:\n\n- `[:builds :* :closure-defines]`\n- `[:builds :* :compiler-options :closure-defines]`\n- `[:builds :* :dev :closure-defines]`\n- `[:builds :* :dev :compiler-options :closure-defines]`\n- `[:builds :* :release :closure-defines]`\n- `[:builds :* :release :compiler-options :closure-defines]`\n\nIf you have a use case for injecting values in a part of the config that is not listed above, please raise an issue.\n\n## Embedding Build-Time Values In Your App\n\nThis build hook provides a way\nto embed any of these four build-time values into our ClojureScript application. \nThis is often a very useful outcome - these values are useful at runtime for display and logging purposes. And it can \nbe achieved in an automated, DRY way.\n\nThe trick is to place the substitution keys into specific places within the build configuration - ones which control \nthe actions of the ClojureScript compiler. We want to take advantage of the [`:closure-defines` feature](https://clojurescript.org/reference/compiler-options#closure-defines) feature of the ClojureScript complier which permits us to \"set\" values for `defs` at compile time.\n\nBelow, the Annotated Example section demonstrates how to achieve this outcome using shadow-cljs.\n\n\n## Configuration\n\nA map of configuration options can, optionally, be added to the build configuration via the key `:git-inject`, like this:\n\n```clj\n{:builds {:my-app {:target :browser\n                   \n\t\t   :git-inject { ... } ;; a map of configuration options\n\t\t   }}}\n```\n\nThe two configuration options are:\n  -  `:ignore-dirty?` \n  -  `:version-pattern` \n  \n#### :ignore-dirty?\n\nA boolean value which specifies if the dirty state of the repo should be ignored when calculating the version. \n\nDefaults to `false`.\n\nCan be supplied as an explicit boolean or via an environment variable as the string \"true\" or \"false\".\n\n```clj\n:git-inject {\n  :ignore-dirty? true\n}\n```\nOR\n```clj\n:git-inject {\n  ;; Will only be true if IGNORE_DIRTY environment variable is the string \"true\"\n  ;; If the environment variable is not found, defaults to \"false\"\n  :ignore-dirty? #shadow/env \"IGNORE_DIRTY\"\n}\n```\n\n#### :version-pattern\n\nA string containing the regex which is used to differentiate between `version tags` and other `tags`. If the regex\nmatches a tag, then that tag is assumed to be a `version tag`, otherwise the tag will be ignored. \n\nDefaults to `\"^v(\\\\d+\\\\.\\\\d+\\\\.\\\\d+)$\"` which matches tags like `v1.0.0` and `v12.3.99`.\n\nWhen designing the textual structure for your \"version tags\", remember that \ngit tags are git references and that there are rules about well formedness. \nFor example, you can't have a \":\" in a tag. See https://git-scm.com/docs/git-check-ref-format\n\nThe regex you supply has two jobs:\n  1. to \"match\" version tags \n  2. to return one capturing group which extracts the text within the tag which is to \n     be used as the version itself. In the example below, the regex will match the tag \"version/1.2.3\" \n     but it will also capture the \"1.2.3\" part and it is THAT part which will be used in the computed version. \n    \n```clj\n:git-inject {\n  :version-pattern  \"^version/(.*)$\" \n}\n```\n\n**Note #1:** This build hook uses [`re-pattern`](https://clojuredocs.org/clojure.core/re-pattern) to turn the string into a regex. \n\n**Note #2:**  Because you supply a string and not a regex, be careful to use `\\\\` where normally you could just use `\\` in a regex. \n\n**Note #3:**  Why a string and not a regex?  Because EDN doesn't accomodate regex. \n\n## An Annotated Example\n\nHere's how to write your `shadow-cljs.edn` ... \n\n```clojure\n{:dependencies [[day8/shadow-git-inject \"0.0.4\"]] ;; \u003c--- include hook dependency (see https://clojars.org/day8/shadow-git-inject)\n\n :builds {:app {:target :browser\n                 \n\t\t:build-hooks \n\t\t[(shadow-git-inject.core/hook)] ;; \u003c--- you must include this build hook\n\n                ;; Below is an example of how to \n                ;; combine this build hook with a `:clojure-define` in order to \n                ;; inject build-time values into your application, for later run-time use.\n                ;; \n                ;; You'll notice the use of the substitution key :shadow-git-inject/version.  \n                ;; At build time, this build hook will replace that keyword with `the computed version`.\n                ;; In turn, that value is used within a `:clojure-define` to bind it\n                ;; to a var, via a `def` in your code (called `version` within the namespace `some.namespace`). \n\t\t:compiler-options        \n\t\t{:closure-defines {some.namespace/version  :shadow-git-inject/version}}\n\n                ;; Optional - see the `Configuration` section for explanation\n                :git-inject\n                {:version-pattern \"^version/(.*)$\" \n                 :ignore-dirty?   true}}}}\n```\n\n\n## License\n\nCopyright © 2021 Mike Thompson\n\nDerived from lein-git-inject © 2020 Mike Thompson\n\nDerived from cuddlefish © 2018 Reid \"arrdem\" McKenzie\n\nDerived from lein-git-version © 2017 Reid \"arrdem\" McKenzie\n\nDerived from lein-git-version © 2016 Colin Steele\n\nDerived from lein-git-version © 2011 Michał Marczyk\n\nDistributed under the Eclipse Public License, the same as Clojure.\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fday8%2Fshadow-git-inject","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fday8%2Fshadow-git-inject","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fday8%2Fshadow-git-inject/lists"}