feat(FN-1246): add app version tracking and sync infrastructure
- Add getAppVersion() utility that walks up directories to find package.json - Add parseSemver() for semver string parsing with major/minor/patch components - Add CentralCore version sync methods (getAppVersion, syncVersion, getLastSyncTime, getSyncStatus) - Add CentralCore events for version sync lifecycle (version-sync-started, version-sync-completed, version-sync-failed) - Add schema v4 migration with appVersion and lastSyncTime columns to projects table - Export new types (CentralSyncStatus, SyncResult) and utilities via @fusion/core - Update memory.md documentation with new types - Fix TypeScript error in app-version.ts (return pkg.version directly instead of cached variable)
This commit is contained in:
63
packages/core/src/app-version.ts
Normal file
63
packages/core/src/app-version.ts
Normal file
@@ -0,0 +1,63 @@
|
||||
import { readFileSync } from "node:fs";
|
||||
import { join, dirname } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
|
||||
/**
|
||||
* Cached app version once resolved.
|
||||
*/
|
||||
let cachedVersion: string | null = null;
|
||||
|
||||
/**
|
||||
* Get the current Fusion application version by reading the nearest package.json.
|
||||
* Walks up from the current file to find the root package.json.
|
||||
* Results are cached for the process lifetime.
|
||||
*
|
||||
* @returns Semver version string (e.g., "0.1.0")
|
||||
*/
|
||||
export function getAppVersion(): string {
|
||||
if (cachedVersion !== null) return cachedVersion;
|
||||
|
||||
// Start from this file's directory and walk up
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
let currentDir = __dirname;
|
||||
|
||||
// Walk up to 10 levels looking for package.json
|
||||
for (let i = 0; i < 10; i++) {
|
||||
try {
|
||||
const pkgPath = join(currentDir, "package.json");
|
||||
const pkg = JSON.parse(readFileSync(pkgPath, "utf-8"));
|
||||
if (pkg.version && typeof pkg.version === "string") {
|
||||
cachedVersion = pkg.version;
|
||||
return pkg.version;
|
||||
}
|
||||
} catch {
|
||||
// package.json not found or not parseable — continue walking up
|
||||
}
|
||||
const parentDir = dirname(currentDir);
|
||||
if (parentDir === currentDir) break; // Reached filesystem root
|
||||
currentDir = parentDir;
|
||||
}
|
||||
|
||||
// Fallback if no package.json found
|
||||
cachedVersion = "0.0.0";
|
||||
return cachedVersion;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse a semver string into its components.
|
||||
* Supports basic semver format: MAJOR.MINOR.PATCH with optional prerelease suffix.
|
||||
* The string must match the pattern starting from the beginning.
|
||||
*
|
||||
* @param version - Semver version string (e.g., "1.2.3", "1.2.3-beta.1")
|
||||
* @returns Parsed components or null if invalid
|
||||
*/
|
||||
export function parseSemver(version: string): { major: number; minor: number; patch: number } | null {
|
||||
// Strict semver regex: anchored to start, requires MAJOR.MINOR.PATCH, allows optional prerelease/build
|
||||
const match = version.match(/^(\d+)\.(\d+)\.(\d+)(?:-[0-9A-Za-z.-]+)?(?:\+[0-9A-Za-z.-]+)?$/);
|
||||
if (!match) return null;
|
||||
return {
|
||||
major: parseInt(match[1], 10),
|
||||
minor: parseInt(match[2], 10),
|
||||
patch: parseInt(match[3], 10),
|
||||
};
|
||||
}
|
||||
Reference in New Issue
Block a user