taler-typescript-core

Wallet core logic and WebUIs for various components
Log | Files | Refs | Submodules | README | LICENSE

version.mjs (3451B)


      1 /*
      2  This file is part of GNU Taler
      3  (C) 2026 Taler Systems S.A.
      4 
      5  GNU Taler is free software; you can redistribute it and/or modify it under the
      6  terms of the GNU General Public License as published by the Free Software
      7  Foundation; either version 3, or (at your option) any later version.
      8 
      9  GNU Taler is distributed in the hope that it will be useful, but WITHOUT ANY
     10  WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
     11  A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
     12 
     13  You should have received a copy of the GNU General Public License along with
     14  GNU Taler; see the file COPYING.  If not, see <http://www.gnu.org/licenses/>
     15  */
     16 
     17 import { execFileSync } from "node:child_process";
     18 import { existsSync, readFileSync } from "node:fs";
     19 import { dirname, join, resolve } from "node:path";
     20 
     21 function repositoryRoot(startDirectory) {
     22   let directory = resolve(startDirectory);
     23   while (!existsSync(join(directory, "pnpm-workspace.yaml"))) {
     24     const parent = dirname(directory);
     25     if (parent === directory) {
     26       throw Error(
     27         `build version: cannot find the workspace from ${startDirectory}`,
     28       );
     29     }
     30     directory = parent;
     31   }
     32   return directory;
     33 }
     34 
     35 function validateBuildInfo(info) {
     36   if (
     37     !info ||
     38     typeof info.tag !== "string" ||
     39     !/^v[0-9][0-9A-Za-z.+-]*$/.test(info.tag) ||
     40     typeof info.version !== "string" ||
     41     typeof info.gitHash !== "string" ||
     42     !/^(?:[0-9a-f]{40}|[0-9a-f]{64})$/.test(info.gitHash)
     43   ) {
     44     throw Error("invalid version, tag, or commit hash");
     45   }
     46   const version = info.tag.slice(1);
     47   if (info.version !== version) {
     48     const suffix = info.version.slice(version.length);
     49     const match = /^-[1-9][0-9]*-g([0-9a-f]+)$/.exec(suffix);
     50     if (
     51       !info.version.startsWith(version) ||
     52       !match ||
     53       !info.gitHash.startsWith(match[1])
     54     ) {
     55       throw Error("version does not match the tag and commit hash");
     56     }
     57   }
     58   return { version: info.version, tag: info.tag, gitHash: info.gitHash };
     59 }
     60 
     61 /** Resolve at invocation time, even when this helper is bundled into web-util. */
     62 export function getBuildInfo(startDirectory = process.cwd()) {
     63   const root = repositoryRoot(startDirectory);
     64   if (existsSync(join(root, ".git"))) {
     65     const git = (args) =>
     66       execFileSync("git", args, {
     67         cwd: root,
     68         encoding: "utf8",
     69         stdio: ["ignore", "pipe", "pipe"],
     70       }).trim();
     71     try {
     72       const description = git(["describe", "--tags", "--match", "v[0-9]*"]);
     73       return validateBuildInfo({
     74         version: description.slice(1),
     75         tag: description.replace(/-[0-9]+-g[0-9a-f]+$/, ""),
     76         gitHash: git(["rev-parse", "HEAD"]),
     77       });
     78     } catch (cause) {
     79       throw Error(
     80         "build version: cannot describe this checkout; ensure Git is installed and fetch the release tags and history (git fetch --tags; git fetch --unshallow for a shallow clone)",
     81         { cause },
     82       );
     83     }
     84   }
     85 
     86   // An extracted archive can live inside another repository. Never use that
     87   // enclosing repository's tags, or a package.json placeholder, as its version.
     88   try {
     89     return validateBuildInfo(
     90       JSON.parse(readFileSync(join(root, "build-info.json"), "utf8")),
     91     );
     92   } catch (cause) {
     93     throw Error(
     94       "build version: missing or invalid build-info.json; use a source archive created by make dist, or build from a checkout with release tags",
     95       { cause },
     96     );
     97   }
     98 }