From 17a2bbde91a882ceabe8e6b58fc25ae18f8b7a70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9e=20Kooi?= Date: Fri, 18 Sep 2020 16:25:05 +0200 Subject: [PATCH 1/7] support postcss 8 plugins, closes #5 (#6) --- .travis.yml | 14 ++++++++- package.json | 13 +++++--- src/index.js | 42 +++++++++++++++++++++----- test/{plugin.js => postcss7-plugin.js} | 0 test/postcss8-plugin.js | 14 +++++++++ 5 files changed, 70 insertions(+), 13 deletions(-) rename test/{plugin.js => postcss7-plugin.js} (100%) create mode 100644 test/postcss8-plugin.js diff --git a/.travis.yml b/.travis.yml index 12ea3cd..3e1b0d4 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,9 +8,21 @@ os: - osx node_js: + - 14 - 12 - 10 - - 8 + +jobs: + include: + - node_js: 8 + os: windows + before_script: | + npm install postcss@7 + nvs add 12 + nvs use 12 + npm run pretest:tape + nvs use 8 + script: npm run test:tape:7 -- --ci true install: - git config --global core.autocrlf false diff --git a/package.json b/package.json index ee567a2..84f94d7 100644 --- a/package.json +++ b/package.json @@ -21,20 +21,25 @@ "pretest:tape": "npm run build", "test": "npm run test:js && npm run test:tape", "test:js": "eslint src/{*,**/*}.js --cache --ignore-pattern .gitignore", - "test:tape": "node . --plugin test/plugin.js --config test", - "test:tape:ci": "node . --ci true --plugin test/plugin.js --config test" + "test:tape:7": "node . --plugin test/postcss7-plugin.js --config test", + "test:tape:8": "node . --plugin test/postcss8-plugin.js --config test", + "test:tape": "npm run test:tape:7 && npm run test:tape:8", + "test:tape:ci": "npm run test:tape:7 -- --ci true && npm run test:tape:8 -- --ci true" }, "engines": { "node": ">=8.0.0" }, + "peerDependencies": { + "postcss": "^7.0.0 || ^8.0.0" + }, "devDependencies": { "@babel/core": "^7.7.2", "@babel/plugin-syntax-dynamic-import": "^7.2.0", "@babel/preset-env": "^7.7.1", "babel-eslint": "^10.0.3", "eslint": "^6.6.0", - "postcss": "^7.0.21", - "rollup": "^1.26.3", + "postcss": "^8.0.5", + "rollup": "^2.27.1", "rollup-plugin-babel": "^4.3.3", "rollup-plugin-terser": "^5.1.2" }, diff --git a/src/index.js b/src/index.js index 8d2a7cb..4938bb1 100644 --- a/src/index.js +++ b/src/index.js @@ -5,6 +5,20 @@ import getErrorMessage from './lib/get-error-message'; import getOptions from './lib/get-options'; import path from 'path'; +async function postcss8(plugins) { + const pkg = await import('postcss/package.json'); + if (pkg.version[0] === '8') { + const m = await import('postcss'); + return m.default(plugins); + } else { + throw new Error(`postcss@8 must be installed, found ${pkg.version}`); + } +} + +function isPostcss8Plugin(plugin) { + return typeof plugin === 'function' && Object(plugin).postcss === true; +} + getOptions().then( async options => { let hadError = false; @@ -13,12 +27,6 @@ getOptions().then( for (const name in options.config) { const test = options.config[name]; - const testPlugin = typeof Object(test.plugin).process === 'function' - ? test.plugin - : typeof test.plugin === 'function' - ? { process: test.plugin } - : options.plugin; - const testBase = name.split(':')[0]; const testFull = name.split(':').join('.'); @@ -30,7 +38,19 @@ getOptions().then( const processOptions = Object.assign({ from: sourcePath, to: resultPath }, test.processOptions); const pluginOptions = test.options; - const pluginName = Object(testPlugin.postcss).postcssPlugin || 'postcss'; + let rawPlugin = test.plugin || options.plugin; + if (rawPlugin.default) { + rawPlugin = rawPlugin.default; + } + const plugin = isPostcss8Plugin(rawPlugin) + ? rawPlugin(pluginOptions) + : typeof Object(rawPlugin).process === 'function' + ? rawPlugin + : typeof rawPlugin === 'function' + ? { process: rawPlugin } + : Object(rawPlugin).postcssPlugin; + + const pluginName = plugin.postcssPlugin || Object(rawPlugin.postcss).postcssPlugin || 'postcss'; log.wait(pluginName, test.message, options.ci); @@ -42,7 +62,13 @@ getOptions().then( const expectCSS = await safelyReadFile(expectPath); const sourceCSS = await readOrWriteFile(sourcePath, expectCSS); - const result = await testPlugin.process(sourceCSS, processOptions, pluginOptions); + let result; + if (isPostcss8Plugin(rawPlugin)) { + const postcss = await postcss8([ plugin ]); + result = await postcss.process(sourceCSS, processOptions); + } else { + result = await plugin.process(sourceCSS, processOptions, pluginOptions); + } const resultCSS = result.css; if (options.fix) { diff --git a/test/plugin.js b/test/postcss7-plugin.js similarity index 100% rename from test/plugin.js rename to test/postcss7-plugin.js diff --git a/test/postcss8-plugin.js b/test/postcss8-plugin.js new file mode 100644 index 0000000..b4e1bc1 --- /dev/null +++ b/test/postcss8-plugin.js @@ -0,0 +1,14 @@ +module.exports = function testPlugin(options) { + return { + postcssPlugin: 'test-plugin', + Root (root, { result }) { + if (Object(options).shouldFail) { + throw new Error('This should fail.'); + } else if (Object(options).shouldWarn) { + result.warn('This should warn.'); + } + } + }; +}; + +module.exports.postcss = true; From 99de157add131dcfe4586b87056e5ef127bfdcd7 Mon Sep 17 00:00:00 2001 From: Jonathan Neal Date: Fri, 18 Sep 2020 14:49:21 -0400 Subject: [PATCH 2/7] Support Node 10 (#7) --- .gitignore | 2 +- .travis.yml | 16 +--- README.md | 11 +-- package.json | 60 ++++++++----- rollup.config.js | 36 +++++--- src/index.js | 124 +++++++++++++------------- src/lib/color.js | 4 +- src/lib/exit.js | 6 +- src/lib/get-error-message.js | 2 +- src/lib/get-options-from-arguments.js | 24 ++--- src/lib/get-options.js | 68 ++++++++------ src/lib/log.js | 56 ++++++------ src/lib/utils.js | 24 ++--- test/postcss-tape.config.js | 34 +++---- test/postcss7-plugin.js | 8 +- test/postcss8-plugin.js | 24 ++--- 16 files changed, 259 insertions(+), 240 deletions(-) diff --git a/.gitignore b/.gitignore index d5cc2c0..606c1c5 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +dist node_modules package-lock.json .* @@ -6,4 +7,3 @@ package-lock.json !.travis.yml *.log* *.result.css -/index.* diff --git a/.travis.yml b/.travis.yml index 3e1b0d4..1dfa391 100644 --- a/.travis.yml +++ b/.travis.yml @@ -8,21 +8,9 @@ os: - osx node_js: - - 14 - - 12 - 10 - -jobs: - include: - - node_js: 8 - os: windows - before_script: | - npm install postcss@7 - nvs add 12 - nvs use 12 - npm run pretest:tape - nvs use 8 - script: npm run test:tape:7 -- --ci true + - 12 + - 14 install: - git config --global core.autocrlf false diff --git a/README.md b/README.md index c36627d..6bb14fd 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # PostCSS Tape [PostCSS][PostCSS] -[![NPM Version][npm-img]][npm-url] -[![Build Status][cli-img]][cli-url] -[![Gitter Chat][git-img]][git-url] +[NPM Version][npm-url] +[Build Status][cli-url] +[Support Chat][git-url] [PostCSS Tape] lets you quickly test [PostCSS] plugins. @@ -22,7 +22,7 @@ 3. Add tests to your `.tape.js` file: ```js - module.exports = { + export default { 'basic': { message: 'supports basic usage' } @@ -310,11 +310,8 @@ postcss-tape --fixtures path/to/tests } ``` -[npm-img]: https://img.shields.io/npm/v/postcss-tape.svg [npm-url]: https://www.npmjs.com/package/postcss-tape -[cli-img]: https://img.shields.io/travis/csstools/postcss-tape/master.svg [cli-url]: https://travis-ci.org/csstools/postcss-tape -[git-img]: https://img.shields.io/badge/chat-gitter-blue.svg [git-url]: https://gitter.im/postcss/postcss [PostCSS]: https://github.com/postcss/postcss diff --git a/package.json b/package.json index 84f94d7..367740c 100644 --- a/package.json +++ b/package.json @@ -1,20 +1,20 @@ { "name": "postcss-tape", - "version": "5.0.2", "description": "Quickly test PostCSS plugins", + "version": "5.0.2", + "type": "commonjs", + "main": "./dist/index.js", + "bin": { + "postcss-tape": "./dist/index.js" + }, + "repository": { + "type": "git", + "url": "https://github.com/csstools/postcss-tape.git" + }, "author": "Jonathan Neal ", - "license": "CC0-1.0", - "repository": "csstools/postcss-tape", "homepage": "https://github.com/csstools/postcss-tape#readme", "bugs": "https://github.com/csstools/postcss-tape/issues", - "main": "index.js", - "bin": { - "postcss-tape": "index.js" - }, - "files": [ - "index.js", - "index.js.map" - ], + "license": "CC0-1.0", "scripts": { "build": "rollup --config --silent", "prepublish": "npm test", @@ -27,21 +27,19 @@ "test:tape:ci": "npm run test:tape:7 -- --ci true && npm run test:tape:8 -- --ci true" }, "engines": { - "node": ">=8.0.0" + "node": "^10 || ^12 || ^14" }, "peerDependencies": { - "postcss": "^7.0.0 || ^8.0.0" + "postcss": "^7 || ^8" }, "devDependencies": { - "@babel/core": "^7.7.2", - "@babel/plugin-syntax-dynamic-import": "^7.2.0", - "@babel/preset-env": "^7.7.1", - "babel-eslint": "^10.0.3", - "eslint": "^6.6.0", + "@babel/core": "^7.11.6", + "@babel/preset-env": "^7.11.5", + "eslint": "^7.9.0", + "magic-string": "^0.25.7", "postcss": "^8.0.5", "rollup": "^2.27.1", - "rollup-plugin-babel": "^4.3.3", - "rollup-plugin-terser": "^5.1.2" + "rollup-plugin-babel": "^4.4.0" }, "eslintConfig": { "env": { @@ -50,13 +48,29 @@ "node": true }, "extends": "eslint:recommended", - "parser": "babel-eslint", "parserOptions": { - "ecmaVersion": 2018, + "ecmaVersion": 12, "impliedStrict": true, "sourceType": "module" }, - "root": true + "root": true, + "rules": { + "semi": [ + "error", + "never" + ] + } + }, + "prettier": { + "arrowParens": "avoid", + "bracketSpacing": true, + "endOfLine": "lf", + "printWidth": 360, + "quoteProps": "consistent", + "semi": false, + "singleQuote": true, + "trailingComma": "all", + "useTabs": true }, "keywords": [ "postcss", diff --git a/rollup.config.js b/rollup.config.js index fabbf5f..a7aae43 100644 --- a/rollup.config.js +++ b/rollup.config.js @@ -1,24 +1,32 @@ -import babel from 'rollup-plugin-babel'; -import { terser } from 'rollup-plugin-terser'; +import babel from 'rollup-plugin-babel' +import MagicString from 'magic-string' export default { input: 'src/index.js', - output: { file: 'index.js', format: 'cjs', sourcemap: true, strict: false }, + output: { + file: 'dist/index.js', + format: 'cjs', + sourcemap: true, + strict: true, + }, plugins: [ babel({ - plugins: [ '@babel/syntax-dynamic-import' ], - presets: [ ['@babel/env', { targets: { node: 8 } }] ] + presets: [['@babel/env', { targets: { node: 10 } }]], }), - terser(), - addHashBang() - ] -}; + addHashBang(), + ], +} -function addHashBang () { +function addHashBang() { return { name: 'add-hash-bang', - renderChunk (code) { - return `#!/usr/bin/env node\n${code}`; - } - }; + renderChunk(code) { + const str = new MagicString(code) + str.prepend(`#!/usr/bin/env node\n`) + return { + code: str.toString(), + map: str.generateMap({ hires: true }), + } + }, + } } diff --git a/src/index.js b/src/index.js index 4938bb1..7a0fdeb 100644 --- a/src/index.js +++ b/src/index.js @@ -1,46 +1,46 @@ -import { exitFail, exitPass } from './lib/exit'; -import { readOrWriteFile, safelyReadFile, writeFile } from './lib/utils'; -import * as log from './lib/log'; -import getErrorMessage from './lib/get-error-message'; -import getOptions from './lib/get-options'; -import path from 'path'; +import { exitFail, exitPass } from './lib/exit' +import { readOrWriteFile, safelyReadFile, writeFile } from './lib/utils' +import * as log from './lib/log' +import getErrorMessage from './lib/get-error-message' +import getOptions from './lib/get-options' +import path from 'path' async function postcss8(plugins) { - const pkg = await import('postcss/package.json'); + const pkg = await import('postcss/package.json') if (pkg.version[0] === '8') { - const m = await import('postcss'); - return m.default(plugins); + const m = await import('postcss') + return m.default(plugins) } else { - throw new Error(`postcss@8 must be installed, found ${pkg.version}`); + throw new Error(`postcss@8 must be installed, found ${pkg.version}`) } } function isPostcss8Plugin(plugin) { - return typeof plugin === 'function' && Object(plugin).postcss === true; + return typeof plugin === 'function' && Object(plugin).postcss === true } getOptions().then( async options => { - let hadError = false; + let hadError = false // runner for (const name in options.config) { - const test = options.config[name]; + const test = options.config[name] - const testBase = name.split(':')[0]; - const testFull = name.split(':').join('.'); + const testBase = name.split(':')[0] + const testFull = name.split(':').join('.') // test paths - const sourcePath = path.resolve(options.fixtures, test.source || `${testBase}.css`); - const expectPath = path.resolve(options.fixtures, test.expect || `${testFull}.expect.css`); - const resultPath = path.resolve(options.fixtures, test.result || `${testFull}.result.css`); + const sourcePath = path.resolve(options.fixtures, test.source || `${testBase}.css`) + const expectPath = path.resolve(options.fixtures, test.expect || `${testFull}.expect.css`) + const resultPath = path.resolve(options.fixtures, test.result || `${testFull}.result.css`) - const processOptions = Object.assign({ from: sourcePath, to: resultPath }, test.processOptions); - const pluginOptions = test.options; + const processOptions = Object.assign({ from: sourcePath, to: resultPath }, test.processOptions) + const pluginOptions = test.options - let rawPlugin = test.plugin || options.plugin; + let rawPlugin = test.plugin || options.plugin if (rawPlugin.default) { - rawPlugin = rawPlugin.default; + rawPlugin = rawPlugin.default } const plugin = isPostcss8Plugin(rawPlugin) ? rawPlugin(pluginOptions) @@ -48,50 +48,50 @@ getOptions().then( ? rawPlugin : typeof rawPlugin === 'function' ? { process: rawPlugin } - : Object(rawPlugin).postcssPlugin; + : Object(rawPlugin).postcssPlugin - const pluginName = plugin.postcssPlugin || Object(rawPlugin.postcss).postcssPlugin || 'postcss'; + const pluginName = plugin.postcssPlugin || Object(rawPlugin.postcss).postcssPlugin || 'postcss' - log.wait(pluginName, test.message, options.ci); + log.wait(pluginName, test.message, options.ci) try { if (Object(test.before) instanceof Function) { - await test.before(); + await test.before() } - const expectCSS = await safelyReadFile(expectPath); - const sourceCSS = await readOrWriteFile(sourcePath, expectCSS); + const expectCSS = await safelyReadFile(expectPath) + const sourceCSS = await readOrWriteFile(sourcePath, expectCSS) - let result; + let result if (isPostcss8Plugin(rawPlugin)) { - const postcss = await postcss8([ plugin ]); - result = await postcss.process(sourceCSS, processOptions); + const postcss = await postcss8([ plugin ]) + result = await postcss.process(sourceCSS, processOptions) } else { - result = await plugin.process(sourceCSS, processOptions, pluginOptions); + result = await plugin.process(sourceCSS, processOptions, pluginOptions) } - const resultCSS = result.css; + const resultCSS = result.css if (options.fix) { - await writeFile(expectPath, resultCSS); - await writeFile(resultPath, resultCSS); + await writeFile(expectPath, resultCSS) + await writeFile(resultPath, resultCSS) } else { - await writeFile(resultPath, resultCSS); + await writeFile(resultPath, resultCSS) if (expectCSS !== resultCSS) { throw new Error([ `Expected: ${JSON.stringify(expectCSS).slice(1, -1)}`, `Received: ${JSON.stringify(resultCSS).slice(1, -1)}` - ].join('\n')); + ].join('\n')) } } - const warnings = result.warnings(); + const warnings = result.warnings() if (typeof test.warnings === 'number') { if (test.warnings !== warnings.length) { - const s = warnings.length !== 1 ? 's' : ''; + const s = warnings.length !== 1 ? 's' : '' - throw new Error(`Expected: ${test.warnings} warning${s}\nReceived: ${warnings.length} warnings`); + throw new Error(`Expected: ${test.warnings} warning${s}\nReceived: ${warnings.length} warnings`) } } else if (warnings.length) { const areExpectedWarnings = warnings.every( @@ -100,74 +100,74 @@ getOptions().then( ? test.warnings[key].test(warning[key]) : test.warnings[key] === warning[key] ) - ); + ) if (!areExpectedWarnings) { - const s = warnings.length !== 1 ? 's' : ''; + const s = warnings.length !== 1 ? 's' : '' - throw new Error(`Unexpected warning${s}:\n${warnings.join('\n')}`); + throw new Error(`Unexpected warning${s}:\n${warnings.join('\n')}`) } } else if (test.warnings) { - throw new Error(`Expected a warning`); + throw new Error(`Expected a warning`) } else if (test.errors) { - throw new Error(`Expected an error`); + throw new Error(`Expected an error`) } if (Object(test.after) instanceof Function) { - await test.after(); + await test.after() } - log.pass(pluginName, test.message, options.ci); + log.pass(pluginName, test.message, options.ci) } catch (error) { if ('error' in test) { - const isObjectError = test.error === Object(test.error); + const isObjectError = test.error === Object(test.error) if (isObjectError) { const isExpectedError = Object.keys(test.error).every( key => test.error[key] instanceof RegExp ? test.error[key].test(Object(error)[key]) : test.error[key] === Object(error)[key] - ); + ) if (isExpectedError) { - log.pass(pluginName, test.message, options.ci); + log.pass(pluginName, test.message, options.ci) } else { const reportedError = Object.keys(test.error).reduce( (reportedError, key) => Object.assign(reportedError, { [key]: Object(error)[key] }), {} - ); + ) - hadError = error; + hadError = error - log.fail(pluginName, test.message, ` Expected Error: ${JSON.stringify(test.error)}\n Received Error: ${JSON.stringify(reportedError)}`, options.ci); + log.fail(pluginName, test.message, ` Expected Error: ${JSON.stringify(test.error)}\n Received Error: ${JSON.stringify(reportedError)}`, options.ci) } } else { - const isExpectedError = typeof test.error === 'boolean' && test.error; + const isExpectedError = typeof test.error === 'boolean' && test.error if (isExpectedError) { - log.pass(pluginName, test.message, options.ci); + log.pass(pluginName, test.message, options.ci) } else { - hadError = error; + hadError = error - log.fail(pluginName, test.message, ` Expected Error`, options.ci); + log.fail(pluginName, test.message, ` Expected Error`, options.ci) } if (options.ci) { - break; + break } } } else { - hadError = error; + hadError = error - log.fail(pluginName, test.message, getErrorMessage(error), options.ci); + log.fail(pluginName, test.message, getErrorMessage(error), options.ci) } } } if (hadError) { - throw hadError; + throw hadError } } -).then(exitPass, exitFail); +).then(exitPass, exitFail) diff --git a/src/lib/color.js b/src/lib/color.js index 144bb92..dadf489 100644 --- a/src/lib/color.js +++ b/src/lib/color.js @@ -22,8 +22,8 @@ const colors = { bgMagenta: '\x1b[45m', bgCyan: '\x1b[46m', bgWhite: '\x1b[47m' -}; +} export default function color (name, string) { - return colors[name] + string.replace(colors.reset, colors.reset + colors[name]) + colors.reset; + return colors[name] + string.replace(colors.reset, colors.reset + colors[name]) + colors.reset } diff --git a/src/lib/exit.js b/src/lib/exit.js index d801abc..69556f2 100644 --- a/src/lib/exit.js +++ b/src/lib/exit.js @@ -1,9 +1,9 @@ export function exitFail (error) { - console.log(error); + console.log(error) - process.exit(1); + process.exit(1) } export function exitPass () { - process.exit(0); + process.exit(0) } diff --git a/src/lib/get-error-message.js b/src/lib/get-error-message.js index b2bbf6d..abce20e 100644 --- a/src/lib/get-error-message.js +++ b/src/lib/get-error-message.js @@ -1,3 +1,3 @@ export default function getErrorMessage (error) { - return Object(error).message || error; + return Object(error).message || error } diff --git a/src/lib/get-options-from-arguments.js b/src/lib/get-options-from-arguments.js index dcd3de9..21bbdee 100644 --- a/src/lib/get-options-from-arguments.js +++ b/src/lib/get-options-from-arguments.js @@ -1,20 +1,20 @@ -const argRegExp = /^--([\w-]+)$/; -const primativeRegExp = /^(false|null|true|undefined|(\d+\.)?\d+|\{.*\}|\[.*\])$/; -const relaxedJsonPropRegExp = /(['"])?([a-z0-9A-Z_]+)\1:/g; -const relaxedJsonValueRegExp = /("[a-z0-9A-Z_]+":\s*)(?!true|false|null|\d+)'?([A-z0-9]+)'?([,}])/g; +const argRegExp = /^--([\w-]+)$/ +const primativeRegExp = /^(false|null|true|undefined|(\d+\.)?\d+|\{.*\}|\[.*\])$/ +const relaxedJsonPropRegExp = /(['"])?([a-z0-9A-Z_]+)\1:/g +const relaxedJsonValueRegExp = /("[a-z0-9A-Z_]+":\s*)(?!true|false|null|\d+)'?([A-z0-9]+)'?([,}])/g export default function getOptionsFromArguments (defaultOptions) { return process.argv.slice(2).reduce( (args, arg, index, argv) => { - const nextIndex = index + 1; - const nextArg = argv[nextIndex]; - const argMatch = arg.match(argRegExp); + const nextIndex = index + 1 + const nextArg = argv[nextIndex] + const argMatch = arg.match(argRegExp) if (argMatch) { - const [, name] = argMatch; + const [, name] = argMatch if (!nextArg || argRegExp.test(nextArg)) { - args[name] = true; + args[name] = true } else { args[name] = primativeRegExp.test(nextArg) ? JSON.parse( @@ -22,12 +22,12 @@ export default function getOptionsFromArguments (defaultOptions) { .replace(relaxedJsonPropRegExp, '"$2": ') .replace(relaxedJsonValueRegExp, '$1"$2"$3') ) - : nextArg; + : nextArg } } - return args; + return args }, Object.assign({}, defaultOptions) - ); + ) } diff --git a/src/lib/get-options.js b/src/lib/get-options.js index ad15dd3..39faed0 100644 --- a/src/lib/get-options.js +++ b/src/lib/get-options.js @@ -1,42 +1,54 @@ -import getOptionsFromArguments from './get-options-from-arguments'; -import { readJSON } from './utils'; -import path from 'path'; +import getOptionsFromArguments from './get-options-from-arguments' +import { readJSON } from './utils' +import path from 'path' -export default async function getOptions () { - const cwd = process.cwd(); +export default async function getOptions() { + const cwd = process.cwd() // default options const defaultOptions = { - plugin: cwd, - config: cwd, - fixtures: path.resolve(cwd, 'test') + plugin: cwd, + config: cwd, + fixtures: path.resolve(cwd, 'test'), } - const options = await readJSON('package.json', 'postcss', 'postcssConfig').then( - packageOptions => getOptionsFromArguments( - Object.assign(defaultOptions, packageOptions) - ) - ); + const options = await readJSON('package.json', 'postcss', 'postcssConfig').then(packageOptions => getOptionsFromArguments(Object.assign(defaultOptions, packageOptions))) - const importedPluginFile = path.resolve(options.plugin); - const importedPlugin = await import(importedPluginFile); + const importedPluginFile = path.resolve(options.plugin) + const importedPlugin = await import(importedPluginFile) - options.plugin = importedPlugin; + options.plugin = importedPlugin - try { - const importedConfigFile = path.extname(options.config) - ? path.resolve(options.config) - : path.resolve(options.config, 'postcss-tape.config.js'); + if (path.extname(options.config)) { + const importedConfig = await import(path.resolve(options.config)) - const importedConfig = await import(importedConfigFile); + options.config = importedConfig.default || importedConfig - options.config = importedConfig.default || importedConfig; - } catch (ignoredError) { - const importedConfigFile = path.resolve(options.config, '.tape.js'); - const importedConfig = await import(importedConfigFile); + return options + } else { + const postcssTapeConfigFiles = [ + 'postcss-tape.config.js', + 'postcss-tape.config.mjs', + 'postcss-tape.config.cjs', + '.tape.js', + '.tape.mjs', + '.tape.cjs' + ] + let returnError - options.config = importedConfig.default || importedConfig; - } + while (postcssTapeConfigFiles.length) { + const postcssTapeConfigFile = path.resolve(options.config, postcssTapeConfigFiles.shift()) + + try { + const importedConfig = await import(postcssTapeConfigFile) + options.config = importedConfig.default || importedConfig + return options + } catch (error) { + if (!returnError) returnError = error + continue + } + } - return options; + throw returnError + } } diff --git a/src/lib/log.js b/src/lib/log.js index bce7867..d8cc637 100644 --- a/src/lib/log.js +++ b/src/lib/log.js @@ -1,65 +1,65 @@ -import color from './color'; -import readline from 'readline'; +import color from './color' +import readline from 'readline' // symbols -const isWin32 = process.platform === 'win32'; -const tick = isWin32 ? '√' : '✔'; -const cross = isWin32 ? '×' : '✖'; -const stdout = process.stdout; +const isWin32 = process.platform === 'win32' +const tick = isWin32 ? '√' : '✔' +const cross = isWin32 ? '×' : '✖' +const stdout = process.stdout -let interval; +let interval export function pass (name, message, ci) { - clearInterval(interval); + clearInterval(interval) if (ci) { - stdout.write(` ${color('green', tick)}\n`); + stdout.write(` ${color('green', tick)}\n`) } else { // reset current stream line - readline.clearLine(stdout, 0); - readline.cursorTo(stdout, 0); + readline.clearLine(stdout, 0) + readline.cursorTo(stdout, 0) - stdout.write(`${color('green', tick)} ${name} ${color('dim', message)}\n`); + stdout.write(`${color('green', tick)} ${name} ${color('dim', message)}\n`) } } export function fail (name, message, details, ci) { - clearInterval(interval); + clearInterval(interval) if (ci) { - stdout.write(` ${color('red', cross)}\n${details}\n`); + stdout.write(` ${color('red', cross)}\n${details}\n`) } else { // reset current stream line - readline.clearLine(stdout, 0); - readline.cursorTo(stdout, 0); + readline.clearLine(stdout, 0) + readline.cursorTo(stdout, 0) - stdout.write(`${color('red', cross)} ${name} ${color('dim', message)}\n${details}\n`); + stdout.write(`${color('red', cross)} ${name} ${color('dim', message)}\n${details}\n`) } } // log with a waiting appearance export function wait (name, message, ci) { if (ci) { - stdout.write(`${name} ${color('dim', message)}`); + stdout.write(`${name} ${color('dim', message)}`) } else { - const spinner = isWin32 ? '-–—–-' : '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'; + const spinner = isWin32 ? '-–—–-' : '⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' - let index = 0; + let index = 0 - clearInterval(interval); + clearInterval(interval) // reset current stream line - readline.clearLine(stdout, 0); - readline.cursorTo(stdout, 0); + readline.clearLine(stdout, 0) + readline.cursorTo(stdout, 0) - stdout.write(`${color('yellow', spinner[index])} ${name} ${color('dim', message)}`); + stdout.write(`${color('yellow', spinner[index])} ${name} ${color('dim', message)}`) interval = setInterval(() => { - index = (index + 1) % spinner.length; + index = (index + 1) % spinner.length - readline.cursorTo(stdout, 0); + readline.cursorTo(stdout, 0) - stdout.write(`${color('yellow', spinner[index])} ${name} ${color('dim', message)}`); - }, 60); + stdout.write(`${color('yellow', spinner[index])} ${name} ${color('dim', message)}`) + }, 60) } } diff --git a/src/lib/utils.js b/src/lib/utils.js index 7470721..b0bfffe 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,15 +1,15 @@ -import fs from 'fs'; +import fs from 'fs' export function readFile (pathname) { return new Promise((resolve, reject) => { fs.readFile(pathname, 'utf8', (error, data) => { if (error) { - reject(error); + reject(error) } else { - resolve(data); + resolve(data) } - }); - }); + }) + }) } export function readJSON (pathname, ...keys) { @@ -23,7 +23,7 @@ export function readJSON (pathname, ...keys) { : options ).catch( () => ({}) - ); + ) } export function readOrWriteFile (pathname, data) { @@ -31,23 +31,23 @@ export function readOrWriteFile (pathname, data) { () => writeFile(pathname, data || '').then( () => '' ) - ); + ) } export function safelyReadFile (pathname) { return readFile(pathname).catch( () => '' - ); + ) } export function writeFile (pathname, data) { return new Promise((resolve, reject) => { fs.writeFile(pathname, data, (error) => { if (error) { - reject(error); + reject(error) } else { - resolve(); + resolve() } - }); - }); + }) + }) } diff --git a/test/postcss-tape.config.js b/test/postcss-tape.config.js index 9c03071..33a2b8e 100644 --- a/test/postcss-tape.config.js +++ b/test/postcss-tape.config.js @@ -1,41 +1,41 @@ module.exports = { 'basic': { - message: 'supports basic usage' + message: 'supports basic usage', }, 'basic:generate': { message: 'supports generating all files', after() { - require('fs').unlinkSync('test/basic.generate.result.css'); - } + require('fs').unlinkSync('test/basic.generate.result.css') + }, }, 'basic:sources': { message: 'supports specifying files', - source: 'basic.css', - expect: 'basic.custom-expect.css', - result: 'basic.custom-result.css', + source: 'basic.css', + expect: 'basic.custom-expect.css', + result: 'basic.custom-result.css', after() { - require('fs').unlinkSync('test/basic.custom-result.css'); - } + require('fs').unlinkSync('test/basic.custom-result.css') + }, }, 'basic:error': { message: 'supports failing', options: { - shouldFail: true + shouldFail: true, }, error: { - message: /should fail/ - } + message: /should fail/, + }, }, 'basic:warnings': { message: 'supports warnings', options: { - shouldWarn: true + shouldWarn: true, }, warnings: { - text: /should warn/ + text: /should warn/, }, after() { - require('fs').unlinkSync('test/basic.warnings.result.css'); - } - } -}; + require('fs').unlinkSync('test/basic.warnings.result.css') + }, + }, +} diff --git a/test/postcss7-plugin.js b/test/postcss7-plugin.js index ce547af..6995e42 100644 --- a/test/postcss7-plugin.js +++ b/test/postcss7-plugin.js @@ -1,11 +1,11 @@ -const postcss = require('postcss'); +const postcss = require('postcss') module.exports = postcss.plugin('test-plugin', options => { return (root, result) => { if (Object(options).shouldFail) { - throw new Error('This should fail.'); + throw new Error('This should fail.') } else if (Object(options).shouldWarn) { - result.warn('This should warn.'); + result.warn('This should warn.') } } -}); +}) diff --git a/test/postcss8-plugin.js b/test/postcss8-plugin.js index b4e1bc1..59b9ff3 100644 --- a/test/postcss8-plugin.js +++ b/test/postcss8-plugin.js @@ -1,14 +1,14 @@ module.exports = function testPlugin(options) { - return { - postcssPlugin: 'test-plugin', - Root (root, { result }) { - if (Object(options).shouldFail) { - throw new Error('This should fail.'); - } else if (Object(options).shouldWarn) { - result.warn('This should warn.'); - } - } - }; -}; + return { + postcssPlugin: 'test-plugin', + Root(root, { result }) { + if (Object(options).shouldFail) { + throw new Error('This should fail.') + } else if (Object(options).shouldWarn) { + result.warn('This should warn.') + } + }, + } +} -module.exports.postcss = true; +module.exports.postcss = true From a441778823555bdaea2c1e4ec08853ac24e60393 Mon Sep 17 00:00:00 2001 From: Jonathan Neal Date: Fri, 18 Sep 2020 15:02:13 -0400 Subject: [PATCH 3/7] Update CHANGELOG.md --- CHANGELOG.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f1b8bc..9e9a004 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,13 @@ # Changes to PostCSS Tape +### 6.0.0 (September 18, 2020) + +- Supports PostCSS 7 _and_ 8 (_major_) +- Supports Node 10+ (_major_) +- Supports configurations from (in order) `postcss-tape.config.js`, + `postcss-tape.config.mjs`, `postcss-tape.config.cjs`, `.tape.js`, `.tape.mjs`, + and `.tape.cjs`. + ### 5.0.2 (July 29, 2019) - Fixed: Issue loading a test plugin From 68982feba2e94d754b67e97ca963b0d4bec4b75b Mon Sep 17 00:00:00 2001 From: Jonathan Neal Date: Fri, 18 Sep 2020 15:02:39 -0400 Subject: [PATCH 4/7] 6.0.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 367740c..3f77ff3 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "postcss-tape", "description": "Quickly test PostCSS plugins", - "version": "5.0.2", + "version": "6.0.0", "type": "commonjs", "main": "./dist/index.js", "bin": { From f042e7831ca1ab2b9f47392cb97bedd1916b81a7 Mon Sep 17 00:00:00 2001 From: Jonathan Neal Date: Tue, 16 Mar 2021 08:42:40 -0400 Subject: [PATCH 5/7] Update project conventions --- .gitignore | 1 + README.md | 2 +- package.json | 2 +- src/index.js | 25 ++++++----- src/lib/color.js | 10 +++-- src/lib/exit.js | 6 ++- src/lib/get-error-message.js | 7 +-- src/lib/get-options-from-arguments.js | 47 ++++++++++---------- src/lib/get-options.js | 8 ++-- src/lib/log.js | 12 ++--- src/lib/utils.js | 63 ++++++++++++--------------- 11 files changed, 94 insertions(+), 89 deletions(-) diff --git a/.gitignore b/.gitignore index 606c1c5..ffc4416 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ dist node_modules package-lock.json +yarn.lock .* !.editorconfig !.gitignore diff --git a/README.md b/README.md index 6bb14fd..3ade2ed 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PostCSS Tape [PostCSS][PostCSS] [NPM Version][npm-url] -[Build Status][cli-url] +[Build Status][cli-url] [Support Chat][git-url] [PostCSS Tape] lets you quickly test [PostCSS] plugins. diff --git a/package.json b/package.json index 3f77ff3..ce274e5 100644 --- a/package.json +++ b/package.json @@ -27,7 +27,7 @@ "test:tape:ci": "npm run test:tape:7 -- --ci true && npm run test:tape:8 -- --ci true" }, "engines": { - "node": "^10 || ^12 || ^14" + "node": ">=10.0.0" }, "peerDependencies": { "postcss": "^7 || ^8" diff --git a/src/index.js b/src/index.js index 7a0fdeb..ef868a5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,13 @@ -import { exitFail, exitPass } from './lib/exit' -import { readOrWriteFile, safelyReadFile, writeFile } from './lib/utils' -import * as log from './lib/log' -import getErrorMessage from './lib/get-error-message' -import getOptions from './lib/get-options' +import * as exit from './lib/exit.js' +import { readOrWriteFile, safelyReadFile, writeFile } from './lib/utils.js' +import { getErrorMessage } from './lib/get-error-message.js' +import { getOptions } from './lib/get-options.js' +import * as log from './lib/log.js' import path from 'path' -async function postcss8(plugins) { +const postcss8 = async (plugins) => { const pkg = await import('postcss/package.json') + if (pkg.version[0] === '8') { const m = await import('postcss') return m.default(plugins) @@ -15,9 +16,7 @@ async function postcss8(plugins) { } } -function isPostcss8Plugin(plugin) { - return typeof plugin === 'function' && Object(plugin).postcss === true -} +const isPostcss8Plugin = (plugin) => typeof plugin === 'function' && Object(plugin).postcss === true getOptions().then( async options => { @@ -39,9 +38,11 @@ getOptions().then( const pluginOptions = test.options let rawPlugin = test.plugin || options.plugin + if (rawPlugin.default) { rawPlugin = rawPlugin.default } + const plugin = isPostcss8Plugin(rawPlugin) ? rawPlugin(pluginOptions) : typeof Object(rawPlugin).process === 'function' @@ -63,12 +64,15 @@ getOptions().then( const sourceCSS = await readOrWriteFile(sourcePath, expectCSS) let result + if (isPostcss8Plugin(rawPlugin)) { const postcss = await postcss8([ plugin ]) + result = await postcss.process(sourceCSS, processOptions) } else { result = await plugin.process(sourceCSS, processOptions, pluginOptions) } + const resultCSS = result.css if (options.fix) { @@ -168,6 +172,5 @@ getOptions().then( throw hadError } } -).then(exitPass, exitFail) - +).then(exit.pass, exit.fail) diff --git a/src/lib/color.js b/src/lib/color.js index dadf489..17095c9 100644 --- a/src/lib/color.js +++ b/src/lib/color.js @@ -1,3 +1,4 @@ +/** Color keys. */ const colors = { reset: '\x1b[0m', bold: '\x1b[1m', @@ -24,6 +25,9 @@ const colors = { bgWhite: '\x1b[47m' } -export default function color (name, string) { - return colors[name] + string.replace(colors.reset, colors.reset + colors[name]) + colors.reset -} +/** Return a string wrapped in a CLI color. */ +export const color = (/** @type {keyof colors} */ name, /** @type {string} */ string) => ( + colors[name] + + string.replace(colors.reset, colors.reset + colors[name]) + + colors.reset +) diff --git a/src/lib/exit.js b/src/lib/exit.js index 69556f2..57cb124 100644 --- a/src/lib/exit.js +++ b/src/lib/exit.js @@ -1,9 +1,11 @@ -export function exitFail (error) { +/** Exit the process logging an error and with a failing exit code. */ +export const fail = (error) => { console.log(error) process.exit(1) } -export function exitPass () { +/** Exit the process with a passing exit code. */ +export const pass = () => { process.exit(0) } diff --git a/src/lib/get-error-message.js b/src/lib/get-error-message.js index abce20e..f66c8ca 100644 --- a/src/lib/get-error-message.js +++ b/src/lib/get-error-message.js @@ -1,3 +1,4 @@ -export default function getErrorMessage (error) { - return Object(error).message || error -} +/** Return the error message. */ +export const getErrorMessage = (/** @type {Error | string} */ error) => String( + Object(error).message || error +) diff --git a/src/lib/get-options-from-arguments.js b/src/lib/get-options-from-arguments.js index 21bbdee..51189cb 100644 --- a/src/lib/get-options-from-arguments.js +++ b/src/lib/get-options-from-arguments.js @@ -3,31 +3,30 @@ const primativeRegExp = /^(false|null|true|undefined|(\d+\.)?\d+|\{.*\}|\[.*\])$ const relaxedJsonPropRegExp = /(['"])?([a-z0-9A-Z_]+)\1:/g const relaxedJsonValueRegExp = /("[a-z0-9A-Z_]+":\s*)(?!true|false|null|\d+)'?([A-z0-9]+)'?([,}])/g -export default function getOptionsFromArguments (defaultOptions) { - return process.argv.slice(2).reduce( - (args, arg, index, argv) => { - const nextIndex = index + 1 - const nextArg = argv[nextIndex] - const argMatch = arg.match(argRegExp) +/** Return an object of options from a CLI array of arguments. */ +export const getOptionsFromArguments = (/** @type {object} */ defaultOptions) => process.argv.slice(2).reduce( + (/** @type {object} */ args, /** @type {string} */ arg, /** @type {number} */ index, /** @type {object} */ argv) => { + const nextIndex = index + 1 + const nextArg = argv[nextIndex] + const argMatch = arg.match(argRegExp) - if (argMatch) { - const [, name] = argMatch + if (argMatch) { + const [, name] = argMatch - if (!nextArg || argRegExp.test(nextArg)) { - args[name] = true - } else { - args[name] = primativeRegExp.test(nextArg) - ? JSON.parse( - nextArg - .replace(relaxedJsonPropRegExp, '"$2": ') - .replace(relaxedJsonValueRegExp, '$1"$2"$3') - ) - : nextArg - } + if (!nextArg || argRegExp.test(nextArg)) { + args[name] = true + } else { + args[name] = primativeRegExp.test(nextArg) + ? JSON.parse( + nextArg + .replace(relaxedJsonPropRegExp, '"$2": ') + .replace(relaxedJsonValueRegExp, '$1"$2"$3') + ) + : nextArg } + } - return args - }, - Object.assign({}, defaultOptions) - ) -} + return args + }, + Object.assign({}, defaultOptions) +) diff --git a/src/lib/get-options.js b/src/lib/get-options.js index 39faed0..2c5c8da 100644 --- a/src/lib/get-options.js +++ b/src/lib/get-options.js @@ -1,8 +1,9 @@ -import getOptionsFromArguments from './get-options-from-arguments' -import { readJSON } from './utils' +import { getOptionsFromArguments } from './get-options-from-arguments.js' +import { readJSON } from './utils.js' import path from 'path' -export default async function getOptions() { +/** Asynchronously return the options from the project. */ +export const getOptions = async () => { const cwd = process.cwd() // default options @@ -34,6 +35,7 @@ export default async function getOptions() { '.tape.mjs', '.tape.cjs' ] + let returnError while (postcssTapeConfigFiles.length) { diff --git a/src/lib/log.js b/src/lib/log.js index d8cc637..8a11098 100644 --- a/src/lib/log.js +++ b/src/lib/log.js @@ -1,4 +1,4 @@ -import color from './color' +import { color } from './color.js' import readline from 'readline' // symbols @@ -9,7 +9,8 @@ const stdout = process.stdout let interval -export function pass (name, message, ci) { +/** Log as a passing state. */ +export const pass = (/** @type {string} */ name, /** @type {string} */ message, /** @type {boolean} */ ci) => { clearInterval(interval) if (ci) { @@ -23,7 +24,8 @@ export function pass (name, message, ci) { } } -export function fail (name, message, details, ci) { +/** Log as a failing state. */ +export const fail = (/** @type {string} */ name, /** @type {string} */ message, /** @type {string} */ details, /** @type {boolean} */ ci) => { clearInterval(interval) if (ci) { @@ -37,8 +39,8 @@ export function fail (name, message, details, ci) { } } -// log with a waiting appearance -export function wait (name, message, ci) { +/** Log as a waiting state. */ +export const wait = (/** @type {string} */ name, /** @type {string} */ message, /** @type {boolean} */ ci) => { if (ci) { stdout.write(`${name} ${color('dim', message)}`) } else { diff --git a/src/lib/utils.js b/src/lib/utils.js index b0bfffe..9f6c7b4 100644 --- a/src/lib/utils.js +++ b/src/lib/utils.js @@ -1,7 +1,8 @@ import fs from 'fs' -export function readFile (pathname) { - return new Promise((resolve, reject) => { +/** Reads a file and returns its string content */ +export const readFile = (/** @type {string} */ pathname) => /** @type {Promise} */ ( + new Promise((resolve, reject) => { fs.readFile(pathname, 'utf8', (error, data) => { if (error) { reject(error) @@ -10,44 +11,34 @@ export function readFile (pathname) { } }) }) -} +) -export function readJSON (pathname, ...keys) { - return readFile(pathname).then( - data => JSON.parse(data) - ).then( - options => keys.length ? - options[ - Object.keys(options).find(key => keys.includes(key)) +/** Returns a value from a JSON file. */ +export const readJSON = (/** @type {string} */ pathname, /** @type {string[]} */ ...keys) => readFile(pathname).then(JSON.parse).then( + opts => ( + keys.length + ? opts[ + Object.keys(opts).find(key => keys.includes(key)) ] - : options - ).catch( - () => ({}) + : opts ) -} +) -export function readOrWriteFile (pathname, data) { - return readFile(pathname).catch( - () => writeFile(pathname, data || '').then( - () => '' - ) - ) -} +/** Returns the string content of a file if it exists, and otherwise creates the file and returns an empty string. */ +export const readOrWriteFile = (/** @type {string} */ pathname, /** @type {string} */ data) => readFile(pathname).catch( + () => writeFile(pathname, data || '').then(() => '') +) -export function safelyReadFile (pathname) { - return readFile(pathname).catch( - () => '' - ) -} +/** Reads a file without throwing for any reason. */ +export const safelyReadFile = (/** @type {string} */ pathname) => readFile(pathname).catch(() => '') -export function writeFile (pathname, data) { - return new Promise((resolve, reject) => { - fs.writeFile(pathname, data, (error) => { - if (error) { - reject(error) - } else { - resolve() - } - }) +/** Writes a file. */ +export const writeFile = (/** @type {string} */ pathname, /** @type {string} */ data) => new Promise((resolve, reject) => { + fs.writeFile(pathname, data, (error) => { + if (error) { + reject(error) + } else { + resolve() + } }) -} +}) From ebf24fab8d97f130d579c1c1843114b1cc7e05be Mon Sep 17 00:00:00 2001 From: Jonathan Neal Date: Tue, 16 Mar 2021 09:02:28 -0400 Subject: [PATCH 6/7] 6.0.1 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index ce274e5..abee57f 100644 --- a/package.json +++ b/package.json @@ -1,11 +1,11 @@ { "name": "postcss-tape", "description": "Quickly test PostCSS plugins", - "version": "6.0.0", + "version": "6.0.1", "type": "commonjs", "main": "./dist/index.js", "bin": { - "postcss-tape": "./dist/index.js" + "postcss-tape": "dist/index.js" }, "repository": { "type": "git", From 85cf8178bdfc391240f23d9a0e3a1d1e5c7cad90 Mon Sep 17 00:00:00 2001 From: Romain Menke <11521496+romainmenke@users.noreply.github.com> Date: Wed, 27 Dec 2023 17:49:51 +0100 Subject: [PATCH 7/7] Update README.md --- README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/README.md b/README.md index 3ade2ed..e8136b2 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +
⚠️ PostCSS Tape is deprecated in favor of @csstools/postcss-tape. ⚠️
+ # PostCSS Tape [PostCSS][PostCSS] [NPM Version][npm-url]