diff --git a/app/main.js b/app/main.js index c4342db..e73a9f2 100644 --- a/app/main.js +++ b/app/main.js @@ -21,6 +21,7 @@ const { readLock } = require('../core/locks'); const CaptureService = require('./capture'); const { TextIntelService } = require('./text-intel'); const { keepProcessesResponsive } = require('./win-power'); +const PACKAGE_JSON = require(path.join(__dirname, '..', 'package.json')); const APP_ID = 'com.stepforge.app'; @@ -762,6 +763,7 @@ function setupIpc() { h('shell:showItemInFolder', ({ target }) => shell.showItemInFolder(target)); h('app:info', () => ({ version: app.getVersion(), + buildVersion: PACKAGE_JSON.buildVersion || app.getVersion(), dataDir: store.root, platform: process.platform, })); diff --git a/app/renderer/app.js b/app/renderer/app.js index f0842da..760ca6d 100644 --- a/app/renderer/app.js +++ b/app/renderer/app.js @@ -401,7 +401,7 @@ class StepForgeApp { el('div', { style: { fontWeight: 650 } }, folderLabel), q ? el('div.muted', {}, `Search: ${q}`) : el('div.muted', {}, `${this.state.library.guides.length} guides`), ), - el('div.muted', {}, this.state.info ? `StepForge ${this.state.info.version}` : ''), + el('div.muted', {}, this.state.info ? `StepForge ${this.state.info.buildVersion || this.state.info.version}` : ''), ), this.domBulkBar = el('div', {}), this.domLibraryResults = el('div', {}), diff --git a/build/installer.nsh b/build/installer.nsh index 32d47b6..5391d8e 100644 --- a/build/installer.nsh +++ b/build/installer.nsh @@ -1,10 +1,10 @@ !include LogicLib.nsh !include nsDialogs.nsh -Var StepForgeDesktopShortcutCheckbox -Var StepForgeDesktopShortcutState - !ifndef BUILD_UNINSTALLER + Var StepForgeDesktopShortcutCheckbox + Var StepForgeDesktopShortcutState + ; Assisted installer page for the desktop shortcut choice. !macro customInit StrCpy $StepForgeDesktopShortcutState "true" diff --git a/package-lock.json b/package-lock.json index b21f5fe..2c6eee8 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "stepforge", - "version": "0.3.2.1", + "version": "0.3.2", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "stepforge", - "version": "0.3.2.1", + "version": "0.3.2", "license": "MPL-2.0", "dependencies": { "@tesseract.js-data/eng": "^1.0.0", diff --git a/package.json b/package.json index 7ed31dd..61d17b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,7 @@ { "name": "stepforge", - "version": "0.3.2.1", + "version": "0.3.2", + "buildVersion": "0.3.2.1", "description": "Fully offline desktop tool for capturing, annotating, and exporting step-by-step guides.", "main": "app/main.js", "author": "StepForge tyler@twestbrook.com", diff --git a/scripts/bootstrap-offline.sh b/scripts/bootstrap-offline.sh index 8dd2dfd..ffb8f68 100644 --- a/scripts/bootstrap-offline.sh +++ b/scripts/bootstrap-offline.sh @@ -20,5 +20,5 @@ fi node - <<'NODE' const pkg = require('./package.json'); -console.log(`StepForge ${pkg.version} bootstrap OK`); +console.log(`StepForge ${pkg.buildVersion || pkg.version} bootstrap OK`); NODE diff --git a/scripts/build-release.sh b/scripts/build-release.sh index 60546d0..e5c51d3 100644 --- a/scripts/build-release.sh +++ b/scripts/build-release.sh @@ -70,6 +70,7 @@ for (const rel of walk(examplesRoot, examplesRoot)) { } const pkg = require(path.join(rootDir, 'package.json')); +const buildVersion = pkg.buildVersion || pkg.version; const { execSync } = require('node:child_process'); function toolAvailable(cmd) { @@ -88,7 +89,8 @@ const toolRows = Object.entries(tools) const report = `# StepForge Build Report -Version: ${pkg.version} +Build version: ${buildVersion} +Package version: ${pkg.version} Generated: ${new Date().toISOString()} Host: ${process.platform} ${process.arch} (node ${process.version}) @@ -133,6 +135,7 @@ fs.writeFileSync(manifestFile, JSON.stringify({ version: 1, generatedAt: new Date().toISOString(), packageVersion: pkg.version, + buildVersion, files, }, null, 2) + '\n'); NODE diff --git a/scripts/package-linux.sh b/scripts/package-linux.sh index a96f907..cf0fc0d 100644 --- a/scripts/package-linux.sh +++ b/scripts/package-linux.sh @@ -3,7 +3,7 @@ set -euo pipefail ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" -VERSION="$(node -p "require('${ROOT_DIR}/package.json').version" 2>/dev/null || echo 0.0.0)" +VERSION="$(node -p "const pkg=require('${ROOT_DIR}/package.json'); pkg.buildVersion || pkg.version" 2>/dev/null || echo 0.0.0)" OUT_DIR="${STEPFORGE_PACKAGE_DIR:-$ROOT_DIR/build/artifacts}" mkdir -p "$OUT_DIR" WORK_DIR="$(mktemp -d "${OUT_DIR%/}/.pkg.XXXXXX")" diff --git a/scripts/package-windows.js b/scripts/package-windows.js index f37cec0..18dba03 100644 --- a/scripts/package-windows.js +++ b/scripts/package-windows.js @@ -10,6 +10,7 @@ const { build, Platform } = require('electron-builder'); const ROOT_DIR = path.resolve(__dirname, '..'); const PACKAGE_JSON = require(path.join(ROOT_DIR, 'package.json')); const APP_ID = 'com.stepforge.app'; +const BUILD_VERSION = PACKAGE_JSON.buildVersion || PACKAGE_JSON.version; function findInstallerExe(dir) { if (!fs.existsSync(dir)) return null; @@ -32,6 +33,7 @@ function createWindowsInstallerConfig(outputDir) { return { appId: APP_ID, productName: 'StepForge', + buildVersion: BUILD_VERSION, directories: { output: outputDir, }, @@ -52,6 +54,7 @@ function createWindowsInstallerConfig(outputDir) { createDesktopShortcut: true, createStartMenuShortcut: true, shortcutName: 'StepForge', + artifactName: '${productName} Setup ${buildVersion}.${ext}', include: 'build/installer.nsh', }, }; @@ -87,7 +90,7 @@ async function buildWindowsInstaller() { const releaseInstaller = path.join(releaseDir, path.basename(builtInstaller)); fs.copyFileSync(builtInstaller, releaseInstaller); - console.log(`StepForge ${PACKAGE_JSON.version} Windows installer written to ${releaseInstaller}`); + console.log(`StepForge ${BUILD_VERSION} Windows installer written to ${releaseInstaller}`); } if (require.main === module) { diff --git a/scripts/stamp-version.js b/scripts/stamp-version.js index 74d6770..60af74f 100644 --- a/scripts/stamp-version.js +++ b/scripts/stamp-version.js @@ -16,18 +16,25 @@ function stampVersion(rootDir, version) { throw new Error('version is required'); } + const normalized = version.replace(/^v/i, ''); + const parts = normalized.split('.'); + const isFourPartBuild = parts.length === 4 && parts.every((part) => /^\d+$/.test(part)); + const packageVersion = isFourPartBuild ? parts.slice(0, 3).join('.') : normalized; + const buildVersion = normalized; + const pkgPath = path.join(rootDir, 'package.json'); const pkg = readJson(pkgPath); - pkg.version = version; + pkg.version = packageVersion; + pkg.buildVersion = buildVersion; writeJson(pkgPath, pkg); const lockPath = path.join(rootDir, 'package-lock.json'); if (!fs.existsSync(lockPath)) return; const lock = readJson(lockPath); - lock.version = version; + lock.version = packageVersion; if (lock.packages && lock.packages['']) { - lock.packages[''].version = version; + lock.packages[''].version = packageVersion; } writeJson(lockPath, lock); } diff --git a/tests/unit/package-windows.test.js b/tests/unit/package-windows.test.js index 123e557..ecad59b 100644 --- a/tests/unit/package-windows.test.js +++ b/tests/unit/package-windows.test.js @@ -21,6 +21,8 @@ test('Windows packaging uses an assisted NSIS installer', (t) => { assert.equal(config.nsis.createDesktopShortcut, true); assert.equal(config.nsis.createStartMenuShortcut, true); assert.equal(config.nsis.shortcutName, 'StepForge'); + assert.equal(config.buildVersion, '0.3.2.1'); + assert.equal(config.nsis.artifactName, '${productName} Setup ${buildVersion}.${ext}'); assert.equal(config.nsis.include, 'build/installer.nsh'); assert.equal(config.asar, true); assert.ok(config.files.includes('app/**/*')); @@ -35,7 +37,7 @@ test('Windows packaging uses an assisted NSIS installer', (t) => { const tmp = makeTmpDir('windows-installer'); t.after(() => rmrf(tmp)); fs.mkdirSync(path.join(tmp, 'nested', 'deeper'), { recursive: true }); - const installer = path.join(tmp, 'nested', 'deeper', 'StepForge Setup 0.2.0.exe'); + const installer = path.join(tmp, 'nested', 'deeper', 'StepForge Setup 0.3.2.1.exe'); fs.writeFileSync(installer, Buffer.from('fake installer')); assert.equal(findInstallerExe(tmp), installer); }); diff --git a/tests/unit/stamp-version.test.js b/tests/unit/stamp-version.test.js index 4fa8fec..9b49509 100644 --- a/tests/unit/stamp-version.test.js +++ b/tests/unit/stamp-version.test.js @@ -8,13 +8,14 @@ const assert = require('node:assert/strict'); const { makeTmpDir, rmrf } = require('./helpers'); const { stampVersion } = require('../../scripts/stamp-version'); -test('stampVersion updates package metadata without semver validation', () => { +test('stampVersion splits build labels into package and build versions', () => { const root = makeTmpDir('stamp-version'); try { fs.writeFileSync(path.join(root, 'package.json'), JSON.stringify({ name: 'stepforge', version: '0.1.0', private: true, + buildVersion: '0.1.0', }, null, 2)); fs.writeFileSync(path.join(root, 'package-lock.json'), JSON.stringify({ @@ -35,9 +36,10 @@ test('stampVersion updates package metadata without semver validation', () => { const pkg = JSON.parse(fs.readFileSync(path.join(root, 'package.json'), 'utf8')); const lock = JSON.parse(fs.readFileSync(path.join(root, 'package-lock.json'), 'utf8')); - assert.equal(pkg.version, '0.3.2.1'); - assert.equal(lock.version, '0.3.2.1'); - assert.equal(lock.packages[''].version, '0.3.2.1'); + assert.equal(pkg.version, '0.3.2'); + assert.equal(pkg.buildVersion, '0.3.2.1'); + assert.equal(lock.version, '0.3.2'); + assert.equal(lock.packages[''].version, '0.3.2'); } finally { rmrf(root); }