🏡 index : quine-global-boneyard/electron-builder.git

import { fromUrl } from "hosted-git-info"
import * as semver from "semver"
import * as url from "url"

export function normalizePackageData(data: any) {
  for (const it of check) {
    it(data)
  }
}

const depTypes = ["dependencies", "devDependencies", "optionalDependencies"]
const check = [
  function (data: any) {
    if (data.repositories) {
      data.repository = data.repositories[0]
    }
    if (typeof data.repository === "string") {
      data.repository = {
        type: "git",
        url: data.repository,
      }
    }
    if (data.repository != null && data.repository.url) {
      const hosted = fromUrl(data.repository.url)
      if (hosted) {
        data.repository.url = hosted.getDefaultRepresentation() == "shortcut" ? hosted.https() : hosted.toString()
      }
    }
  },
  function (data: any) {
    const files = data.files
    if (files && !Array.isArray(files)) {
      delete data.files
    } else if (data.files) {
      data.files = data.files.filter(function (file: any) {
        return !(!file || typeof file !== "string")
      })
    }
  },
  function (data: any) {
    if (!data.bin) {
      return
    }
    if (typeof data.bin === "string") {
      const b: any = {}
      const match = data.name.match(/^@[^/]+[/](.*)$/)
      if (match) {
        b[match[1]] = data.bin
      } else {
        b[data.name] = data.bin
      }
      data.bin = b
    }
  },
  function (data: any) {
    if (data.description && typeof data.description !== "string") {
      delete data.description
    }
    if (data.description === undefined) {
      delete data.description
    }
  },
  fixBundleDependenciesField,
  function fixDependencies(data: any) {
    objectifyDeps(data)
    fixBundleDependenciesField(data)

    for (const deps of ["dependencies", "devDependencies", "optionalDependencies"]) {
      if (!(deps in data)) {
        continue
      }
      if (!data[deps] || typeof data[deps] !== "object") {
        delete data[deps]
        continue
      }
      Object.keys(data[deps]).forEach(function (d) {
        const r = data[deps][d]
        if (typeof r !== "string") {
          delete data[deps][d]
        }
        const hosted = fromUrl(data[deps][d])
        if (hosted) {
          data[deps][d] = hosted.toString()
        }
      })
    }
  },
  function fixBugsField(data: any) {
    if (!data.bugs && data.repository && data.repository.url) {
      const hosted = fromUrl(data.repository.url)
      if (hosted && hosted.bugs()) {
        data.bugs = { url: hosted.bugs() }
      }
    } else if (data.bugs) {
      const emailRe = /^.+@.*\..+$/
      if (typeof data.bugs == "string") {
        if (emailRe.test(data.bugs)) {
          data.bugs = { email: data.bugs }
        } else if (url.parse(data.bugs).protocol) {
          data.bugs = { url: data.bugs }
        }
      } else {
        bugsTypos(data.bugs)
        const oldBugs = data.bugs
        data.bugs = {}
        if (oldBugs.url) {
          if (typeof oldBugs.url == "string" && url.parse(oldBugs.url).protocol) {
            data.bugs.url = oldBugs.url
          }
        }
        if (oldBugs.email) {
          if (typeof oldBugs.email == "string" && emailRe.test(oldBugs.email)) {
            data.bugs.email = oldBugs.email
          }
        }
      }
      if (!data.bugs.email && !data.bugs.url) {
        delete data.bugs
      }
    }
  },
  function fixModulesField(data: any) {
    if (data.modules) {
      delete data.modules
    }
  },
  function fixKeywordsField(data: any) {
    if (typeof data.keywords === "string") {
      data.keywords = data.keywords.split(/,\s+/)
    }
    if (data.keywords && !Array.isArray(data.keywords)) {
      delete data.keywords
    } else if (data.keywords) {
      data.keywords = data.keywords.filter(function (kw: any) {
        return !(typeof kw !== "string" || !kw)
      })
    }
  },
  function fixVersionField(data: any) {
    const loose = true
    if (!data.version) {
      data.version = ""
      return true
    }
    if (!semver.valid(data.version, loose)) {
      throw new Error(`Invalid version: "${data.version}"`)
    }
    data.version = semver.clean(data.version, loose)
    return true
  },
  function fixPeople(data: any) {
    modifyPeople(data, unParsePerson)
    modifyPeople(data, parsePerson)
  },
  function fixNameField(data: any) {
    if (!data.name) {
      data.name = ""
      return
    }
    if (typeof data.name !== "string") {
      throw new Error("name field must be a string.")
    }
    data.name = data.name.trim()
    ensureValidName(data.name)
  },
  function fixHomepageField(data: any) {
    if (!data.homepage && data.repository && data.repository.url) {
      const hosted = fromUrl(data.repository.url)
      if (hosted && hosted.docs()) {
        data.homepage = hosted.docs()
      }
    }
    if (!data.homepage) {
      return
    }

    if (typeof data.homepage !== "string") {
      delete data.homepage
    }
    if (!url.parse(data.homepage).protocol) {
      data.homepage = `https://${data.homepage}`
    }
    return
  },
]

function fixBundleDependenciesField(data: any) {
  const bdd = "bundledDependencies"
  const bd = "bundleDependencies"
  if (data[bdd] && !data[bd]) {
    data[bd] = data[bdd]
    delete data[bdd]
  }
  if (data[bd] && !Array.isArray(data[bd])) {
    delete data[bd]
  } else if (data[bd]) {
    data[bd] = data[bd].filter(function (bd: any) {
      if (!bd || typeof bd !== "string") {
        return false
      } else {
        if (!data.dependencies) {
          data.dependencies = {}
        }
        if (!("bd" in data.dependencies)) {
          data.dependencies[bd] = "*"
        }
        return true
      }
    })
  }
}

function isValidScopedPackageName(spec: string): boolean {
  if (spec.charAt(0) !== "@") {
    return false
  }

  const rest = spec.slice(1).split("/")
  if (rest.length !== 2) {
    return false
  }

  return rest[0] !== "" && rest[1] !== "" && rest[0] != null && rest[1] != null && rest[0] === encodeURIComponent(rest[0]) && rest[1] === encodeURIComponent(rest[1])
}

function isCorrectlyEncodedName(spec: string): boolean {
  return !/[/@\s+%:]/.test(spec) && spec === encodeURIComponent(spec)
}

function ensureValidName(name: string): void {
  if (
    name.charAt(0) === "." ||
    !(isValidScopedPackageName(name) || isCorrectlyEncodedName(name)) ||
    name.toLowerCase() === "node_modules" ||
    name.toLowerCase() === "favicon.ico"
  ) {
    throw new Error("Invalid name: " + JSON.stringify(name))
  }
}

function modifyPeople(data: any, fn: any): any {
  if (data.author) {
    data.author = fn(data.author)
  }
  for (const set of ["maintainers", "contributors"]) {
    if (!Array.isArray(data[set])) {
      continue
    }
    data[set] = data[set].map(fn)
  }
  return data
}

function unParsePerson(person: any): string {
  if (typeof person === "string") {
    return person
  }
  const name = person.name || ""
  const u = person.url || person.web
  const url = u ? ` (${u})` : ""
  const e = person.email || person.mail
  const email = e ? ` <${e}>` : ""
  return `${name}${email}${url}`
}

function parsePerson(person: any) {
  if (typeof person !== "string") {
    return person
  }
  const name = /^([^(<]+)/.exec(person)
  const url = /\(([^)]+)\)/.exec(person)
  const email = /<([^>]+)>/.exec(person)
  const obj: any = {}
  if (name && name[0].trim()) {
    obj.name = name[0].trim()
  }
  if (email) {
    obj.email = email[1]
  }
  if (url) {
    obj.url = url[1]
  }
  return obj
}

function depObjectify(deps: any): any {
  if (!deps) {
    return {}
  }
  if (typeof deps === "string") {
    deps = deps.trim().split(/[\n\r\s\t ,]+/)
  }
  if (!Array.isArray(deps)) {
    return deps
  }
  const o: any = {}
  deps
    .filter(function (d) {
      return typeof d === "string"
    })
    .forEach(function (d: string) {
      const arr: string[] = d.trim().split(/(:?[@\s><=])/)
      const dn = arr.shift()
      let dv = arr.join("")
      dv = dv.trim()
      dv = dv.replace(/^@/, "")
      o[dn!] = dv
    })
  return o
}

function objectifyDeps(data: any) {
  depTypes.forEach(function (type) {
    if (!data[type]) {
      return
    }
    data[type] = depObjectify(data[type])
  })
}

const typoBugs: any = { web: "url", name: "url" }

function bugsTypos(bugs: any) {
  if (!bugs) {
    return
  }
  Object.keys(bugs).forEach(function (k) {
    if (typoBugs[k]) {
      bugs[typoBugs[k]] = bugs[k]
      delete bugs[k]
    }
  })
}