diff --git a/README.md b/README.md index ae866e9..c3d0d9e 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ -# supported ![CI](https://github.com/stefanpenner/supported/workflows/CI/badge.svg) \ No newline at end of file +# supported ![CI](https://github.com/stefanpenner/supported/workflows/CI/badge.svg) + + +## Usage + +```sh +npx supported +``` \ No newline at end of file diff --git a/bin/supported b/bin/supported index 5edb4fe..34279f7 100755 --- a/bin/supported +++ b/bin/supported @@ -1,7 +1,9 @@ #!/usr/bin/env node 'use strict'; const ora = require('ora'); +const { writeFileSync } = require('fs'); const { displayResult } = require('../lib/output/cli-output'); +const { writeToCsv } = require('../lib/output/csv-output'); (async function main(cli) { if (cli.input.length === 0) { @@ -31,31 +33,38 @@ const { displayResult } = require('../lib/output/cli-output'); 2, ), ); + } else if(cli.flags.csv && result) { + let fileName = `${result.projectName}-support-audit.csv`; + let filePath = `${projectPath}/${fileName}`; + writeFileSync(filePath, writeToCsv(result), 'utf-8' ); + console.log(`Report created at ${filePath}`); } else { displayResult(result, cli.flags); } } -})(require('meow')(require('../lib/help'), { - flags: { - verbose: { - type: 'boolean', - alias: 'd' +})( + require('meow')(require('../lib/help'), { + flags: { + verbose: { + type: 'boolean', + alias: 'd', + }, + json: { + type: 'boolean', + alias: 'j', + }, + unsupported: { + type: 'boolean', + alias: 'u', + }, + supported: { + type: 'boolean', + alias: 's', + }, + expiring: { + type: 'boolean', + alias: 'e', + }, }, - json: { - type: 'boolean', - alias: 'j' - }, - unsupported: { - type: 'boolean', - alias: 'u' - }, - supported: { - type: 'boolean', - alias: 's' - }, - expiring: { - type: 'boolean', - alias: 'e' - }, - }, -})); + }), +); diff --git a/lib/help.js b/lib/help.js index 51b6fe8..06dcb66 100644 --- a/lib/help.js +++ b/lib/help.js @@ -15,6 +15,7 @@ module.exports = chalk` {cyan --unsupported, -u} outputs detailed report of unsupport packages only {cyan --expiring, -e} outputs detailed report of expiring packages only {cyan --supported, -s} outputs detailed report of support packages only + {cyan --csv} outputs csv file in the project path {bold Examples} {gray $} {cyan supported ./path/to/project/} `; diff --git a/lib/lts/ember-lts.json b/lib/lts/ember-lts.json index 6f8368f..ad8e041 100644 --- a/lib/lts/ember-lts.json +++ b/lib/lts/ember-lts.json @@ -1,14 +1,14 @@ { "3.16.*": { "versionRange": "3.16.*", - "start_date": "March 4, 2020", - "maintenance_start_date": "November 11, 2020", - "end_date": "March 17, 2021" + "start_date": "2020-03-04T00:00:00.000Z", + "maintenance_start_date": "2020-11-11T00:00:00.000Z", + "end_date": "2021-03-17T00:00:00.000Z" }, "3.20.*": { "versionRange": ">=3.20.*", - "start_date": "August 24, 2020", - "maintenance_start_date": "May 3, 2021", - "end_date": "September 6, 2021" + "start_date": "2020-08-24T00:00:00.000Z", + "maintenance_start_date": "2021-05-03T00:00:00.000Z", + "end_date": "2021-09-06T00:00:00.000Z" } } \ No newline at end of file diff --git a/lib/lts/index.js b/lib/lts/index.js index dac5415..867cb15 100644 --- a/lib/lts/index.js +++ b/lib/lts/index.js @@ -2,7 +2,7 @@ const NODE_LTS = require('./node-lts.json'); const EMBER_LTS = require('./ember-lts.json'); -const { isExpiringSoon } = require('../util'); +const { isExpiringSoon, dateDiff } = require('../util'); function sortByMinor(a, b) { return a.minor - b.minor; @@ -146,6 +146,7 @@ function isLtsOrLatest(info, resolvedVersion, currentDate /* this used for testi let packageName = 'node'; let message = ''; let duration = 0; + const today = currentDate || new Date(); if (info.type == 'node') { // Check when there is no node version could be found in package.json. @@ -162,7 +163,7 @@ function isLtsOrLatest(info, resolvedVersion, currentDate /* this used for testi const isMaintenanceLts = new Date(data.maintenance_start_date) <= today; if (isMaintenanceLts) { message = 'Using maintenance LTS. Update to latest LTS'; - duration = new Date(data.end_date) - today; + duration = dateDiff(new Date(data.end_date), today); } return true; } @@ -178,13 +179,14 @@ function isLtsOrLatest(info, resolvedVersion, currentDate /* this used for testi // if maintenance version is in the list of ember LTS then do not match semver.gtr. // We discourage people from using 3.18 when 3.20 is active LTS and 3.16 is maintenance. const isMaintenanceLts = new Date(data.maintenance_start_date) <= today; + if ( semver.satisfies(resolvedVersion, versionRange) || (!isMaintenanceLts && semver.gtr(resolvedVersion, versionRange)) ) { if (isMaintenanceLts) { message = 'Using maintenance LTS. Update to latest LTS'; - duration = new Date(data.end_date) - today; + duration = dateDiff(new Date(data.end_date), today); } return true; } @@ -192,18 +194,21 @@ function isLtsOrLatest(info, resolvedVersion, currentDate /* this used for testi } if (isSupported) { - let returnVal = { + const returnVal = { isSupported: true, resolvedVersion, latestVersion: getCurrentLts(ltsList, packageName, currentDate), }; + if (isExpiringSoon(duration)) { returnVal['duration'] = duration; returnVal['message'] = message; } + if (!duration && message) { returnVal['message'] = message; } + return returnVal; } else { let version = ''; @@ -215,10 +220,11 @@ function isLtsOrLatest(info, resolvedVersion, currentDate /* this used for testi today <= new Date(data.maintenance_start_date) ) { version = key; - duration = today - new Date(data.start_date); + duration = dateDiff(today, new Date(data.start_date)); return true; } }); + return { isSupported: false, message: `${packageName} needs to be on v${version} or above LTS version`, diff --git a/lib/lts/node-lts.json b/lib/lts/node-lts.json index edfe6b7..ee1a820 100644 --- a/lib/lts/node-lts.json +++ b/lib/lts/node-lts.json @@ -1,26 +1,26 @@ { "10.*": { "versionRange": "10.*", - "start_date": "Oct 30 2018", - "maintenance_start_date": "May 19 2020", - "end_date": "Apr 30 2021" + "start_date": "2018-10-30T00:00:00.000Z", + "maintenance_start_date": "2020-05-19T00:00:00.000Z", + "end_date": "2021-04-30T00:00:00.000Z" }, "12.*": { "versionRange": "12.*", - "start_date": "Oct 21 2019", - "maintenance_start_date": "Nov 30 2020", - "end_date": "Apr 30 2022" + "start_date": "2019-10-21T00:00:00.000Z", + "maintenance_start_date": "2020-11-30T00:00:00.000Z", + "end_date": "2022-04-30T00:00:00.000Z" }, "14.*": { "versionRange": ">=14.*", - "start_date": "Oct 27 2020", - "maintenance_start_date": "Oct 19 2021", - "end_date": "Apr 30 2023" + "start_date": "2020-10-27T00:00:00.000Z", + "maintenance_start_date": "2021-10-19T00:00:00.000Z", + "end_date": "2023-04-30T00:00:00.000Z" }, "16.*": { "versionRange": ">=16.*", - "start_date": "2021-10-26", - "maintenance_start_date": "2022-10-18", - "end_date": "2024-04-30" + "start_date": "2021-10-26T00:00:00.000Z", + "maintenance_start_date": "2022-10-18T00:00:00.000Z", + "end_date": "2024-04-30T00:00:00.000Z" } } diff --git a/lib/npm/config.js b/lib/npm/config.js index 67bc2ea..7b38dc7 100644 --- a/lib/npm/config.js +++ b/lib/npm/config.js @@ -2,6 +2,7 @@ const execa = require('execa'); const camelcaseKeys = require('camelcase-keys'); + module.exports = async function (cwd) { const child = execa.sync('npm', ['config', 'list', '--json'], { cwd, diff --git a/lib/output/cli-output.js b/lib/output/cli-output.js index 43e8061..e122b27 100644 --- a/lib/output/cli-output.js +++ b/lib/output/cli-output.js @@ -103,7 +103,7 @@ function getBodyContent(results, flags) { supportedTable.push([ chalk` {dim ${name}}`, chalk`{dim ${resolvedVersion}}`, - chalk`{dim ${latestVersion}}` + chalk`{dim ${latestVersion}}`, ]); } } @@ -160,7 +160,12 @@ function getLtsViolation(packageInfo) { * @param {boolean | undefined} isWarning: is it only warning not violation * @returns {string} formatted package semver violation title string */ -function getPackageViolationTitle(violatingPackages, totalSemVerViolation, totalPackages, isWarning) { +function getPackageViolationTitle( + violatingPackages, + totalSemVerViolation, + totalPackages, + isWarning, +) { if (violatingPackages.length) { return LOG_SEMVER_TITLE(totalSemVerViolation, totalPackages, isWarning); } @@ -286,24 +291,23 @@ function getHead( /** * - * @param {object} reportContent : processed details from the displayContent - * @returns {string} formatted output for the support check run + * @param {object} supportResult : support check result + * */ -function makeConsoleReport(reportContent) { - const { - body, - isInSupportWindow, - currentPolicy, +function makeConsoleReport(supportResult, flags, supportMessage) { + const isInSupportWindow = supportResult.isInSupportWindow; + const currentPolicy = supportMessage ? supportMessage : DEFAULT_SUPPORT_MESSAGE(); + let { expiresSoon, unsupportedPackages, - supportedPackages, nodePackage, emberPackage, - } = reportContent; + supportedPackages, + } = getCategorisedList(supportResult.supportChecks); - let title = getTitle(isInSupportWindow, expiresSoon, nodePackage, emberPackage); + const title = getTitle(isInSupportWindow, expiresSoon, nodePackage, emberPackage); - let head = getHead( + const head = getHead( isInSupportWindow, unsupportedPackages, supportedPackages, @@ -313,9 +317,13 @@ function makeConsoleReport(reportContent) { currentPolicy, ); - return `${title}${head} -${body} -`; + const body = getBodyContent(supportResult.supportChecks, flags); + + return { + title, + head, + body, + }; } /** @@ -408,36 +416,17 @@ function getCategorisedList(pkgList) { * */ function displayResult(supportResult, flags, supportMessage) { - const title = supportResult.projectName; - const isInSupportWindow = supportResult.isInSupportWindow; - const currentPolicy = supportMessage ? supportMessage : DEFAULT_SUPPORT_MESSAGE(); - let { - expiresSoon, - unsupportedPackages, - nodePackage, - emberPackage, - supportedPackages, - } = getCategorisedList(supportResult.supportChecks); + const { title, head, body } = makeConsoleReport(supportResult, flags, supportMessage); - console.log( - makeConsoleReport({ - title, - isInSupportWindow, - body: getBodyContent(supportResult.supportChecks, flags), - currentPolicy, - expiresSoon, - unsupportedPackages, - supportedPackages, - nodePackage, - emberPackage, - }), - ); + console.log(`${title}${head} + ${body}`); } module.exports = { displayResult, - getBodyContent, getCategorisedList, + getBodyContent, getHead, getTitle, + makeConsoleReport, }; diff --git a/lib/output/csv-output.js b/lib/output/csv-output.js new file mode 100644 index 0000000..1da24ba --- /dev/null +++ b/lib/output/csv-output.js @@ -0,0 +1,63 @@ +'use strict'; + +const { Parser } = require('json2csv'); +const stripAnsi = require('strip-ansi'); +const { makeConsoleReport } = require('./cli-output'); +const { MILLSINQUARTER } = require('../util'); +const { getQtrLocale } = require('./messages'); + +module.exports = { + writeToCsv, +}; +function writeToCsv(result, policyDetails) { + const fields = [ + { + label: 'Name', + value: 'name', + }, + { + label: 'Supported', + value: 'isSupported', + }, + { + label: 'Status', + value: row => { + if (!row.isSupported) { + return `${row.type} version violated`; + } else if (row.isSupported && row.duration) { + return `${row.type ? row.type : ''} version expiring soon`; + } else { + return `up-to-date`; + } + }, + }, + { + label: 'Unsupported Since/In', + value: row => { + if (row.duration) { + const qtrs = Math.ceil(row.duration / MILLSINQUARTER); + return `${qtrs} ${getQtrLocale(qtrs)}`; + } else { + return `-`; + } + }, + }, + { + label: 'Resolved Version', + value: 'resolvedVersion', + }, + { + label: 'Latest Version', + value: 'latestVersion', + }, + ]; + + const json2csvParser = new Parser({ fields }); + const csv = json2csvParser.parse(result.supportChecks); + const { title, head } = makeConsoleReport(result, {}, policyDetails); + const fileContent = `${stripAnsi(title)}${stripAnsi(head)} + + ${csv} + `; + return fileContent; +} diff --git a/lib/output/messages.js b/lib/output/messages.js index b81d61a..1876713 100644 --- a/lib/output/messages.js +++ b/lib/output/messages.js @@ -27,7 +27,9 @@ const LOG_SEMVER_VIOLATION = (type, total, duration, isWarning) => { } else { let qtrs = Math.ceil(duration / MILLSINQUARTER); return chalk` - {yellow ⚠ ${type} [${total} ${getDepLocale(total)} will expire within ${qtrs} ${getQtrLocale(qtrs)}}]`; + {yellow ⚠ ${type} [${total} ${getDepLocale(total)} will expire within ${qtrs} ${getQtrLocale( + qtrs, + )}}]`; } }; @@ -43,13 +45,15 @@ const LOG_LTS_VIOLATION = (type, isSupported, duration, message, resolvedVersion const LTS_POLICY = `${type} LTS Policy`; if (!isSupported) { return chalk`{red ✗} ${LTS_POLICY}\n {red ✗ ${message}}`; - } else if (isSupported && ( message || duration)) { + } else if (isSupported && (message || duration)) { let qtrs = Math.ceil(duration / MILLSINQUARTER); return chalk`{yellow ⚠} ${LTS_POLICY} ${ !duration ? chalk`{yellow ⚠ ${message}}` - : chalk`{yellow ⚠ version/version-range ${resolvedVersion} will be deprecated within ${qtrs} ${getQtrLocale(qtrs)}}` + : chalk`{yellow ⚠ version/version-range ${resolvedVersion} will be deprecated within ${qtrs} ${getQtrLocale( + qtrs, + )}}` }`; } return chalk`{green ✓} {dim ${LTS_POLICY}}`; diff --git a/lib/project/index.js b/lib/project/index.js index fc8a2b5..d4b67b8 100644 --- a/lib/project/index.js +++ b/lib/project/index.js @@ -25,39 +25,43 @@ module.exports = async function isInSupportWindow(projectRoot) { const { object: lockfile } = YarnLockfile.parse(fs.readFileSync(lockfilePath, 'utf-8')); const dependenciesToCheck = []; - for (const [name, version] of Object.entries(pkg.dependencies)) { - const key = `${name}@${version}`; - - if (!(key in lockfile)) { - throw new Error(`could not find: '${key}' in '${lockfilePath}'`); - } - if (semverCoerce(version)) { - dependenciesToCheck.push({ - name, - version, - type: 'dependency', - resolvedVersion: lockfile[key].version, - }); - } else { - debug('Invalid version/local link found for %o %o ', name, version); + if (pkg.dependencies) { + for (const [name, version] of Object.entries(pkg.dependencies)) { + const key = `${name}@${version}`; + + if (!(key in lockfile)) { + throw new Error(`could not find: '${key}' in '${lockfilePath}'`); + } + if (semverCoerce(version)) { + dependenciesToCheck.push({ + name, + version, + type: 'dependency', + resolvedVersion: lockfile[key].version, + }); + } else { + debug('Invalid version/local link found for %o %o ', name, version); + } } } - for (const [name, version] of Object.entries(pkg.devDependencies)) { - const key = `${name}@${version}`; + if (pkg.devDependencies) { + for (const [name, version] of Object.entries(pkg.devDependencies)) { + const key = `${name}@${version}`; - if (!(key in lockfile)) { - throw new Error(`could not find: '${key}' in '${lockfilePath}'`); - } + if (!(key in lockfile)) { + throw new Error(`could not find: '${key}' in '${lockfilePath}'`); + } - if (semverCoerce(version)) { - dependenciesToCheck.push({ - name, - version, - type: 'devDependency', - resolvedVersion: lockfile[key].version, - }); - } else { - debug('Invalid version/local link found for %o %o ', name, version); + if (semverCoerce(version)) { + dependenciesToCheck.push({ + name, + version, + type: 'devDependency', + resolvedVersion: lockfile[key].version, + }); + } else { + debug('Invalid version/local link found for %o %o ', name, version); + } } } diff --git a/lib/time/index.js b/lib/time/index.js index d2b2e96..961299f 100644 --- a/lib/time/index.js +++ b/lib/time/index.js @@ -3,7 +3,7 @@ // TODO: refactor this file const parsePackageName = require('parse-package-name'); const semver = require('semver'); -const { isExpiringSoon } = require('../util'); +const { isExpiringSoon, dateDiff } = require('../util'); module.exports.supportedRanges = supportedRanges; const POLICY_TYPES = ['major', 'minor', 'patch']; @@ -71,7 +71,7 @@ function supported(info, packageName, policies) { continue; } - const result = current - policy.date; + const result = dateDiff(current, policy.date); if (isNaN(result)) { throw new Error(`Invalid Date: ${current} - ${policy.date}`); } diff --git a/lib/util.js b/lib/util.js index b12b932..ebc4ea1 100644 --- a/lib/util.js +++ b/lib/util.js @@ -1,5 +1,7 @@ 'use strict'; +const moment = require('moment'); + // 91 days in a quarter, 24hrs per day, 60 minutes per hour, 60 seconds per hour, 1000 millseconds per sec const MILLSINQUARTER = 91 * 24 * 60 * 60 * 1000; @@ -15,6 +17,13 @@ function isExpiringSoon(timeDiff) { return timeDiff && Math.ceil(timeDiff / MILLSINQUARTER) < THRESHOLDQTRS; } +function dateDiff(a, b) { + const utc1 = moment.utc(a); + const utc2 = moment.utc(b); + + return utc1.diff(utc2); +} + /** * Sorts library in the following order * - unsupported at the top @@ -58,4 +67,5 @@ module.exports = { isExpiringSoon, MILLSINQUARTER, sortLibraries, + dateDiff, }; diff --git a/package.json b/package.json index 4643729..924f96c 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,8 @@ "scripts": { "lint": "eslint .", "test": "mocha tests/**/*-test.js", - "test:inspect": "mocha inspect tests/**/*-test.js" + "test:inspect": "mocha inspect tests/**/*-test.js", + "start:registry": "node -e 'require(`./tests/registries.js`).startAll()'" }, "volta": { "node": "15.3.0", @@ -45,8 +46,10 @@ "debug": "^4.3.1", "execa": "^5.0.0", "ini": "^1.3.7", + "json2csv": "^5.0.6", "meow": "^8.0.0", "minipass-fetch": "^1.3.2", + "moment": "^2.29.1", "npm-package-arg": "^8.1.0", "npm-registry-fetch": "^9.0.0", "ora": "^5.1.0", @@ -54,6 +57,7 @@ "parse-package-name": "^0.1.0", "promise.allsettled": "^1.0.4", "semver": "^7.3.4", + "strip-ansi": "^6.0.0", "terminal-link": "^2.1.1" }, "files": [ diff --git a/tests/cli-test.js b/tests/cli-test.js index b0851d5..f672673 100644 --- a/tests/cli-test.js +++ b/tests/cli-test.js @@ -2,8 +2,8 @@ const { expect } = require('chai'); const execa = require('execa'); -const fs = require('fs'); const { getBinPath } = require('get-bin-path'); +const fs = require('fs'); const registries = require('./registries'); async function runSupportedCmd(inputArgs) { @@ -11,13 +11,10 @@ async function runSupportedCmd(inputArgs) { if (inputArgs && inputArgs.length) { args.push.apply(args, inputArgs); } - return execa( - 'node', args, - { - shell: true, - reject: false, - }, - ) + return execa('node', args, { + shell: true, + reject: false, + }); } describe('CLI', function () { @@ -80,16 +77,15 @@ describe('CLI', function () { describe('--verbose', function () { it('works against a unsupported project', async function () { - const child = await runSupportedCmd([`${__dirname}/fixtures/unsupported-project`, '--verbose']); + const child = await runSupportedCmd([ + `${__dirname}/fixtures/unsupported-project`, + '--verbose', + ]); expect(child.exitCode).to.eql(1); expect(child.stderr).to.eql('- working'); expect(child.stdout).to.includes('Support Policy Problem Detected!'); - expect(child.stdout).to.includes( - ' @eslint-ast/eslint-plugin-graphql 1.0.4 1.0.4', - ); - expect(child.stdout).to.includes( - 'es6-promise 3.3.1 4.2.8 major 7 qtrs', - ); + expect(child.stdout).to.includes(' @eslint-ast/eslint-plugin-graphql 1.0.4 1.0.4'); + expect(child.stdout).to.includes('es6-promise 3.3.1 4.2.8 major 7 qtrs'); }); it('works against a supported project', async function () { @@ -102,42 +98,46 @@ describe('CLI', function () { }); it('works against a version expires soon project', async function () { - const child = await runSupportedCmd([`${__dirname}/fixtures/version-expire-soon`, '--verbose']) + const child = await runSupportedCmd([ + `${__dirname}/fixtures/version-expire-soon`, + '--verbose', + ]); expect(child.exitCode).to.eql(0); expect(child.stderr).to.eql('- working'); expect(child.stdout).to.includes('⚠ Warning!'); - expect(child.stdout).to.includes( - `@stefanpenner/a 1.0.3 2.0.0 major 3 qtrs`, - ); - expect(child.stdout).to.includes( - `node 10.0.0 >=14.* LTS 1 qtr`, - ); + expect(child.stdout).to.includes(`@stefanpenner/a 1.0.3 2.0.0 major 3 qtrs`); + expect(child.stdout).to.includes(`node 10.0.0 >=14.* LTS 1 qtr`); }); }); - describe('Filter options like --unsupported/expiring/supported', function() { + describe('Filter options like --unsupported/expiring/supported', function () { it('works against a unsupported project with --unsupported option', async function () { - const child = await runSupportedCmd([`${__dirname}/fixtures/unsupported-project`, '--unsupported']); + const child = await runSupportedCmd([ + `${__dirname}/fixtures/unsupported-project`, + '--unsupported', + ]); expect(child.exitCode).to.eql(1); expect(child.stderr).to.eql('- working'); expect(child.stdout).to.includes('Support Policy Problem Detected!'); - expect(child.stdout).to.includes( - 'es6-promise 3.3.1 4.2.8 major 7 qtrs', - ); + expect(child.stdout).to.includes('es6-promise 3.3.1 4.2.8 major 7 qtrs'); }); it('works against a unsupported project with --supported option', async function () { - const child = await runSupportedCmd([`${__dirname}/fixtures/unsupported-project`, '--supported']); + const child = await runSupportedCmd([ + `${__dirname}/fixtures/unsupported-project`, + '--supported', + ]); expect(child.exitCode).to.eql(1); expect(child.stderr).to.eql('- working'); expect(child.stdout).to.includes('Support Policy Problem Detected!'); - expect(child.stdout).to.includes( - '@eslint-ast/eslint-plugin-graphql 1.0.4 1.0.4', - ); + expect(child.stdout).to.includes('@eslint-ast/eslint-plugin-graphql 1.0.4 1.0.4'); }); it('works against a unsupported project with --expiring option', async function () { - const child = await runSupportedCmd([`${__dirname}/fixtures/unsupported-project`, '--expiring']); + const child = await runSupportedCmd([ + `${__dirname}/fixtures/unsupported-project`, + '--expiring', + ]); expect(child.exitCode).to.eql(1); expect(child.stderr).to.eql('- working'); expect(child.stdout).to.includes('Support Policy Problem Detected!'); @@ -146,6 +146,20 @@ describe('CLI', function () { ); }); }); + describe('--csv', function () { + afterEach(function () { + let filePath = `${__dirname}/fixtures/unsupported-project/unsupported-project-support-audit.csv`; + if (fs.existsSync(filePath)) fs.unlinkSync(filePath); + }); + it('works against a unsupported project', async function () { + const child = await runSupportedCmd([`${__dirname}/fixtures/unsupported-project`, '--csv']); + expect(child.exitCode).to.eql(1); + expect(child.stderr).to.eql('- working'); + expect(child.stdout).to.includes( + `Report created at ${__dirname}/fixtures/unsupported-project`, + ); + }); + }); describe('--json', function () { it('works against a fully supported project', async function () { const child = await runSupportedCmd([`${__dirname}/fixtures/supported-project`, '--json']); diff --git a/tests/csv-output-test.js b/tests/csv-output-test.js new file mode 100644 index 0000000..45c0591 --- /dev/null +++ b/tests/csv-output-test.js @@ -0,0 +1,56 @@ +'use strict'; + +const { expect } = require('chai'); +const { writeToCsv } = require('../lib/output/csv-output'); + +describe('csv', function () { + it('supported project', function () { + let supportResult = { + projectName: 'example', + isInSupportWindow: true, + supportChecks: [ + { + isSupported: true, + latestVersion: '4.8.5', + name: 'rsvp', + resolvedVersion: '4.8.5', + }, + { + isSupported: true, + name: 'node', + resolvedVersion: '15.3.0', + latestVersion: '>=14.*', + message: '', + }, + ], + }; + let result = writeToCsv(supportResult); + expect(result).to.includes(`✓ Congrats! + Your project is using only supported versions of libraries. No action is required.`); + }); + it('unsupported project', function () { + let supportResult = { + projectName: 'example', + isInSupportWindow: false, + supportChecks: [ + { + isSupported: false, + latestVersion: '4.8.5', + name: 'rsvp', + resolvedVersion: '4.5.5', + }, + { + isSupported: true, + name: 'node', + resolvedVersion: '15.3.0', + latestVersion: '>=14.*', + message: '', + }, + ], + }; + let result = writeToCsv(supportResult); + expect(result).to.includes(`Support Policy Problem Detected! + Please upgrade your dependencies! + Your project is not within the support policy window because of outdated dependencies.`); + }); +}); diff --git a/tests/ember-lts-test.js b/tests/ember-lts-test.js index 8f55785..c5caa3a 100644 --- a/tests/ember-lts-test.js +++ b/tests/ember-lts-test.js @@ -216,33 +216,36 @@ describe('ember LTS Policy based policy', function () { describe('isLtsOrLatest', function () { it('resolved version is LTS', function () { - let currentDate = new Date(`Feb 24, 2021`); + let currentDate = new Date(`2021-02-24T22:56:00.185Z`); expect(isLtsOrLatest({}, '3.16.0', currentDate)).to.eql({ isSupported: true, - duration: 1810800000, + duration: 1731839815, message: 'Using maintenance LTS. Update to latest LTS', latestVersion: '>=3.20.*', resolvedVersion: '3.16.0', }); }); + it('resolved version is older version', function () { - let currentDate = new Date(`Feb 22, 2021`); + let currentDate = new Date(`2021-02-22T22:56:00.185Z`); expect(isLtsOrLatest({}, '3.14.0', currentDate)).to.eql({ isSupported: false, - duration: 15728400000, + duration: 15807360185, message: 'ember-cli needs to be on v3.20.* or above LTS version', type: 'ember', }); }); + it('Above maintenance LTS, update to next LTS', function () { - let currentDate = new Date(`Feb 22, 2021`); + let currentDate = new Date(`2021-02-24T22:56:00.185Z`); expect(isLtsOrLatest({}, '3.18.0', currentDate)).to.eql({ isSupported: false, - duration: 15728400000, + duration: 15980160185, message: 'ember-cli needs to be on v3.20.* or above LTS version', type: 'ember', }); }); + it('resolved version is LTS latest', function () { expect(isLtsOrLatest({}, '3.20.0')).to.eql({ isSupported: true, @@ -250,6 +253,7 @@ describe('ember LTS Policy based policy', function () { resolvedVersion: '3.20.0', }); }); + it('resolved version is Latest', function () { expect(isLtsOrLatest({}, '3.25.0')).to.eql({ isSupported: true, @@ -257,6 +261,7 @@ describe('ember LTS Policy based policy', function () { resolvedVersion: '3.25.0', }); }); + it('throws error when LTS file is not updated', function () { expect(() => isLtsOrLatest({}, '3.25.0', new Date('September 7, 2021'))).to.throw( 'Please create PR to update lts ember-cli-lts.json file in lts/ folder or create an issue in supported project', diff --git a/tests/node-lts-test.js b/tests/node-lts-test.js index 3181542..938cee9 100644 --- a/tests/node-lts-test.js +++ b/tests/node-lts-test.js @@ -3,6 +3,7 @@ const { expect } = require('chai'); const { isLtsOrLatest } = require('../lib/lts/index'); const NODE_LTS = require('../lib/lts/node-lts.json'); + describe('node LTS Policy based policy', function () { /* * @@ -33,15 +34,16 @@ describe('node LTS Policy based policy', function () { describe('validates node versions', function () { it('node version with range', function () { - let currentDate = new Date(`Feb 24, 2021`); + let currentDate = new Date(`2021-02-24T00:00:00.000Z`); expect(isLtsOrLatest({ type: 'node' }, '10.* || 12.* || 14.* || >= 15', currentDate)).to.eql({ isSupported: true, - duration: 5612400000, + duration: 5616000000, message: 'Using maintenance LTS. Update to latest LTS', resolvedVersion: '10.* || 12.* || 14.* || >= 15', latestVersion: '>=14.*', }); }); + it('node version with above current LTS range', function () { expect(isLtsOrLatest({ type: 'node' }, '15.3.0')).to.eql({ isSupported: true, @@ -49,6 +51,7 @@ describe('node LTS Policy based policy', function () { resolvedVersion: '15.3.0', }); }); + it('node version with fixed value in current LTS range', function () { expect(isLtsOrLatest({ type: 'node' }, '14.3.0')).to.eql({ isSupported: true, @@ -56,34 +59,38 @@ describe('node LTS Policy based policy', function () { resolvedVersion: '14.3.0', }); }); + it('node version with below and in support range value', function () { - let currentDate = new Date(`Feb 24, 2021`); + let currentDate = new Date(`2021-02-24T00:00:00.000Z`); expect(isLtsOrLatest({ type: 'node' }, '8.* || 10.*', currentDate)).to.eql({ isSupported: true, - duration: 5612400000, + duration: 5616000000, message: 'Using maintenance LTS. Update to latest LTS', latestVersion: '>=14.*', resolvedVersion: '8.* || 10.*', }); }); + it('node version with fixed value below LTS range', function () { - const fakeToday = new Date(`Feb 22, 2021`); + const fakeToday = new Date(`2021-02-22T00:00:00.000Z`); expect(isLtsOrLatest({ type: 'node' }, '8.0.0', fakeToday)).to.eql({ isSupported: false, - duration: 10198800000, + duration: 10195200000, message: `node needs to be on v14.* or above LTS version`, type: 'node', }); }); + it('node version with range value below LTS', function () { - const fakeToday = new Date(`Feb 22, 2021`); + const fakeToday = new Date(`2021-02-22T00:00:00.000Z`); expect(isLtsOrLatest({ type: 'node' }, '6.* || 8.*', fakeToday)).to.eql({ isSupported: false, - duration: 10198800000, + duration: 10195200000, message: `node needs to be on v14.* or above LTS version`, type: 'node', }); }); + it('node version invalid after end of LTS date', function () { const lastDay = new Date(NODE_LTS['10.*'].end_date); const nextDay = new Date(lastDay); @@ -95,6 +102,7 @@ describe('node LTS Policy based policy', function () { type: 'node', }); }); + it('node version is valid till last day', function () { const lastDay = new Date(NODE_LTS['10.*'].end_date); expect(isLtsOrLatest({ type: 'node' }, '10.2.0', lastDay)).to.eql({ @@ -104,6 +112,7 @@ describe('node LTS Policy based policy', function () { latestVersion: '>=14.*', }); }); + it('node version not found', function () { expect(isLtsOrLatest({ type: 'node' }, '0.0.0')).to.eql({ isSupported: true, diff --git a/tests/time-based-test.js b/tests/time-based-test.js index cbbf674..1329785 100644 --- a/tests/time-based-test.js +++ b/tests/time-based-test.js @@ -122,14 +122,18 @@ describe('time based policy: 1 year for major, 6 months for minor, 3 months of p const policies = supportedRanges(info.time[info.version]); expect(supported(info, 'console-ui@3.1.2', policies)).to.eql({ isSupported: true }); - expect(supported(info, 'console-ui@3.1.0', policies)).to.eql({ - duration: 36282002692, + let result = supported(info, 'console-ui@3.1.0', policies); + expect(typeof result.duration).to.eql('number'); + delete result.duration; + expect(result).to.eql({ isSupported: false, message: 'violated: patch version must be within 3 months of latest', type: 'patch', }); - expect(supported(info, 'console-ui@2.0.0', policies)).to.eql({ - duration: 70592816607, + result = supported(info, 'console-ui@2.0.0', policies); + expect(typeof result.duration).to.eql('number'); + delete result.duration; + expect(result).to.eql({ isSupported: false, message: 'violated: major version must be within 1 year of latest', type: 'major', @@ -143,8 +147,16 @@ describe('time based policy: 1 year for major, 6 months for minor, 3 months of p const policies = supportedRanges(info.time[info.version]); expect(supported(info, 'ember-cli@3.22.0', policies)).to.eql({ isSupported: true }); - expect(supported(info, 'ember-cli@3.21.0', policies)).to.eql({ duration: 11840496435, isSupported: true, type: "minor" }); - expect(supported(info, 'ember-cli@3.20.0', policies)).to.eql({ duration: 8756624607, isSupported: true, type: "minor" }); + expect(supported(info, 'ember-cli@3.21.0', policies)).to.eql({ + duration: 11840496435, + isSupported: true, + type: 'minor', + }); + expect(supported(info, 'ember-cli@3.20.0', policies)).to.eql({ + duration: 8756624607, + isSupported: true, + type: 'minor', + }); expect(supported(info, 'ember-cli@3.12.1', policies)).to.eql({ duration: 11061239038, isSupported: false, diff --git a/tests/util-test.js b/tests/util-test.js index 58772e1..6f2160c 100644 --- a/tests/util-test.js +++ b/tests/util-test.js @@ -3,9 +3,9 @@ const { expect } = require('chai'); const { sortLibraries } = require('../lib/util'); -describe('util test', function() { - describe('sort packages', function(){ - it('sorts unsupported to top', function() { +describe('util test', function () { + describe('sort packages', function () { + it('sorts unsupported to top', function () { let input = [ { isSupported: true, @@ -98,7 +98,8 @@ describe('util test', function() { name: 'test6', }, ]; + expect(input.sort(sortLibraries)).to.be.eql(result); }); }); -}); \ No newline at end of file +}); diff --git a/yarn.lock b/yarn.lock index e21ca05..91acd9c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -455,6 +455,11 @@ color-name@~1.1.4: resolved "https://registry.yarnpkg.com/color-name/-/color-name-1.1.4.tgz#c2a09a87acbde69543de6f63fa3995c826c536a2" integrity sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA== +commander@^6.1.0: + version "6.2.1" + resolved "https://registry.yarnpkg.com/commander/-/commander-6.2.1.tgz#0792eb682dfbc325999bb2b84fddddba110ac73c" + integrity sha512-U7VdrJFnJgo4xjrHpTzu0yrHPGImdsmD95ZlgYSEajAn2JKzDhDTPG9kBTefmObL2w/ngeZnilk+OV9CG3d7UA== + concat-map@0.0.1: version "0.0.1" resolved "https://registry.yarnpkg.com/concat-map/-/concat-map-0.0.1.tgz#d8a96bd77fd68df7793a73036a3ba0d5405d477b" @@ -1471,6 +1476,15 @@ json-stable-stringify-without-jsonify@^1.0.1: resolved "https://registry.yarnpkg.com/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz#9db7b59496ad3f3cfef30a75142d2d930ad72651" integrity sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE= +json2csv@^5.0.6: + version "5.0.6" + resolved "https://registry.yarnpkg.com/json2csv/-/json2csv-5.0.6.tgz#590e0e1b9579e59baa53bda0c0d840f4d8009687" + integrity sha512-0/4Lv6IenJV0qj2oBdgPIAmFiKKnh8qh7bmLFJ+/ZZHLjSeiL3fKKGX3UryvKPbxFbhV+JcYo9KUC19GJ/Z/4A== + dependencies: + commander "^6.1.0" + jsonparse "^1.3.1" + lodash.get "^4.4.2" + jsonparse@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/jsonparse/-/jsonparse-1.3.1.tgz#3f4dae4a91fac315f71062f8521cc239f1366280" @@ -1592,6 +1606,16 @@ locate-path@^6.0.0: dependencies: p-locate "^5.0.0" +lodash.get@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/lodash.get/-/lodash.get-4.4.2.tgz#2d177f652fa31e939b4438d5341499dfa3825e99" + integrity sha1-LRd/ZS+jHpObRDjVNBSZ36OCXpk= + +lodash.toarray@^4.4.0: + version "4.4.0" + resolved "https://registry.yarnpkg.com/lodash.toarray/-/lodash.toarray-4.4.0.tgz#24c4bfcd6b2fba38bfd0594db1179d8e9b656561" + integrity sha1-JMS/zWsvuji/0FlNsRedjptlZWE= + lodash@^4.17.14, lodash@^4.17.19: version "4.17.20" resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.20.tgz#b44a9b6297bcb698f1c51a3545a2b3b368d59c52" @@ -1822,6 +1846,11 @@ mocha@^8.2.1: yargs-parser "13.1.2" yargs-unparser "2.0.0" +moment@^2.29.1: + version "2.29.1" + resolved "https://registry.yarnpkg.com/moment/-/moment-2.29.1.tgz#b2be769fa31940be9eeea6469c075e35006fa3d3" + integrity sha512-kHmoybcPV8Sqy59DwNDY3Jefr64lK/by/da0ViFcuA4DH0vQg5Q6Ze5VimxkfQNSC+Mls/Kx53s7TjP1RhFEDQ== + ms@2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/ms/-/ms-2.0.0.tgz#5608aeadfc00be6c2901df5f9861788de0d597c8"