Skip to content
Merged
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
16 changes: 11 additions & 5 deletions src/core/global-config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,30 +69,36 @@ export interface GlobalDataDirOptions {
homedir?: string;
}

function joinGlobalDataPath(platform: NodeJS.Platform, ...segments: string[]): string {
return platform === 'win32'
? path.win32.join(...segments)
: path.posix.join(...segments);
}

export function getGlobalDataDir(options: GlobalDataDirOptions = {}): string {
const env = options.env ?? process.env;
const platform = options.platform ?? os.platform();

// XDG_DATA_HOME takes precedence on all platforms when explicitly set
const xdgDataHome = env.XDG_DATA_HOME;
if (xdgDataHome) {
return path.join(xdgDataHome, GLOBAL_DATA_DIR_NAME);
return joinGlobalDataPath(platform, xdgDataHome, GLOBAL_DATA_DIR_NAME);
}

const platform = options.platform ?? os.platform();
const homedir = options.homedir ?? os.homedir();

if (platform === 'win32') {
// Windows: use %LOCALAPPDATA%
const localAppData = env.LOCALAPPDATA;
if (localAppData) {
return path.win32.join(localAppData, GLOBAL_DATA_DIR_NAME);
return joinGlobalDataPath(platform, localAppData, GLOBAL_DATA_DIR_NAME);
}
// Fallback for Windows if LOCALAPPDATA is not set
return path.win32.join(homedir, 'AppData', 'Local', GLOBAL_DATA_DIR_NAME);
return joinGlobalDataPath(platform, homedir, 'AppData', 'Local', GLOBAL_DATA_DIR_NAME);
}

// Unix/macOS fallback: ~/.local/share
return path.join(homedir, '.local', 'share', GLOBAL_DATA_DIR_NAME);
return joinGlobalDataPath(platform, homedir, '.local', 'share', GLOBAL_DATA_DIR_NAME);
}

/**
Expand Down
4 changes: 3 additions & 1 deletion test/cli-e2e/basic.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,9 @@ describe('openspec CLI e2e basics', () => {
const result = await runCLI(['init', '--tools', 'all'], {
cwd: emptyProjectDir,
env: { CODEX_HOME: codexHome },
timeoutMs: 20000,
});
expect(result.timedOut).toBe(false);
expect(result.exitCode).toBe(0);
expect(result.stdout).toContain('OpenSpec Setup Complete');

Expand All @@ -143,7 +145,7 @@ describe('openspec CLI e2e basics', () => {
const cursorSkillPath = path.join(emptyProjectDir, '.cursor/skills/openspec-explore/SKILL.md');
expect(await fileExists(claudeSkillPath)).toBe(true);
expect(await fileExists(cursorSkillPath)).toBe(true);
});
}, 25000);

it('initializes with --tools list option', async () => {
const projectDir = await prepareFixture('tmp-init');
Expand Down
39 changes: 39 additions & 0 deletions test/core/global-config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import * as os from 'node:os';
import {
getGlobalConfigDir,
getGlobalConfigPath,
getGlobalDataDir,
getGlobalConfig,
saveGlobalConfig,
GLOBAL_CONFIG_DIR_NAME,
Expand Down Expand Up @@ -95,6 +96,44 @@ describe('global-config', () => {
});
});

describe('getGlobalDataDir', () => {
it('should use POSIX separators for Unix-like platform overrides', () => {
expect(
getGlobalDataDir({
env: {},
platform: 'linux',
homedir: '/home/tabish',
})
).toBe('/home/tabish/.local/share/openspec');

expect(
getGlobalDataDir({
env: { XDG_DATA_HOME: '/var/data' },
platform: 'darwin',
homedir: '/Users/tabish',
})
).toBe('/var/data/openspec');
});

it('should use Windows separators for native Windows platform overrides', () => {
expect(
getGlobalDataDir({
env: {},
platform: 'win32',
homedir: 'C:\\Users\\Tabish',
})
).toBe('C:\\Users\\Tabish\\AppData\\Local\\openspec');

expect(
getGlobalDataDir({
env: { LOCALAPPDATA: 'D:\\Users\\Tabish\\AppData\\Local' },
platform: 'win32',
homedir: 'C:\\Users\\Tabish',
})
).toBe('D:\\Users\\Tabish\\AppData\\Local\\openspec');
});
});

describe('getGlobalConfig', () => {
it('should return defaults when config file does not exist', () => {
process.env.XDG_CONFIG_HOME = tempDir;
Expand Down
Loading