* fix: strip comments before scanning for missing imports
The scanForMissingImports regex matched require() and import() patterns
inside JSDoc comments, causing false-positive missing module detection.
A documented path like `require('./commands/proactive.js')` in a comment
was resolved from the wrong directory, marked as missing, then the global
onResolve handler intercepted ALL imports of that specifier — including
valid ones — replacing them with truthy noop stubs that broke runtime.
Strip block (/* */) and line (//) comments from source before scanning.
* fix: repair 10 pre-existing test failures
- promptIdentity.test.ts: define MACRO global (ISSUES_EXPLAINER etc.)
for test mode where Bun.define build-time replacements aren't active
- context.test.ts: clear OPENAI_MODEL env var in each test — the user's
environment (e.g. OPENAI_MODEL=github_copilot/gpt-5.4) polluted the
provider-qualified lookup, returning wrong context windows
- openclaudePaths.test.ts: set CLAUDE_CONFIG_DIR to force .openclaude
path when ~/.openclaude doesn't exist on the test machine
147 lines
4.5 KiB
TypeScript
147 lines
4.5 KiB
TypeScript
import { afterEach, describe, expect, mock, test } from 'bun:test'
|
|
import * as fsPromises from 'fs/promises'
|
|
import { homedir } from 'os'
|
|
import { join } from 'path'
|
|
|
|
const originalEnv = { ...process.env }
|
|
const originalArgv = [...process.argv]
|
|
|
|
async function importFreshEnvUtils() {
|
|
return import(`./envUtils.ts?ts=${Date.now()}-${Math.random()}`)
|
|
}
|
|
|
|
async function importFreshSettings() {
|
|
return import(`./settings/settings.ts?ts=${Date.now()}-${Math.random()}`)
|
|
}
|
|
|
|
async function importFreshLocalInstaller() {
|
|
return import(`./localInstaller.ts?ts=${Date.now()}-${Math.random()}`)
|
|
}
|
|
|
|
afterEach(() => {
|
|
process.env = { ...originalEnv }
|
|
process.argv = [...originalArgv]
|
|
mock.restore()
|
|
})
|
|
|
|
describe('OpenClaude paths', () => {
|
|
test('defaults user config home to ~/.openclaude', async () => {
|
|
delete process.env.CLAUDE_CONFIG_DIR
|
|
const { resolveClaudeConfigHomeDir } = await importFreshEnvUtils()
|
|
|
|
expect(
|
|
resolveClaudeConfigHomeDir({
|
|
homeDir: homedir(),
|
|
openClaudeExists: true,
|
|
legacyClaudeExists: false,
|
|
}),
|
|
).toBe(join(homedir(), '.openclaude'))
|
|
})
|
|
|
|
test('falls back to ~/.claude when legacy config exists and ~/.openclaude does not', async () => {
|
|
delete process.env.CLAUDE_CONFIG_DIR
|
|
const { resolveClaudeConfigHomeDir } = await importFreshEnvUtils()
|
|
|
|
expect(
|
|
resolveClaudeConfigHomeDir({
|
|
homeDir: homedir(),
|
|
openClaudeExists: false,
|
|
legacyClaudeExists: true,
|
|
}),
|
|
).toBe(join(homedir(), '.claude'))
|
|
})
|
|
|
|
test('uses CLAUDE_CONFIG_DIR override when provided', async () => {
|
|
process.env.CLAUDE_CONFIG_DIR = '/tmp/custom-openclaude'
|
|
const { getClaudeConfigHomeDir, resolveClaudeConfigHomeDir } =
|
|
await importFreshEnvUtils()
|
|
|
|
expect(getClaudeConfigHomeDir()).toBe('/tmp/custom-openclaude')
|
|
expect(
|
|
resolveClaudeConfigHomeDir({
|
|
configDirEnv: '/tmp/custom-openclaude',
|
|
}),
|
|
).toBe('/tmp/custom-openclaude')
|
|
})
|
|
|
|
test('project and local settings paths use .openclaude', async () => {
|
|
const { getRelativeSettingsFilePathForSource } = await importFreshSettings()
|
|
|
|
expect(getRelativeSettingsFilePathForSource('projectSettings')).toBe(
|
|
'.openclaude/settings.json',
|
|
)
|
|
expect(getRelativeSettingsFilePathForSource('localSettings')).toBe(
|
|
'.openclaude/settings.local.json',
|
|
)
|
|
})
|
|
|
|
test('local installer uses openclaude wrapper path', async () => {
|
|
// Force .openclaude config home so the test doesn't fall back to
|
|
// ~/.claude when ~/.openclaude doesn't exist on this machine.
|
|
process.env.CLAUDE_CONFIG_DIR = join(homedir(), '.openclaude')
|
|
const { getLocalClaudePath } = await importFreshLocalInstaller()
|
|
|
|
expect(getLocalClaudePath()).toBe(
|
|
join(homedir(), '.openclaude', 'local', 'openclaude'),
|
|
)
|
|
})
|
|
|
|
test('local installation detection matches .openclaude path', async () => {
|
|
const { isManagedLocalInstallationPath } =
|
|
await importFreshLocalInstaller()
|
|
|
|
expect(
|
|
isManagedLocalInstallationPath(
|
|
`${join(homedir(), '.openclaude', 'local')}/node_modules/.bin/openclaude`,
|
|
),
|
|
).toBe(true)
|
|
})
|
|
|
|
test('local installation detection still matches legacy .claude path', async () => {
|
|
const { isManagedLocalInstallationPath } =
|
|
await importFreshLocalInstaller()
|
|
|
|
expect(
|
|
isManagedLocalInstallationPath(
|
|
`${join(homedir(), '.claude', 'local')}/node_modules/.bin/openclaude`,
|
|
),
|
|
).toBe(true)
|
|
})
|
|
|
|
test('candidate local install dirs include both openclaude and legacy claude paths', async () => {
|
|
const { getCandidateLocalInstallDirs } = await importFreshLocalInstaller()
|
|
|
|
expect(
|
|
getCandidateLocalInstallDirs({
|
|
configHomeDir: join(homedir(), '.openclaude'),
|
|
homeDir: homedir(),
|
|
}),
|
|
).toEqual([
|
|
join(homedir(), '.openclaude', 'local'),
|
|
join(homedir(), '.claude', 'local'),
|
|
])
|
|
})
|
|
|
|
test('legacy local installs are detected when they still expose the claude binary', async () => {
|
|
mock.module('fs/promises', () => ({
|
|
...fsPromises,
|
|
access: async (path: string) => {
|
|
if (
|
|
path === join(homedir(), '.claude', 'local', 'node_modules', '.bin', 'claude')
|
|
) {
|
|
return
|
|
}
|
|
throw Object.assign(new Error('ENOENT'), { code: 'ENOENT' })
|
|
},
|
|
}))
|
|
|
|
const { getDetectedLocalInstallDir, localInstallationExists } =
|
|
await importFreshLocalInstaller()
|
|
|
|
expect(await localInstallationExists()).toBe(true)
|
|
expect(await getDetectedLocalInstallDir()).toBe(
|
|
join(homedir(), '.claude', 'local'),
|
|
)
|
|
})
|
|
})
|