Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/thirty-ideas-glow.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@workflow/world-local": patch
---

Fix package info stored in data dir showing the wrong version
17 changes: 1 addition & 16 deletions packages/cli/src/lib/inspect/setup.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
import { join } from 'node:path';
import { createWorld } from '@workflow/core/runtime';
import chalk from 'chalk';
import terminalLink from 'terminal-link';
import XDGAppPaths from 'xdg-app-paths';
import { logger, setJsonMode, setVerboseMode } from '../config/log.js';
import { checkForUpdateCached } from '../update-check.js';
import {
Expand All @@ -11,18 +9,6 @@ import {
writeEnvVars,
} from './env.js';

// Get XDG-compliant cache directory for workflow
const getXDGAppPaths = (app: string) => {
return (
XDGAppPaths as unknown as (app: string) => { dataDirs: () => string[] }
)(app);
};

const getWorkflowCacheDir = (): string => {
const dirs = getXDGAppPaths('workflow').dataDirs();
return dirs[0];
};

/**
* Setup CLI world configuration.
* If throwOnConfigError is false, will return null world with the error message
Expand All @@ -45,8 +31,7 @@ export const setupCliWorld = async (
setVerboseMode(Boolean(flags.verbose));

// Check for updates
const cacheFile = join(getWorkflowCacheDir(), 'version-check.json');
const updateCheck = await checkForUpdateCached(version, cacheFile);
const updateCheck = await checkForUpdateCached(version);

const withAnsiLinks = flags.json ? false : true;
const docsUrl = withAnsiLinks
Expand Down
24 changes: 21 additions & 3 deletions packages/cli/src/lib/update-check.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,29 @@
import { mkdir, readFile, writeFile } from 'node:fs/promises';
import { dirname } from 'node:path';
import { dirname, join } from 'node:path';
import XDGAppPaths from 'xdg-app-paths';
import { logger } from './config/log.js';

// Constants
const PACKAGE_NAME = '@workflow/cli';
const NPM_REGISTRY = 'https://registry.npmjs.org';
const CACHE_DURATION_MS = 3 * 24 * 60 * 60 * 1000; // 3 days
const REQUEST_TIMEOUT_MS = 5000;
const VERSION_CHECK_CACHE_FILENAME = 'version-check.json';

// Get XDG-compliant cache directory for workflow
const getXDGAppPaths = (app: string) => {
return (
XDGAppPaths as unknown as (app: string) => { dataDirs: () => string[] }
)(app);
};

/**
* Get the cache file path for version checks.
*/
export function getVersionCheckCacheFile(): string {
const dirs = getXDGAppPaths('workflow').dataDirs();
return join(dirs[0], VERSION_CHECK_CACHE_FILENAME);
}

interface VersionCheckResult {
currentVersion: string;
Expand Down Expand Up @@ -231,9 +248,10 @@ async function isCacheValid(
* Cache is valid unless the local version changes
*/
export async function checkForUpdateCached(
currentVersion: string,
cacheFile: string
currentVersion: string
): Promise<VersionCheckResult> {
const cacheFile = getVersionCheckCacheFile();

// Check if cache is valid
if (await isCacheValid(cacheFile, currentVersion)) {
logger.debug('Using cached version check result');
Expand Down
83 changes: 46 additions & 37 deletions packages/world-local/src/init.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
ensureDataDir,
formatVersion,
formatVersionFile,
getPackageInfo,
initDataDir,
type ParsedVersion,
parseVersion,
Expand Down Expand Up @@ -165,39 +166,39 @@ describe('ensureDataDir', () => {
});

describe('directory creation', () => {
it('should create the directory if it does not exist', () => {
it('should create the directory if it does not exist', async () => {
const dataDir = path.join(testBaseDir, 'new-data-dir');
expect(existsSync(dataDir)).toBe(false);

ensureDataDir(dataDir);
await ensureDataDir(dataDir);

expect(existsSync(dataDir)).toBe(true);
});

it('should create nested directories recursively', () => {
it('should create nested directories recursively', async () => {
const dataDir = path.join(testBaseDir, 'level1', 'level2', 'level3');
expect(existsSync(dataDir)).toBe(false);

ensureDataDir(dataDir);
await ensureDataDir(dataDir);

expect(existsSync(dataDir)).toBe(true);
});

it('should not throw if the directory already exists', () => {
it('should not throw if the directory already exists', async () => {
const dataDir = path.join(testBaseDir, 'existing-dir');
mkdirSync(dataDir);
expect(existsSync(dataDir)).toBe(true);

expect(() => ensureDataDir(dataDir)).not.toThrow();
await expect(ensureDataDir(dataDir)).resolves.not.toThrow();
});

it('should handle relative paths by resolving to absolute paths', () => {
it('should handle relative paths by resolving to absolute paths', async () => {
const originalCwd = process.cwd();
try {
process.chdir(testBaseDir);
const relativeDir = 'relative-data-dir';

ensureDataDir(relativeDir);
await ensureDataDir(relativeDir);

expect(existsSync(path.join(testBaseDir, relativeDir))).toBe(true);
} finally {
Expand All @@ -211,14 +212,16 @@ describe('ensureDataDir', () => {

it.skipIf(isWindows)(
'should throw DataDirAccessError if directory is not readable',
() => {
async () => {
const dataDir = path.join(testBaseDir, 'unreadable-dir');
mkdirSync(dataDir);
chmodSync(dataDir, 0o000);

try {
expect(() => ensureDataDir(dataDir)).toThrow(DataDirAccessError);
expect(() => ensureDataDir(dataDir)).toThrow(/not readable/);
await expect(ensureDataDir(dataDir)).rejects.toThrow(
DataDirAccessError
);
await expect(ensureDataDir(dataDir)).rejects.toThrow(/not readable/);
} finally {
chmodSync(dataDir, 0o755);
}
Expand All @@ -227,14 +230,16 @@ describe('ensureDataDir', () => {

it.skipIf(isWindows)(
'should throw DataDirAccessError if directory is not writable',
() => {
async () => {
const dataDir = path.join(testBaseDir, 'readonly-dir');
mkdirSync(dataDir);
chmodSync(dataDir, 0o555);

try {
expect(() => ensureDataDir(dataDir)).toThrow(DataDirAccessError);
expect(() => ensureDataDir(dataDir)).toThrow(/not writable/);
await expect(ensureDataDir(dataDir)).rejects.toThrow(
DataDirAccessError
);
await expect(ensureDataDir(dataDir)).rejects.toThrow(/not writable/);
} finally {
chmodSync(dataDir, 0o755);
}
Expand All @@ -243,16 +248,18 @@ describe('ensureDataDir', () => {

it.skipIf(isWindows)(
'should throw DataDirAccessError if parent directory is not writable',
() => {
async () => {
const parentDir = path.join(testBaseDir, 'readonly-parent');
mkdirSync(parentDir);
chmodSync(parentDir, 0o555);

const dataDir = path.join(parentDir, 'new-child-dir');

try {
expect(() => ensureDataDir(dataDir)).toThrow(DataDirAccessError);
expect(() => ensureDataDir(dataDir)).toThrow(
await expect(ensureDataDir(dataDir)).rejects.toThrow(
DataDirAccessError
);
await expect(ensureDataDir(dataDir)).rejects.toThrow(
/Failed to create data directory/
);
} finally {
Expand All @@ -263,7 +270,7 @@ describe('ensureDataDir', () => {
});

describe('DataDirAccessError', () => {
it('should include the data directory path in the error', () => {
it('should include the data directory path in the error', async () => {
const dataDir = path.join(testBaseDir, 'readonly-parent-for-error');
const isWindows = process.platform === 'win32';

Expand All @@ -274,7 +281,7 @@ describe('ensureDataDir', () => {
const childDir = path.join(dataDir, 'child');

try {
ensureDataDir(childDir);
await ensureDataDir(childDir);
} catch (error) {
expect(error).toBeInstanceOf(DataDirAccessError);
expect((error as DataDirAccessError).dataDir).toBe(childDir);
Expand All @@ -284,7 +291,7 @@ describe('ensureDataDir', () => {
}
});

it('should include the error code when available', () => {
it('should include the error code when available', async () => {
const isWindows = process.platform === 'win32';

if (!isWindows) {
Expand All @@ -295,7 +302,7 @@ describe('ensureDataDir', () => {
const dataDir = path.join(parentDir, 'child');

try {
ensureDataDir(dataDir);
await ensureDataDir(dataDir);
} catch (error) {
expect(error).toBeInstanceOf(DataDirAccessError);
expect((error as DataDirAccessError).code).toBeDefined();
Expand All @@ -307,28 +314,28 @@ describe('ensureDataDir', () => {
});

describe('edge cases', () => {
it('should handle empty string by creating directory at current path', () => {
it('should handle empty string by creating directory at current path', async () => {
const originalCwd = process.cwd();
try {
process.chdir(testBaseDir);
expect(() => ensureDataDir('.')).not.toThrow();
await expect(ensureDataDir('.')).resolves.not.toThrow();
} finally {
process.chdir(originalCwd);
}
});

it('should handle paths with special characters', () => {
it('should handle paths with special characters', async () => {
const dataDir = path.join(testBaseDir, 'special-chars-dir-@#$%');

ensureDataDir(dataDir);
await ensureDataDir(dataDir);

expect(existsSync(dataDir)).toBe(true);
});

it('should handle paths with spaces', () => {
it('should handle paths with spaces', async () => {
const dataDir = path.join(testBaseDir, 'dir with spaces');

ensureDataDir(dataDir);
await ensureDataDir(dataDir);

expect(existsSync(dataDir)).toBe(true);
});
Expand All @@ -355,10 +362,10 @@ describe('initDataDir', () => {
vi.restoreAllMocks();
});

it('should create version.txt for new data directory', () => {
it('should create version.txt for new data directory', async () => {
const dataDir = path.join(testBaseDir, 'new-data');

initDataDir(dataDir);
await initDataDir(dataDir);

const versionPath = path.join(dataDir, 'version.txt');
expect(existsSync(versionPath)).toBe(true);
Expand All @@ -367,27 +374,29 @@ describe('initDataDir', () => {
expect(content).toMatch(/^@workflow\/world-local@\d+\.\d+\.\d+/);
});

it('should not modify version.txt if version matches', () => {
it('should not modify version.txt if version matches', async () => {
const dataDir = path.join(testBaseDir, 'existing-data');
mkdirSync(dataDir, { recursive: true });

// Write the current version
const packageInfo = await getPackageInfo();
const versionPath = path.join(dataDir, 'version.txt');
writeFileSync(versionPath, '@workflow/[email protected]');
const currentVersion = `${packageInfo.name}@${packageInfo.version}`;
writeFileSync(versionPath, currentVersion);

const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});

initDataDir(dataDir);
await initDataDir(dataDir);

// Should not log upgrade message since versions match
expect(consoleSpy).not.toHaveBeenCalled();

// File should remain unchanged
const content = readFileSync(versionPath, 'utf-8');
expect(content).toBe('@workflow/[email protected]');
expect(content).toBe(currentVersion);
});

it('should call upgradeVersion when versions differ', () => {
it('should call upgradeVersion when versions differ', async () => {
const dataDir = path.join(testBaseDir, 'old-data');
mkdirSync(dataDir, { recursive: true });

Expand All @@ -397,7 +406,7 @@ describe('initDataDir', () => {

const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});

initDataDir(dataDir);
await initDataDir(dataDir);

// Should log upgrade message
expect(consoleSpy).toHaveBeenCalledWith(
Expand All @@ -409,7 +418,7 @@ describe('initDataDir', () => {
expect(content).toMatch(/^@workflow\/world-local@4\.0\.1/);
});

it('should handle data directory with newer version', () => {
it('should handle data directory with newer version', async () => {
const dataDir = path.join(testBaseDir, 'newer-data');
mkdirSync(dataDir, { recursive: true });

Expand All @@ -420,7 +429,7 @@ describe('initDataDir', () => {
const consoleSpy = vi.spyOn(console, 'log').mockImplementation(() => {});

// This will call upgradeVersion which just logs for now
initDataDir(dataDir);
await initDataDir(dataDir);

// Should log the upgrade message (even for "downgrades")
expect(consoleSpy).toHaveBeenCalledWith(
Expand Down
Loading
Loading