add version in top bar and update checking

This commit is contained in:
Boof2015
2026-04-22 00:05:56 -04:00
parent 70d299d5b8
commit 7044a331ae
17 changed files with 1154 additions and 3 deletions
+88
View File
@@ -0,0 +1,88 @@
import { execFileSync } from 'node:child_process'
export interface AppBuildMetadata {
commitHash: string | null
isDirty: boolean
}
interface ResolveAppBuildMetadataOptions {
cwd?: string
env?: NodeJS.ProcessEnv
runGitCommand?: (cwd: string, args: string[]) => string
}
const DIRTY_ENV_TRUE_VALUES = new Set(['1', 'true', 'yes', 'dirty'])
const DIRTY_ENV_FALSE_VALUES = new Set(['0', 'false', 'no', 'clean'])
function normalizeCommitHash(value: string | undefined): string | null {
if (typeof value !== 'string') return null
const trimmed = value.trim()
return trimmed.length > 0 ? trimmed : null
}
function parseDirtyEnvValue(value: string | undefined): boolean | null {
if (typeof value !== 'string') return null
const normalized = value.trim().toLowerCase()
if (!normalized) return null
if (DIRTY_ENV_TRUE_VALUES.has(normalized)) return true
if (DIRTY_ENV_FALSE_VALUES.has(normalized)) return false
return null
}
function execGitCommand(cwd: string, args: string[]): string {
return execFileSync('git', args, {
cwd,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
})
}
function resolveGitCommitHash(
cwd: string,
runGitCommand: (cwd: string, args: string[]) => string,
): string | null {
try {
return normalizeCommitHash(runGitCommand(cwd, ['rev-parse', 'HEAD']))
} catch {
return null
}
}
function resolveGitDirtyState(cwd: string, runGitCommand: (cwd: string, args: string[]) => string): boolean {
try {
return runGitCommand(cwd, ['status', '--porcelain']).trim().length > 0
} catch {
return false
}
}
export function resolveAppBuildMetadata(options: ResolveAppBuildMetadataOptions = {}): AppBuildMetadata {
const cwd = options.cwd ?? process.cwd()
const env = options.env ?? process.env
const runGitCommand = options.runGitCommand ?? execGitCommand
const envCommitHash = normalizeCommitHash(env.PRISM_GIT_COMMIT)
const envDirtyState = parseDirtyEnvValue(env.PRISM_GIT_DIRTY)
if (envCommitHash) {
return {
commitHash: envCommitHash,
isDirty: envDirtyState ?? false,
}
}
const commitHash = resolveGitCommitHash(cwd, runGitCommand)
if (!commitHash) {
return {
commitHash: null,
isDirty: false,
}
}
return {
commitHash,
isDirty: envDirtyState ?? resolveGitDirtyState(cwd, runGitCommand),
}
}
+71
View File
@@ -0,0 +1,71 @@
#!/usr/bin/env node
const fs = require('fs')
const path = require('path')
const { execFileSync } = require('child_process')
const outputPath = path.resolve(__dirname, '../../out/build-metadata.json')
const repoRoot = path.resolve(__dirname, '../..')
const DIRTY_ENV_TRUE_VALUES = new Set(['1', 'true', 'yes', 'dirty'])
const DIRTY_ENV_FALSE_VALUES = new Set(['0', 'false', 'no', 'clean'])
function normalizeCommitHash(value) {
if (typeof value !== 'string') return null
const trimmed = value.trim()
return trimmed.length > 0 ? trimmed : null
}
function parseDirtyEnvValue(value) {
if (typeof value !== 'string') return null
const normalized = value.trim().toLowerCase()
if (!normalized) return null
if (DIRTY_ENV_TRUE_VALUES.has(normalized)) return true
if (DIRTY_ENV_FALSE_VALUES.has(normalized)) return false
return null
}
function execGit(args) {
return execFileSync('git', args, {
cwd: repoRoot,
encoding: 'utf8',
stdio: ['ignore', 'pipe', 'ignore'],
})
}
function resolveBuildMetadata() {
const envCommitHash = normalizeCommitHash(process.env.PRISM_GIT_COMMIT)
const envDirty = parseDirtyEnvValue(process.env.PRISM_GIT_DIRTY)
if (envCommitHash) {
return {
commitHash: envCommitHash,
isDirty: envDirty ?? false,
}
}
try {
const commitHash = normalizeCommitHash(execGit(['rev-parse', 'HEAD']))
if (!commitHash) {
return {
commitHash: null,
isDirty: false,
}
}
const isDirty = envDirty ?? execGit(['status', '--porcelain']).trim().length > 0
return {
commitHash,
isDirty,
}
} catch {
return {
commitHash: null,
isDirty: false,
}
}
}
const metadata = resolveBuildMetadata()
fs.mkdirSync(path.dirname(outputPath), { recursive: true })
fs.writeFileSync(outputPath, `${JSON.stringify(metadata, null, 2)}\n`, 'utf8')
+44
View File
@@ -0,0 +1,44 @@
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { spawn } from 'node:child_process'
import { build } from 'esbuild'
const rootDir = dirname(dirname(fileURLToPath(import.meta.url)))
const tempDir = await mkdtemp(join(tmpdir(), 'prism-build-metadata-tests-'))
const bundledTestPath = join(tempDir, 'build-metadata.test.mjs')
const entryPoint = join(rootDir, 'test', 'build-metadata.test.ts')
let exitCode = 1
try {
await build({
entryPoints: [entryPoint],
outfile: bundledTestPath,
bundle: true,
platform: 'node',
format: 'esm',
target: 'node23',
sourcemap: 'inline',
})
exitCode = await new Promise((resolve) => {
const child = spawn(process.execPath, ['--test', bundledTestPath], {
stdio: 'inherit',
cwd: rootDir,
})
child.on('exit', (code) => {
resolve(code ?? 1)
})
child.on('error', () => {
resolve(1)
})
})
} finally {
await rm(tempDir, { recursive: true, force: true })
}
process.exit(exitCode)
+44
View File
@@ -0,0 +1,44 @@
import { mkdtemp, rm } from 'node:fs/promises'
import { tmpdir } from 'node:os'
import { join, dirname } from 'node:path'
import { fileURLToPath } from 'node:url'
import { spawn } from 'node:child_process'
import { build } from 'esbuild'
const rootDir = dirname(dirname(fileURLToPath(import.meta.url)))
const tempDir = await mkdtemp(join(tmpdir(), 'prism-update-tests-'))
const bundledTestPath = join(tempDir, 'updates.test.mjs')
const entryPoint = join(rootDir, 'test', 'updates.test.ts')
let exitCode = 1
try {
await build({
entryPoints: [entryPoint],
outfile: bundledTestPath,
bundle: true,
platform: 'node',
format: 'esm',
target: 'node23',
sourcemap: 'inline',
})
exitCode = await new Promise((resolve) => {
const child = spawn(process.execPath, ['--test', bundledTestPath], {
stdio: 'inherit',
cwd: rootDir,
})
child.on('exit', (code) => {
resolve(code ?? 1)
})
child.on('error', () => {
resolve(1)
})
})
} finally {
await rm(tempDir, { recursive: true, force: true })
}
process.exit(exitCode)