update-check
Advanced tools
Comparing version 1.0.0 to 1.1.0
238
index.js
@@ -0,2 +1,4 @@ | ||
// Native | ||
const {get} = require('https'); | ||
const {URL} = require('url'); | ||
const {join} = require('path'); | ||
@@ -7,104 +9,192 @@ const fs = require('fs'); | ||
// Packages | ||
const registryUrl = require('registry-url'); | ||
const writeFile = promisify(fs.writeFile); | ||
const mkdir = promisify(fs.mkdir); | ||
const readFile = promisify(fs.readFile); | ||
const compareVersions = (a, b) => a.localeCompare(b, 'en-US', { numeric: true }); | ||
const compareVersions = (a, b) => a.localeCompare(b, 'en-US', {numeric: true}); | ||
const encode = value => encodeURIComponent(value).replace(/^%40/, '@'); | ||
const shouldCheck = async (name, interval) => { | ||
const rootDir = tmpdir(); | ||
const subDir = join(rootDir, `update-check`); | ||
const getFile = async (details, distTag) => { | ||
const rootDir = tmpdir(); | ||
const subDir = join(rootDir, 'update-check'); | ||
if (!fs.existsSync(subDir)) { | ||
mkdir(subDir); | ||
} | ||
if (!fs.existsSync(subDir)) { | ||
mkdir(subDir); | ||
} | ||
const file = join(subDir, `${name}.json`); | ||
const time = Date.now() | ||
let name = `${details.name}-${distTag}.json`; | ||
if (fs.existsSync(file)) { | ||
const content = await readFile(file, 'utf8'); | ||
const {lastCheck} = JSON.parse(content); | ||
const nextCheck = lastCheck + interval; | ||
if (details.scope) { | ||
name = `${details.scope}-${name}`; | ||
} | ||
// As long as the time of the next check is in | ||
// the future, we don't need to run it yet. | ||
if (nextCheck > time) { | ||
return false; | ||
} | ||
} | ||
return join(subDir, name); | ||
}; | ||
const content = JSON.stringify({ | ||
lastCheck: time | ||
}); | ||
const evaluateCache = async (file, time, interval) => { | ||
if (fs.existsSync(file)) { | ||
const content = await readFile(file, 'utf8'); | ||
const {lastUpdate, latest} = JSON.parse(content); | ||
const nextCheck = lastUpdate + interval; | ||
await writeFile(file, content, 'utf8'); | ||
return true; | ||
} | ||
// As long as the time of the next check is in | ||
// the future, we don't need to run it yet | ||
if (nextCheck > time) { | ||
return { | ||
shouldCheck: false, | ||
latest | ||
}; | ||
} | ||
} | ||
const getMostRecent = async (name, distTag) => { | ||
const url = `https://registry.npmjs.org/${encode(name)}/${encode(distTag)}`; | ||
return { | ||
shouldCheck: true, | ||
latest: null | ||
}; | ||
}; | ||
return new Promise((resolve, reject) => get(url, response => { | ||
const {statusCode, headers} = response; | ||
const contentType = headers['content-type']; | ||
const updateCache = async (file, latest, lastUpdate) => { | ||
const content = JSON.stringify({ | ||
latest, | ||
lastUpdate | ||
}); | ||
let error = null; | ||
await writeFile(file, content, 'utf8'); | ||
}; | ||
if (statusCode !== 200) { | ||
error = new Error(`Request failed with code ${statusCode}`); | ||
} else if (!/^application\/json/.test(contentType)) { | ||
error = new Error(`Expected application/json but received ${contentType}`); | ||
} | ||
const loadPackage = (url, authInfo) => new Promise((resolve, reject) => { | ||
const options = { | ||
host: url.hostname, | ||
path: url.pathname, | ||
headers: { | ||
accept: 'application/vnd.npm.install-v1+json; q=1.0, application/json; q=0.8, */*' | ||
} | ||
}; | ||
if (error) { | ||
reject(error.message); | ||
if (authInfo) { | ||
options.headers.authorization = `${authInfo.type} ${authInfo.token}`; | ||
} | ||
// Consume response data to free up RAM | ||
response.resume(); | ||
return; | ||
} | ||
get(options, response => { | ||
const {statusCode} = response; | ||
let rawData = ''; | ||
if (statusCode !== 200) { | ||
const error = new Error(`Request failed with code ${statusCode}`); | ||
error.code = statusCode; | ||
response.setEncoding('utf8'); | ||
response.on('data', chunk => { rawData += chunk; }); | ||
reject(error); | ||
response.on('end', () => { | ||
try { | ||
const parsedData = JSON.parse(rawData); | ||
resolve(parsedData); | ||
} catch (e) { | ||
reject(e.message); | ||
} | ||
}); | ||
}).on('error', reject)); | ||
} | ||
// Consume response data to free up RAM | ||
response.resume(); | ||
return; | ||
} | ||
let rawData = ''; | ||
response.setEncoding('utf8'); | ||
response.on('data', chunk => { | ||
rawData += chunk; | ||
}); | ||
response.on('end', () => { | ||
try { | ||
const parsedData = JSON.parse(rawData); | ||
resolve(parsedData); | ||
} catch (e) { | ||
reject(e); | ||
} | ||
}); | ||
}).on('error', reject); | ||
}); | ||
const getMostRecent = async ({full, scope}, distTag) => { | ||
const regURL = registryUrl(scope); | ||
// For scoped packages, getting a certain dist tag is not supported | ||
const url = new URL(scope ? full : `${full}/${encode(distTag)}`, regURL); | ||
let version = null; | ||
try { | ||
({version} = await loadPackage(url)); | ||
} catch (err) { | ||
// We need to cover: | ||
// 401 or 403 for when we don't have access | ||
// 404 when the package is hidden | ||
if (err.code && String(err.code).startsWith(4)) { | ||
// We only want to load this package for when we | ||
// really need to use the token | ||
const registryAuthToken = require('registry-auth-token'); | ||
const authInfo = registryAuthToken(regURL, {recursive: true}); | ||
({version} = loadPackage(url, authInfo)); | ||
} | ||
throw err; | ||
} | ||
return version; | ||
}; | ||
const defaultConfig = { | ||
interval: 3600000, | ||
distTag: 'latest' | ||
} | ||
interval: 3600000, | ||
distTag: 'latest' | ||
}; | ||
const getDetails = name => { | ||
const spec = { | ||
full: encode(name) | ||
}; | ||
if (name.includes('/')) { | ||
const parts = name.split('/'); | ||
spec.scope = parts[0]; | ||
spec.name = parts[1]; | ||
} else { | ||
spec.scope = null; | ||
spec.name = name; | ||
} | ||
return spec; | ||
}; | ||
module.exports = async (pkg, config) => { | ||
if (typeof pkg !== 'object') { | ||
throw new Error('The first parameter should be your package.json file content'); | ||
} | ||
if (typeof pkg !== 'object') { | ||
throw new Error('The first parameter should be your package.json file content'); | ||
} | ||
const {distTag, interval} = Object.assign({}, defaultConfig, config); | ||
const check = await shouldCheck(pkg.name, interval); | ||
const details = getDetails(pkg.name); | ||
if (check === false) { | ||
return null; | ||
} | ||
console.log('masklol') | ||
if (details.scope && config.distTag) { | ||
throw new Error('For scoped packages, the npm registry does not support getting a certain tag'); | ||
} | ||
const mostRecent = await getMostRecent(pkg.name, distTag); | ||
const comparision = compareVersions(pkg.version, mostRecent.version); | ||
const time = Date.now(); | ||
const {distTag, interval} = Object.assign({}, defaultConfig, config); | ||
const file = await getFile(details, distTag); | ||
if (comparision === -1) { | ||
return mostRecent; | ||
} | ||
let latest = null; | ||
let shouldCheck = true; | ||
return null; | ||
} | ||
({shouldCheck, latest} = await evaluateCache(file, time, interval)); | ||
if (shouldCheck) { | ||
latest = await getMostRecent(details, distTag); | ||
// If we pulled an update, we need to update the cache | ||
await updateCache(file, latest, time); | ||
} | ||
const comparision = compareVersions(pkg.version, latest); | ||
if (comparision === -1) { | ||
return { | ||
latest, | ||
fromCache: !shouldCheck | ||
}; | ||
} | ||
return null; | ||
}; |
{ | ||
"name": "update-check", | ||
"version": "1.0.0", | ||
"version": "1.1.0", | ||
"description": "Minimalistic update notifications for command line interfaces", | ||
@@ -32,3 +32,7 @@ "main": "./index.js", | ||
"pre-commit": "lint-staged" | ||
}, | ||
"dependencies": { | ||
"registry-auth-token": "3.3.2", | ||
"registry-url": "3.1.0" | ||
} | ||
} |
@@ -7,4 +7,2 @@ # update-check | ||
To reduce the bootup time to a minimum, no dependencies and the tiniest amount of code possible is used. | ||
## Usage | ||
@@ -33,3 +31,3 @@ | ||
if (update) { | ||
console.log(`The latest version is ${update.version}. Please update!`) | ||
console.log(`The latest version is ${update.latest}. Please update!`) | ||
} | ||
@@ -54,3 +52,3 @@ ``` | ||
if (update) { | ||
console.log(`The latest version is ${update.version}. Please update!`) | ||
console.log(`The latest version is ${update.latest}. Please update!`) | ||
} | ||
@@ -57,0 +55,0 @@ ``` |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
9050
156
2
64
+ Addedregistry-auth-token@3.3.2
+ Addedregistry-url@3.1.0
+ Addeddeep-extend@0.6.0(transitive)
+ Addedini@1.3.8(transitive)
+ Addedminimist@1.2.8(transitive)
+ Addedrc@1.2.8(transitive)
+ Addedregistry-auth-token@3.3.2(transitive)
+ Addedregistry-url@3.1.0(transitive)
+ Addedsafe-buffer@5.2.1(transitive)
+ Addedstrip-json-comments@2.0.1(transitive)