Team Ai
Datasetpublic

codekingpro/portable-devtools

sourceHugging Faceupdated 5mo agoView on Hugging Face
1likes15kdownloads
normalize-data.js255 linesDownload Raw Back to lib
1// Originally normalize-package-data
2
3const { URL } = require('node:url')
4const hostedGitInfo = require('hosted-git-info')
5const validateLicense = require('./license.js')
6
7const typos = {
8  dependancies: 'dependencies',
9  dependecies: 'dependencies',
10  depdenencies: 'dependencies',
11  devEependencies: 'devDependencies',
12  depends: 'dependencies',
13  'dev-dependencies': 'devDependencies',
14  devDependences: 'devDependencies',
15  devDepenencies: 'devDependencies',
16  devdependencies: 'devDependencies',
17  repostitory: 'repository',
18  repo: 'repository',
19  prefereGlobal: 'preferGlobal',
20  hompage: 'homepage',
21  hampage: 'homepage',
22  autohr: 'author',
23  autor: 'author',
24  contributers: 'contributors',
25  publicationConfig: 'publishConfig',
26  script: 'scripts',
27}
28
29const isEmail = str => str.includes('@') && (str.indexOf('@') < str.lastIndexOf('.'))
30
31// Extracts description from contents of a readme file in markdown format
32function extractDescription (description) {
33  // the first block of text before the first heading that isn't the first line heading
34  const lines = description.trim().split('\n')
35  let start = 0
36  // skip initial empty lines and lines that start with #
37  while (lines[start]?.trim().match(/^(#|$)/)) {
38    start++
39  }
40  let end = start + 1
41  // keep going till we get to the end or an empty line
42  while (end < lines.length && lines[end].trim()) {
43    end++
44  }
45  return lines.slice(start, end).join(' ').trim()
46}
47
48function stringifyPerson (person) {
49  if (typeof person !== 'string') {
50    const name = person.name || ''
51    const u = person.url || person.web
52    const wrappedUrl = u ? (' (' + u + ')') : ''
53    const e = person.email || person.mail
54    const wrappedEmail = e ? (' <' + e + '>') : ''
55    person = name + wrappedEmail + wrappedUrl
56  }
57  const matchedName = person.match(/^([^(<]+)/)
58  const matchedUrl = person.match(/\(([^()]+)\)/)
59  const matchedEmail = person.match(/<([^<>]+)>/)
60  const parsed = {}
61  if (matchedName?.[0].trim()) {
62    parsed.name = matchedName[0].trim()
63  }
64  if (matchedEmail) {
65    parsed.email = matchedEmail[1]
66  }
67  if (matchedUrl) {
68    parsed.url = matchedUrl[1]
69  }
70  return parsed
71}
72
73function normalizeData (data, changes) {
74  // fixDescriptionField
75  if (data.description && typeof data.description !== 'string') {
76    changes?.push(`'description' field should be a string`)
77    delete data.description
78  }
79  if (data.readme && !data.description && data.readme !== 'ERROR: No README data found!') {
80    data.description = extractDescription(data.readme)
81  }
82  if (data.description === undefined) {
83    delete data.description
84  }
85  if (!data.description) {
86    changes?.push('No description')
87  }
88
89  // fixModulesField
90  if (data.modules) {
91    changes?.push(`modules field is deprecated`)
92    delete data.modules
93  }
94
95  // fixFilesField
96  const files = data.files
97  if (files && !Array.isArray(files)) {
98    changes?.push(`Invalid 'files' member`)
99    delete data.files
100  } else if (data.files) {
101    data.files = data.files.filter(function (file) {
102      if (!file || typeof file !== 'string') {
103        changes?.push(`Invalid filename in 'files' list: ${file}`)
104        return false
105      } else {
106        return true
107      }
108    })
109  }
110
111  // fixManField
112  if (data.man && typeof data.man === 'string') {
113    data.man = [data.man]
114  }
115
116  // fixBugsField
117  if (!data.bugs && data.repository?.url) {
118    const hosted = hostedGitInfo.fromUrl(data.repository.url)
119    if (hosted && hosted.bugs()) {
120      data.bugs = { url: hosted.bugs() }
121    }
122  } else if (data.bugs) {
123    if (typeof data.bugs === 'string') {
124      if (isEmail(data.bugs)) {
125        data.bugs = { email: data.bugs }
126      } else if (URL.canParse(data.bugs)) {
127        data.bugs = { url: data.bugs }
128      } else {
129        changes?.push(`Bug string field must be url, email, or {email,url}`)
130      }
131    } else {
132      for (const k in data.bugs) {
133        if (['web', 'name'].includes(k)) {
134          changes?.push(`bugs['${k}'] should probably be bugs['url'].`)
135          data.bugs.url = data.bugs[k]
136          delete data.bugs[k]
137        }
138      }
139      const oldBugs = data.bugs
140      data.bugs = {}
141      if (oldBugs.url) {
142        if (URL.canParse(oldBugs.url)) {
143          data.bugs.url = oldBugs.url
144        } else {
145          changes?.push('bugs.url field must be a string url. Deleted.')
146        }
147      }
148      if (oldBugs.email) {
149        if (typeof (oldBugs.email) === 'string' && isEmail(oldBugs.email)) {
150          data.bugs.email = oldBugs.email
151        } else {
152          changes?.push('bugs.email field must be a string email. Deleted.')
153        }
154      }
155    }
156    if (!data.bugs.email && !data.bugs.url) {
157      delete data.bugs
158      changes?.push('Normalized value of bugs field is an empty object. Deleted.')
159    }
160  }
161  // fixKeywordsField
162  if (typeof data.keywords === 'string') {
163    data.keywords = data.keywords.split(/,\s+/)
164  }
165  if (data.keywords && !Array.isArray(data.keywords)) {
166    delete data.keywords
167    changes?.push(`keywords should be an array of strings`)
168  } else if (data.keywords) {
169    data.keywords = data.keywords.filter(function (kw) {
170      if (typeof kw !== 'string' || !kw) {
171        changes?.push(`keywords should be an array of strings`)
172        return false
173      } else {
174        return true
175      }
176    })
177  }
178  // fixBundleDependenciesField
179  const bdd = 'bundledDependencies'
180  const bd = 'bundleDependencies'
181  if (data[bdd] && !data[bd]) {
182    data[bd] = data[bdd]
183    delete data[bdd]
184  }
185  if (data[bd] && !Array.isArray(data[bd])) {
186    changes?.push(`Invalid 'bundleDependencies' list. Must be array of package names`)
187    delete data[bd]
188  } else if (data[bd]) {
189    data[bd] = data[bd].filter(function (filtered) {
190      if (!filtered || typeof filtered !== 'string') {
191        changes?.push(`Invalid bundleDependencies member: ${filtered}`)
192        return false
193      } else {
194        if (!data.dependencies) {
195          data.dependencies = {}
196        }
197        if (!Object.prototype.hasOwnProperty.call(data.dependencies, filtered)) {
198          changes?.push(`Non-dependency in bundleDependencies: ${filtered}`)
199          data.dependencies[filtered] = '*'
200        }
201        return true
202      }
203    })
204  }
205  // fixHomepageField
206  if (!data.homepage && data.repository && data.repository.url) {
207    const hosted = hostedGitInfo.fromUrl(data.repository.url)
208    if (hosted) {
209      data.homepage = hosted.docs()
210    }
211  }
212  if (data.homepage) {
213    if (typeof data.homepage !== 'string') {
214      changes?.push('homepage field must be a string url. Deleted.')
215      delete data.homepage
216    } else {
217      if (!URL.canParse(data.homepage)) {
218        data.homepage = 'http://' + data.homepage
219      }
220    }
221  }
222  // fixReadmeField
223  if (!data.readme) {
224    changes?.push('No README data')
225    data.readme = 'ERROR: No README data found!'
226  }
227  // fixLicenseField
228  const license = data.license || data.licence
229  if (!license) {
230    changes?.push('No license field.')
231  } else if (typeof (license) !== 'string' || license.length < 1 || license.trim() === '') {
232    changes?.push('license should be a valid SPDX license expression')
233  } else if (!validateLicense(license)) {
234    changes?.push('license should be a valid SPDX license expression')
235  }
236  // fixPeople
237  if (data.author) {
238    data.author = stringifyPerson(data.author)
239  }
240  ['maintainers', 'contributors'].forEach(function (set) {
241    if (!Array.isArray(data[set])) {
242      return
243    }
244    data[set] = data[set].map(stringifyPerson)
245  })
246  // fixTypos
247  for (const d in typos) {
248    if (Object.prototype.hasOwnProperty.call(data, d)) {
249      changes?.push(`${d} should probably be ${typos[d]}.`)
250    }
251  }
252}
253
254module.exports = { normalizeData }
255 
codekingpro/portable-devtools · Team Ai