diff --git a/lib/manager/meteor/index.js b/lib/manager/meteor/index.js
index 6c097c1df03abbb334dc8a064d9a0edf16c3fd85..5a9388513ac93fd5f50d434e666eb023dc9d7b4e 100644
--- a/lib/manager/meteor/index.js
+++ b/lib/manager/meteor/index.js
@@ -1,5 +1,4 @@
 const { extractDependencies } = require('./extract');
-const { getPackageUpdates } = require('../npm/package');
 const { updateDependency } = require('./update');
 
 const contentPattern = new RegExp('(^|\\n)\\s*Npm.depends\\(\\s*{');
@@ -7,6 +6,5 @@ const contentPattern = new RegExp('(^|\\n)\\s*Npm.depends\\(\\s*{');
 module.exports = {
   contentPattern,
   extractDependencies,
-  getPackageUpdates,
   updateDependency,
 };
diff --git a/lib/manager/npm/index.js b/lib/manager/npm/index.js
index 930494df38652a742980e7a7b477080cdf61e763..7e2a63ab1926e516feda4e27b55b2f701207f7a9 100644
--- a/lib/manager/npm/index.js
+++ b/lib/manager/npm/index.js
@@ -1,11 +1,9 @@
 const { extractDependencies, postExtract } = require('./extract');
-const { getPackageUpdates } = require('./package');
 const { updateDependency } = require('./update');
 
 module.exports = {
   extractDependencies,
   postExtract,
-  getPackageUpdates,
   updateDependency,
   supportsLockFileMaintenance: true,
 };
diff --git a/lib/manager/npm/package.js b/lib/manager/npm/package.js
deleted file mode 100644
index b3dd27416a26ce84211384d41b7d5212f4ec73a0..0000000000000000000000000000000000000000
--- a/lib/manager/npm/package.js
+++ /dev/null
@@ -1,59 +0,0 @@
-const npmApi = require('../../datasource/npm');
-const lookup = require('./lookup');
-const { isValid } = require('../../versioning/semver');
-const nodeManager = require('../_helpers/node/package');
-
-module.exports = {
-  getPackageUpdates,
-};
-
-async function getPackageUpdates(config) {
-  logger.trace({ config }, `npm.getPackageUpdates()`);
-  const { depType, depName, currentVersion } = config;
-  if (depType === 'engines') {
-    if (depName !== 'node') {
-      logger.debug('Skipping non-node engine');
-      return [];
-    }
-    return nodeManager.getPackageUpdates(config);
-  }
-  let results = [];
-  if (currentVersion === '*') {
-    logger.debug(
-      { dependency: depName, currentVersion },
-      'Skipping * dependency'
-    );
-    return [];
-  }
-  if (currentVersion.startsWith('file:')) {
-    logger.debug(
-      { dependency: depName, currentVersion },
-      'Skipping file: dependency'
-    );
-    return [];
-  }
-  if (!isValid(currentVersion)) {
-    results.push({
-      depName,
-      type: 'warning',
-      message: `Dependency uses tag "\`${currentVersion}\`" as its version so that will never be changed by Renovate`,
-    });
-    logger.debug(results[0].message);
-    return results;
-  }
-  npmApi.setNpmrc(
-    config.npmrc,
-    config.global ? config.global.exposeEnv : false
-  );
-  results = await lookup.lookupUpdates(config);
-  // istanbul ignore if
-  if (results.length > 0 && results[0].type !== 'warning') {
-    logger.info(
-      { dependency: depName },
-      `${results.length} result(s): ${results.map(
-        upgrade => upgrade.newVersion
-      )}`
-    );
-  }
-  return results;
-}
diff --git a/lib/workers/repository/process/fetch.js b/lib/workers/repository/process/fetch.js
index a057a15323d04a79a0077057db6942e47f5fe2a7..df6b6aa77d8d314559712406d9620a6224527fd0 100644
--- a/lib/workers/repository/process/fetch.js
+++ b/lib/workers/repository/process/fetch.js
@@ -4,6 +4,7 @@ const { getPackageUpdates } = require('../../../manager');
 const { mergeChildConfig } = require('../../../config');
 const { applyPackageRules } = require('../../../util/package-rules');
 const { getManagerConfig } = require('../../../config');
+const { lookupUpdates } = require('./lookup');
 
 module.exports = {
   fetchUpdates,
@@ -35,7 +36,12 @@ async function fetchDepUpdates(packageFileConfig, dep) {
     logger.debug({ depName: dep.depName }, 'Dependency is disabled');
     dep.skipReason = 'disabled';
   } else {
-    const lookupResults = await getPackageUpdates(manager, depConfig);
+    let lookupResults;
+    if (depConfig.purl) {
+      lookupResults = await lookupUpdates(depConfig);
+    } else {
+      lookupResults = await getPackageUpdates(manager, depConfig);
+    }
     // istanbul ignore else
     if (Array.isArray(lookupResults)) {
       dep.updates = lookupResults;
diff --git a/lib/manager/npm/lookup/filter.js b/lib/workers/repository/process/lookup/filter.js
similarity index 97%
rename from lib/manager/npm/lookup/filter.js
rename to lib/workers/repository/process/lookup/filter.js
index e07095a0d80bb5dbde176d0292457227783acfde..09b7442fac25797c46b6bc2e0807ae05acdd69b8 100644
--- a/lib/manager/npm/lookup/filter.js
+++ b/lib/workers/repository/process/lookup/filter.js
@@ -4,7 +4,7 @@ const {
   isStable,
   isValid,
   matches,
-} = require('../../../versioning/semver');
+} = require('../../../../versioning/semver');
 
 module.exports = {
   filterVersions,
diff --git a/lib/manager/npm/lookup/index.js b/lib/workers/repository/process/lookup/index.js
similarity index 83%
rename from lib/manager/npm/lookup/index.js
rename to lib/workers/repository/process/lookup/index.js
index 2a7ec42b153ed2d3c76172b4c6411b8ced89a410..cbd57b7b4dd3a7f37df0aa1f20579e5c2175b5d7 100644
--- a/lib/manager/npm/lookup/index.js
+++ b/lib/workers/repository/process/lookup/index.js
@@ -1,9 +1,10 @@
-const versioning = require('../../../versioning/semver');
+const versioning = require('../../../../versioning/semver');
 const moment = require('moment');
 const { getRollbackUpdate } = require('./rollback');
 const { getRangeStrategy } = require('./range');
 const { filterVersions } = require('./filter');
-const npmApi = require('../../../datasource/npm');
+const npmApi = require('../../../../datasource/npm');
+const github = require('../../../../datasource/github');
 
 const {
   getMajor,
@@ -21,8 +22,27 @@ module.exports = {
 };
 
 async function lookupUpdates(config) {
-  const { depName, currentVersion, lockedVersion } = config;
-  const dependency = await npmApi.getDependency(depName);
+  const { depName, currentVersion, lockedVersion, purl } = config;
+  logger.debug({ depName, currentVersion }, 'lookupUpdates');
+  let dependency;
+  if (!purl) {
+    logger.error('Missing purl');
+    return [];
+  }
+  if (purl.startsWith('pkg:npm/')) {
+    npmApi.setNpmrc(
+      config.npmrc,
+      config.global ? config.global.exposeEnv : false
+    );
+    const npmPackage = purl.substring('pkg:npm/'.length).replace('%40', '@');
+    dependency = await npmApi.getDependency(npmPackage);
+  } else if (purl.startsWith('pkg:github/')) {
+    const repo = purl.substring('pkg:github/'.length);
+    dependency = await github.getDependency(repo);
+  } else {
+    logger.warn({ config }, 'Unknown purl');
+    return [];
+  }
   if (!dependency) {
     // If dependency lookup fails then warn and return
     const result = {
diff --git a/lib/manager/npm/lookup/range.js b/lib/workers/repository/process/lookup/range.js
similarity index 100%
rename from lib/manager/npm/lookup/range.js
rename to lib/workers/repository/process/lookup/range.js
diff --git a/lib/manager/npm/lookup/rollback.js b/lib/workers/repository/process/lookup/rollback.js
similarity index 96%
rename from lib/manager/npm/lookup/rollback.js
rename to lib/workers/repository/process/lookup/rollback.js
index 921207541bee3762e7f5acfceeb9166b28e4b053..4ea24eed66b2256d64940139ff40e1247ab6194e 100644
--- a/lib/manager/npm/lookup/rollback.js
+++ b/lib/workers/repository/process/lookup/rollback.js
@@ -3,7 +3,7 @@ const {
   isLessThan,
   rangify,
   sortVersions,
-} = require('../../../versioning/semver');
+} = require('../../../../versioning/semver');
 
 module.exports = {
   getRollbackUpdate,
diff --git a/test/manager/npm/__snapshots__/package.spec.js.snap b/test/manager/npm/__snapshots__/package.spec.js.snap
deleted file mode 100644
index 0fd4ff8644a7f183d50cd7c82e14d77e6564d474..0000000000000000000000000000000000000000
--- a/test/manager/npm/__snapshots__/package.spec.js.snap
+++ /dev/null
@@ -1,20 +0,0 @@
-// Jest Snapshot v1, https://goo.gl/fbAQLP
-
-exports[`lib/manager/npm/package getPackageUpdates returns warning if no npm dep found 1`] = `
-Array [
-  Object {
-    "message": "Failed to look up dependency some-dep",
-    "type": "warning",
-  },
-]
-`;
-
-exports[`lib/manager/npm/package getPackageUpdates returns warning if using invalid version 1`] = `
-Array [
-  Object {
-    "depName": "some-dep",
-    "message": "Dependency uses tag \\"\`git+ssh://git@github.com/joefraley/eslint-config-meridian.git\`\\" as its version so that will never be changed by Renovate",
-    "type": "warning",
-  },
-]
-`;
diff --git a/test/manager/npm/package.spec.js b/test/manager/npm/package.spec.js
deleted file mode 100644
index 9773cb5eab740e3ae0f1ccfbd719b1fe6aa07a31..0000000000000000000000000000000000000000
--- a/test/manager/npm/package.spec.js
+++ /dev/null
@@ -1,68 +0,0 @@
-const npmApi = require('../../../lib/datasource/npm');
-const lookup = require('../../../lib/manager/npm/lookup');
-const npm = require('../../../lib/manager/npm/package');
-const defaultConfig = require('../../../lib/config/defaults').getConfig();
-
-jest.mock('../../../lib/datasource/npm');
-jest.mock('../../../lib/manager/_helpers/node/package');
-npmApi.getDependency = jest.fn();
-
-describe('lib/manager/npm/package', () => {
-  describe('getPackageUpdates', () => {
-    let config;
-    beforeEach(() => {
-      jest.resetAllMocks();
-      config = {
-        ...defaultConfig,
-        depName: 'some-dep',
-        currentVersion: '1.0.0',
-      };
-    });
-    it('skips non-node engines', async () => {
-      config.depType = 'engines';
-      const res = await npm.getPackageUpdates(config);
-      expect(res).toHaveLength(0);
-    });
-    it('calls node for node engines', async () => {
-      config.depType = 'engines';
-      config.depName = 'node';
-      config.currentVersion = '8.9.0';
-      const res = await npm.getPackageUpdates(config);
-      expect(res).toBeUndefined();
-    });
-    it('returns if using a * reference', async () => {
-      config.currentVersion = '*';
-      const res = await npm.getPackageUpdates(config);
-      expect(res).toHaveLength(0);
-    });
-    it('returns if using a file reference', async () => {
-      config.currentVersion = 'file:../sibling/package.json';
-      const res = await npm.getPackageUpdates(config);
-      expect(res).toHaveLength(0);
-    });
-    it('returns warning if using invalid version', async () => {
-      config.currentVersion =
-        'git+ssh://git@github.com/joefraley/eslint-config-meridian.git';
-      const res = await npm.getPackageUpdates(config);
-      expect(res).toMatchSnapshot();
-    });
-    it('returns warning if no npm dep found', async () => {
-      const res = await npm.getPackageUpdates(config);
-      expect(res).toMatchSnapshot();
-      expect(res).toHaveLength(1);
-      expect(res[0].type).toEqual('warning');
-      expect(npmApi.getDependency.mock.calls.length).toBe(1);
-    });
-    it('returns warning if warning found', async () => {
-      npmApi.getDependency.mockReturnValueOnce({});
-      lookup.lookupUpdates = jest.fn(() => [
-        {
-          type: 'warning',
-          message: 'bad version',
-        },
-      ]);
-      const res = await npm.getPackageUpdates(config);
-      expect(res[0].type).toEqual('warning');
-    });
-  });
-});
diff --git a/test/workers/repository/process/__snapshots__/fetch.spec.js.snap b/test/workers/repository/process/__snapshots__/fetch.spec.js.snap
index 7d35587cef6670bf856b30fb65655159994d369b..2d18b55afbc6b09317b6c3ba7002e9ab5262dd5f 100644
--- a/test/workers/repository/process/__snapshots__/fetch.spec.js.snap
+++ b/test/workers/repository/process/__snapshots__/fetch.spec.js.snap
@@ -8,6 +8,7 @@ Object {
         Object {
           "depName": "aaa",
           "depType": "devDependencies",
+          "purl": "pkg:npm/aaa",
           "updates": Array [
             "a",
             "b",
diff --git a/test/workers/repository/process/fetch.spec.js b/test/workers/repository/process/fetch.spec.js
index e1c6c7cf2379d6e4cb3867368d6bf79696fe3452..90839717f334a0bb4d54366aedb85ce2d69f5fc1 100644
--- a/test/workers/repository/process/fetch.spec.js
+++ b/test/workers/repository/process/fetch.spec.js
@@ -3,6 +3,9 @@ const {
 } = require('../../../../lib/workers/repository/process/fetch');
 
 const npm = require('../../../../lib/manager/npm');
+const lookup = require('../../../../lib/workers/repository/process/lookup');
+
+jest.mock('../../../../lib/workers/repository/process/lookup');
 
 describe('workers/repository/process/fetch', () => {
   describe('fetchUpdates()', () => {
@@ -59,12 +62,17 @@ describe('workers/repository/process/fetch', () => {
             packageFile: 'package.json',
             packageJsonType: 'app',
             deps: [
-              { depName: 'aaa', depType: 'devDependencies' },
+              {
+                depName: 'aaa',
+                depType: 'devDependencies',
+                purl: 'pkg:npm/aaa',
+              },
               { depName: 'bbb', depType: 'dependencies' },
             ],
           },
         ],
       };
+      lookup.lookupUpdates.mockReturnValue(['a', 'b']);
       npm.getPackageUpdates = jest.fn(() => ['a', 'b']);
       await fetchUpdates(config, packageFiles);
       expect(packageFiles).toMatchSnapshot();
diff --git a/test/manager/npm/__snapshots__/lookup.spec.js.snap b/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap
similarity index 98%
rename from test/manager/npm/__snapshots__/lookup.spec.js.snap
rename to test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap
index dcd381aee5ceaf543ee097e60e802e4190859388..47774973802afb5609a43183faa40609852b8a03 100644
--- a/test/manager/npm/__snapshots__/lookup.spec.js.snap
+++ b/test/workers/repository/process/lookup/__snapshots__/index.spec.js.snap
@@ -38,6 +38,15 @@ Array [
 ]
 `;
 
+exports[`manager/npm/lookup .lookupUpdates() handles github 404 1`] = `
+Array [
+  Object {
+    "message": "Failed to look up dependency foo",
+    "type": "warning",
+  },
+]
+`;
+
 exports[`manager/npm/lookup .lookupUpdates() handles prerelease jumps 1`] = `
 Array [
   Object {
@@ -54,6 +63,8 @@ Array [
 ]
 `;
 
+exports[`manager/npm/lookup .lookupUpdates() handles unknown purl 1`] = `Array []`;
+
 exports[`manager/npm/lookup .lookupUpdates() ignores pinning for ranges when other upgrade exists 1`] = `
 Array [
   Object {
@@ -95,21 +106,6 @@ exports[`manager/npm/lookup .lookupUpdates() rejects in-range unsupported operat
 
 exports[`manager/npm/lookup .lookupUpdates() rejects non-fully specified in-range updates 1`] = `Array []`;
 
-exports[`manager/npm/lookup .lookupUpdates() rejects non-range in-range updates 1`] = `
-Array [
-  Object {
-    "fromVersion": "1.0.0",
-    "newVersion": "1.4.1",
-    "newVersionMajor": 1,
-    "newVersionMinor": 4,
-    "repositoryUrl": "https://github.com/kriskowal/q",
-    "toVersion": "1.4.1",
-    "type": "minor",
-    "unpublishable": false,
-  },
-]
-`;
-
 exports[`manager/npm/lookup .lookupUpdates() rejects reverse ordered less than greater than 1`] = `Array []`;
 
 exports[`manager/npm/lookup .lookupUpdates() replaces major complex ranged versions if configured 1`] = `
@@ -144,6 +140,21 @@ Array [
 ]
 `;
 
+exports[`manager/npm/lookup .lookupUpdates() replaces non-range in-range updates 1`] = `
+Array [
+  Object {
+    "fromVersion": "1.0.0",
+    "newVersion": "1.4.1",
+    "newVersionMajor": 1,
+    "newVersionMinor": 4,
+    "repositoryUrl": "https://github.com/kriskowal/q",
+    "toVersion": "1.4.1",
+    "type": "minor",
+    "unpublishable": false,
+  },
+]
+`;
+
 exports[`manager/npm/lookup .lookupUpdates() returns both updates if automerging minor 1`] = `
 Array [
   Object {
diff --git a/test/manager/npm/lookup.spec.js b/test/workers/repository/process/lookup/index.spec.js
similarity index 87%
rename from test/manager/npm/lookup.spec.js
rename to test/workers/repository/process/lookup/index.spec.js
index 39fb48183dc9284a5235133ea8bc23b86e5601d5..11ecaee6379e772d7ea5351d65be83d54ea6d7a7 100644
--- a/test/manager/npm/lookup.spec.js
+++ b/test/workers/repository/process/lookup/index.spec.js
@@ -1,12 +1,12 @@
 const nock = require('nock');
-const lookup = require('../../../lib/manager/npm/lookup');
-const qJson = require('../../_fixtures/npm/01.json');
-const helmetJson = require('../../_fixtures/npm/02.json');
-const coffeelintJson = require('../../_fixtures/npm/coffeelint.json');
-const webpackJson = require('../../_fixtures/npm/webpack.json');
-const nextJson = require('../../_fixtures/npm/next.json');
-const vueJson = require('../../_fixtures/npm/vue.json');
-const typescriptJson = require('../../_fixtures/npm/typescript.json');
+const lookup = require('../../../../../lib/workers/repository/process/lookup');
+const qJson = require('../../../../_fixtures/npm/01.json');
+const helmetJson = require('../../../../_fixtures/npm/02.json');
+const coffeelintJson = require('../../../../_fixtures/npm/coffeelint.json');
+const webpackJson = require('../../../../_fixtures/npm/webpack.json');
+const nextJson = require('../../../../_fixtures/npm/next.json');
+const vueJson = require('../../../../_fixtures/npm/vue.json');
+const typescriptJson = require('../../../../_fixtures/npm/typescript.json');
 
 qJson.latestVersion = '1.4.1';
 
@@ -14,7 +14,7 @@ let config;
 
 describe('manager/npm/lookup', () => {
   beforeEach(() => {
-    config = { ...require('../../../lib/config/defaults').getConfig() };
+    config = { ...require('../../../../../lib/config/defaults').getConfig() };
     config.rangeStrategy = 'replace';
     jest.resetAllMocks();
   });
@@ -23,6 +23,7 @@ describe('manager/npm/lookup', () => {
     it('returns rollback for pinned version', async () => {
       config.currentVersion = '0.9.99';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -31,6 +32,7 @@ describe('manager/npm/lookup', () => {
     it('returns rollback for ranged version', async () => {
       config.currentVersion = '^0.9.99';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -40,6 +42,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '^0.4.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -50,6 +53,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '^0.4.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -60,6 +64,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '0.4.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -72,6 +77,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '^0.4.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -82,6 +88,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '^0.4.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -91,6 +98,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '0.4.0';
       config.allowedVersions = '<1';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -100,6 +108,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '0.4.0';
       config.allowedVersions = 'less than 1';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -109,6 +118,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '0.9.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -125,6 +135,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '0.9.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -142,6 +153,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '0.9.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -154,6 +166,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '0.9.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -164,6 +177,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '0.8.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -176,6 +190,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '^0.4.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -186,6 +201,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '1.0.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -195,6 +211,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '~0.4.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -204,6 +221,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '~0.9.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -213,6 +231,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '~1.0.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -222,6 +241,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '~1.3.0';
       config.rangeStrategy = 'widen';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -231,6 +251,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '~1.2.0 || ~1.3.0';
       config.rangeStrategy = 'replace';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -240,6 +261,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '^2.0.0';
       config.rangeStrategy = 'widen';
       config.depName = 'webpack';
+      config.purl = 'pkg:npm/webpack';
       nock('https://registry.npmjs.org')
         .get('/webpack')
         .reply(200, webpackJson);
@@ -249,6 +271,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '^1.0.0 || ^2.0.0';
       config.rangeStrategy = 'replace';
       config.depName = 'webpack';
+      config.purl = 'pkg:npm/webpack';
       nock('https://registry.npmjs.org')
         .get('/webpack')
         .reply(200, webpackJson);
@@ -258,6 +281,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '^1.0.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -268,6 +292,7 @@ describe('manager/npm/lookup', () => {
       config.lockedVersion = '1.0.0';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -277,6 +302,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '^1.0.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -286,6 +312,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'pin';
       config.currentVersion = '~1.3.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -295,6 +322,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '1.3.x';
       config.rangeStrategy = 'pin';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -304,6 +332,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '~1.3.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -313,6 +342,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '0.x';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -322,6 +352,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '1.3.x';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -331,6 +362,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '1.2.x - 1.3.x';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -340,6 +372,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -349,6 +382,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '1.3';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -358,6 +392,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '~0.7.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -367,6 +402,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '^0.7.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -376,6 +412,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '^0.7.0 || ^0.8.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -387,6 +424,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '^1.0.0 || ^2.0.0';
       config.depName = 'webpack';
+      config.purl = 'pkg:npm/webpack';
       nock('https://registry.npmjs.org')
         .get('/webpack')
         .reply(200, webpackJson);
@@ -396,6 +434,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '1.x - 2.x';
       config.depName = 'webpack';
+      config.purl = 'pkg:npm/webpack';
       nock('https://registry.npmjs.org')
         .get('/webpack')
         .reply(200, webpackJson);
@@ -405,6 +444,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '1.x || 2.x';
       config.depName = 'webpack';
+      config.purl = 'pkg:npm/webpack';
       nock('https://registry.npmjs.org')
         .get('/webpack')
         .reply(200, webpackJson);
@@ -414,6 +454,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '1 || 2';
       config.depName = 'webpack';
+      config.purl = 'pkg:npm/webpack';
       nock('https://registry.npmjs.org')
         .get('/webpack')
         .reply(200, webpackJson);
@@ -423,6 +464,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '~1.2.0 || ~1.3.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -432,6 +474,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '>= 0.7.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -441,6 +484,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '<= 0.7.2';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -450,6 +494,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '< 0.7.2';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -459,6 +504,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '< 1';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -468,6 +514,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '<= 1.3';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -478,6 +525,7 @@ describe('manager/npm/lookup', () => {
       config.respectLatest = false;
       config.currentVersion = '<= 1';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -487,6 +535,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '<= 1.0.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -498,6 +547,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '< 1.0.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -509,6 +559,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '>= 0.5.0 < 1.0.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -520,6 +571,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '>= 0.5.0 <0.8';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -532,6 +584,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '>= 0.5.0 <= 0.8.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -544,6 +597,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'widen';
       config.currentVersion = '<= 0.8.0 >= 0.5.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -554,6 +608,7 @@ describe('manager/npm/lookup', () => {
       config.respectLatest = false;
       config.currentVersion = '1.4.1';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -562,6 +617,7 @@ describe('manager/npm/lookup', () => {
     it('should ignore unstable versions if the current version is stable', async () => {
       config.currentVersion = '2.5.16';
       config.depName = 'vue';
+      config.purl = 'pkg:npm/vue';
       nock('https://registry.npmjs.org')
         .get('/vue')
         .reply(200, vueJson);
@@ -571,6 +627,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '2.5.16';
       config.ignoreUnstable = false;
       config.depName = 'vue';
+      config.purl = 'pkg:npm/vue';
       nock('https://registry.npmjs.org')
         .get('/vue')
         .reply(200, vueJson);
@@ -582,6 +639,7 @@ describe('manager/npm/lookup', () => {
     it('should allow unstable versions if the current version is unstable', async () => {
       config.currentVersion = '2.3.0-beta.1';
       config.depName = 'vue';
+      config.purl = 'pkg:npm/vue';
       nock('https://registry.npmjs.org')
         .get('/vue')
         .reply(200, vueJson);
@@ -594,6 +652,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '~0.0.34';
       config.depName = 'helmet';
+      config.purl = 'pkg:npm/helmet';
       nock('https://registry.npmjs.org')
         .get('/helmet')
         .reply(200, helmetJson);
@@ -603,6 +662,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'replace';
       config.currentVersion = '^0.0.34';
       config.depName = 'helmet';
+      config.purl = 'pkg:npm/helmet';
       nock('https://registry.npmjs.org')
         .get('/helmet')
         .reply(200, helmetJson);
@@ -611,6 +671,7 @@ describe('manager/npm/lookup', () => {
     it('should downgrade from missing versions', async () => {
       config.currentVersion = '1.16.1';
       config.depName = 'coffeelint';
+      config.purl = 'pkg:npm/coffeelint';
       nock('https://registry.npmjs.org')
         .get('/coffeelint')
         .reply(200, coffeelintJson);
@@ -621,6 +682,7 @@ describe('manager/npm/lookup', () => {
     it('should upgrade to only one major', async () => {
       config.currentVersion = '1.0.0';
       config.depName = 'webpack';
+      config.purl = 'pkg:npm/webpack';
       nock('https://registry.npmjs.org')
         .get('/webpack')
         .reply(200, webpackJson);
@@ -631,6 +693,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '1.0.0';
       config.separateMultipleMajor = true;
       config.depName = 'webpack';
+      config.purl = 'pkg:npm/webpack';
       nock('https://registry.npmjs.org')
         .get('/webpack')
         .reply(200, webpackJson);
@@ -651,6 +714,7 @@ describe('manager/npm/lookup', () => {
       config.currentVersion = '^2.9.0-rc';
       config.rangeStrategy = 'replace';
       config.depName = 'typescript';
+      config.purl = 'pkg:npm/typescript';
       nock('https://registry.npmjs.org')
         .get('/typescript')
         .reply(200, typescriptJson);
@@ -661,6 +725,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'bump';
       config.currentVersion = '^1.0.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -670,6 +735,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'bump';
       config.currentVersion = '~1.0.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -679,6 +745,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'bump';
       config.currentVersion = '>=1.0.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -688,6 +755,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'bump';
       config.currentVersion = '>1.0.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -697,6 +765,7 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'bump';
       config.currentVersion = '1.x';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
@@ -706,21 +775,39 @@ describe('manager/npm/lookup', () => {
       config.rangeStrategy = 'bump';
       config.currentVersion = '^0.9.0 || ^1.0.0';
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
       expect(await lookup.lookupUpdates(config)).toMatchSnapshot();
     });
-    it('rejects non-range in-range updates', async () => {
+    it('replaces non-range in-range updates', async () => {
       config.depName = 'q';
+      config.purl = 'pkg:npm/q';
       config.packageFile = 'package.json';
       config.rangeStrategy = 'bump';
       config.currentVersion = '1.0.0';
-      config.depName = 'q';
       nock('https://registry.npmjs.org')
         .get('/q')
         .reply(200, qJson);
       expect(await lookup.lookupUpdates(config)).toMatchSnapshot();
     });
+    it('handles github 404', async () => {
+      config.depName = 'foo';
+      config.purl = 'pkg:github/some/repo';
+      config.packageFile = 'package.json';
+      config.currentVersion = '1.0.0';
+      nock('https://api.github.com')
+        .get('/repos/some/repo/git/refs/tags?per_page=100')
+        .reply(404);
+      expect(await lookup.lookupUpdates(config)).toMatchSnapshot();
+    });
+    it('handles unknown purl', async () => {
+      config.depName = 'foo';
+      config.purl = 'pkg:typo/some/repo';
+      config.packageFile = 'package.json';
+      config.currentVersion = '1.0.0';
+      expect(await lookup.lookupUpdates(config)).toMatchSnapshot();
+    });
   });
 });
diff --git a/test/manager/npm/lookup/range.spec.js b/test/workers/repository/process/lookup/range.spec.js
similarity index 94%
rename from test/manager/npm/lookup/range.spec.js
rename to test/workers/repository/process/lookup/range.spec.js
index 3891f1778b7918142b4d67b84605402c6dfa7960..f7e3622095f0905c4c06cdc815c4c8c29b781793 100644
--- a/test/manager/npm/lookup/range.spec.js
+++ b/test/workers/repository/process/lookup/range.spec.js
@@ -1,6 +1,6 @@
 const {
   getRangeStrategy,
-} = require('../../../../lib/manager/npm/lookup/range');
+} = require('../../../../../lib/workers/repository/process/lookup/range');
 
 describe('getRangeStrategy', () => {
   it('returns same if not auto', () => {