airtap-match-browsers
Advanced tools
Comparing version 0.1.0 to 1.0.0
207
index.js
@@ -8,4 +8,4 @@ 'use strict' | ||
const names = require('browser-names') | ||
const isForkPr = require('is-fork-pr').isForkPr | ||
const defaults = { version: 'latest' } | ||
const prerelease = /[^\d.]/ | ||
@@ -17,43 +17,18 @@ const numeric = /^\d+$/ | ||
function matchAll (available, wanted) { | ||
wanted = wanted.map(withDefaults) | ||
wanted.sort((a, b) => a.name.localeCompare(b.name)) | ||
explode(wanted) | ||
const matches = [] | ||
const groups = new Map() | ||
const eqlOptions = { strict: true } | ||
// Group by name for faster matching | ||
for (const manifest of available) { | ||
const name = manifest.name | ||
for (const original of wanted) { | ||
const w = normalize(original) | ||
const explicit = new Set(['version']) | ||
if (groups.has(name)) { | ||
groups.get(name).push(manifest) | ||
} else { | ||
groups.set(name, [manifest]) | ||
} | ||
} | ||
// Match by properties other than version | ||
let group = available.filter(m => match(m, w, explicit)) | ||
for (const w of wanted) { | ||
const explicit = new Set() | ||
// Match by name | ||
let group = findName(groups, w.name) || [] | ||
// Lazily sort by version | ||
if (!group.sorted) { | ||
group.sort((a, b) => cmpVersion(a.version, b.version)) | ||
group.sorted = true | ||
} | ||
// Match by other properties | ||
const skip = ['name', 'version'] | ||
group = group.filter(m => match(m, w, explicit, skip)) | ||
// Match by version | ||
w.version = lower(w.version) | ||
group = filterVersions(group, w.version) | ||
group.sort((a, b) => cmpVersion(a.version, b.version)) | ||
group = filterVersions(group, w.version || 'latest') | ||
if (group.length === 0) { | ||
throw new Error('Zero matches for ' + JSON.stringify(w, null, 2)) | ||
throw new NotFoundError(original) | ||
} | ||
@@ -69,3 +44,3 @@ | ||
if (same(a, b, explicit)) { | ||
if (same(a, b, explicit, eqlOptions)) { | ||
// Last manifest wins (for no particular reason) | ||
@@ -80,26 +55,25 @@ winner = b | ||
// assume that `available` itself doesn't contain duplicates. | ||
matches.push({ manifest: winner, options: w.options }) | ||
matches.push({ manifest: winner, options: w.options || {} }) | ||
} | ||
} | ||
consolidate(matches) | ||
consolidate(matches, eqlOptions) | ||
return matches | ||
} | ||
function findName (groups, name) { | ||
if (groups.has(name)) { | ||
return groups.get(name) | ||
} | ||
function consolidate (matches, eqlOptions) { | ||
const insecure = insecureEnv() | ||
for (const alias of names(name)) { | ||
if (alias !== name && groups.has(alias)) { | ||
return groups.get(alias) | ||
} | ||
} | ||
} | ||
function consolidate (matches) { | ||
for (let i = 0; i < matches.length; i++) { | ||
const { manifest, options } = matches[i] | ||
// Skip browsers that need secure environment variables and are therefore | ||
// not available on pull requests from forks. Done here (after matching) | ||
// so that the order of precedence between browsers is consistent in | ||
// secure and insecure envs. Could reconsider that; needs a discussion. | ||
if (insecure && manifest.wants && manifest.wants.secureEnv) { | ||
matches.splice(i--, 1) | ||
continue | ||
} | ||
// Add user-provided options to manifest | ||
@@ -111,3 +85,3 @@ matches[i] = mergeDeep(manifest, { options }) | ||
if (matches[j].manifest === manifest && | ||
deepEqual(matches[j].options, options, { strict: true })) { | ||
deepEqual(matches[j].options, options, eqlOptions)) { | ||
matches.splice(j--, 1) | ||
@@ -119,56 +93,21 @@ } | ||
function explode (manifests) { | ||
for (let i = 0; i < manifests.length; i++) { | ||
const manifest = manifests[i] | ||
for (const k of ['version']) { | ||
if (Array.isArray(manifest[k])) { | ||
manifests.splice(i--, 1, ...manifest[k].map(v => ({ ...manifest, [k]: v }))) | ||
break | ||
} | ||
} | ||
} | ||
} | ||
function withDefaults (manifest) { | ||
manifest = { ...manifest } | ||
for (const k in defaults) { | ||
manifest[k] = manifest[k] || defaults[k] | ||
} | ||
if (typeof manifest.name !== 'string' || manifest.name === '') { | ||
throw new TypeError('Manifest "name" is required') | ||
} | ||
manifest.name = lower(manifest.name) | ||
manifest.options = manifest.options || {} | ||
function normalize (wanted) { | ||
// For airtap < 4 compatibility | ||
// TODO: consider adding a shorthand "device" property for ipad & iphone | ||
if (manifest.name === 'iphone' || manifest.name === 'ipad') { | ||
const device = manifest.name === 'iphone' ? 'iphone simulator' : 'ipad simulator' | ||
const caps = manifest.capabilities = { ...manifest.capabilities } | ||
if (wanted.name === 'iphone' || wanted.name === 'ipad') { | ||
wanted = { ...wanted } | ||
const device = wanted.name === 'iphone' ? 'iphone simulator' : 'ipad simulator' | ||
const caps = wanted.capabilities = { ...wanted.capabilities } | ||
const appium = caps.appium = { ...caps.appium } | ||
manifest.name = 'ios_saf' | ||
wanted.name = 'ios_saf' | ||
appium.deviceName = appium.deviceName || device | ||
} | ||
return manifest | ||
return wanted | ||
} | ||
function lower (value) { | ||
return value != null ? String(value).toLowerCase() : '' | ||
} | ||
function match (available, wanted, explicit, skip, key) { | ||
if (Array.isArray(available)) { | ||
return available.some(el => match(el, wanted, explicit, skip, key)) | ||
} else if (Array.isArray(wanted)) { | ||
throw new Error('Array is not yet supported on ' + key) | ||
// TODO: explode into multiple browsers, instead of this "oneof" behavior | ||
// return wanted.some(el => match(available, el, explicit, skip, key)) | ||
} else if (isObject(wanted)) { | ||
function match (available, wanted, explicit, key) { | ||
if (isObject(wanted)) { | ||
if (!isObject(available)) return false | ||
@@ -180,27 +119,37 @@ | ||
if (!hasOwnProperty.call(wanted, k)) continue | ||
if (fqk === 'options') continue | ||
if (!match(available[k], wanted[k], explicit, skip, fqk)) return false | ||
if (fqk === 'options' || fqk === 'version') continue | ||
explicit.add(fqk) | ||
if (!match(available[k], wanted[k], explicit, fqk)) return false | ||
} | ||
return true | ||
} else if (wanted === 'any') { | ||
return true | ||
} else { | ||
explicit.add(key) | ||
return skip.includes(key) || matchPrimitive(available, wanted) | ||
return matchPrimitive(available, wanted, key) | ||
} | ||
} | ||
function matchPrimitive (available, wanted) { | ||
function matchPrimitive (available, wanted, key) { | ||
if (typeof wanted === 'string') { | ||
wanted = lower(wanted) | ||
available = lower(available) | ||
wanted = wanted != null ? String(wanted).toLowerCase() : '' | ||
available = available != null ? String(available).toLowerCase() : '' | ||
} | ||
return available === wanted | ||
if (available === wanted) { | ||
return true | ||
} | ||
if (key === 'name') { | ||
for (const alias of names(wanted)) { | ||
if (available === alias) return true | ||
} | ||
} | ||
return false | ||
} | ||
function same (a, b, explicit) { | ||
function same (a, b, explicit, eqlOptions) { | ||
for (const k of explicit) { | ||
if (!deepEqual(deep(a, k), deep(b, k), { strict: true })) { | ||
if (!deepEqual(deep(a, k), deep(b, k), eqlOptions)) { | ||
return false | ||
@@ -220,12 +169,4 @@ } | ||
const test = range(version, manifests) | ||
const result = [] | ||
const result = manifests.filter(m => test(m.version)) | ||
for (const m of manifests) { | ||
if (test(m.version)) { | ||
result.push(m) | ||
} else if (result.length) { | ||
break | ||
} | ||
} | ||
return result | ||
@@ -236,2 +177,10 @@ } | ||
function range (version, manifests) { | ||
if (Array.isArray(version)) { | ||
const tests = version.map(v => range(v, manifests)) | ||
return function test (v) { | ||
return tests.some(fn => fn(v)) | ||
} | ||
} | ||
let gte | ||
@@ -322,4 +271,28 @@ let lte | ||
function insecureEnv () { | ||
if (process.env.AIRTAP_IS_SECURE_ENV) { | ||
return process.env.AIRTAP_IS_SECURE_ENV === 'false' | ||
} | ||
if (process.env.CI) { | ||
if (isForkPr()) return true | ||
if (process.env.TRAVIS_SECURE_ENV_VARS === 'false') return true | ||
} | ||
return false | ||
} | ||
function isObject (o) { | ||
return typeof o === 'object' && o !== null && !Array.isArray(o) | ||
} | ||
class NotFoundError extends Error { | ||
constructor (input) { | ||
super('No matching manifest found') | ||
Object.defineProperty(this, 'name', { value: 'NotFoundError' }) | ||
Object.defineProperty(this, 'code', { value: 'ERR_MANIFEST_NOT_FOUND' }) | ||
Object.defineProperty(this, 'expected', { value: true }) | ||
Object.defineProperty(this, 'input', { value: input }) | ||
} | ||
} |
{ | ||
"name": "airtap-match-browsers", | ||
"version": "0.1.0", | ||
"version": "1.0.0", | ||
"description": "Match browser manifests to a desired set of browsers", | ||
@@ -8,3 +8,3 @@ "license": "MIT", | ||
"scripts": { | ||
"test": "standard && node test" | ||
"test": "standard && nyc node test" | ||
}, | ||
@@ -18,5 +18,7 @@ "files": [ | ||
"deep-equal": "^2.0.1", | ||
"is-fork-pr": "^2.5.0", | ||
"merge-deep": "^3.0.2" | ||
}, | ||
"devDependencies": { | ||
"nyc": "^15.1.0", | ||
"standard": "^14.3.1", | ||
@@ -23,0 +25,0 @@ "tape": "^4.13.0" |
@@ -42,6 +42,6 @@ # airtap-match-browsers | ||
- An exact or partial version ("6" matches "6.0"). | ||
- A keyword, one of "oldest" (first version) or "latest" (last numeric version). | ||
- A keyword, one of "oldest" (first version) or "latest" (last stable version). | ||
- A range in the form of `<start>..<end>`, where `start` and `end` are either a version or a keyword. This will result in one or more matches. | ||
- A negative range in the form of `-<n>..latest`, for example `-1..latest` which means the last 2 numeric versions. | ||
- A prerelease version like "dev", "beta", "80.0a1". Such versions sort after numeric versions, so that `oldest..latest` excludes "dev" and `latest..dev` includes e.g. latest, "beta" and "dev". | ||
- A negative range in the form of `-<n>..latest`, for example `-1..latest` which means the last 2 stable versions. | ||
- A prerelease version like "dev", "beta", "80.0a1". Such versions sort after stable versions, so that `oldest..latest` excludes "dev" and `latest..dev` includes e.g. latest, "beta" and "dev". | ||
- An array of versions. | ||
@@ -48,0 +48,0 @@ |
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 3 instances in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
0
11901
5
3
223
4
+ Addedis-fork-pr@^2.5.0
+ Addedis-fork-pr@2.5.0(transitive)