Skip to content

Commit 3cab70a

Browse files
committed
Add support for deno (#1267)
1 parent 97f24f8 commit 3cab70a

17 files changed

+148
-75
lines changed

README.md

+3-2
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,9 @@ ncu "/^(?!react-).*$/" # windows
201201
--packageData <value> Package file data (you can also use stdin).
202202
--packageFile <path|glob> Package file(s) location. (default:
203203
./package.json)
204-
-p, --packageManager <s> npm, yarn, pnpm, staticRegistry (default: npm).
205-
Run "ncu --help --packageManager" for details.
204+
-p, --packageManager <s> npm, yarn, pnpm, deno, staticRegistry (default:
205+
npm). Run "ncu --help --packageManager" for
206+
details.
206207
--peer Check peer dependencies of installed packages and
207208
filter updates to compatible versions. Run "ncu
208209
--help --peer" for details.

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@
3737
"prepare": "husky install",
3838
"prepublishOnly": "npm run build",
3939
"prettier": "prettier .",
40-
"test": "mocha test test/package-managers/npm test/package-managers/yarn test/package-managers/staticRegistry",
40+
"test": "mocha test test/package-managers/*",
4141
"test:cov": "npm run c8 npm run test",
4242
"ncu": "node build/src/bin/cli.js"
4343
},

src/cli-options.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -517,9 +517,9 @@ const cliOptions: CLIOption[] = [
517517
long: 'packageManager',
518518
short: 'p',
519519
arg: 's',
520-
description: 'npm, yarn, pnpm, staticRegistry (default: npm).',
520+
description: 'npm, yarn, pnpm, deno, staticRegistry (default: npm).',
521521
help: extendedHelpPackageManager,
522-
type: `'npm' | 'yarn' | 'pnpm' | 'staticRegistry'`,
522+
type: `'npm' | 'yarn' | 'pnpm' | 'deno' | 'staticRegistry'`,
523523
},
524524
{
525525
long: 'peer',

src/lib/determinePackageManager.ts

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ const packageManagerLockfileMap: Index<PackageManagerName> = {
99
'package-lock': 'npm',
1010
yarn: 'yarn',
1111
'pnpm-lock': 'pnpm',
12+
deno: 'deno',
1213
}
1314

1415
/**

src/lib/findLockfile.ts

+2
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@ export default async function findLockfile(
2929
return { directoryPath: currentPath, filename: 'yarn.lock' }
3030
} else if (files.includes('pnpm-lock.yaml')) {
3131
return { directoryPath: currentPath, filename: 'pnpm-lock.yaml' }
32+
} else if (files.includes('deno.json')) {
33+
return { directoryPath: currentPath, filename: 'deno.json' }
3234
}
3335

3436
const pathParent = path.resolve(currentPath, '..')

src/lib/findPackage.ts

+3-1
Original file line numberDiff line numberDiff line change
@@ -42,7 +42,9 @@ async function findPackage(options: Options) {
4242
)
4343
}
4444

45-
return fs.readFile(pkgFile!, 'utf-8')
45+
return fs.readFile(pkgFile!, 'utf-8').catch(e => {
46+
programError(options, chalk.red(e))
47+
})
4648
}
4749

4850
print(options, 'Running in local mode', 'verbose')

src/lib/getCurrentDependencies.ts

+2-15
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { VersionSpec } from '../types/VersionSpec'
66
import filterAndReject from './filterAndReject'
77
import filterObject from './filterObject'
88
import { keyValueBy } from './keyValueBy'
9+
import resolveDepSections from './resolveDepSections'
910

1011
/** Returns true if spec1 is greater than spec2, ignoring invalid version ranges. */
1112
const isGreaterThanSafe = (spec1: VersionSpec, spec2: VersionSpec) =>
@@ -32,21 +33,7 @@ const parsePackageManager = (pkgData: PackageFile) => {
3233
* @returns Promised {packageName: version} collection
3334
*/
3435
function getCurrentDependencies(pkgData: PackageFile = {}, options: Options = {}) {
35-
const depOptions = options.dep
36-
? typeof options.dep === 'string'
37-
? options.dep.split(',')
38-
: options.dep
39-
: ['prod', 'dev', 'optional']
40-
41-
// map the dependency section option to a full dependency section name
42-
const depSections = depOptions.map(
43-
short =>
44-
(short === 'prod'
45-
? 'dependencies'
46-
: short === 'packageManager'
47-
? short
48-
: short + 'Dependencies') as keyof PackageFile,
49-
)
36+
const depSections = resolveDepSections(options.dep)
5037

5138
// get all dependencies from the selected sections
5239
// if a dependency appears in more than one section, take the lowest version number

src/lib/getPackageManager.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ import { PackageManager } from '../types/PackageManager'
1212
*/
1313
function getPackageManager(name: Maybe<string>): PackageManager {
1414
// default to npm
15-
if (!name) {
15+
if (!name || name === 'deno') {
1616
return packageManagers.npm
1717
}
1818

src/lib/initOptions.ts

+13-7
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import isEqual from 'lodash/isEqual'
22
import propertyOf from 'lodash/propertyOf'
3-
import cliOptions from '../cli-options'
3+
import cliOptions, { cliOptionsMap } from '../cli-options'
44
import { print } from '../lib/logging'
55
import { FilterPattern } from '../types/FilterPattern'
66
import { Options } from '../types/Options'
@@ -156,14 +156,14 @@ async function initOptions(runOptions: RunOptions, { cli }: { cli?: boolean } =
156156

157157
const packageManager = await determinePackageManager(options)
158158

159-
// print 'Using yarn/pnpm/etc' when autodetected
160-
if (!options.packageManager && packageManager !== 'npm') {
161-
print(options, `Using ${packageManager}`)
162-
}
163-
164159
const resolvedOptions: Options = {
165160
...options,
166-
...(options.deep ? { packageFile: '**/package.json' } : null),
161+
...(options.deep
162+
? { packageFile: '**/package.json' }
163+
: packageManager === 'deno' && !options.packageFile
164+
? { packageFile: 'deno.json' }
165+
: null),
166+
...(packageManager === 'deno' && options.dep !== cliOptionsMap.dep.default ? { dep: ['imports'] } : null),
167167
...(options.format && options.format.length > 0 ? { format: options.format } : null),
168168
filter: args || filter,
169169
// add shortcut for any keys that start with 'json'
@@ -180,6 +180,12 @@ async function initOptions(runOptions: RunOptions, { cli }: { cli?: boolean } =
180180
}
181181
resolvedOptions.cacher = await cacher(resolvedOptions)
182182

183+
// print 'Using yarn/pnpm/etc' when autodetected
184+
// use resolved options so that options.json is set
185+
if (!options.packageManager && packageManager !== 'npm') {
186+
print(resolvedOptions, `Using ${packageManager}`)
187+
}
188+
183189
return resolvedOptions
184190
}
185191

src/lib/resolveDepSections.ts

+24
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
import { cliOptionsMap } from '../cli-options'
2+
import { Index } from '../types/IndexType'
3+
import { PackageFile } from '../types/PackageFile'
4+
5+
// dependency section aliases that will be resolved to the full name
6+
const depAliases: Index<keyof PackageFile> = {
7+
dev: 'devDependencies',
8+
peer: 'peerDependencies',
9+
prod: 'dependencies',
10+
optional: 'optionalDependencies',
11+
}
12+
13+
/** Gets a list of dependency sections based on options.dep. */
14+
const resolveDepSections = (dep?: string | string[]): (keyof PackageFile)[] => {
15+
// parse dep string and set default
16+
const depOptions: string[] = dep ? (typeof dep === 'string' ? dep.split(',') : dep) : cliOptionsMap.dep.default
17+
18+
// map the dependency section option to a full dependency section name
19+
const depSections = depOptions.map(name => depAliases[name] || name)
20+
21+
return depSections
22+
}
23+
24+
export default resolveDepSections

src/lib/runLocal.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import getPeerDependencies from './getPeerDependencies'
2020
import keyValueBy from './keyValueBy'
2121
import { print, printIgnoredUpdates, printJson, printUpgrades, toDependencyTable } from './logging'
2222
import programError from './programError'
23+
import resolveDepSections from './resolveDepSections'
2324
import upgradePackageData from './upgradePackageData'
2425
import upgradePackageDefinitions from './upgradePackageDefinitions'
2526
import { getDependencyGroups } from './version-util'
@@ -255,7 +256,7 @@ async function runLocal(
255256
const output = options.jsonAll
256257
? (jph.parse(newPkgData) as PackageFile)
257258
: options.jsonDeps
258-
? pick(jph.parse(newPkgData) as PackageFile, 'dependencies', 'devDependencies', 'optionalDependencies')
259+
? pick(jph.parse(newPkgData) as PackageFile, resolveDepSections(options.dep))
259260
: chosenUpgraded
260261

261262
// will be overwritten with the result of fs.writeFile so that the return promise waits for the package file to be written

src/lib/upgradePackageData.ts

+3-16
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import { Index } from '../types/IndexType'
22
import { Options } from '../types/Options'
33
import { PackageFile } from '../types/PackageFile'
44
import { VersionSpec } from '../types/VersionSpec'
5+
import resolveDepSections from './resolveDepSections'
56

67
/**
78
* @returns String safe for use in `new RegExp()`
@@ -25,21 +26,7 @@ async function upgradePackageData(
2526
upgraded: Index<VersionSpec>,
2627
options: Options,
2728
) {
28-
const depOptions = options.dep
29-
? typeof options.dep === 'string'
30-
? options.dep.split(',')
31-
: options.dep
32-
: ['prod', 'dev', 'optional']
33-
34-
// map the dependency section option to a full dependency section name
35-
const depSections = depOptions.map(
36-
short =>
37-
(short === 'prod'
38-
? 'dependencies'
39-
: short === 'packageManager'
40-
? short
41-
: short + 'Dependencies') as keyof PackageFile,
42-
)
29+
const depSections = resolveDepSections(options.dep)
4330

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

57-
if (depOptions.includes('packageManager')) {
44+
if (depSections.includes('packageManager')) {
5845
const pkg = JSON.parse(pkgData) as PackageFile
5946
if (pkg.packageManager) {
6047
const [name] = pkg.packageManager.split('@')

src/types/PackageFile.ts

+2
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,8 @@ import { VersionSpec } from './VersionSpec'
66
export interface PackageFile {
77
dependencies?: Index<VersionSpec>
88
devDependencies?: Index<VersionSpec>
9+
// deno only
10+
imports?: Index<VersionSpec>
911
engines?: Index<VersionSpec>
1012
name?: string
1113
// https://nodejs.org/api/packages.html#packagemanager

src/types/PackageManagerName.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,2 @@
11
/** A valid package manager. */
2-
export type PackageManagerName = 'npm' | 'yarn' | 'pnpm' | 'staticRegistry'
2+
export type PackageManagerName = 'npm' | 'yarn' | 'pnpm' | 'deno' | 'staticRegistry'

src/types/RunOptions.ts

+2-2
Original file line numberDiff line numberDiff line change
@@ -99,8 +99,8 @@ export interface RunOptions {
9999
/** Package file(s) location. (default: ./package.json) */
100100
packageFile?: string
101101

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

105105
/** Check peer dependencies of installed packages and filter updates to compatible versions. Run "ncu --help --peer" for details. */
106106
peer?: boolean
+60
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
import chai from 'chai'
2+
import chaiAsPromised from 'chai-as-promised'
3+
import chaiString from 'chai-string'
4+
import fs from 'fs/promises'
5+
import jph from 'json-parse-helpfulerror'
6+
import os from 'os'
7+
import path from 'path'
8+
import spawn from 'spawn-please'
9+
10+
chai.should()
11+
chai.use(chaiAsPromised)
12+
chai.use(chaiString)
13+
14+
process.env.NCU_TESTS = 'true'
15+
16+
const bin = path.join(__dirname, '../../../build/src/bin/cli.js')
17+
18+
describe('deno', async function () {
19+
it('handle import map', async () => {
20+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))
21+
const pkgFile = path.join(tempDir, 'deno.json')
22+
const pkg = {
23+
imports: {
24+
'ncu-test-v2': 'npm:ncu-test-v2@1.0.0',
25+
},
26+
}
27+
await fs.writeFile(pkgFile, JSON.stringify(pkg), 'utf-8')
28+
try {
29+
const pkgData = await spawn(
30+
'node',
31+
[bin, '--jsonUpgraded', '--verbose', '--packageManager', 'deno', '--packageFile', pkgFile],
32+
undefined,
33+
)
34+
const pkg = jph.parse(pkgData)
35+
pkg.should.have.property('ncu-test-v2')
36+
} finally {
37+
await fs.rm(tempDir, { recursive: true, force: true })
38+
}
39+
})
40+
41+
it('auto detect deno.json', async () => {
42+
const tempDir = await fs.mkdtemp(path.join(os.tmpdir(), 'npm-check-updates-'))
43+
const pkgFile = path.join(tempDir, 'deno.json')
44+
const pkg = {
45+
imports: {
46+
'ncu-test-v2': 'npm:ncu-test-v2@1.0.0',
47+
},
48+
}
49+
await fs.writeFile(pkgFile, JSON.stringify(pkg), 'utf-8')
50+
try {
51+
const pkgData = await spawn('node', [bin, '--jsonUpgraded', '--verbose'], undefined, {
52+
cwd: tempDir,
53+
})
54+
const pkg = jph.parse(pkgData)
55+
pkg.should.have.property('ncu-test-v2')
56+
} finally {
57+
await fs.rm(tempDir, { recursive: true, force: true })
58+
}
59+
})
60+
})

test/package-managers/yarn/index.test.ts

+26-26
Original file line numberDiff line numberDiff line change
@@ -94,35 +94,35 @@ describe('yarn', function () {
9494
should.equal(authToken, null)
9595
})
9696
})
97-
})
9897

99-
describe('getPathToLookForLocalYarnrc', () => {
100-
it('returns the correct path when using Yarn workspaces', async () => {
101-
/** Mock for filesystem calls. */
102-
function readdirMock(path: string): Promise<string[]> {
103-
switch (path) {
104-
case '/home/test-repo/packages/package-a':
105-
case 'C:\\home\\test-repo\\packages\\package-a':
106-
return Promise.resolve(['index.ts'])
107-
case '/home/test-repo/packages':
108-
case 'C:\\home\\test-repo\\packages':
109-
return Promise.resolve([])
110-
case '/home/test-repo':
111-
case 'C:\\home\\test-repo':
112-
return Promise.resolve(['yarn.lock'])
98+
describe('getPathToLookForLocalYarnrc', () => {
99+
it('returns the correct path when using Yarn workspaces', async () => {
100+
/** Mock for filesystem calls. */
101+
function readdirMock(path: string): Promise<string[]> {
102+
switch (path) {
103+
case '/home/test-repo/packages/package-a':
104+
case 'C:\\home\\test-repo\\packages\\package-a':
105+
return Promise.resolve(['index.ts'])
106+
case '/home/test-repo/packages':
107+
case 'C:\\home\\test-repo\\packages':
108+
return Promise.resolve([])
109+
case '/home/test-repo':
110+
case 'C:\\home\\test-repo':
111+
return Promise.resolve(['yarn.lock'])
112+
}
113+
114+
throw new Error(`Mock cannot handle path: ${path}.`)
113115
}
114116

115-
throw new Error(`Mock cannot handle path: ${path}.`)
116-
}
117-
118-
const yarnrcPath = await getPathToLookForYarnrc(
119-
{
120-
cwd: isWindows ? 'C:\\home\\test-repo\\packages\\package-a' : '/home/test-repo/packages/package-a',
121-
},
122-
readdirMock,
123-
)
117+
const yarnrcPath = await getPathToLookForYarnrc(
118+
{
119+
cwd: isWindows ? 'C:\\home\\test-repo\\packages\\package-a' : '/home/test-repo/packages/package-a',
120+
},
121+
readdirMock,
122+
)
124123

125-
should.exist(yarnrcPath)
126-
yarnrcPath!.should.equal(isWindows ? 'C:\\home\\test-repo\\.yarnrc.yml' : '/home/test-repo/.yarnrc.yml')
124+
should.exist(yarnrcPath)
125+
yarnrcPath!.should.equal(isWindows ? 'C:\\home\\test-repo\\.yarnrc.yml' : '/home/test-repo/.yarnrc.yml')
126+
})
127127
})
128128
})

0 commit comments

Comments
 (0)