Compare commits

..

1 Commits

Author SHA1 Message Date
Jarvis
38c1c68924 fix: retarget updater to @mosaic/mosaic
Some checks failed
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline failed
2026-04-04 20:34:58 -05:00
9 changed files with 12 additions and 85 deletions

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mosaic/gateway", "name": "@mosaic/gateway",
"version": "0.0.6", "version": "0.1.0",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git", "url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",

View File

@@ -22,13 +22,12 @@
- `pnpm --filter @mosaic/mosaic test -- __tests__/update-checker.test.ts` - `pnpm --filter @mosaic/mosaic test -- __tests__/update-checker.test.ts`
- `pnpm exec eslint --no-warn-ignored packages/mosaic/src/runtime/update-checker.ts packages/mosaic/src/cli.ts packages/mosaic/src/index.ts packages/mosaic/__tests__/update-checker.test.ts packages/cli/src/cli.ts` - `pnpm exec eslint --no-warn-ignored packages/mosaic/src/runtime/update-checker.ts packages/mosaic/src/cli.ts packages/mosaic/src/index.ts packages/mosaic/__tests__/update-checker.test.ts packages/cli/src/cli.ts`
- `pnpm --filter @mosaic/mosaic lint` - `pnpm --filter @mosaic/mosaic lint`
- pre-push hooks: `typecheck`, `lint`, `format:check`
## Review ## Review
- Manual review of the updater diff caught and fixed a cache regression where fallback results would lose the resolved package target on subsequent cached checks. - Manual review of the updater diff caught and fixed a cache regression where fallback results would lose the resolved package target on subsequent cached checks.
## Risks / Notes ## Blockers / Risks
- Direct `pnpm --filter @mosaic/mosaic typecheck` and `pnpm --filter @mosaic/cli ...` checks were not representative in this worktree because `packages/cli` is excluded from `pnpm-workspace.yaml` and the standalone package check lacked the built workspace dependency graph. - `pnpm --filter @mosaic/mosaic typecheck` fails in this worktree on pre-existing unresolved workspace imports such as `@mosaic/quality-rails`, `@mosaic/prdy`, and `@mosaic/types`; this is broader than the updater change.
- The repo's pre-push hooks provided the authoritative validation path here and passed: root `typecheck`, `lint`, and `format:check`. - `packages/cli` is excluded from `pnpm-workspace.yaml`, so package-script lint/typecheck are not runnable via normal workspace filters in this worktree.

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mosaic/config", "name": "@mosaic/config",
"version": "0.0.2", "version": "0.0.1",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git", "url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mosaic/db", "name": "@mosaic/db",
"version": "0.0.3", "version": "0.0.2",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git", "url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mosaic/memory", "name": "@mosaic/memory",
"version": "0.0.3", "version": "0.0.2",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git", "url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",

View File

@@ -2,34 +2,14 @@ import { beforeEach, describe, expect, it, vi } from 'vitest';
import { semverLt, formatUpdateNotice } from '../src/runtime/update-checker.js'; import { semverLt, formatUpdateNotice } from '../src/runtime/update-checker.js';
import type { UpdateCheckResult } from '../src/runtime/update-checker.js'; import type { UpdateCheckResult } from '../src/runtime/update-checker.js';
const { execSyncMock, cacheFiles } = vi.hoisted(() => ({ const { execSyncMock } = vi.hoisted(() => ({
execSyncMock: vi.fn(), execSyncMock: vi.fn(),
cacheFiles: new Map<string, string>(),
})); }));
vi.mock('node:child_process', () => ({ vi.mock('node:child_process', () => ({
execSync: execSyncMock, execSync: execSyncMock,
})); }));
vi.mock('node:fs', () => ({
existsSync: vi.fn((path: string) => cacheFiles.has(path)),
mkdirSync: vi.fn(),
readFileSync: vi.fn((path: string) => {
const value = cacheFiles.get(path);
if (value === undefined) {
throw new Error(`ENOENT: ${path}`);
}
return value;
}),
writeFileSync: vi.fn((path: string, content: string) => {
cacheFiles.set(path, content);
}),
}));
vi.mock('node:os', () => ({
homedir: vi.fn(() => '/mock-home'),
}));
async function importUpdateChecker() { async function importUpdateChecker() {
vi.resetModules(); vi.resetModules();
return import('../src/runtime/update-checker.js'); return import('../src/runtime/update-checker.js');
@@ -60,7 +40,6 @@ describe('semverLt', () => {
describe('formatUpdateNotice', () => { describe('formatUpdateNotice', () => {
beforeEach(() => { beforeEach(() => {
execSyncMock.mockReset(); execSyncMock.mockReset();
cacheFiles.clear();
}); });
it('returns empty string when up to date', () => { it('returns empty string when up to date', () => {
@@ -145,51 +124,4 @@ describe('formatUpdateNotice', () => {
expect(result.latest).toBe('0.0.17'); expect(result.latest).toBe('0.0.17');
expect(notice).toContain('@mosaic/mosaic@latest'); expect(notice).toContain('@mosaic/mosaic@latest');
}); });
it('does not reuse a cached modern-package result for a legacy install', async () => {
let installedPackage = '@mosaic/mosaic';
execSyncMock.mockImplementation((command: string) => {
if (command.includes('ls -g --depth=0 --json')) {
return JSON.stringify({
dependencies:
installedPackage === '@mosaic/mosaic'
? { '@mosaic/mosaic': { version: '0.0.17' } }
: { '@mosaic/cli': { version: '0.0.16' } },
});
}
if (command.includes('view @mosaic/mosaic version')) {
return installedPackage === '@mosaic/mosaic' ? '0.0.18' : '0.0.17';
}
if (command.includes('view @mosaic/cli version')) {
throw new Error('not found');
}
throw new Error(`Unexpected command: ${command}`);
});
const { checkForUpdate } = await importUpdateChecker();
const modernResult = checkForUpdate();
installedPackage = '@mosaic/cli';
const legacyResult = checkForUpdate();
expect(modernResult.currentPackage).toBe('@mosaic/mosaic');
expect(modernResult.targetPackage).toBe('@mosaic/mosaic');
expect(modernResult.latest).toBe('0.0.18');
expect(legacyResult.currentPackage).toBe('@mosaic/cli');
expect(legacyResult.targetPackage).toBe('@mosaic/mosaic');
expect(legacyResult.latest).toBe('0.0.17');
expect(execSyncMock).toHaveBeenCalledWith(
expect.stringContaining('view @mosaic/cli version'),
expect.any(Object),
);
expect(execSyncMock).toHaveBeenCalledWith(
expect.stringContaining('view @mosaic/mosaic version'),
expect.any(Object),
);
});
}); });

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mosaic/mosaic", "name": "@mosaic/mosaic",
"version": "0.0.19", "version": "0.0.18",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git", "url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",

View File

@@ -131,20 +131,18 @@ export function semverLt(a: string, b: string): boolean {
/** Cache stores only the latest registry version (the expensive network call). /** Cache stores only the latest registry version (the expensive network call).
* The installed version is always checked fresh — it's a local `npm ls`. */ * The installed version is always checked fresh — it's a local `npm ls`. */
interface RegistryCache { interface RegistryCache {
currentPackage?: string;
latest: string; latest: string;
targetPackage?: string; targetPackage?: string;
checkedAt: string; checkedAt: string;
registry: string; registry: string;
} }
function readCache(currentPackage: string): RegistryCache | null { function readCache(): RegistryCache | null {
try { try {
if (!existsSync(CACHE_FILE)) return null; if (!existsSync(CACHE_FILE)) return null;
const raw = JSON.parse(readFileSync(CACHE_FILE, 'utf-8')) as RegistryCache; const raw = JSON.parse(readFileSync(CACHE_FILE, 'utf-8')) as RegistryCache;
const age = Date.now() - new Date(raw.checkedAt).getTime(); const age = Date.now() - new Date(raw.checkedAt).getTime();
if (age > CACHE_TTL_MS) return null; if (age > CACHE_TTL_MS) return null;
if ((raw.currentPackage || '') !== currentPackage) return null;
return raw; return raw;
} catch { } catch {
return null; return null;
@@ -225,7 +223,7 @@ export function checkForUpdate(options?: { skipCache?: boolean }): UpdateCheckRe
let checkedAt: string; let checkedAt: string;
if (!options?.skipCache) { if (!options?.skipCache) {
const cached = readCache(currentInfo.name); const cached = readCache();
if (cached) { if (cached) {
latestInfo = { latestInfo = {
name: cached.targetPackage || MODERN_PKG, name: cached.targetPackage || MODERN_PKG,
@@ -236,7 +234,6 @@ export function checkForUpdate(options?: { skipCache?: boolean }): UpdateCheckRe
latestInfo = getLatestVersion(currentInfo.name); latestInfo = getLatestVersion(currentInfo.name);
checkedAt = new Date().toISOString(); checkedAt = new Date().toISOString();
writeCache({ writeCache({
currentPackage: currentInfo.name,
latest: latestInfo.version, latest: latestInfo.version,
targetPackage: latestInfo.name, targetPackage: latestInfo.name,
checkedAt, checkedAt,
@@ -247,7 +244,6 @@ export function checkForUpdate(options?: { skipCache?: boolean }): UpdateCheckRe
latestInfo = getLatestVersion(currentInfo.name); latestInfo = getLatestVersion(currentInfo.name);
checkedAt = new Date().toISOString(); checkedAt = new Date().toISOString();
writeCache({ writeCache({
currentPackage: currentInfo.name,
latest: latestInfo.version, latest: latestInfo.version,
targetPackage: latestInfo.name, targetPackage: latestInfo.name,
checkedAt, checkedAt,

View File

@@ -1,6 +1,6 @@
{ {
"name": "@mosaic/queue", "name": "@mosaic/queue",
"version": "0.0.3", "version": "0.0.2",
"repository": { "repository": {
"type": "git", "type": "git",
"url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git", "url": "https://git.mosaicstack.dev/mosaic/mosaic-stack.git",