Skip to content

Commit

Permalink
Merge b028ac5 into 61e73b4
Browse files Browse the repository at this point in the history
  • Loading branch information
jeffijoe committed Dec 27, 2023
2 parents 61e73b4 + b028ac5 commit a9b94ec
Show file tree
Hide file tree
Showing 9 changed files with 188 additions and 71 deletions.
12 changes: 7 additions & 5 deletions .github/workflows/ci.yml
@@ -1,8 +1,12 @@
# Name of the pipeline
name: CI

# When pushing to any branch
on: [push, pull_request]
# When pushing to `master` or when there is a PR for the branch.
on:
pull_request:
push:
branches:
- 'master'

jobs:
ci:
Expand Down Expand Up @@ -40,10 +44,8 @@ jobs:
run: npm run cover

- name: Coveralls
uses: coverallsapp/github-action@1.1.3
uses: coverallsapp/github-action@v2
if: ${{ matrix.version == 'current' }}
with:
github-token: ${{ secrets.github_token }}

# Cancel running workflows for the same branch when a new one is started.
concurrency:
Expand Down
6 changes: 6 additions & 0 deletions .typesyncrc.yaml
Expand Up @@ -3,3 +3,9 @@ ignorePackages:
- nodemon
- prettier
- rimraf
- eslint
# Can't upgrade these libraries yet since they require ESM.
- chalk
- detect-indent
- ora
- eslint-config-prettier
9 changes: 8 additions & 1 deletion CHANGELOG.md
@@ -1,6 +1,13 @@
# v0.12.0

- **[BREAKING CHANGE]** [#86](https://github.com/jeffijoe/typesync/issues/86): Use the DefinitelyTyped strategy for resolving typings versions. This also means we no longer use the existing semver range specifier used in `package.json`.
- **[BREAKING CHANGE]** Bump minimum supported Node version to 16.
- The success message after running `typesync` now indicates when `--dry` is used.
- Upgrade packages.

# v0.11.1

- [#79](https://github.com/jeffijoe/typesync/issues/79): Ignore deprecated `@typings/` packages.
- [#79](https://github.com/jeffijoe/typesync/issues/79): Ignore deprecated `@typings/` packages.
- Upgrade packages.

# v0.11.0
Expand Down
6 changes: 4 additions & 2 deletions README.md
Expand Up @@ -120,7 +120,9 @@ TypeSync will add typings for packages that:

TypeSync will try to respect semver parity for the code and typings packages, and will fall back to the latest available typings package.

If you use a Semver `^` or `~` for a package, the same prefix will be used for the typings package. If you pin to an exact version (`"some-package": "1.2.3"`), no prefix will be written.
When writing the typings package version to `package.json`, the `~` semver range is used. This is because typings published via [DefinitelyTyped](https://github.com/DefinitelyTyped/DefinitelyTyped#how-do-definitely-typed-package-versions-relate-to-versions-of-the-corresponding-library) align typings versions with library versions using major and minor only.

For example, if you depend on `react@^16.14.0`, then TypeSync will only look for typings packages that match `16.14.*`.

# Monorepos

Expand All @@ -138,4 +140,4 @@ See [CHANGELOG.md](/CHANGELOG.md)

Jeff Hansen - [@Jeffijoe](https://twitter.com/jeffijoe)

[cosmiconfig]: https://github.com/davidtheclark/cosmiconfig
[cosmiconfig]: https://github.com/davidtheclark/cosmiconfig
32 changes: 16 additions & 16 deletions src/__tests__/type-syncer.test.ts
Expand Up @@ -226,14 +226,14 @@ describe('type syncer', () => {
packageService.writePackageFile as jest.Mock<any>
).mock.calls.find((c) => c[0] === 'package.json')[1] as IPackageFile
expect(writtenPackage.devDependencies).toEqual({
'@types/package1': '^1.0.0',
'@types/package3': '^1.0.0',
'@types/package1': '~1.0.0',
'@types/package3': '~1.0.0',
'@types/package4': '^1.0.0',
'@types/package5': '^1.0.0',
'@types/myorg__package7': '^1.0.0',
'@types/package5': '~1.0.0',
'@types/myorg__package7': '~1.0.0',
'@types/package8': '~1.0.0',
'@types/package9': '1.0.0',
'@types/packageWithOldTypings': '^2.0.0',
'@types/package9': '~1.0.0',
'@types/packageWithOldTypings': '~2.0.0',
package4: '^1.0.0',
package5: '^1.0.0',
})
Expand Down Expand Up @@ -276,16 +276,16 @@ describe('type syncer', () => {
(c) => c[0] === 'package-ignore-dev.json',
)[1] as IPackageFile
expect(writtenPackage.devDependencies).toEqual({
'@types/package1': '^1.0.0',
'@types/package3': '^1.0.0',
'@types/package1': '~1.0.0',
'@types/package3': '~1.0.0',
// Package 4's typings were already in the root package's `devDependencies`,
// but package 5's were not, that's why we still write package4's typings but not
// package 5's.
'@types/package4': '^1.0.0',
'@types/myorg__package7': '^1.0.0',
'@types/myorg__package7': '~1.0.0',
'@types/package8': '~1.0.0',
'@types/package9': '1.0.0',
'@types/packageWithOldTypings': '^2.0.0',
'@types/package9': '~1.0.0',
'@types/packageWithOldTypings': '~2.0.0',
package4: '^1.0.0',
package5: '^1.0.0',
})
Expand All @@ -300,13 +300,13 @@ describe('type syncer', () => {
(c) => c[0] === 'package-ignore-package1.json',
)[1] as IPackageFile
expect(writtenPackage.devDependencies).toEqual({
'@types/package3': '^1.0.0',
'@types/package3': '~1.0.0',
'@types/package4': '^1.0.0',
'@types/package5': '^1.0.0',
'@types/myorg__package7': '^1.0.0',
'@types/package5': '~1.0.0',
'@types/myorg__package7': '~1.0.0',
'@types/package8': '~1.0.0',
'@types/package9': '1.0.0',
'@types/packageWithOldTypings': '^2.0.0',
'@types/package9': '~1.0.0',
'@types/packageWithOldTypings': '~2.0.0',
package4: '^1.0.0',
package5: '^1.0.0',
})
Expand Down
80 changes: 80 additions & 0 deletions src/__tests__/versioning.test.ts
@@ -0,0 +1,80 @@
import { IPackageVersionInfo } from '../types'
import { getClosestMatchingVersion } from '../versioning'

describe('getClosestMatchingVersion', () => {
it('returns the closest matching version', () => {
const inputVersions: IPackageVersionInfo[] = [
{
containsInternalTypings: false,
version: '1.16.0',
},
{
containsInternalTypings: false,
version: '1.15.2',
},
{
containsInternalTypings: false,
version: '1.15.1',
},
{
containsInternalTypings: false,
version: '1.15.0',
},
{
containsInternalTypings: false,
version: '1.14.2',
},
{
containsInternalTypings: false,
version: '1.14.1',
},
{
containsInternalTypings: false,
version: '1.14.0',
},
{
containsInternalTypings: false,
version: '1.13.0',
},
]

expect(getClosestMatchingVersion(inputVersions, '^1.17.0').version).toBe(
'1.16.0',
)
expect(getClosestMatchingVersion(inputVersions, '^1.16.1').version).toBe(
'1.16.0',
)
expect(getClosestMatchingVersion(inputVersions, '^1.16.0').version).toBe(
'1.16.0',
)
expect(getClosestMatchingVersion(inputVersions, '^1.15.4').version).toBe(
'1.15.2',
)
expect(getClosestMatchingVersion(inputVersions, '^1.15.3').version).toBe(
'1.15.2',
)
expect(getClosestMatchingVersion(inputVersions, '^1.15.2').version).toBe(
'1.15.2',
)
expect(getClosestMatchingVersion(inputVersions, '^1.15.1').version).toBe(
'1.15.2',
)
expect(getClosestMatchingVersion(inputVersions, '^1.14.0').version).toBe(
'1.14.2',
)
})

it('throws when unable to parse version', () => {
expect(() =>
getClosestMatchingVersion(
[
{
containsInternalTypings: false,
version: 'not a version',
},
],
'also not a version',
),
).toThrow()
})
})
6 changes: 4 additions & 2 deletions src/cli.ts
Expand Up @@ -72,8 +72,10 @@ async function run(syncer: ITypeSyncer) {
}
C.success(
totals.newTypings === 0
? `No new typings added, looks like you're all synced up!`
: chalk`${totals.newTypings.toString()} new typings added.\n\n${syncedFilesOutput}\n\n✨ Go ahead and run {green npm install} or {green yarn} to install the packages that were added.`,
? `No new typings to add, looks like you're all synced up!`
: flags.dry
? chalk`${totals.newTypings.toString()} new typings can be added.\n\n${syncedFilesOutput}\n\n✨ Run {green typesync} again without the {gray --dry} flag to update your {gray package.json}.`
: chalk`${totals.newTypings.toString()} new typings added.\n\n${syncedFilesOutput}\n\n✨ Go ahead and run {green npm install} or {green yarn} to install the packages that were added.`,
)
}

Expand Down
54 changes: 9 additions & 45 deletions src/type-syncer.ts
Expand Up @@ -9,7 +9,6 @@ import {
ISyncResult,
ISyncedFile,
IPackageSource,
IPackageInfo,
IConfigService,
IDependencySection,
ICLIArguments,
Expand All @@ -25,7 +24,7 @@ import {
ensureWorkspacesArray,
} from './util'
import { IGlobber } from './globber'
import { satisfies } from 'semver'
import { getClosestMatchingVersion } from './versioning'

/**
* Creates a type syncer.
Expand Down Expand Up @@ -95,14 +94,14 @@ export function createTypeSyncer(

const packageFile =
file || (await packageJSONService.readPackageFile(filePath))
const allPackages = flatten(
const allLocalPackages = flatten(
Object.values(IDependencySection).map((dep) => {
const section = getDependenciesBySection(packageFile, dep)
const ignoredSection = ignoreDeps?.includes(dep)
return getPackagesFromSection(section, ignoredSection, ignorePackages)
}),
)
const allPackageNames = uniq(allPackages.map((p) => p.name))
const allPackageNames = uniq(allLocalPackages.map((p) => p.name))
const potentiallyUntypedPackages =
getPotentiallyUntypedPackages(allPackageNames)
// This is pushed to in the inner `map`, because packages that have DT-typings
Expand All @@ -119,14 +118,14 @@ export function createTypeSyncer(
return {}
}

const codePackage = allPackages.find(
const localCodePackage = allLocalPackages.find(
(p) => p.name === t.codePackageName,
)!

// Find the closest matching code package version relative to what's in our package.json
const closestMatchingCodeVersion = getClosestMatchingVersion(
codePackageInfo,
codePackage.version,
codePackageInfo.versions,
localCodePackage.version,
)

// If the closest matching version contains internal typings, don't include it.
Expand All @@ -144,15 +143,12 @@ export function createTypeSyncer(

// Gets the closest matching typings version, or the newest one.
const closestMatchingTypingsVersion = getClosestMatchingVersion(
typePackageInfo,
codePackage.version,
typePackageInfo.versions,
localCodePackage.version,
)

const version = closestMatchingTypingsVersion.version
const semverRangeSpecifier = getSemverRangeSpecifier(
codePackage.version,
)

const semverRangeSpecifier = '~'
used.push(t)
return {
[t.typesPackageName]: semverRangeSpecifier + version,
Expand All @@ -178,19 +174,6 @@ export function createTypeSyncer(
}
}

/**
* Gets the closest matching package version info.
*
* @param packageInfo
* @param version
*/
function getClosestMatchingVersion(packageInfo: IPackageInfo, version: string) {
return (
packageInfo.versions.find((v) => satisfies(v.version, version)) ||
packageInfo.versions[0]
)
}

/**
* Returns an array of packages that do not have a `@types/` package.
*
Expand Down Expand Up @@ -305,22 +288,3 @@ function getDependenciesBySection(
})()
return dependenciesSection ?? {}
}

const CARET = '^'.charCodeAt(0)
const TILDE = '~'.charCodeAt(0)

/**
* Gets the semver range specifier (~, ^)
* @param version
*/
function getSemverRangeSpecifier(version: string): string {
if (version.charCodeAt(0) === CARET) {
return '^'
}

if (version.charCodeAt(0) === TILDE) {
return '~'
}

return ''
}
54 changes: 54 additions & 0 deletions src/versioning.ts
@@ -0,0 +1,54 @@
import { IPackageVersionInfo } from './types'
import { parse } from 'semver'

/**
* Gets the closest matching package version info.
*
* @param availableVersions
* @param version
*/
export function getClosestMatchingVersion(
availableVersions: IPackageVersionInfo[],
version: string,
) {
const parsedVersion = parseOrThrow(version)

return (
availableVersions.find((v) => {
const parsedAvailableVersion = parseOrThrow(v.version)
if (parsedVersion.major !== parsedAvailableVersion.major) {
return false
}

if (parsedVersion.minor !== parsedAvailableVersion.minor) {
return false
}

return true
}) || availableVersions[0]
)
}

/**
* Parses the version or throws an error.
*
* @param version
* @returns
*/
function parseOrThrow(version: string) {
const parsed = parse(cleanVersion(version))
if (!parsed) {
throw new Error(`Could not parse version '${version}'`)
}

return parsed
}

/**
* Cleans the version of any semver range specifiers.
* @param version
* @returns
*/
function cleanVersion(version: string) {
return version.replace(/^[\^~=\s]/, '')
}

0 comments on commit a9b94ec

Please sign in to comment.