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
55 changes: 45 additions & 10 deletions src/utils/version.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,13 +30,52 @@ async function resolveInstalledVersion(cwd: string): Promise<ResolvedVersion> {
return parseVersion(packageInfo?.version ?? '')
}

export async function resolveFallBack(dataPath = getDataPath()): Promise<ResolvedVersion> {
async function getAvailableVersions(dataPath: string): Promise<string[]> {
const entries = await readdir(dataPath, { withFileTypes: true })
const versions = entries

return entries
.filter(entry => entry.isFile())
.map(entry => entry.name.match(VERSION_FILE_RE)?.[1])
.filter((version): version is string => Boolean(version && semver.valid(version)))
.sort(semver.rcompare)
}

async function resolveAvailableVersion(resolvedVersion: ResolvedVersion, dataPath: string): Promise<ResolvedVersion> {
if (!resolvedVersion.version) {
return resolvedVersion
}

const requestedVersion = semver.parse(resolvedVersion.version)
if (!requestedVersion) {
return resolvedVersion
}

const versions = await getAvailableVersions(dataPath)

const exactVersion = versions.find(version => semver.eq(version, requestedVersion))
if (exactVersion) {
return parseVersion(exactVersion)
}

const latestMinorVersion = versions.find((version) => {
return semver.major(version) === requestedVersion.major
&& semver.minor(version) === requestedVersion.minor
})
if (latestMinorVersion) {
return parseVersion(latestMinorVersion)
}

// v{major}.json is the latest-version alias for that major. Returning its
// concrete semantic version keeps loadVersionMetaData pointed at a versioned file.
const latestMajorVersion = versions.find((version) => {
return semver.major(version) === requestedVersion.major
})

return latestMajorVersion ? parseVersion(latestMajorVersion) : resolvedVersion
}

export async function resolveFallBack(dataPath = getDataPath()): Promise<ResolvedVersion> {
const versions = await getAvailableVersions(dataPath)

const resolvedVersion = versions[0] && parseVersion(versions[0])

Expand All @@ -47,23 +86,19 @@ export async function resolveFallBack(dataPath = getDataPath()): Promise<Resolve
return resolvedVersion
}

export async function resolveVersion(config: ResolvedConfig): Promise<ResolvedVersion> {
export async function resolveVersion(config: ResolvedConfig, dataPath = getDataPath()): Promise<ResolvedVersion> {
const { version, cwd } = config

if (version) {
const resolvedVersion = parseVersion(version)

if (!resolvedVersion) {
throw new TypeError(`Invalid antdv-next version: ${version}`)
}

return resolvedVersion
return await resolveAvailableVersion(resolvedVersion, dataPath)
}

const pkgVersion = await resolveInstalledVersion(cwd)
if (pkgVersion.version) {
return pkgVersion
return await resolveAvailableVersion(pkgVersion, dataPath)
}

return await resolveFallBack()
return await resolveFallBack(dataPath)
}
30 changes: 27 additions & 3 deletions test/version.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,12 @@ function createConfig(version = '', cwd = '/project'): ResolvedConfig {
beforeEach(async () => {
mockedGetPackageInfo.mockReset()
await mkdir(dataPath, { recursive: true })
await Promise.all([
writeFile(join(dataPath, 'v1.2.3.json'), '{}'),
writeFile(join(dataPath, 'v1.5.1.json'), '{}'),
writeFile(join(dataPath, 'v1.5.2.json'), '{}'),
writeFile(join(dataPath, 'v1.json'), '{}'),
])
})

afterEach(async () => {
Expand All @@ -31,15 +37,31 @@ afterEach(async () => {

describe('resolveVersion', () => {
it('resolves and normalizes an explicitly provided version', async () => {
await expect(resolveVersion(createConfig(' v1.2.3 '))).resolves.toEqual({
await expect(resolveVersion(createConfig(' v1.2.3 '), dataPath)).resolves.toEqual({
version: '1.2.3',
majorVersion: 'v1',
})
expect(mockedGetPackageInfo).not.toHaveBeenCalled()
})

it('resolves a missing patch to the greatest version in the same minor', async () => {
await expect(resolveVersion(createConfig('v1.5.4'), dataPath)).resolves.toEqual({
version: '1.5.2',
majorVersion: 'v1',
})
expect(mockedGetPackageInfo).not.toHaveBeenCalled()
})

it('resolves a missing minor to the latest version in the same major', async () => {
await expect(resolveVersion(createConfig('v1.6.0'), dataPath)).resolves.toEqual({
version: '1.5.2',
majorVersion: 'v1',
})
expect(mockedGetPackageInfo).not.toHaveBeenCalled()
})

it('returns an empty sentinel when an explicit version cannot be coerced', async () => {
await expect(resolveVersion(createConfig('latest'))).resolves.toEqual({
await expect(resolveVersion(createConfig('latest'), dataPath)).resolves.toEqual({
version: '',
majorVersion: 'v0',
})
Expand All @@ -55,7 +77,7 @@ describe('resolveVersion', () => {
packageJson: {},
})

await expect(resolveVersion(createConfig())).resolves.toEqual({
await expect(resolveVersion(createConfig(), dataPath)).resolves.toEqual({
version: '2.4.1',
majorVersion: 'v2',
})
Expand All @@ -82,6 +104,8 @@ describe('resolveFallBack', () => {
})

it('throws when the data directory has no valid version files', async () => {
await rm(dataPath, { recursive: true })
await mkdir(dataPath)
await writeFile(join(dataPath, 'version.json'), '{}')

await expect(resolveFallBack(dataPath)).rejects.toThrow(
Expand Down
Loading