Skip to content
This repository has been archived by the owner on Aug 11, 2022. It is now read-only.

Commit

Permalink
ci: add new npm ci installer
Browse files Browse the repository at this point in the history
  • Loading branch information
zkat authored and iarna committed Feb 20, 2018
1 parent 26cd648 commit 5e4de9c
Show file tree
Hide file tree
Showing 7 changed files with 409 additions and 33 deletions.
60 changes: 60 additions & 0 deletions doc/cli/npm-ci.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
npm-ci(1) -- Install a project with a clean slate
===================================

## SYNOPSIS

npm ci

## EXAMPLE

Make sure you have a package-lock and an up-to-date install:

```
$ cd ./my/npm/project
$ npm install
added 154 packages in 10s
$ ls | grep package-lock
```

Run `npm ci` in that project

```
$ npm ci
added 154 packages in 5s
```

Configure Travis to build using `npm ci` instead of `npm install`:

```
# .travis.yml
install:
- npm ci
# keep the npm cache around to speed up installs
cache:
directories:
- "$HOME/.npm"
```

## DESCRIPTION

This command is similar to `npm-install(1)`, except it's meant to be used in
automated environments such as test platforms, continuous integration, and
deployment. It can be significantly faster than a regular npm install by
skipping certain user-oriented features. It is also more strict than a regular
install, which can help catch errors or inconsistencies caused by the
incrementally-installed local environments of most npm users.

Concretely, the main differences between an `npm install` call and an `npm ci`
call are:

* The resulting `node_modules/` is not meant for interactive development.
* The project **must** have an existing `package-lock.json` or `npm-shrinkwrap.json`.
* If dependencies in the package lock do not match those in `package.json`, `npm ci` will exit with an error, instead of updating the package lock.
* `npm ci` can only install entire projects at a time: individual dependencies cannot be added with this command.
* If a `node_modules` is already present, it will be automatically removed before `npm ci` begins its install.
* It will never write to `package.json` or any of the package-locks: installs are essentially frozen.

## SEE ALSO

* npm-install(1)
* npm-package-locks(5)
37 changes: 37 additions & 0 deletions lib/ci.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
'use strict'

const Installer = require('libcipm')
const lifecycleOpts = require('./config/lifecycle.js')
const npm = require('./npm.js')
const npmlog = require('npmlog')
const pacoteOpts = require('./config/pacote.js')

ci.usage = 'npm ci'

ci.completion = (cb) => cb(null, [])

Installer.CipmConfig.impl(npm.config, {
get: npm.config.get,
set: npm.config.set,
toLifecycle (moreOpts) {
return lifecycleOpts(moreOpts)
},
toPacote (moreOpts) {
return pacoteOpts(moreOpts)
}
})

module.exports = ci
function ci (args, cb) {
return new Installer({
config: npm.config,
log: npmlog
})
.run()
.then(
(details) => console.error(`added ${details.pkgCount} packages in ${
details.runTime / 1000
}s`)
)
.then(() => cb(), cb)
}
2 changes: 2 additions & 0 deletions lib/config/cmd-list.js
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ var affordances = {
'la': 'ls',
'll': 'ls',
'verison': 'version',
'ic': 'ci',
'isntall': 'install',
'dist-tags': 'dist-tag',
'apihelp': 'help',
Expand All @@ -46,6 +47,7 @@ var affordances = {

// these are filenames in .
var cmdList = [
'ci',
'install',
'install-test',
'uninstall',
Expand Down
4 changes: 2 additions & 2 deletions lib/install/action/extract-worker.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ module.exports = (args, cb) => {
const spec = parsed[0]
const extractTo = parsed[1]
const opts = parsed[2]
if (!opts.log && opts.loglevel) {
if (!opts.log) {
opts.log = npmlog
opts.log.level = opts.loglevel
}
opts.log.level = opts.loglevel || opts.log.level
BB.resolve(extract(spec, extractTo, opts)).nodeify(cb)
}
7 changes: 3 additions & 4 deletions lib/install/action/extract.js
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,11 @@ function extract (staging, pkg, log) {
pacoteOpts = require('../../config/pacote')
}
const opts = pacoteOpts({
integrity: pkg.package._integrity
integrity: pkg.package._integrity,
resolved: pkg.package._resolved
})
const args = [
pkg.package._resolved
? npa.resolve(pkg.package.name, pkg.package._resolved)
: pkg.package._requested,
pkg.package._requested,
extractTo,
opts
]
Expand Down
28 changes: 1 addition & 27 deletions lib/install/inflate-shrinkwrap.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const realizeShrinkwrapSpecifier = require('./realize-shrinkwrap-specifier.js')
const validate = require('aproba')
const path = require('path')
const isRegistry = require('../utils/is-registry.js')
const url = require('url')
const hasModernMeta = require('./has-modern-meta.js')

module.exports = function (tree, sw, opts, finishInflating) {
Expand Down Expand Up @@ -108,7 +107,7 @@ function makeFakeChild (name, topPath, tree, sw, requested) {
name: name,
version: sw.version,
_id: name + '@' + sw.version,
_resolved: adaptResolved(requested, sw.resolved),
_resolved: sw.resolved,
_requested: requested,
_optional: sw.optional,
_development: sw.dev,
Expand Down Expand Up @@ -146,31 +145,6 @@ function makeFakeChild (name, topPath, tree, sw, requested) {
return child
}

function isFile (href) {
try {
return url.parse(href).protocol === 'file:'
} catch (_) {
return false
}
}

function adaptResolved (requested, resolved) {
const registry = requested.scope
? npm.config.get(`${requested.scope}:registry`) || npm.config.get('registry')
: npm.config.get('registry')
if (!isRegistry(requested) || (resolved && resolved.indexOf(registry) === 0) || isFile(resolved)) {
// Nothing to worry about here. Pass it through.
return resolved
} else {
// We could fast-path for registry.npmjs.org here, but if we do, it
// would end up getting written back to the `resolved` field. By always
// returning `null` for other registries, `pacote.extract()` will take
// care of any required metadata fetches internally, without altering
// the tree we're going to write out to shrinkwrap/lockfile.
return null
}
}

function fetchChild (topPath, tree, sw, requested) {
return fetchPackageMetadata(requested, topPath).then((pkg) => {
pkg._from = sw.from || requested.raw
Expand Down
Loading

2 comments on commit 5e4de9c

@branpar
Copy link

@branpar branpar commented on 5e4de9c Mar 6, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will npm ci have any conflict if I already have a ci script in my package.json file?

@prashantpalikhe
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You run your ci script with npm run ci isntead of npm ci. So should be no conflict.

Please sign in to comment.