@nodevu/core
Advanced tools
+189
-193
@@ -1,224 +0,218 @@ | ||
| const { fetch: defaultFetch } = require('undici') | ||
| const { DateTime } = require('luxon') | ||
| const semver = require('semver') | ||
| const parsefiles = require('@nodevu/parsefiles') | ||
| const { DateTime } = require('luxon'); | ||
| const semver = require('semver'); | ||
| const parsefiles = require('@nodevu/parsefiles'); | ||
| const optionsParser = require('./util/prod/optionsParser'); | ||
| const versionFetcher = require('./util/prod/versions'); | ||
| const scheduleFetcher = require('./util/prod/schedule'); | ||
| async function core (options) { | ||
| // parse our user's options and set up our fetch/DateTime implementations | ||
| const parsedOptions = await parseOptions(options) | ||
| async function core(options) { | ||
| // parse our user's options and set up our fetch/DateTime implementations | ||
| const parsedOptions = optionsParser(options); | ||
| const fetch = parsedOptions.fetch | ||
| const now = DateTime.fromISO(parsedOptions.now) | ||
| const nodejsIndexURL = parsedOptions.urls.index | ||
| const nodejsScheduleURL = parsedOptions.urls.schedule | ||
| const now = DateTime.fromISO(parsedOptions.now); | ||
| // collect and configure our data sources | ||
| const rawVersions = await fetch(nodejsIndexURL) | ||
| const rawSchedule = await fetch(nodejsScheduleURL) | ||
| const versions = await rawVersions.json() | ||
| const schedule = await rawSchedule.json() | ||
| const data = {} | ||
| // collect and configure our data sources | ||
| const versions = await versionFetcher(parsedOptions); | ||
| const schedule = await scheduleFetcher(parsedOptions); | ||
| Object.keys(versions).map(async (version) => { | ||
| const versionSemver = semver.coerce(versions[version].version) | ||
| const name = versionSemver.major !== 0 ? `v${versionSemver.major}` : `v${versionSemver.major}.${versionSemver.minor}` | ||
| // instantiate our data object, to be expanded upon | ||
| const data = {}; | ||
| // define the shape of the object we're going to use | ||
| if (!data[name]) { | ||
| data[name] = {} | ||
| } | ||
| Object.keys(versions).map(async (version) => { | ||
| const versionSemver = semver.coerce(versions[version].version); | ||
| const name = | ||
| versionSemver.major !== 0 | ||
| ? `v${versionSemver.major}` | ||
| : `v${versionSemver.major}.${versionSemver.minor}`; | ||
| if (!data[name].releases) { | ||
| data[name].releases = {} | ||
| } | ||
| // define the shape of the object we're going to use | ||
| if (!data[name]) { | ||
| data[name] = {}; | ||
| } | ||
| data[name].releases[`v${versionSemver.version}`] = {} | ||
| if (!data[name].releases) { | ||
| data[name].releases = {}; | ||
| } | ||
| // define semver object | ||
| const semverToReturn = { | ||
| raw: versionSemver.raw, | ||
| major: versionSemver.major, | ||
| minor: versionSemver.minor, | ||
| patch: versionSemver.patch, | ||
| line: name | ||
| } | ||
| data[name].releases[`v${versionSemver.version}`] = {}; | ||
| data[name].releases[`v${versionSemver.version}`].semver = semverToReturn | ||
| // define semver object | ||
| const semverToReturn = { | ||
| raw: versionSemver.raw, | ||
| major: versionSemver.major, | ||
| minor: versionSemver.minor, | ||
| patch: versionSemver.patch, | ||
| line: name, | ||
| }; | ||
| data[name].releases[`v${versionSemver.version}`].releaseDate = versions[version].date ?? undefined | ||
| data[name].releases[`v${versionSemver.version}`].semver = semverToReturn; | ||
| const modules = { | ||
| version: versions[version].modules ?? undefined | ||
| } | ||
| data[name].releases[`v${versionSemver.version}`].releaseDate = | ||
| versions[version].date ?? undefined; | ||
| data[name].releases[`v${versionSemver.version}`].modules = modules | ||
| const modules = { | ||
| version: versions[version].modules ?? undefined, | ||
| }; | ||
| // define the dependencies object | ||
| data[name].releases[`v${versionSemver.version}`].dependencies = {} | ||
| data[name].releases[`v${versionSemver.version}`].modules = modules; | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.npm = versions[version].npm ?? undefined | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.v8 = versions[version].v8 ?? undefined | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.uv = versions[version].uv ?? undefined | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.zlib = versions[version].zlib ?? undefined | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.openssl = versions[version].openssl ?? undefined | ||
| // define the dependencies object | ||
| data[name].releases[`v${versionSemver.version}`].dependencies = {}; | ||
| // surface file information | ||
| data[name].releases[`v${versionSemver.version}`].files = {} | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.npm = | ||
| versions[version].npm ?? undefined; | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.v8 = | ||
| versions[version].v8 ?? undefined; | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.uv = | ||
| versions[version].uv ?? undefined; | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.zlib = | ||
| versions[version].zlib ?? undefined; | ||
| data[name].releases[`v${versionSemver.version}`].dependencies.openssl = | ||
| versions[version].openssl ?? undefined; | ||
| // TODO: parse versions[version].files and convert them to URLs that can be directly accessed | ||
| data[name].releases[`v${versionSemver.version}`].files.available = versions[version].files ?? undefined | ||
| const availableShorthand = data[name].releases[`v${versionSemver.version}`].files.available // since we're going to be writing this a lot for assignments, it's nice to have shorthand for readability | ||
| // surface file information | ||
| data[name].releases[`v${versionSemver.version}`].files = {}; | ||
| data[name].releases[`v${versionSemver.version}`].files.links = {} | ||
| const linksShorthand = data[name].releases[`v${versionSemver.version}`].files.links // since we're going to be writing this a lot for assignments, it's nice to have shorthand for readability | ||
| // TODO: parse versions[version].files and convert them to URLs that can be directly accessed | ||
| data[name].releases[`v${versionSemver.version}`].files.available = | ||
| versions[version].files ?? undefined; | ||
| const availableShorthand = | ||
| data[name].releases[`v${versionSemver.version}`].files.available; // since we're going to be writing this a lot for assignments, it's nice to have shorthand for readability | ||
| Object.keys(availableShorthand).forEach(filename => { | ||
| const id = data[name].releases[`v${versionSemver.version}`].files.available[filename] | ||
| const parsedFile = parsefiles(id, versionSemver.version) | ||
| data[name].releases[`v${versionSemver.version}`].files.links = {}; | ||
| const linksShorthand = | ||
| data[name].releases[`v${versionSemver.version}`].files.links; // since we're going to be writing this a lot for assignments, it's nice to have shorthand for readability | ||
| if (!linksShorthand[parsedFile.type]) { | ||
| linksShorthand[parsedFile.type] = [] | ||
| } | ||
| for (const filename of Object.keys(availableShorthand)) { | ||
| const id = | ||
| data[name].releases[`v${versionSemver.version}`].files.available[ | ||
| filename | ||
| ]; | ||
| const parsedFile = parsefiles(id, versionSemver.version); | ||
| linksShorthand[parsedFile.type].push({ | ||
| id: parsedFile.id, | ||
| files: parsedFile.files, | ||
| architecture: parsedFile.architecture | ||
| }) | ||
| }) | ||
| if (!linksShorthand[parsedFile.type]) { | ||
| linksShorthand[parsedFile.type] = []; | ||
| } | ||
| // # LTS | ||
| // ## define the release-line specific support objec`t | ||
| if (schedule[name]?.start !== undefined) { // hack-y way to skip this logic on releases that don't have a listed start | ||
| if (!data[name].support) { // check to see if we've already written it. if we have, we don't need to waste time on it. | ||
| data[name].support = {} | ||
| data[name].support.codename = schedule[name]?.codename ?? undefined | ||
| data[name].support.lts = schedule[name]?.lts ? {} : undefined | ||
| linksShorthand[parsedFile.type].push({ | ||
| id: parsedFile.id, | ||
| files: parsedFile.files, | ||
| architecture: parsedFile.architecture, | ||
| }); | ||
| } | ||
| // run this the first time we start working on the support object, | ||
| // since that will be the newest version | ||
| if (versions[version].lts) { | ||
| data[name].support.lts.newest = versionSemver.version | ||
| } | ||
| // # LTS | ||
| // ## define the release-line specific support objec`t | ||
| if (schedule[name]?.start !== undefined) { | ||
| // hack-y way to skip this logic on releases that don't have a listed start | ||
| if (!data[name].support) { | ||
| // check to see if we've already written it. if we have, we don't need to waste time on it. | ||
| data[name].support = {}; | ||
| data[name].support.codename = schedule[name]?.codename ?? undefined; | ||
| data[name].support.lts = schedule[name]?.lts ? {} : undefined; | ||
| data[name].support.phases = {} | ||
| data[name].support.phases.dates = {} | ||
| data[name].support.phases.dates.start = schedule[name]?.start ?? undefined | ||
| data[name].support.phases.dates.lts = schedule[name]?.lts ?? undefined | ||
| data[name].support.phases.dates.maintenance = schedule[name]?.maintenance ?? undefined | ||
| data[name].support.phases.dates.end = schedule[name]?.end ?? undefined | ||
| } | ||
| data[name].support.phases.current = await determineCurrentReleasePhase(now, data[name].support.phases.dates) ?? {} | ||
| } | ||
| // run this the first time we start working on the support object, | ||
| // since that will be the newest version | ||
| if (versions[version].lts) { | ||
| data[name].support.lts.newest = versionSemver.version; | ||
| } | ||
| // this is a slightly inefficient way to do this but it's also easy | ||
| // | ||
| // tl;dr we're just assigning this every single iteration and the last | ||
| // iteration will be the oldest version, since we're going from newest | ||
| // to oldest | ||
| if (versions[version].lts) { | ||
| data[name].support.lts.oldest = versionSemver.version | ||
| } | ||
| data[name].support.phases = {}; | ||
| data[name].support.phases.dates = {}; | ||
| data[name].support.phases.dates.start = | ||
| schedule[name]?.start ?? undefined; | ||
| data[name].support.phases.dates.lts = schedule[name]?.lts ?? undefined; | ||
| data[name].support.phases.dates.maintenance = | ||
| schedule[name]?.maintenance ?? undefined; | ||
| data[name].support.phases.dates.end = schedule[name]?.end ?? undefined; | ||
| } | ||
| data[name].support.phases.current = | ||
| (await determineCurrentReleasePhase( | ||
| now, | ||
| data[name].support.phases.dates, | ||
| )) ?? {}; | ||
| } | ||
| // ## define the lts object in each specific version | ||
| data[name].releases[`v${versionSemver.version}`].lts = {} | ||
| // this is a slightly inefficient way to do this but it's also easy | ||
| // | ||
| // tl;dr we're just assigning this every single iteration and the last | ||
| // iteration will be the oldest version, since we're going from newest | ||
| // to oldest | ||
| if (versions[version].lts) { | ||
| data[name].support.lts.oldest = versionSemver.version; | ||
| } | ||
| data[name].releases[`v${versionSemver.version}`].lts.isLts = !!versions[version].lts | ||
| // ## define the lts object in each specific version | ||
| data[name].releases[`v${versionSemver.version}`].lts = {}; | ||
| // # Security | ||
| // ## define the release-line specific security object | ||
| if (!data[name].security) { // check to see if we've already written it. if we have, we don't need to waste time on it. | ||
| data[name].security = {} | ||
| data[name].security.all = [] | ||
| } | ||
| data[name].releases[`v${versionSemver.version}`].lts.isLts = | ||
| !!versions[version].lts; | ||
| // the newest security release, which can be populated on the first run | ||
| if (!data[name].security.newest) { | ||
| if (versions[version].security === true) { | ||
| data[name].security.newest = versionSemver.version | ||
| } | ||
| } | ||
| // # Security | ||
| // ## define the release-line specific security object | ||
| if (!data[name].security) { | ||
| // check to see if we've already written it. if we have, we don't need to waste time on it. | ||
| data[name].security = {}; | ||
| data[name].security.all = []; | ||
| } | ||
| // same inefficient hack as we do in the LTS 'oldest' logic. ineffecient but gets the job done. | ||
| if (versions[version].security === true) { | ||
| data[name].security.oldest = versionSemver.version | ||
| } | ||
| // the newest security release, which can be populated on the first run | ||
| if (!data[name].security.newest) { | ||
| if (versions[version].security === true) { | ||
| data[name].security.newest = versionSemver.version; | ||
| } | ||
| } | ||
| // throw the current loop's iteration into the security.all array if it's a security release | ||
| if (versions[version].security === true) { | ||
| data[name].security.all.push(versionSemver.version) | ||
| } | ||
| // same inefficient hack as we do in the LTS 'oldest' logic. ineffecient but gets the job done. | ||
| if (versions[version].security === true) { | ||
| data[name].security.oldest = versionSemver.version; | ||
| } | ||
| // ## define the security object in each specfic version | ||
| data[name].releases[`v${versionSemver.version}`].security = {} | ||
| // throw the current loop's iteration into the security.all array if it's a security release | ||
| if (versions[version].security === true) { | ||
| data[name].security.all.push(versionSemver.version); | ||
| } | ||
| data[name].releases[`v${versionSemver.version}`].security.isSecurity = versions[version].security ?? false | ||
| }) | ||
| // ## define the security object in each specfic version | ||
| data[name].releases[`v${versionSemver.version}`].security = {}; | ||
| return data | ||
| } | ||
| data[name].releases[`v${versionSemver.version}`].security.isSecurity = | ||
| versions[version].security ?? false; | ||
| }); | ||
| async function determineCurrentReleasePhase (now, dates = {}) { | ||
| // we set set this up to enable custom `now` passing, since passing an ISO | ||
| // const usableNow = DateTime.fromISO(now) | ||
| // here we figure out if the dates for each release line passed is in the past or future | ||
| // `true` is in the past | ||
| // `false` is in the future | ||
| const isoified = { | ||
| start: isInPast(DateTime.fromISO(dates.start).diff(now).toMillis()) ?? undefined, | ||
| lts: isInPast(DateTime.fromISO(dates.lts).diff(now).toMillis()) ?? undefined, | ||
| maintenance: isInPast(DateTime.fromISO(dates.maintenance).diff(now).toMillis()) ?? undefined, | ||
| end: isInPast(DateTime.fromISO(dates.end).diff(now).toMillis()) ?? undefined | ||
| } | ||
| // set up our result to return | ||
| let result | ||
| // iterate over the past/future object and set the above variable to whatever the first date is in the future. | ||
| Object.keys(isoified).forEach(async (phase) => { | ||
| // since we're looping, the last true is the current phase | ||
| // since the start date will always be in the past | ||
| if (isoified[phase] === true) { | ||
| result = phase | ||
| } | ||
| }) | ||
| return result | ||
| return data; | ||
| } | ||
| // this function allows us to parse user-passed options. | ||
| // | ||
| // this is particularly useful for tests so we can reduce variables | ||
| // and ensure that our test suite is able to be consistent. | ||
| async function parseOptions (options) { | ||
| // set up our defaults | ||
| const parsedOptions = { | ||
| fetch: await defaultFetch, | ||
| now: DateTime.now(), | ||
| urls: { | ||
| index: 'https://nodejs.org/dist/index.json', | ||
| schedule: 'https://raw.githubusercontent.com/nodejs/Release/master/schedule.json' | ||
| } | ||
| } | ||
| async function determineCurrentReleasePhase(now, dates = {}) { | ||
| // we set set this up to enable custom `now` passing, since passing an ISO | ||
| // const usableNow = DateTime.fromISO(now) | ||
| // allow the end-user to replace our fetch implementation with another one of their precernece. | ||
| if (options?.fetch) { | ||
| parsedOptions.fetch = options.fetch | ||
| } | ||
| // here we figure out if the dates for each release line passed is in the past or future | ||
| // `true` is in the past | ||
| // `false` is in the future | ||
| const isoified = { | ||
| start: | ||
| isInPast(DateTime.fromISO(dates.start).diff(now).toMillis()) ?? undefined, | ||
| lts: | ||
| isInPast(DateTime.fromISO(dates.lts).diff(now).toMillis()) ?? undefined, | ||
| maintenance: | ||
| isInPast(DateTime.fromISO(dates.maintenance).diff(now).toMillis()) ?? | ||
| undefined, | ||
| end: | ||
| isInPast(DateTime.fromISO(dates.end).diff(now).toMillis()) ?? undefined, | ||
| }; | ||
| // allow the end-user to provide a custom DateTime. This is particularly useful for tests. | ||
| if (options?.now) { | ||
| parsedOptions.now = options.now | ||
| } | ||
| // set up our result to return | ||
| let result; | ||
| if (options?.urls?.index) { | ||
| parsedOptions.urls.index = options.urls.index | ||
| } | ||
| // iterate over the past/future object and set the above variable to whatever the first date is in the future. | ||
| for (const phase of Object.keys(isoified)) { | ||
| // since we're looping, the last true is the current phase | ||
| // since the start date will always be in the past | ||
| if (isoified[phase] === true) { | ||
| result = phase; | ||
| } | ||
| } | ||
| if (options?.urls?.schedule) { | ||
| parsedOptions.urls.schedule = options.urls.schedule | ||
| } | ||
| return parsedOptions | ||
| return result; | ||
| } | ||
@@ -232,13 +226,15 @@ | ||
| // where DATE is your date value | ||
| function isInPast (number) { | ||
| const sign = Math.sign(number) | ||
| if (sign === -1 || sign === 0) { | ||
| return true | ||
| } else if (sign === 1) { | ||
| return false | ||
| } else { | ||
| return undefined | ||
| } | ||
| function isInPast(number) { | ||
| const sign = Math.sign(number); | ||
| if (sign === -1 || sign === 0) { | ||
| return true; | ||
| } | ||
| if (sign === 1) { | ||
| return false; | ||
| } | ||
| return undefined; | ||
| } | ||
| module.exports = core | ||
| module.exports = core; |
+32
-32
| { | ||
| "name": "@nodevu/core", | ||
| "version": "0.1.0", | ||
| "description": "nodevu core API: comprehensive node.js version tooling", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "lint": "standard --env mocha", | ||
| "lint:fix": "standard --fix --env mocha", | ||
| "test": "node--test", | ||
| "test:update": "npm run test:update:static && npm run test:update:now", | ||
| "test:update:static": "node ./util/dev/updateStaticData.js", | ||
| "test:update:now": "node ./util/dev/updateStaticNow.js", | ||
| "coverage": "nyc node--test", | ||
| "updates:check": "npx npm-check-updates", | ||
| "updates:update": "npx npm-check-updates -u" | ||
| }, | ||
| "author": "Tierney Cyren <hello@bnb.im> (https://bnb.im/)", | ||
| "license": "MIT", | ||
| "files": [ | ||
| "index.js", | ||
| "LICENSE" | ||
| ], | ||
| "dependencies": { | ||
| "@nodevu/parsefiles": "^0.0.3", | ||
| "luxon": "^3.3.0", | ||
| "semver": "^7.5.1", | ||
| "undici": "^5.22.1" | ||
| }, | ||
| "devDependencies": { | ||
| "nyc": "^15.1.0", | ||
| "standard": "^17.0.0", | ||
| "test": "^3.3.0" | ||
| } | ||
| "name": "@nodevu/core", | ||
| "version": "0.2.0", | ||
| "description": "nodevu core API: comprehensive node.js version tooling", | ||
| "main": "index.js", | ||
| "scripts": { | ||
| "lint": "biome check ./", | ||
| "lint:write": "biome check ./ --write", | ||
| "test": "node--test", | ||
| "test:update": "npm run test:update:static && npm run test:update:now", | ||
| "test:update:static": "node ./util/dev/updateStaticData.js", | ||
| "test:update:now": "node ./util/dev/updateStaticNow.js", | ||
| "coverage": "nyc node--test", | ||
| "updates:check": "npx npm-check-updates", | ||
| "updates:update": "npx npm-check-updates -u" | ||
| }, | ||
| "author": "Tierney Cyren <hello@bnb.im> (https://bnb.im/)", | ||
| "license": "MIT", | ||
| "files": [ | ||
| "index.js", | ||
| "LICENSE" | ||
| ], | ||
| "dependencies": { | ||
| "@nodevu/parsefiles": "^0.0.3", | ||
| "luxon": "^3.5.0", | ||
| "semver": "^7.6.3" | ||
| }, | ||
| "devDependencies": { | ||
| "@biomejs/biome": "1.9.4", | ||
| "nyc": "^17.1.0", | ||
| "test": "^3.3.0", | ||
| "undici": "^6.20.1" | ||
| } | ||
| } |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Network access
Supply chain riskThis module accesses the network.
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
3
-25%1
-50%1
-88.89%17866
-6.72%4
33.33%196
-0.51%- Removed
- Removed
- Removed
Updated
Updated