Skip to content

Commit

Permalink
Add support for deno (#1267)
Browse files Browse the repository at this point in the history
  • Loading branch information
raineorshine committed Feb 9, 2023
1 parent 97f24f8 commit 3cab70a
Show file tree
Hide file tree
Showing 17 changed files with 148 additions and 75 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,8 +201,9 @@ ncu "/^(?!react-).*$/" # windows
--packageData <value> Package file data (you can also use stdin).
--packageFile <path|glob> Package file(s) location. (default:
./package.json)
-p, --packageManager <s> npm, yarn, pnpm, staticRegistry (default: npm).
Run "ncu --help --packageManager" for details.
-p, --packageManager <s> npm, yarn, pnpm, deno, staticRegistry (default:
npm). Run "ncu --help --packageManager" for
details.
--peer Check peer dependencies of installed packages and
filter updates to compatible versions. Run "ncu
--help --peer" for details.
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@
"prepare": "husky install",
"prepublishOnly": "npm run build",
"prettier": "prettier .",
"test": "mocha test test/package-managers/npm test/package-managers/yarn test/package-managers/staticRegistry",
"test": "mocha test test/package-managers/*",
"test:cov": "npm run c8 npm run test",
"ncu": "node build/src/bin/cli.js"
},
Expand Down
4 changes: 2 additions & 2 deletions src/cli-options.ts
Original file line number Diff line number Diff line change
Expand Up @@ -517,9 +517,9 @@ const cliOptions: CLIOption[] = [
long: 'packageManager',
short: 'p',
arg: 's',
description: 'npm, yarn, pnpm, staticRegistry (default: npm).',
description: 'npm, yarn, pnpm, deno, staticRegistry (default: npm).',
help: extendedHelpPackageManager,
type: `'npm' | 'yarn' | 'pnpm' | 'staticRegistry'`,
type: `'npm' | 'yarn' | 'pnpm' | 'deno' | 'staticRegistry'`,
},
{
long: 'peer',
Expand Down
1 change: 1 addition & 0 deletions src/lib/determinePackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ const packageManagerLockfileMap: Index<PackageManagerName> = {
'package-lock': 'npm',
yarn: 'yarn',
'pnpm-lock': 'pnpm',
deno: 'deno',
}

/**
Expand Down
2 changes: 2 additions & 0 deletions src/lib/findLockfile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ export default async function findLockfile(
return { directoryPath: currentPath, filename: 'yarn.lock' }
} else if (files.includes('pnpm-lock.yaml')) {
return { directoryPath: currentPath, filename: 'pnpm-lock.yaml' }
} else if (files.includes('deno.json')) {
return { directoryPath: currentPath, filename: 'deno.json' }
}

const pathParent = path.resolve(currentPath, '..')
Expand Down
4 changes: 3 additions & 1 deletion src/lib/findPackage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,9 @@ async function findPackage(options: Options) {
)
}

return fs.readFile(pkgFile!, 'utf-8')
return fs.readFile(pkgFile!, 'utf-8').catch(e => {
programError(options, chalk.red(e))
})
}

print(options, 'Running in local mode', 'verbose')
Expand Down
17 changes: 2 additions & 15 deletions src/lib/getCurrentDependencies.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { VersionSpec } from '../types/VersionSpec'
import filterAndReject from './filterAndReject'
import filterObject from './filterObject'
import { keyValueBy } from './keyValueBy'
import resolveDepSections from './resolveDepSections'

/** Returns true if spec1 is greater than spec2, ignoring invalid version ranges. */
const isGreaterThanSafe = (spec1: VersionSpec, spec2: VersionSpec) =>
Expand All @@ -32,21 +33,7 @@ const parsePackageManager = (pkgData: PackageFile) => {
* @returns Promised {packageName: version} collection
*/
function getCurrentDependencies(pkgData: PackageFile = {}, options: Options = {}) {
const depOptions = options.dep
? typeof options.dep === 'string'
? options.dep.split(',')
: options.dep
: ['prod', 'dev', 'optional']

// map the dependency section option to a full dependency section name
const depSections = depOptions.map(
short =>
(short === 'prod'
? 'dependencies'
: short === 'packageManager'
? short
: short + 'Dependencies') as keyof PackageFile,
)
const depSections = resolveDepSections(options.dep)

// get all dependencies from the selected sections
// if a dependency appears in more than one section, take the lowest version number
Expand Down
2 changes: 1 addition & 1 deletion src/lib/getPackageManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import { PackageManager } from '../types/PackageManager'
*/
function getPackageManager(name: Maybe<string>): PackageManager {
// default to npm
if (!name) {
if (!name || name === 'deno') {
return packageManagers.npm
}

Expand Down
20 changes: 13 additions & 7 deletions src/lib/initOptions.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import isEqual from 'lodash/isEqual'
import propertyOf from 'lodash/propertyOf'
import cliOptions from '../cli-options'
import cliOptions, { cliOptionsMap } from '../cli-options'
import { print } from '../lib/logging'
import { FilterPattern } from '../types/FilterPattern'
import { Options } from '../types/Options'
Expand Down Expand Up @@ -156,14 +156,14 @@ async function initOptions(runOptions: RunOptions, { cli }: { cli?: boolean } =

const packageManager = await determinePackageManager(options)

// print 'Using yarn/pnpm/etc' when autodetected
if (!options.packageManager && packageManager !== 'npm') {
print(options, `Using ${packageManager}`)
}

const resolvedOptions: Options = {
...options,
...(options.deep ? { packageFile: '**/package.json' } : null),
...(options.deep
? { packageFile: '**/package.json' }
: packageManager === 'deno' && !options.packageFile
? { packageFile: 'deno.json' }
: null),
...(packageManager === 'deno' && options.dep !== cliOptionsMap.dep.default ? { dep: ['imports'] } : null),
...(options.format && options.format.length > 0 ? { format: options.format } : null),
filter: args || filter,
// add shortcut for any keys that start with 'json'
Expand All @@ -180,6 +180,12 @@ async function initOptions(runOptions: RunOptions, { cli }: { cli?: boolean } =
}
resolvedOptions.cacher = await cacher(resolvedOptions)

// print 'Using yarn/pnpm/etc' when autodetected
// use resolved options so that options.json is set
if (!options.packageManager && packageManager !== 'npm') {
print(resolvedOptions, `Using ${packageManager}`)
}

return resolvedOptions
}

Expand Down
24 changes: 24 additions & 0 deletions src/lib/resolveDepSections.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { cliOptionsMap } from '../cli-options'
import { Index } from '../types/IndexType'
import { PackageFile } from '../types/PackageFile'

// dependency section aliases that will be resolved to the full name
const depAliases: Index<keyof PackageFile> = {
dev: 'devDependencies',
peer: 'peerDependencies',
prod: 'dependencies',
optional: 'optionalDependencies',
}

/** Gets a list of dependency sections based on options.dep. */
const resolveDepSections = (dep?: string | string[]): (keyof PackageFile)[] => {
// parse dep string and set default
const depOptions: string[] = dep ? (typeof dep === 'string' ? dep.split(',') : dep) : cliOptionsMap.dep.default

// map the dependency section option to a full dependency section name
const depSections = depOptions.map(name => depAliases[name] || name)

return depSections
}

export default resolveDepSections
3 changes: 2 additions & 1 deletion src/lib/runLocal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import getPeerDependencies from './getPeerDependencies'
import keyValueBy from './keyValueBy'
import { print, printIgnoredUpdates, printJson, printUpgrades, toDependencyTable } from './logging'
import programError from './programError'
import resolveDepSections from './resolveDepSections'
import upgradePackageData from './upgradePackageData'
import upgradePackageDefinitions from './upgradePackageDefinitions'
import { getDependencyGroups } from './version-util'
Expand Down Expand Up @@ -255,7 +256,7 @@ async function runLocal(
const output = options.jsonAll
? (jph.parse(newPkgData) as PackageFile)
: options.jsonDeps
? pick(jph.parse(newPkgData) as PackageFile, 'dependencies', 'devDependencies', 'optionalDependencies')
? pick(jph.parse(newPkgData) as PackageFile, resolveDepSections(options.dep))
: chosenUpgraded

// will be overwritten with the result of fs.writeFile so that the return promise waits for the package file to be written
Expand Down
19 changes: 3 additions & 16 deletions src/lib/upgradePackageData.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { Index } from '../types/IndexType'
import { Options } from '../types/Options'
import { PackageFile } from '../types/PackageFile'
import { VersionSpec } from '../types/VersionSpec'
import resolveDepSections from './resolveDepSections'

/**
* @returns String safe for use in `new RegExp()`
Expand All @@ -25,21 +26,7 @@ async function upgradePackageData(
upgraded: Index<VersionSpec>,
options: Options,
) {
const depOptions = options.dep
? typeof options.dep === 'string'
? options.dep.split(',')
: options.dep
: ['prod', 'dev', 'optional']

// map the dependency section option to a full dependency section name
const depSections = depOptions.map(
short =>
(short === 'prod'
? 'dependencies'
: short === 'packageManager'
? short
: short + 'Dependencies') as keyof PackageFile,
)
const depSections = resolveDepSections(options.dep)

// iterate through each dependency section
const sectionRegExp = new RegExp(`"(${depSections.join(`|`)})"s*:[^}]*`, 'g')
Expand All @@ -54,7 +41,7 @@ async function upgradePackageData(
return section
})

if (depOptions.includes('packageManager')) {
if (depSections.includes('packageManager')) {
const pkg = JSON.parse(pkgData) as PackageFile
if (pkg.packageManager) {
const [name] = pkg.packageManager.split('@')
Expand Down
2 changes: 2 additions & 0 deletions src/types/PackageFile.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import { VersionSpec } from './VersionSpec'
export interface PackageFile {
dependencies?: Index<VersionSpec>
devDependencies?: Index<VersionSpec>
// deno only
imports?: Index<VersionSpec>
engines?: Index<VersionSpec>
name?: string
// https://nodejs.org/api/packages.html#packagemanager
Expand Down
2 changes: 1 addition & 1 deletion src/types/PackageManagerName.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
/** A valid package manager. */
export type PackageManagerName = 'npm' | 'yarn' | 'pnpm' | 'staticRegistry'
export type PackageManagerName = 'npm' | 'yarn' | 'pnpm' | 'deno' | 'staticRegistry'
4 changes: 2 additions & 2 deletions src/types/RunOptions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -99,8 +99,8 @@ export interface RunOptions {
/** Package file(s) location. (default: ./package.json) */
packageFile?: string

/** npm, yarn, pnpm, staticRegistry (default: npm). Run "ncu --help --packageManager" for details. */
packageManager?: 'npm' | 'yarn' | 'pnpm' | 'staticRegistry'
/** npm, yarn, pnpm, deno, staticRegistry (default: npm). Run "ncu --help --packageManager" for details. */
packageManager?: 'npm' | 'yarn' | 'pnpm' | 'deno' | 'staticRegistry'

/** Check peer dependencies of installed packages and filter updates to compatible versions. Run "ncu --help --peer" for details. */
peer?: boolean
Expand Down
60 changes: 60 additions & 0 deletions test/package-managers/deno/index.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import chai from 'chai'
import chaiAsPromised from 'chai-as-promised'
import chaiString from 'chai-string'
import fs from 'fs/promises'
import jph from 'json-parse-helpfulerror'
import os from 'os'
import path from 'path'
import spawn from 'spawn-please'

chai.should()
chai.use(chaiAsPromised)
chai.use(chaiString)

process.env.NCU_TESTS = 'true'

const bin = path.join(__dirname, '../../../build/src/bin/cli.js')

describe('deno', async function () {
it('handle import map', async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))
const pkgFile = path.join(tempDir, 'deno.json')
const pkg = {
imports: {
'ncu-test-v2': 'npm:[email protected]',
},
}
await fs.writeFile(pkgFile, JSON.stringify(pkg), 'utf-8')
try {
const pkgData = await spawn(
'node',
[bin, '--jsonUpgraded', '--verbose', '--packageManager', 'deno', '--packageFile', pkgFile],
undefined,
)
const pkg = jph.parse(pkgData)
pkg.should.have.property('ncu-test-v2')
} finally {
await fs.rm(tempDir, { recursive: true, force: true })
}
})

it('auto detect deno.json', async () => {
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))
const pkgFile = path.join(tempDir, 'deno.json')
const pkg = {
imports: {
'ncu-test-v2': 'npm:[email protected]',
},
}
await fs.writeFile(pkgFile, JSON.stringify(pkg), 'utf-8')
try {
const pkgData = await spawn('node', [bin, '--jsonUpgraded', '--verbose'], undefined, {
cwd: tempDir,
})
const pkg = jph.parse(pkgData)
pkg.should.have.property('ncu-test-v2')
} finally {
await fs.rm(tempDir, { recursive: true, force: true })
}
})
})
52 changes: 26 additions & 26 deletions test/package-managers/yarn/index.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -94,35 +94,35 @@ describe('yarn', function () {
should.equal(authToken, null)
})
})
})

describe('getPathToLookForLocalYarnrc', () => {
it('returns the correct path when using Yarn workspaces', async () => {
/** Mock for filesystem calls. */
function readdirMock(path: string): Promise<string[]> {
switch (path) {
case '/home/test-repo/packages/package-a':
case 'C:\\home\\test-repo\\packages\\package-a':
return Promise.resolve(['index.ts'])
case '/home/test-repo/packages':
case 'C:\\home\\test-repo\\packages':
return Promise.resolve([])
case '/home/test-repo':
case 'C:\\home\\test-repo':
return Promise.resolve(['yarn.lock'])
describe('getPathToLookForLocalYarnrc', () => {
it('returns the correct path when using Yarn workspaces', async () => {
/** Mock for filesystem calls. */
function readdirMock(path: string): Promise<string[]> {
switch (path) {
case '/home/test-repo/packages/package-a':
case 'C:\\home\\test-repo\\packages\\package-a':
return Promise.resolve(['index.ts'])
case '/home/test-repo/packages':
case 'C:\\home\\test-repo\\packages':
return Promise.resolve([])
case '/home/test-repo':
case 'C:\\home\\test-repo':
return Promise.resolve(['yarn.lock'])
}

throw new Error(`Mock cannot handle path: ${path}.`)
}

throw new Error(`Mock cannot handle path: ${path}.`)
}

const yarnrcPath = await getPathToLookForYarnrc(
{
cwd: isWindows ? 'C:\\home\\test-repo\\packages\\package-a' : '/home/test-repo/packages/package-a',
},
readdirMock,
)
const yarnrcPath = await getPathToLookForYarnrc(
{
cwd: isWindows ? 'C:\\home\\test-repo\\packages\\package-a' : '/home/test-repo/packages/package-a',
},
readdirMock,
)

should.exist(yarnrcPath)
yarnrcPath!.should.equal(isWindows ? 'C:\\home\\test-repo\\.yarnrc.yml' : '/home/test-repo/.yarnrc.yml')
should.exist(yarnrcPath)
yarnrcPath!.should.equal(isWindows ? 'C:\\home\\test-repo\\.yarnrc.yml' : '/home/test-repo/.yarnrc.yml')
})
})
})

0 comments on commit 3cab70a

Please sign in to comment.