diff --git a/.github/workflows/release-build.yml b/.github/workflows/release-build.yml index cbcd3568..848ffa9f 100644 --- a/.github/workflows/release-build.yml +++ b/.github/workflows/release-build.yml @@ -28,6 +28,9 @@ jobs: ref: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release-tag }} - name: Build release assets uses: ./.github/actions/build-release-assets + env: + # The footer shows the theme's version. + THEME_VERSION: ${{ github.event_name == 'release' && github.event.release.tag_name || inputs.release-tag }} - name: Upload assets to release env: GH_TOKEN: ${{ github.token }} diff --git a/ARCHITECTURE.md b/ARCHITECTURE.md index a379df71..535e95b6 100644 --- a/ARCHITECTURE.md +++ b/ARCHITECTURE.md @@ -177,6 +177,13 @@ only. Templates therefore link assets without a version, and the assembly adds o the content hash of the file it loads, the theme link in `base/head_style.tmpl` gets the hash of all theme files, and each auto theme's `@import` gets the hash of the imported file. +### Theme version + +The footer in `base/footer_content.tmpl` links the theme's repository with its version. The template declares +`{{$themeVersion := "dev"}}` and the assembly replaces the value: with `THEME_VERSION`, which the release build sets to +the release tag, or else with `git describe --tags`, such as `1.4.0-3-g54b4d73` for a build three commits after 1.4.0. +Admins hide the credit with `--custom-footer-credit: none`. + ## Local preview `dev/docker-compose.yml` runs the Forgejo release pinned in `package.json` (`17.0.1-rootless`) and mounts diff --git a/README.md b/README.md index 15a759b5..c05c6579 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,7 @@ Add the following code at the beginning or end of the theme's CSS file | --custom-clone-menu-width | Clone button menu width | Gitea | 332px | 200px | 150px | 400px | | --custom-user-menu-width | User menu width | 192px | 200px | | Gitea | 320px | | --custom-owner-switcher | Owner switcher in the navbar; `none` hides it | flex | flex | | | | +| --custom-footer-credit | Theme name and version in the footer; `none` hides it | inline | inline | | | | | --custom-explore-repolist-columns | Number of repository list columns on explore page | 2 | 2 | 2 | | | | --custom-explore-userlist-columns | Number of user/organization list columns on explore page | 3 | 1 | 2/3 | | | | --custom-user-repolist-columns | Number of repository list columns on user page | 2 | 2 | 1/2 | | | diff --git a/scripts/assembleForgejo.ts b/scripts/assembleForgejo.ts index 7c67543c..eac84840 100644 --- a/scripts/assembleForgejo.ts +++ b/scripts/assembleForgejo.ts @@ -1,3 +1,4 @@ +import { execFileSync } from "node:child_process"; import { createHash } from "node:crypto"; import { cpSync, mkdirSync, readFileSync, readdirSync, rmSync, writeFileSync } from "node:fs"; import { join } from "node:path"; @@ -102,3 +103,27 @@ const stampAssetLinks = (directory: string): void => { } }; stampAssetLinks(templates); + +// The footer credits the theme with its version: the release tag in the release build, otherwise the nearest tag as +// git describes it, such as 1.4.0-3-g54b4d73 for a build three commits after 1.4.0. +const describeVersion = (): string => { + try { + return execFileSync("git", ["describe", "--tags", "--match", "[0-9]*", "--always", "--dirty"], { + cwd: root, + encoding: "utf-8", + stdio: ["ignore", "pipe", "ignore"], + }).trim(); + } catch { + return "dev"; + } +}; +const releaseVersion = (process.env.THEME_VERSION?.trim() || describeVersion()).replace(/^v/, ""); +if (!/^[\w.+-]+$/.test(releaseVersion)) throw new Error(`Unexpected theme version: ${releaseVersion}`); +const footer = join(templates, "base", "footer_content.tmpl"); +const versionVariable = /\{\{\$themeVersion := "[^"]*"\}\}/; +const footerSource = readFileSync(footer, "utf-8"); +if (!versionVariable.test(footerSource)) throw new Error(`No theme version to stamp in ${footer}`); +writeFileSync( + footer, + footerSource.replace(versionVariable, () => `{{$themeVersion := "${releaseVersion}"}}`) +); diff --git a/src/build-output.test.ts b/src/build-output.test.ts index e7c5fdd5..0f0979a7 100644 --- a/src/build-output.test.ts +++ b/src/build-output.test.ts @@ -514,6 +514,24 @@ describe("Forgejo 15 native integration", () => { ); }); + it("credits the theme with its version in the footer", () => { + const source = fs.readFileSync(path.join(ROOT_DIR, "templates", "base", "footer_content.tmpl"), "utf-8"); + const footer = fs.readFileSync(path.join(DIST_DIR, "forgejo", "templates", "base", "footer_content.tmpl"), "utf-8"); + + // The link sits with Forgejo's own footer links and shows only the theme's name and version. + expect(source).toContain('{{$themeVersion := "dev"}}'); + expect(source.indexOf('class="footer-theme"')).toBeGreaterThan(source.indexOf("/api/swagger")); + expect(source.indexOf('class="footer-theme"')).toBeLessThan(source.indexOf('"custom/extra_links_footer"')); + expect(source).toContain( + 'GitHub Theme {{$themeVersion}}' + ); + // The assembly stamps the release tag or git's description of the build. + expect(footer).toMatch(/\{\{\$themeVersion := "(?!dev")[\w.+-]+"\}\}/); + // Admins can hide it with a custom variable; it shows by default. + const css = fs.readFileSync(path.join(DIST_DIR, `${PREFIX}light.css`), "utf-8"); + expect(css).toContain(".github-footer .footer-theme{display:var(--custom-footer-credit,inline)}"); + }); + it("ships the mobile drawer assets and keeps its toggle independent of Forgejo's inline menu", () => { const navbar = fs.readFileSync(path.join(ROOT_DIR, "templates", "base", "head_navbar.tmpl"), "utf-8"); expect(navbar).toContain('id="navbar-drawer-toggle" aria-controls="navbar-drawer" aria-haspopup="dialog"'); diff --git a/src/types/vars.ts b/src/types/vars.ts index 46de8c07..2e4e861a 100644 --- a/src/types/vars.ts +++ b/src/types/vars.ts @@ -61,6 +61,7 @@ const customVars = { cloneMenuWidth: "clone-menu-width", userMenuWidth: "user-menu-width", ownerSwitcher: "owner-switcher", + footerCredit: "footer-credit", explore: { repolistColumns: "explore-repolist-columns", userlistColumns: "explore-userlist-columns" }, userRepolistColumns: "user-repolist-columns", org: { repolistColumns: "org-repolist-columns", userlistColumns: "org-userlist-columns" }, diff --git a/styles/components/footer.ts b/styles/components/footer.ts index 184f43c8..bfb1c8d3 100644 --- a/styles/components/footer.ts +++ b/styles/components/footer.ts @@ -17,7 +17,8 @@ * limitations under the License. */ -import { css, cssCombine, themeVars } from "@lutinglt/gitea-github-theme/core"; +import { css, cssCombine, customThemeVars, themeVars } from "@lutinglt/gitea-github-theme/core"; +import { fallbackVar } from "@vanilla-extract/css"; const footer = css` .page-footer { @@ -67,4 +68,12 @@ const footer = css` } `; -export default cssCombine(footer); +const themeCredit = css` + .github-footer .footer-theme { + /* Admins can hide the theme's credit with + --custom-footer-credit: none. */ + display: ${fallbackVar(customThemeVars.footerCredit, "inline")}; + } +`; + +export default cssCombine(footer, themeCredit); diff --git a/templates/base/footer_content.tmpl b/templates/base/footer_content.tmpl index 449db520..8d27cb0e 100644 --- a/templates/base/footer_content.tmpl +++ b/templates/base/footer_content.tmpl @@ -1,4 +1,6 @@ {{/* Keep Forgejo's native footer settings, language menu, and custom links. */}} +{{/* scripts/assembleForgejo.ts stamps the theme's version here. */}} +{{$themeVersion := "dev"}}