Skip to content

Commit

Permalink
feat: default and scoped registries with proxy and auth support (orta#17
Browse files Browse the repository at this point in the history
)
  • Loading branch information
Seb Insua committed Dec 5, 2018
1 parent 9c0d0e3 commit b15754d
Show file tree
Hide file tree
Showing 10 changed files with 5,651 additions and 1,555 deletions.
5 changes: 5 additions & 0 deletions .prettierrc.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"printWidth": 120,
"trailingComma": "es5",
"semi": false
}
17 changes: 7 additions & 10 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,12 +1,9 @@
{
"prettier.singleQuote": false,
"prettier.trailingComma": "es5",
"prettier.semi": false,
"prettier.printWidth": 120,
"files.exclude": {
"**/.git": true,
"**/dist": true,
"**/node_modules": true
},
"editor.formatOnSave": false
"files.exclude": {
"**/.git": true,
"**/dist": true,
"**/node_modules": true
},
"editor.formatOnSave": true,
"typescript.tsdk": "node_modules/typescript/lib"
}
53 changes: 30 additions & 23 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -22,46 +22,47 @@
"main": "dist/index.js",
"types": "dist/index.d.ts",
"scripts": {
"precommit": "lint-staged",
"commit": "git-cz",
"commitmsg": "validate-commit-msg",
"build": "tsc",
"test": "jest",
"predocs": "rm -rf docs/",
"docs": "esdoc -c .esdoc.json",
"prepublishOnly": "npm run build",
"semantic-release": "semantic-release pre && npm publish && semantic-release post",
"prettier": "prettier",
"prettier-write": "npm run prettier -- --parser typescript --no-semi --trailing-comma es5 --write --print-width 120",
"prettier-project": "npm run prettier-write -- 'src/**/*.{ts,tsx}'"
"prettier-write": "yarn prettier --write",
"prettier-project": "yarn prettier-write 'src/**/*.{ts,tsx}'"
},
"license": "MIT",
"engines": {
"node": ">=4.0.0"
},
"dependencies": {
"date-fns": "^1.28.5",
"date-fns": "^1.29.0",
"https-proxy-agent": "^2.2.1",
"lodash.flatten": "^4.4.0",
"lodash.includes": "^4.3.0",
"node-fetch": "^1.7.1",
"semver": "^5.4.1"
"semver": "^5.6.0"
},
"devDependencies": {
"@types/jest": "^19.2.4",
"@types/node": "^7.0.29",
"commitizen": "^2.9.6",
"cz-conventional-changelog": "^2.0.0",
"danger": "*",
"esdoc": "^0.5.2",
"husky": "^0.13.3",
"jest": "^20.0.1",
"lint-staged": "^3.4.1",
"prettier": "^1.3.1",
"semantic-release": "^6.3.6",
"ts-jest": "^20.0.0",
"tslint": "^5.4.3",
"typescript": "^2.3.2",
"validate-commit-msg": "^2.12.1"
"@types/jest": "^23.3.10",
"@types/node": "^10.12.12",
"@types/node-fetch": "^2.1.4",
"@types/semver": "^5.5.0",
"commitizen": "^3.0.5",
"cz-conventional-changelog": "^2.1.0",
"danger": "6.1.9",
"esdoc": "^1.1.0",
"husky": "^1.2.0",
"jest": "^23.6.0",
"lint-staged": "^8.1.0",
"prettier": "^1.15.3",
"semantic-release": "^15.12.4",
"ts-jest": "^23.10.5",
"tslint": "^5.11.0",
"typescript": "^3.2.1",
"validate-commit-msg": "^2.14.0"
},
"config": {
"commitizen": {
Expand All @@ -71,7 +72,7 @@
"lint-staged": {
"*.@(ts|tsx)": [
"tslint --fix",
"npm run prettier-write --",
"yarn prettier-write --",
"git add"
]
},
Expand All @@ -82,12 +83,18 @@
"js"
],
"transform": {
".(ts|tsx)": "<rootDir>/node_modules/ts-jest/preprocessor.js"
".(ts|tsx)": "ts-jest"
},
"testRegex": "(.test)\\.(ts|tsx)$",
"testPathIgnorePatterns": [
"\\.snap$",
"<rootDir>/node_modules/"
]
},
"husky": {
"hooks": {
"commit-msg": "validate-commit-msg",
"pre-commit": "lint-staged"
}
}
}
93 changes: 93 additions & 0 deletions src/getProxyAgentFromUri.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Agent } from "http"
import HttpsProxyAgent from "https-proxy-agent"
import { URL } from "url"

import { YarnConfig } from "./getYarnConfig"

function formatHostname(hostname: string): string {
// canonicalize the hostname, so that 'oogle.com' won't match 'google.com'
return hostname.replace(/^\.*/, ".").toLowerCase()
}

function parseNoProxyZone(zone: string): Readonly<{ hostname: string; port: string; hasPort: boolean }> {
zone = zone.trim().toLowerCase()

const zoneParts = zone.split(":", 2)
const zoneHost = formatHostname(zoneParts[0])
const zonePort = zoneParts[1]
const hasPort = zone.indexOf(":") > -1

return {
hostname: zoneHost,
port: zonePort,
hasPort,
}
}

function uriInNoProxy(uri: URL, noProxy: string): boolean {
const port = uri.port || (uri.protocol === "https:" ? "443" : "80")
const hostname = formatHostname(uri.hostname)
const noProxyList = noProxy.split(",")

// iterate through the noProxyList until it finds a match.
return noProxyList.map(parseNoProxyZone).some(noProxyZone => {
const isMatchedAt = hostname.indexOf(noProxyZone.hostname)
const hostnameMatched = isMatchedAt > -1 && isMatchedAt === hostname.length - noProxyZone.hostname.length

if (noProxyZone.hasPort) {
return port === noProxyZone.port && hostnameMatched
}

return hostnameMatched
})
}

function getProxyFromUri(uri: URL, config: YarnConfig): string | undefined {
// Decide the proper request proxy to use based on the request URI object and the
// environmental variables (NO_PROXY, HTTP_PROXY, etc.)
// Respect NO_PROXY environment variables.
// See: http://lynx.isc.org/current/breakout/lynx_help/keystrokes/environments.html
const noProxy = process.env.NO_PROXY || process.env.no_proxy

// If the noProxy is a wildcard then return undefined
if (noProxy === "*") {
return undefined
}

// If the noProxy is not empty and the uri is found return undefined
if (noProxy && uriInNoProxy(uri, noProxy)) {
return undefined
}

// Check for HTTP or HTTPS proxy in environment else default to undefined
if (uri.protocol === "http:") {
return process.env.HTTP_PROXY || process.env.http_proxy || config["http-proxy"] || config.proxy || undefined
}

if (uri.protocol === "https:") {
return (
process.env.HTTPS_PROXY ||
process.env.https_proxy ||
config["https-proxy"] ||
process.env.HTTP_PROXY ||
process.env.http_proxy ||
config["http-proxy"] ||
config.proxy ||
undefined
)
}

// If none of that works, return undefined
// (What uri protocol are you using then?)
return undefined
}

export function getProxyAgentFromUri(uri: URL, config: YarnConfig): Agent | undefined {
const proxy = getProxyFromUri(uri, config)

if (proxy) {
return new HttpsProxyAgent(proxy) as Agent
}

return undefined
}
34 changes: 34 additions & 0 deletions src/getRegistries.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { YarnConfig } from "./getYarnConfig"

export type Registry = Readonly<{
url: string
authToken?: string
}>

export interface Registries {
readonly default: Registry
[scopeName: string]: Registry
}

export function getRegistries(config: YarnConfig): Registries {
const registries = {
default: {
url: config.registry,
authToken: config._auth,
},
}

for (const [key, value] of Object.entries(config)) {
if (key.endsWith(":registry")) {
const [scopeName] = key.split(":registry")
const url = value
const authToken = config[`${url}:_auth`]
registries[scopeName] = {
url,
authToken,
}
}
}

return registries
}
40 changes: 40 additions & 0 deletions src/getYarnConfig.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import * as child_process from "child_process"
import { promisify } from "util"

const exec = promisify(child_process.exec)

export interface YarnConfig {
registry: string
_auth?: string
"https-proxy"?: string
"http-proxy"?: string
proxy?: string
}

export interface YarnConfigListMessage {
type: "info" | "inspect"
data: object
}

export async function getYarnConfig(): Promise<YarnConfig> {
const defaultConfig = {
registry: "https://registry.npmjs.org/",
}

const { stdout } = await exec("yarn config list --json")

if (stdout) {
const jsonLines: YarnConfigListMessage[] = stdout.split("\n").map(line => JSON.parse(line))
// The json lines are ordered from yarn to npm, but we wish to produce
// a config in which npm is overridden with yarn config.
return jsonLines.reduceRight((config, jsonLine) => {
if (jsonLine.type === "inspect") {
return { ...config, ...jsonLine.data }
}

return config
}, defaultConfig)
}

return defaultConfig
}
33 changes: 21 additions & 12 deletions src/index.test.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { JSONDiff } from "../node_modules/danger/distribution/dsl/GitDSL"

import * as mockfs from "fs"
jest.mock("node-fetch", () => () =>
Promise.resolve({
Expand All @@ -6,13 +8,7 @@ jest.mock("node-fetch", () => () =>
})
)

import yarn, {
checkForLockfileDiff,
checkForNewDependencies,
checkForRelease,
checkForTypesInDeps,
getNPMMetadataForDep,
} from "./index"
import { checkForLockfileDiff, checkForRelease, checkForTypesInDeps, getNPMMetadataForDep } from "./index"

declare const global: any
beforeEach(() => {
Expand Down Expand Up @@ -59,8 +55,10 @@ describe("checkForTypesInDeps", () => {
})

it("when there is an @types dependency, it should call fail", () => {
const deps = {
const deps: JSONDiff = {
dependencies: {
before: [],
after: ["@types/danger"],
added: ["@types/danger"],
},
}
Expand All @@ -77,24 +75,35 @@ describe("checkForLockfileDiff", () => {

it("when there are dependency changes, and no lockfile in modified - warn", () => {
global.danger = { git: { modified_files: [] } }
const deps = {
dependencies: {},
const deps: JSONDiff = {
dependencies: {
before: [],
after: [],
},
}
checkForLockfileDiff(deps)
expect(global.warn).toHaveBeenCalledTimes(1)
})

it("when there are dependency changes, and a lockfile in modified - do not warn", () => {
global.danger = { git: { modified_files: ["yarn.lock"] } }
const deps = { dependencies: {} }
const deps: JSONDiff = {
dependencies: {
before: [],
after: [],
},
}
checkForLockfileDiff(deps)
expect(global.warn).toHaveBeenCalledTimes(0)
})
})

describe("npm metadata", () => {
it("Shows a bunch of useful text for a new dep", async () => {
const data = await getNPMMetadataForDep("danger")
const defaultConfig = {
registry: "https://registry.npmjs.org/",
}
const data = await getNPMMetadataForDep("danger", defaultConfig)
expect(data).toMatchSnapshot()
})
})
Loading

0 comments on commit b15754d

Please sign in to comment.