npq 3.0.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseMarshall = require('./baseMarshall')
4
+ const { marshallCategories } = require('./constants')
4
5
 
5
6
  const MARSHALL_NAME = 'age'
6
7
  const PACKAGE_AGE_THRESHOLD = 22 // specified in days
@@ -9,6 +10,7 @@ class Marshall extends BaseMarshall {
9
10
  constructor(options) {
10
11
  super(options)
11
12
  this.name = MARSHALL_NAME
13
+ this.categoryId = marshallCategories.PackageHealth.id
12
14
  }
13
15
 
14
16
  title() {
@@ -2,7 +2,7 @@
2
2
 
3
3
  const BaseMarshall = require('./baseMarshall')
4
4
  const validator = require('validator')
5
- const Warning = require('../helpers/warning')
5
+ const { marshallCategories } = require('./constants')
6
6
 
7
7
  const MARSHALL_NAME = 'author'
8
8
 
@@ -10,38 +10,69 @@ class Marshall extends BaseMarshall {
10
10
  constructor(options) {
11
11
  super(options)
12
12
  this.name = MARSHALL_NAME
13
+ this.categoryId = marshallCategories.SupplyChainSecurity.id
13
14
  }
14
15
 
15
16
  title() {
16
17
  return 'Identifying package author...'
17
18
  }
18
19
 
19
- validate(pkg) {
20
- return this.packageRepoUtils.getPackageInfo(pkg.packageName).then((data) => {
21
- const lastVersionData =
22
- data.versions && data['dist-tags'] && data.versions[data['dist-tags'].latest]
23
-
24
- const hasAuthorEmail =
25
- (lastVersionData &&
26
- lastVersionData.author &&
27
- lastVersionData.author.email &&
28
- validator.isEmail(lastVersionData.author.email)) ||
29
- (data.versions &&
30
- lastVersionData &&
31
- lastVersionData.authors &&
32
- lastVersionData.authors.filter &&
33
- lastVersionData.authors.filter(
34
- (author) => author.email && validator.isEmail(author.email).length
35
- ))
36
-
37
- if (!hasAuthorEmail) {
38
- throw new Warning(
39
- 'the package description has no e-mail associated with author(s). Proceed with care.'
40
- )
20
+ /**
21
+ * What do we check?
22
+ * 1. Who is the user who published the package? in the pakument's `_npmUser` field
23
+ * 2. Is this the first time we see this user publishing a package?
24
+ * If so, halt and report.
25
+ * 3. Start from first version to latest version and check if they published
26
+ * the package before.
27
+ * If they did, and the difference between this published version date and
28
+ * first is more than 30 days then halt and report.
29
+ * @param {*} pkg
30
+ * @returns
31
+ */
32
+ async validate(pkg) {
33
+ const pakument = await this.packageRepoUtils.getPackageInfo(pkg.packageName)
34
+
35
+ // @TODO fix to work for both explicit versions (1.0.0) and also
36
+ // for dis-tags (latest)
37
+ const npmUser = pakument.versions[pkg.packageVersion]._npmUser
38
+ if (!npmUser || !npmUser.email) {
39
+ throw new Error('could not determine publishing user for this package version')
40
+ }
41
+
42
+ if (!validator.isEmail(npmUser.email)) {
43
+ throw new Error('the publishing user has no valid email address')
44
+ }
45
+
46
+ let firstVersionForUser = null
47
+ const versionPublishedDateString = pakument.time[pkg.packageVersion]
48
+ for (const [version, versionMetadata] of Object.entries(pakument.versions)) {
49
+ if (versionMetadata._npmUser && versionMetadata._npmUser.email === npmUser.email) {
50
+ firstVersionForUser = versionMetadata
51
+ break
41
52
  }
53
+ }
54
+
55
+ if (!firstVersionForUser || firstVersionForUser.version === pkg.packageVersion) {
56
+ throw new Error(
57
+ `The user ${npmUser.name} <${npmUser.email}> published this package for the first time only ${dateDiffInDays} days ago. Proceed with care.`
58
+ )
59
+ }
60
+
61
+ const firstPublishedDateString = pakument.time[firstVersionForUser.version]
62
+
63
+ const dateDiffInMs = new Date(versionPublishedDateString) - new Date(firstPublishedDateString)
64
+ let dateDiffInDays = 0
65
+ if (dateDiffInMs > 0) {
66
+ dateDiffInDays = Math.round(dateDiffInMs / (1000 * 60 * 60 * 24))
67
+ }
68
+
69
+ if (dateDiffInDays <= 30) {
70
+ throw new Error(
71
+ `The user ${npmUser.name} <${npmUser.email}> published this package for the first time only ${dateDiffInDays} days ago. Proceed with care.`
72
+ )
73
+ }
42
74
 
43
- return data
44
- })
75
+ return versionPublishedDateString
45
76
  }
46
77
  }
47
78
 
@@ -1,10 +1,12 @@
1
1
  'use strict'
2
2
 
3
3
  const Warning = require('../helpers/warning')
4
+ const { marshallCategories } = require('./constants')
4
5
 
5
6
  class BaseMarshall {
6
7
  constructor(options) {
7
8
  this.packageRepoUtils = options.packageRepoUtils
9
+ this.categoryId = marshallCategories.PackageHealth.id
8
10
  }
9
11
 
10
12
  init(ctx, task) {
@@ -0,0 +1,16 @@
1
+ const marshallCategories = {
2
+ SupplyChainSecurity: {
3
+ id: 'SupplyChainSecurity',
4
+ title: 'Supply Chain Security'
5
+ },
6
+ PackageHealth: {
7
+ id: 'PackageHealth',
8
+ title: 'Package Health'
9
+ },
10
+ MalwareDetection: {
11
+ id: 'MalwareDetection',
12
+ title: 'Malware Detection'
13
+ }
14
+ }
15
+
16
+ module.exports.marshallCategories = marshallCategories
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseMarshall = require('./baseMarshall')
4
+ const { marshallCategories } = require('./constants')
4
5
 
5
6
  const MARSHALL_NAME = 'downloads'
6
7
  const DOWNLOAD_COUNT_THRESHOLD = 20 // threshold per month
@@ -9,6 +10,7 @@ class Marshall extends BaseMarshall {
9
10
  constructor(options) {
10
11
  super(options)
11
12
  this.name = MARSHALL_NAME
13
+ this.categoryId = marshallCategories.MalwareDetection.id
12
14
  }
13
15
 
14
16
  title() {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseMarshall = require('./baseMarshall')
4
+ const { marshallCategories } = require('./constants')
4
5
  const dns = require('dns').promises
5
6
 
6
7
  const MARSHALL_NAME = 'maintainers_expired_emails'
@@ -9,6 +10,7 @@ class Marshall extends BaseMarshall {
9
10
  constructor(options) {
10
11
  super(options)
11
12
  this.name = MARSHALL_NAME
13
+ this.categoryId = marshallCategories.MalwareDetection.id
12
14
  }
13
15
 
14
16
  title() {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const { Listr } = require('listr2')
4
4
  const { glob } = require('glob')
5
+ const { marshallCategories } = require('./constants')
5
6
 
6
7
  class Marshalls {
7
8
  static async collectMarshalls() {
@@ -13,7 +14,7 @@ class Marshalls {
13
14
  return files
14
15
  }
15
16
 
16
- static buildMarshallTasks(marshalls, config) {
17
+ static async buildMarshallTasks(marshalls, config) {
17
18
  if (!marshalls || !Array.isArray(marshalls) || marshalls.length === 0) {
18
19
  return Promise.reject(new Error('unable to collect marshalls, or no marshalls found'))
19
20
  }
@@ -25,15 +26,42 @@ class Marshalls {
25
26
 
26
27
  return prev.concat({
27
28
  title: marshall.title(),
29
+ categoryId: marshall.categoryId,
28
30
  enabled: (ctx) => marshall.isEnabled(ctx),
29
- task: (ctx, task) => {
31
+ task: async (ctx, task) => {
30
32
  marshall.init(ctx, task)
31
- return marshall.run(ctx, task).then(() => marshall.handleMessages())
33
+ await marshall.run(ctx, task)
34
+ const messages = marshall.handleMessages()
35
+ return messages
32
36
  }
33
37
  })
34
38
  }, [])
35
39
 
36
- return new Listr(marshallTasks, { exitOnError: false, concurrent: true })
40
+ const allMarshallTasksByCategories = Object.keys(marshallCategories).map((categoryId) => {
41
+ const category = marshallCategories[categoryId]
42
+ const filteredMarshallTasks = marshallTasks.filter((marshall) => {
43
+ return marshall.categoryId === category.id
44
+ })
45
+
46
+ const marshallsInCategory = {
47
+ title: category.title,
48
+ enabled: true,
49
+ task: () => {
50
+ return new Listr(filteredMarshallTasks, {
51
+ exitOnError: false,
52
+ concurrent: true,
53
+ rendererOptions: { collapseSubtasks: false }
54
+ })
55
+ }
56
+ }
57
+ return marshallsInCategory
58
+ })
59
+
60
+ return new Listr(allMarshallTasksByCategories, {
61
+ exitOnError: false,
62
+ concurrent: true,
63
+ rendererOptions: { collapseSubtasks: false }
64
+ })
37
65
  }
38
66
 
39
67
  static tasks(options) {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseMarshall = require('./baseMarshall')
4
+ const { marshallCategories } = require('./constants')
4
5
 
5
6
  const MARSHALL_NAME = 'license'
6
7
 
@@ -8,6 +9,7 @@ class Marshall extends BaseMarshall {
8
9
  constructor(options) {
9
10
  super(options)
10
11
  this.name = MARSHALL_NAME
12
+ this.categoryId = marshallCategories.PackageHealth.id
11
13
  }
12
14
 
13
15
  title() {
@@ -3,6 +3,7 @@
3
3
  const BaseMarshall = require('./baseMarshall')
4
4
  const pacote = require('pacote')
5
5
  const Warning = require('../helpers/warning')
6
+ const { marshallCategories } = require('./constants')
6
7
 
7
8
  const MARSHALL_NAME = 'provenance'
8
9
 
@@ -10,6 +11,7 @@ class Marshall extends BaseMarshall {
10
11
  constructor(options) {
11
12
  super(options)
12
13
  this.name = MARSHALL_NAME
14
+ this.categoryId = marshallCategories.SupplyChainSecurity.id
13
15
  }
14
16
 
15
17
  title() {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseMarshall = require('./baseMarshall')
4
+ const { marshallCategories } = require('./constants')
4
5
 
5
6
  const MARSHALL_NAME = 'readme'
6
7
 
@@ -8,6 +9,7 @@ class Marshall extends BaseMarshall {
8
9
  constructor(options) {
9
10
  super(options)
10
11
  this.name = MARSHALL_NAME
12
+ this.categoryId = marshallCategories.PackageHealth.id
11
13
  }
12
14
 
13
15
  title() {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseMarshall = require('./baseMarshall')
4
+ const { marshallCategories } = require('./constants')
4
5
  const URL = require('url').URL
5
6
  const MARSHALL_NAME = 'repo'
6
7
 
@@ -8,6 +9,7 @@ class Marshall extends BaseMarshall {
8
9
  constructor(options) {
9
10
  super(options)
10
11
  this.name = MARSHALL_NAME
12
+ this.categoryId = marshallCategories.MalwareDetection.id
11
13
  }
12
14
 
13
15
  title() {
@@ -1,6 +1,7 @@
1
1
  'use strict'
2
2
 
3
3
  const BaseMarshall = require('./baseMarshall')
4
+ const { marshallCategories } = require('./constants')
4
5
 
5
6
  const MARSHALL_NAME = 'scripts'
6
7
 
@@ -8,6 +9,7 @@ class Marshall extends BaseMarshall {
8
9
  constructor(options) {
9
10
  super(options)
10
11
  this.name = MARSHALL_NAME
12
+ this.categoryId = marshallCategories.MalwareDetection.id
11
13
  }
12
14
 
13
15
  title() {
@@ -2,6 +2,7 @@
2
2
 
3
3
  const BaseMarshall = require('./baseMarshall')
4
4
  const pacote = require('pacote')
5
+ const { marshallCategories } = require('./constants')
5
6
 
6
7
  const MARSHALL_NAME = 'signatures'
7
8
 
@@ -9,6 +10,7 @@ class Marshall extends BaseMarshall {
9
10
  constructor(options) {
10
11
  super(options)
11
12
  this.name = MARSHALL_NAME
13
+ this.categoryId = marshallCategories.SupplyChainSecurity.id
12
14
  }
13
15
 
14
16
  title() {
@@ -4,6 +4,7 @@ const fs = require('fs')
4
4
  const os = require('os')
5
5
  const path = require('path')
6
6
  const BaseMarshall = require('./baseMarshall')
7
+ const { marshallCategories } = require('./constants')
7
8
 
8
9
  const MARSHALL_NAME = 'snyk'
9
10
  const SNYK_API_URL = 'https://snyk.io/api/v1/vuln/npm'
@@ -16,6 +17,7 @@ class Marshall extends BaseMarshall {
16
17
  constructor(options) {
17
18
  super(options)
18
19
  this.name = MARSHALL_NAME
20
+ this.categoryId = marshallCategories.SupplyChainSecurity.id
19
21
 
20
22
  this.snykApiToken = this.getSnykToken()
21
23
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "npq",
3
- "version": "3.0.2",
3
+ "version": "3.2.0",
4
4
  "description": "marshall your npm/npm package installs with high quality and class 🎖",
5
5
  "bin": {
6
6
  "npq": "./bin/npq.js",