@igstack/app-catalog-backend-core 0.5.0 → 0.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -95,6 +95,10 @@ interface GroupingTagValue {
95
95
  interface VersionInfo {
96
96
  displayName: string;
97
97
  url?: string;
98
+ /** Short git SHA of the source this artifact was built from (e.g. 'a1b2c3d'). */
99
+ sha?: string;
100
+ /** Link to the commit for `sha` (e.g. a GitHub commit URL). */
101
+ shaUrl?: string;
98
102
  }
99
103
  interface AppVersionInfo {
100
104
  backend?: VersionInfo;
@@ -1 +1 @@
1
- {"version":3,"file":"appCatalogTypes.d.mts","names":[],"sources":["../../../src/types/common/appCatalogTypes.ts"],"mappings":";;;;;;;;UAmBiB,WAAA;EACf,QAAA;EACA,WAAA;EACA,WAAA;EACA,MAAA;EACA,aAAA,GAAgB,gBAAA;AAAA;;;;;UAWD,eAAA;EACf,UAAA;EACA,GAAA;EACA,SAAA;AAAA;;;;;UAOe,QAAA;EACf,EAAA;EACA,IAAA;EAEA;EAAA,IAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;EACA,WAAA;EACA,KAAA;EACA,aAAA,GAAgB,gBAAA;EAChB,KAAA;EACA,IAAA;EACA,MAAA;EACA,KAAA;IAAU,GAAA;IAAa,KAAA;EAAA;EACvB,QAAA;EACA,aAAA;EACA,OAAA,cAAqB,eAAA;EACrB,UAAA;IAEE,0HAAA,IAAA,iCAIA;IAFA,eAAA,WAOF;IALE,OAAA;EAAA;EAWF;EARA,QAAA;EAYA;EAVA,SAAA;EAcA;EAZA,KAAA,GAAQ,WAAA;EAgBR;EAZA,UAAA;EAcQ;EAZR,IAAA;EAYc;EAVd,UAAA;EAc0B;EAZ1B,OAAA;EAaA;EAXA,eAAA;EAee;EAbf,0BAAA;;EAEA,cAAA;EAYA;EAVA,KAAA,GAAQ,MAAA;AAAA;AAAA,UAIO,WAAA;EACf,EAAA;EACA,IAAA;AAAA;AAAA,UAGe,qBAAA;EACf,MAAA;EACA,WAAA;EACA,WAAA;EACA,MAAA,EAAQ,gBAAA;AAAA;AAAA,KAGL,gBAAA,8BAA8C,CAAA,eAC/C,IAAA,CAAK,CAAA,EAAG,IAAA;AAAA,KAGA,iBAAA,GAAoB,gBAAA,CAC9B,cAAA;AAAA,UAIe,gBAAA;EACf,KAAA;EACA,WAAA;EACA,WAAA;AAAA;AAAA,UAGe,WAAA;EACf,WAAA;EACA,GAAA;AAAA;AAAA,UAGe,cAAA;EACf,OAAA,GAAU,WAAA;EACV,QAAA,GAAW,WAAA;EACX,WAAA,GAAc,WAAA;AAAA;AAAA,UAGC,cAAA;EACf,SAAA,EAAW,QAAA;EACX,eAAA,EAAiB,qBAAA;EACjB,eAAA,EAAiB,iBAAA;EACjB,OAAA,EAAS,MAAA;EACT,MAAA,EAAQ,KAAA;EACR,QAAA,GAAW,cAAA;AAAA"}
1
+ {"version":3,"file":"appCatalogTypes.d.mts","names":[],"sources":["../../../src/types/common/appCatalogTypes.ts"],"mappings":";;;;;;;;UAmBiB,WAAA;EACf,QAAA;EACA,WAAA;EACA,WAAA;EACA,MAAA;EACA,aAAA,GAAgB,gBAAA;AAAA;;;;;UAWD,eAAA;EACf,UAAA;EACA,GAAA;EACA,SAAA;AAAA;;;;;UAOe,QAAA;EACf,EAAA;EACA,IAAA;EAEA;EAAA,IAAA;EACA,WAAA;EACA,YAAA;EACA,SAAA;EACA,WAAA;EACA,KAAA;EACA,aAAA,GAAgB,gBAAA;EAChB,KAAA;EACA,IAAA;EACA,MAAA;EACA,KAAA;IAAU,GAAA;IAAa,KAAA;EAAA;EACvB,QAAA;EACA,aAAA;EACA,OAAA,cAAqB,eAAA;EACrB,UAAA;IAEE,0HAAA,IAAA,iCAIA;IAFA,eAAA,WAOF;IALE,OAAA;EAAA;EAWF;EARA,QAAA;EAYA;EAVA,SAAA;EAcA;EAZA,KAAA,GAAQ,WAAA;EAgBR;EAZA,UAAA;EAcQ;EAZR,IAAA;EAYc;EAVd,UAAA;EAc0B;EAZ1B,OAAA;EAaA;EAXA,eAAA;EAee;EAbf,0BAAA;;EAEA,cAAA;EAYA;EAVA,KAAA,GAAQ,MAAA;AAAA;AAAA,UAIO,WAAA;EACf,EAAA;EACA,IAAA;AAAA;AAAA,UAGe,qBAAA;EACf,MAAA;EACA,WAAA;EACA,WAAA;EACA,MAAA,EAAQ,gBAAA;AAAA;AAAA,KAGL,gBAAA,8BAA8C,CAAA,eAC/C,IAAA,CAAK,CAAA,EAAG,IAAA;AAAA,KAGA,iBAAA,GAAoB,gBAAA,CAC9B,cAAA;AAAA,UAIe,gBAAA;EACf,KAAA;EACA,WAAA;EACA,WAAA;AAAA;AAAA,UAGe,WAAA;EACf,WAAA;EACA,GAAA;EAhBc;EAkBd,GAAA;EAf2B;EAiB3B,MAAA;AAAA;AAAA,UAGe,cAAA;EACf,OAAA,GAAU,WAAA;EACV,QAAA,GAAW,WAAA;EACX,WAAA,GAAc,WAAA;AAAA;AAAA,UAGC,cAAA;EACf,SAAA,EAAW,QAAA;EACX,eAAA,EAAiB,qBAAA;EACjB,eAAA,EAAiB,iBAAA;EACjB,OAAA,EAAS,MAAA;EACT,MAAA,EAAQ,KAAA;EACR,QAAA,GAAW,cAAA;AAAA"}
@@ -1 +1 @@
1
- {"version":3,"file":"versionUtils.d.mts","names":[],"sources":["../../src/utils/versionUtils.ts"],"mappings":";;;;;AAQA;;iBAAgB,kBAAA,CAAA;;;AA0ChB;iBA3BgB,yBAAA,CACd,WAAA;;;;;;;iBA0Bc,cAAA,CAAe,OAAA;EAC7B,mBAAA;EACA,MAAA;AAAA,IACE,cAAA"}
1
+ {"version":3,"file":"versionUtils.d.mts","names":[],"sources":["../../src/utils/versionUtils.ts"],"mappings":";;;;;AAQA;;iBAAgB,kBAAA,CAAA;;;;iBA0DA,yBAAA,CACd,WAAA;;;;;;;iBAWc,cAAA,CAAe,OAAA;EAC7B,mBAAA;EACA,MAAA;AAAA,IACE,cAAA"}
@@ -16,19 +16,44 @@ function getBuildPipelineId() {
16
16
  function getBuildPipelineUrl() {
17
17
  return process.env.BUILD_PIPELINE_URL;
18
18
  }
19
+ /** First 7 chars of a git SHA, or undefined when absent. */
20
+ function shortSha(sha) {
21
+ return sha ? sha.slice(0, 7) : void 0;
22
+ }
23
+ /** Build a commit URL from a repo homepage + full/short SHA. */
24
+ function getCommitUrl(homepage, sha) {
25
+ if (!homepage || !sha) return void 0;
26
+ return `${homepage.replace(/\/$/, "")}/commit/${sha}`;
27
+ }
19
28
  /**
20
- * Gets frontend package version from node_modules
29
+ * Reads `version`, `gitHead` (baked at publish time), and `homepage` from an
30
+ * installed package.json under node_modules.
21
31
  */
22
- function getFrontendPackageVersion(packageName = "@igstack/app-catalog-frontend-core") {
32
+ function getPackageMeta(packageName = "@igstack/app-catalog-frontend-core") {
23
33
  try {
24
34
  const pkgPath = join(process.cwd(), "node_modules", packageName, "package.json");
25
- return JSON.parse(readFileSync(pkgPath, "utf-8")).version || null;
35
+ const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
36
+ return {
37
+ version: pkg.version ?? null,
38
+ gitHead: pkg.gitHead ?? null,
39
+ homepage: pkg.homepage ?? null
40
+ };
26
41
  } catch (error) {
27
- console.warn(`[versionUtils] Failed to read frontend version from ${packageName}:`, error);
28
- return null;
42
+ console.warn(`[versionUtils] Failed to read package meta from ${packageName}:`, error);
43
+ return {
44
+ version: null,
45
+ gitHead: null,
46
+ homepage: null
47
+ };
29
48
  }
30
49
  }
31
50
  /**
51
+ * Gets frontend package version from node_modules
52
+ */
53
+ function getFrontendPackageVersion(packageName = "@igstack/app-catalog-frontend-core") {
54
+ return getPackageMeta(packageName).version;
55
+ }
56
+ /**
32
57
  * Complete helper to get both backend and frontend versions
33
58
  * Backend: reads BUILD_PIPELINE_ID and BUILD_PIPELINE_URL from environment
34
59
  * Frontend: reads version from package.json
@@ -42,8 +67,17 @@ function getVersionInfo(options) {
42
67
  displayName: pipelineId,
43
68
  ...pipelineUrl && { url: pipelineUrl }
44
69
  };
45
- const frontendVersion = getFrontendPackageVersion(options?.frontendPackageName);
46
- if (frontendVersion) versions.frontend = { displayName: frontendVersion };
70
+ const feMeta = getPackageMeta(options?.frontendPackageName);
71
+ if (feMeta.version) {
72
+ const sha = shortSha(feMeta.gitHead);
73
+ versions.frontend = {
74
+ displayName: feMeta.version,
75
+ ...sha && {
76
+ sha,
77
+ shaUrl: getCommitUrl(feMeta.homepage ?? void 0, sha)
78
+ }
79
+ };
80
+ }
47
81
  if (!options?.silent) {
48
82
  console.log("=================================");
49
83
  console.log("[Version Info]");
@@ -1 +1 @@
1
- {"version":3,"file":"versionUtils.mjs","names":[],"sources":["../../src/utils/versionUtils.ts"],"sourcesContent":["import type { AppVersionInfo } from '../types/common/appCatalogTypes.js'\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/**\n * Gets backend pipeline ID from environment variable\n * Use BUILD_PIPELINE_ID env var, otherwise 'local'\n */\nexport function getBuildPipelineId(): string {\n return process.env.BUILD_PIPELINE_ID || 'local'\n}\n\n/**\n * Gets backend pipeline URL from environment variable\n * Use BUILD_PIPELINE_URL env var if available\n */\nexport function getBuildPipelineUrl(): string | undefined {\n return process.env.BUILD_PIPELINE_URL\n}\n\n/**\n * Gets frontend package version from node_modules\n */\nexport function getFrontendPackageVersion(\n packageName: string = '@igstack/app-catalog-frontend-core',\n): string | null {\n try {\n const pkgPath = join(\n process.cwd(),\n 'node_modules',\n packageName,\n 'package.json',\n )\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n return pkg.version || null\n } catch (error) {\n console.warn(\n `[versionUtils] Failed to read frontend version from ${packageName}:`,\n error,\n )\n return null\n }\n}\n\n/**\n * Complete helper to get both backend and frontend versions\n * Backend: reads BUILD_PIPELINE_ID and BUILD_PIPELINE_URL from environment\n * Frontend: reads version from package.json\n * Logs version info to help debug CI/CD issues\n */\nexport function getVersionInfo(options?: {\n frontendPackageName?: string\n silent?: boolean\n}): AppVersionInfo {\n const versions: AppVersionInfo = {}\n\n // Backend version from environment\n const pipelineId = getBuildPipelineId()\n const pipelineUrl = getBuildPipelineUrl()\n\n versions.backend = {\n displayName: pipelineId,\n ...(pipelineUrl && { url: pipelineUrl }),\n }\n\n // Frontend version from package.json\n const frontendVersion = getFrontendPackageVersion(\n options?.frontendPackageName,\n )\n if (frontendVersion) {\n versions.frontend = { displayName: frontendVersion }\n }\n\n // Log for debugging CI/CD\n if (!options?.silent) {\n console.log('=================================')\n console.log('[Version Info]')\n console.log(` Backend: ${versions.backend.displayName}`)\n if (versions.backend.url) {\n console.log(` URL: ${versions.backend.url}`)\n }\n if (versions.frontend) {\n console.log(` Frontend: ${versions.frontend.displayName}`)\n }\n console.log(' Environment:')\n console.log(\n ` BUILD_PIPELINE_ID: ${process.env.BUILD_PIPELINE_ID || '(not set)'}`,\n )\n console.log(\n ` BUILD_PIPELINE_URL: ${process.env.BUILD_PIPELINE_URL || '(not set)'}`,\n )\n console.log('=================================')\n }\n\n return versions\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,qBAA6B;AAC3C,QAAO,QAAQ,IAAI,qBAAqB;;;;;;AAO1C,SAAgB,sBAA0C;AACxD,QAAO,QAAQ,IAAI;;;;;AAMrB,SAAgB,0BACd,cAAsB,sCACP;AACf,KAAI;EACF,MAAM,UAAU,KACd,QAAQ,KAAK,EACb,gBACA,aACA,eACD;AAED,SADY,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC,CAC3C,WAAW;UACf,OAAO;AACd,UAAQ,KACN,uDAAuD,YAAY,IACnE,MACD;AACD,SAAO;;;;;;;;;AAUX,SAAgB,eAAe,SAGZ;CACjB,MAAM,WAA2B,EAAE;CAGnC,MAAM,aAAa,oBAAoB;CACvC,MAAM,cAAc,qBAAqB;AAEzC,UAAS,UAAU;EACjB,aAAa;EACb,GAAI,eAAe,EAAE,KAAK,aAAa;EACxC;CAGD,MAAM,kBAAkB,0BACtB,SAAS,oBACV;AACD,KAAI,gBACF,UAAS,WAAW,EAAE,aAAa,iBAAiB;AAItD,KAAI,CAAC,SAAS,QAAQ;AACpB,UAAQ,IAAI,oCAAoC;AAChD,UAAQ,IAAI,iBAAiB;AAC7B,UAAQ,IAAI,eAAe,SAAS,QAAQ,cAAc;AAC1D,MAAI,SAAS,QAAQ,IACnB,SAAQ,IAAI,eAAe,SAAS,QAAQ,MAAM;AAEpD,MAAI,SAAS,SACX,SAAQ,IAAI,eAAe,SAAS,SAAS,cAAc;AAE7D,UAAQ,IAAI,iBAAiB;AAC7B,UAAQ,IACN,2BAA2B,QAAQ,IAAI,qBAAqB,cAC7D;AACD,UAAQ,IACN,2BAA2B,QAAQ,IAAI,sBAAsB,cAC9D;AACD,UAAQ,IAAI,oCAAoC;;AAGlD,QAAO"}
1
+ {"version":3,"file":"versionUtils.mjs","names":[],"sources":["../../src/utils/versionUtils.ts"],"sourcesContent":["import type { AppVersionInfo } from '../types/common/appCatalogTypes.js'\nimport { readFileSync } from 'node:fs'\nimport { join } from 'node:path'\n\n/**\n * Gets backend pipeline ID from environment variable\n * Use BUILD_PIPELINE_ID env var, otherwise 'local'\n */\nexport function getBuildPipelineId(): string {\n return process.env.BUILD_PIPELINE_ID || 'local'\n}\n\n/**\n * Gets backend pipeline URL from environment variable\n * Use BUILD_PIPELINE_URL env var if available\n */\nexport function getBuildPipelineUrl(): string | undefined {\n return process.env.BUILD_PIPELINE_URL\n}\n\n/** First 7 chars of a git SHA, or undefined when absent. */\nexport function shortSha(sha?: string | null): string | undefined {\n return sha ? sha.slice(0, 7) : undefined\n}\n\n/** Build a commit URL from a repo homepage + full/short SHA. */\nexport function getCommitUrl(\n homepage: string | undefined,\n sha: string | undefined,\n): string | undefined {\n if (!homepage || !sha) return undefined\n return `${homepage.replace(/\\/$/, '')}/commit/${sha}`\n}\n\n/**\n * Reads `version`, `gitHead` (baked at publish time), and `homepage` from an\n * installed package.json under node_modules.\n */\nexport function getPackageMeta(\n packageName: string = '@igstack/app-catalog-frontend-core',\n): { version: string | null; gitHead: string | null; homepage: string | null } {\n try {\n const pkgPath = join(\n process.cwd(),\n 'node_modules',\n packageName,\n 'package.json',\n )\n const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))\n return {\n version: pkg.version ?? null,\n gitHead: pkg.gitHead ?? null,\n homepage: pkg.homepage ?? null,\n }\n } catch (error) {\n console.warn(\n `[versionUtils] Failed to read package meta from ${packageName}:`,\n error,\n )\n return { version: null, gitHead: null, homepage: null }\n }\n}\n\n/**\n * Gets frontend package version from node_modules\n */\nexport function getFrontendPackageVersion(\n packageName: string = '@igstack/app-catalog-frontend-core',\n): string | null {\n return getPackageMeta(packageName).version\n}\n\n/**\n * Complete helper to get both backend and frontend versions\n * Backend: reads BUILD_PIPELINE_ID and BUILD_PIPELINE_URL from environment\n * Frontend: reads version from package.json\n * Logs version info to help debug CI/CD issues\n */\nexport function getVersionInfo(options?: {\n frontendPackageName?: string\n silent?: boolean\n}): AppVersionInfo {\n const versions: AppVersionInfo = {}\n\n // Backend version from environment\n const pipelineId = getBuildPipelineId()\n const pipelineUrl = getBuildPipelineUrl()\n\n versions.backend = {\n displayName: pipelineId,\n ...(pipelineUrl && { url: pipelineUrl }),\n }\n\n // Frontend version + git SHA from package.json (SHA baked at publish time)\n const feMeta = getPackageMeta(options?.frontendPackageName)\n if (feMeta.version) {\n const sha = shortSha(feMeta.gitHead)\n versions.frontend = {\n displayName: feMeta.version,\n ...(sha && {\n sha,\n shaUrl: getCommitUrl(feMeta.homepage ?? undefined, sha),\n }),\n }\n }\n\n // Log for debugging CI/CD\n if (!options?.silent) {\n console.log('=================================')\n console.log('[Version Info]')\n console.log(` Backend: ${versions.backend.displayName}`)\n if (versions.backend.url) {\n console.log(` URL: ${versions.backend.url}`)\n }\n if (versions.frontend) {\n console.log(` Frontend: ${versions.frontend.displayName}`)\n }\n console.log(' Environment:')\n console.log(\n ` BUILD_PIPELINE_ID: ${process.env.BUILD_PIPELINE_ID || '(not set)'}`,\n )\n console.log(\n ` BUILD_PIPELINE_URL: ${process.env.BUILD_PIPELINE_URL || '(not set)'}`,\n )\n console.log('=================================')\n }\n\n return versions\n}\n"],"mappings":";;;;;;;;AAQA,SAAgB,qBAA6B;AAC3C,QAAO,QAAQ,IAAI,qBAAqB;;;;;;AAO1C,SAAgB,sBAA0C;AACxD,QAAO,QAAQ,IAAI;;;AAIrB,SAAgB,SAAS,KAAyC;AAChE,QAAO,MAAM,IAAI,MAAM,GAAG,EAAE,GAAG;;;AAIjC,SAAgB,aACd,UACA,KACoB;AACpB,KAAI,CAAC,YAAY,CAAC,IAAK,QAAO;AAC9B,QAAO,GAAG,SAAS,QAAQ,OAAO,GAAG,CAAC,UAAU;;;;;;AAOlD,SAAgB,eACd,cAAsB,sCACuD;AAC7E,KAAI;EACF,MAAM,UAAU,KACd,QAAQ,KAAK,EACb,gBACA,aACA,eACD;EACD,MAAM,MAAM,KAAK,MAAM,aAAa,SAAS,QAAQ,CAAC;AACtD,SAAO;GACL,SAAS,IAAI,WAAW;GACxB,SAAS,IAAI,WAAW;GACxB,UAAU,IAAI,YAAY;GAC3B;UACM,OAAO;AACd,UAAQ,KACN,mDAAmD,YAAY,IAC/D,MACD;AACD,SAAO;GAAE,SAAS;GAAM,SAAS;GAAM,UAAU;GAAM;;;;;;AAO3D,SAAgB,0BACd,cAAsB,sCACP;AACf,QAAO,eAAe,YAAY,CAAC;;;;;;;;AASrC,SAAgB,eAAe,SAGZ;CACjB,MAAM,WAA2B,EAAE;CAGnC,MAAM,aAAa,oBAAoB;CACvC,MAAM,cAAc,qBAAqB;AAEzC,UAAS,UAAU;EACjB,aAAa;EACb,GAAI,eAAe,EAAE,KAAK,aAAa;EACxC;CAGD,MAAM,SAAS,eAAe,SAAS,oBAAoB;AAC3D,KAAI,OAAO,SAAS;EAClB,MAAM,MAAM,SAAS,OAAO,QAAQ;AACpC,WAAS,WAAW;GAClB,aAAa,OAAO;GACpB,GAAI,OAAO;IACT;IACA,QAAQ,aAAa,OAAO,YAAY,QAAW,IAAI;IACxD;GACF;;AAIH,KAAI,CAAC,SAAS,QAAQ;AACpB,UAAQ,IAAI,oCAAoC;AAChD,UAAQ,IAAI,iBAAiB;AAC7B,UAAQ,IAAI,eAAe,SAAS,QAAQ,cAAc;AAC1D,MAAI,SAAS,QAAQ,IACnB,SAAQ,IAAI,eAAe,SAAS,QAAQ,MAAM;AAEpD,MAAI,SAAS,SACX,SAAQ,IAAI,eAAe,SAAS,SAAS,cAAc;AAE7D,UAAQ,IAAI,iBAAiB;AAC7B,UAAQ,IACN,2BAA2B,QAAQ,IAAI,qBAAqB,cAC7D;AACD,UAAQ,IACN,2BAA2B,QAAQ,IAAI,sBAAsB,cAC9D;AACD,UAAQ,IAAI,oCAAoC;;AAGlD,QAAO"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@igstack/app-catalog-backend-core",
3
- "version": "0.5.0",
3
+ "version": "0.6.1",
4
4
  "description": "Backend core library for App Catalog",
5
5
  "homepage": "https://github.com/lislon/app-catalog",
6
6
  "repository": {
@@ -45,8 +45,8 @@
45
45
  "tsyringe": "^4.10.0",
46
46
  "yaml": "^2.8.0",
47
47
  "zod": "^4.3.5",
48
- "@igstack/app-catalog-shared-core": "0.5.0",
49
- "@igstack/app-catalog-table-sync": "0.5.0"
48
+ "@igstack/app-catalog-shared-core": "0.6.1",
49
+ "@igstack/app-catalog-table-sync": "0.6.1"
50
50
  },
51
51
  "devDependencies": {
52
52
  "@tanstack/vite-config": "^0.4.3",
@@ -73,6 +73,7 @@
73
73
  "engines": {
74
74
  "node": ">=24"
75
75
  },
76
+ "gitHead": "e83dc91aa65e1699c3fe578db7a62d81c4b27857",
76
77
  "scripts": {
77
78
  "build": "tsdown",
78
79
  "build:lenient": "tsdown",
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it } from 'vitest'
2
+ import { getCommitUrl, shortSha } from '../../utils/versionUtils.js'
3
+
4
+ describe('shortSha', () => {
5
+ it('returns first 7 chars', () => {
6
+ expect(shortSha('abcdef1234567890')).toBe('abcdef1')
7
+ })
8
+ it('returns undefined for empty', () => {
9
+ expect(shortSha(undefined)).toBeUndefined()
10
+ expect(shortSha(null)).toBeUndefined()
11
+ expect(shortSha('')).toBeUndefined()
12
+ })
13
+ })
14
+
15
+ describe('getCommitUrl', () => {
16
+ it('builds a github commit url', () => {
17
+ expect(
18
+ getCommitUrl('https://github.com/lislon/app-catalog', 'abc1234'),
19
+ ).toBe('https://github.com/lislon/app-catalog/commit/abc1234')
20
+ })
21
+ it('strips a trailing slash from homepage', () => {
22
+ expect(getCommitUrl('https://github.com/lislon/app-catalog/', 'abc')).toBe(
23
+ 'https://github.com/lislon/app-catalog/commit/abc',
24
+ )
25
+ })
26
+ it('returns undefined without homepage or sha', () => {
27
+ expect(getCommitUrl(undefined, 'abc')).toBeUndefined()
28
+ expect(getCommitUrl('https://x', undefined)).toBeUndefined()
29
+ })
30
+ })
@@ -126,6 +126,10 @@ export interface GroupingTagValue {
126
126
  export interface VersionInfo {
127
127
  displayName: string
128
128
  url?: string
129
+ /** Short git SHA of the source this artifact was built from (e.g. 'a1b2c3d'). */
130
+ sha?: string
131
+ /** Link to the commit for `sha` (e.g. a GitHub commit URL). */
132
+ shaUrl?: string
129
133
  }
130
134
 
131
135
  export interface AppVersionInfo {
@@ -18,12 +18,27 @@ export function getBuildPipelineUrl(): string | undefined {
18
18
  return process.env.BUILD_PIPELINE_URL
19
19
  }
20
20
 
21
+ /** First 7 chars of a git SHA, or undefined when absent. */
22
+ export function shortSha(sha?: string | null): string | undefined {
23
+ return sha ? sha.slice(0, 7) : undefined
24
+ }
25
+
26
+ /** Build a commit URL from a repo homepage + full/short SHA. */
27
+ export function getCommitUrl(
28
+ homepage: string | undefined,
29
+ sha: string | undefined,
30
+ ): string | undefined {
31
+ if (!homepage || !sha) return undefined
32
+ return `${homepage.replace(/\/$/, '')}/commit/${sha}`
33
+ }
34
+
21
35
  /**
22
- * Gets frontend package version from node_modules
36
+ * Reads `version`, `gitHead` (baked at publish time), and `homepage` from an
37
+ * installed package.json under node_modules.
23
38
  */
24
- export function getFrontendPackageVersion(
39
+ export function getPackageMeta(
25
40
  packageName: string = '@igstack/app-catalog-frontend-core',
26
- ): string | null {
41
+ ): { version: string | null; gitHead: string | null; homepage: string | null } {
27
42
  try {
28
43
  const pkgPath = join(
29
44
  process.cwd(),
@@ -32,16 +47,29 @@ export function getFrontendPackageVersion(
32
47
  'package.json',
33
48
  )
34
49
  const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
35
- return pkg.version || null
50
+ return {
51
+ version: pkg.version ?? null,
52
+ gitHead: pkg.gitHead ?? null,
53
+ homepage: pkg.homepage ?? null,
54
+ }
36
55
  } catch (error) {
37
56
  console.warn(
38
- `[versionUtils] Failed to read frontend version from ${packageName}:`,
57
+ `[versionUtils] Failed to read package meta from ${packageName}:`,
39
58
  error,
40
59
  )
41
- return null
60
+ return { version: null, gitHead: null, homepage: null }
42
61
  }
43
62
  }
44
63
 
64
+ /**
65
+ * Gets frontend package version from node_modules
66
+ */
67
+ export function getFrontendPackageVersion(
68
+ packageName: string = '@igstack/app-catalog-frontend-core',
69
+ ): string | null {
70
+ return getPackageMeta(packageName).version
71
+ }
72
+
45
73
  /**
46
74
  * Complete helper to get both backend and frontend versions
47
75
  * Backend: reads BUILD_PIPELINE_ID and BUILD_PIPELINE_URL from environment
@@ -63,12 +91,17 @@ export function getVersionInfo(options?: {
63
91
  ...(pipelineUrl && { url: pipelineUrl }),
64
92
  }
65
93
 
66
- // Frontend version from package.json
67
- const frontendVersion = getFrontendPackageVersion(
68
- options?.frontendPackageName,
69
- )
70
- if (frontendVersion) {
71
- versions.frontend = { displayName: frontendVersion }
94
+ // Frontend version + git SHA from package.json (SHA baked at publish time)
95
+ const feMeta = getPackageMeta(options?.frontendPackageName)
96
+ if (feMeta.version) {
97
+ const sha = shortSha(feMeta.gitHead)
98
+ versions.frontend = {
99
+ displayName: feMeta.version,
100
+ ...(sha && {
101
+ sha,
102
+ shaUrl: getCommitUrl(feMeta.homepage ?? undefined, sha),
103
+ }),
104
+ }
72
105
  }
73
106
 
74
107
  // Log for debugging CI/CD