mirror of
https://github.com/tailwindlabs/tailwindcss.git
synced 2025-12-08 21:36:08 +00:00
improve integration tests (#4572)
* remove unused file using syntax.js instead * add a way to remove files that are generated from the tests Essentially setting the restore cache contents to `null` as a special value. * combine the stdout and stderr output * add jest-diff instead of relying on it existing *somewhere* This also came with a small API change. * add `--runInBand` to the test script * add dedicated cli tests for the new Tailwind CLI
This commit is contained in:
parent
f1e31682c8
commit
63a67cb8ef
@ -58,21 +58,29 @@ module.exports = function $(command, options = {}) {
|
||||
|
||||
let stdout = ''
|
||||
let stderr = ''
|
||||
let combined = ''
|
||||
|
||||
child.stdout.on('data', (data) => {
|
||||
stdoutMessages.push(data.toString())
|
||||
notifyNextStdoutActor()
|
||||
stdout += data
|
||||
combined += data
|
||||
})
|
||||
|
||||
child.stderr.on('data', (data) => {
|
||||
stderrMessages.push(data.toString())
|
||||
notifyNextStderrActor()
|
||||
stderr += data
|
||||
combined += data
|
||||
})
|
||||
|
||||
child.on('close', (code, signal) => {
|
||||
;(signal === 'SIGTERM' ? resolve : code === 0 ? resolve : reject)({ code, stdout, stderr })
|
||||
;(signal === 'SIGTERM' ? resolve : code === 0 ? resolve : reject)({
|
||||
code,
|
||||
stdout,
|
||||
stderr,
|
||||
combined,
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
|
||||
@ -1,4 +0,0 @@
|
||||
// Small helper to allow for html highlighting / formatting in most editors.
|
||||
module.exports = function html(templates) {
|
||||
return templates.join('')
|
||||
}
|
||||
@ -30,7 +30,15 @@ module.exports = function ({
|
||||
// Restore all written files
|
||||
afterEach(async () => {
|
||||
await Promise.all(
|
||||
Object.entries(fileCache).map(([file, content]) => fs.writeFile(file, content, 'utf8'))
|
||||
Object.entries(fileCache).map(async ([file, content]) => {
|
||||
try {
|
||||
if (content === null) {
|
||||
return await fs.unlink(file)
|
||||
} else {
|
||||
return await fs.writeFile(file, content, 'utf8')
|
||||
}
|
||||
} catch {}
|
||||
})
|
||||
)
|
||||
})
|
||||
|
||||
@ -68,6 +76,21 @@ module.exports = function ({
|
||||
}
|
||||
|
||||
return {
|
||||
cleanupFile(file) {
|
||||
let filePath = path.resolve(toolRoot, file)
|
||||
fileCache[filePath] = null
|
||||
},
|
||||
async fileExists(file) {
|
||||
let filePath = path.resolve(toolRoot, file)
|
||||
return existsSync(filePath)
|
||||
},
|
||||
async removeFile(file) {
|
||||
let filePath = path.resolve(toolRoot, file)
|
||||
if (!fileCache[filePath]) {
|
||||
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
|
||||
}
|
||||
await fs.unlink(filePath)
|
||||
},
|
||||
async readOutputFile(file) {
|
||||
file = await resolveFile(file, absoluteOutputFolder)
|
||||
return fs.readFile(path.resolve(absoluteOutputFolder, file), 'utf8')
|
||||
@ -83,7 +106,15 @@ module.exports = function ({
|
||||
async writeInputFile(file, contents) {
|
||||
let filePath = path.resolve(absoluteInputFolder, file)
|
||||
if (!fileCache[filePath]) {
|
||||
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
|
||||
try {
|
||||
fileCache[filePath] = await fs.readFile(filePath, 'utf8')
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
fileCache[filePath] = null // Sentinel value to `delete` the file afterwards. This also means that we are writing to a `new` file inside the test.
|
||||
} else {
|
||||
throw err
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fs.writeFile(path.resolve(absoluteInputFolder, file), contents, 'utf8')
|
||||
|
||||
3
integrations/tailwindcss-cli/package-lock.json
generated
3
integrations/tailwindcss-cli/package-lock.json
generated
@ -1,11 +1,10 @@
|
||||
{
|
||||
"name": "postcss-cli",
|
||||
"name": "tailwindcss-cli",
|
||||
"version": "0.0.0",
|
||||
"lockfileVersion": 2,
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "postcss-cli",
|
||||
"version": "0.0.0"
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
"version": "0.0.0",
|
||||
"scripts": {
|
||||
"build": "NODE_ENV=production node ../../lib/cli.js -i ./src/index.css -o ./dist/main.css",
|
||||
"test": "jest"
|
||||
"test": "jest --runInBand"
|
||||
},
|
||||
"jest": {
|
||||
"displayName": "Tailwind CSS CLI",
|
||||
|
||||
269
integrations/tailwindcss-cli/tests/cli.test.js
Normal file
269
integrations/tailwindcss-cli/tests/cli.test.js
Normal file
@ -0,0 +1,269 @@
|
||||
let path = require('path')
|
||||
let $ = require('../../execute')
|
||||
let { css, html } = require('../../syntax')
|
||||
let resolveToolRoot = require('../../resolve-tool-root')
|
||||
|
||||
let { readOutputFile, writeInputFile, cleanupFile, fileExists, removeFile } = require('../../io')({
|
||||
output: 'dist',
|
||||
input: 'src',
|
||||
})
|
||||
|
||||
let EXECUTABLE = 'node ../../lib/cli.js'
|
||||
|
||||
describe('Build command', () => {
|
||||
test('--output', async () => {
|
||||
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
|
||||
|
||||
await $(`${EXECUTABLE} --output ./dist/main.css`)
|
||||
|
||||
let contents = await readOutputFile('main.css')
|
||||
|
||||
// `-i` is omitted, therefore the default `@tailwind base; @tailwind
|
||||
// components; @tailwind utilities` is used. However `preflight` is
|
||||
// disabled. I still want to verify that the `base` got included.
|
||||
expect(contents).toIncludeCss(
|
||||
css`
|
||||
*,
|
||||
::before,
|
||||
::after {
|
||||
--tw-border-opacity: 1;
|
||||
border-color: rgba(229, 231, 235, var(--tw-border-opacity));
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
// Verify `utilities` output is correct
|
||||
expect(contents).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
test('--input, --output', async () => {
|
||||
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
|
||||
|
||||
await $(`${EXECUTABLE} --input ./src/index.css --output ./dist/main.css`)
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
test('--minify', async () => {
|
||||
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
|
||||
|
||||
await $(`${EXECUTABLE} --output ./dist/main.css --minify`)
|
||||
let withMinify = await readOutputFile('main.css')
|
||||
|
||||
// Verify that we got the expected output. Note: `.toIncludeCss` formats
|
||||
// `actual` & `expected`
|
||||
expect(withMinify).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
`
|
||||
)
|
||||
|
||||
await $(`${EXECUTABLE} --output ./dist/main.css`)
|
||||
let withoutMinify = await readOutputFile('main.css')
|
||||
|
||||
// Let's verify that the actual minified output is smaller than the not
|
||||
// minified version.
|
||||
expect(withoutMinify.length).toBeGreaterThan(withMinify.length)
|
||||
})
|
||||
|
||||
test('--no-autoprefixer', async () => {
|
||||
await writeInputFile('index.html', html`<div class="select-none"></div>`)
|
||||
|
||||
await $(`${EXECUTABLE} --output ./dist/main.css`)
|
||||
let withAutoprefixer = await readOutputFile('main.css')
|
||||
|
||||
expect(withAutoprefixer).toIncludeCss(css`
|
||||
.select-none {
|
||||
-webkit-user-select: none;
|
||||
user-select: none;
|
||||
}
|
||||
`)
|
||||
|
||||
await $(`${EXECUTABLE} --output ./dist/main.css --no-autoprefixer`)
|
||||
let withoutAutoprefixer = await readOutputFile('main.css')
|
||||
|
||||
expect(withoutAutoprefixer).toIncludeCss(css`
|
||||
.select-none {
|
||||
user-select: none;
|
||||
}
|
||||
`)
|
||||
})
|
||||
|
||||
test('--config (non-existing config file)', async () => {
|
||||
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
|
||||
|
||||
let { stderr } = await $(
|
||||
`${EXECUTABLE} --output ./dist/main.css --config ./non-existing.config.js`
|
||||
).catch((err) => err)
|
||||
|
||||
let toolRoot = resolveToolRoot()
|
||||
expect(stderr).toEqual(
|
||||
`Specified config file ${path.resolve(toolRoot, 'non-existing.config.js')} does not exist.\n`
|
||||
)
|
||||
})
|
||||
|
||||
test('--config (existing config file)', async () => {
|
||||
await writeInputFile('index.html', html`<div class="font-bold"></div>`)
|
||||
|
||||
let customConfig = `module.exports = ${JSON.stringify(
|
||||
{
|
||||
purge: ['./src/index.html'],
|
||||
mode: 'jit',
|
||||
darkMode: false, // or 'media' or 'class'
|
||||
theme: {
|
||||
extend: {
|
||||
fontWeight: {
|
||||
bold: 'BOLD',
|
||||
},
|
||||
},
|
||||
},
|
||||
variants: {
|
||||
extend: {},
|
||||
},
|
||||
corePlugins: {
|
||||
preflight: false,
|
||||
},
|
||||
plugins: [],
|
||||
},
|
||||
|
||||
null,
|
||||
2
|
||||
)}`
|
||||
|
||||
await writeInputFile('../custom.config.js', customConfig)
|
||||
|
||||
await $(`${EXECUTABLE} --output ./dist/main.css --config ./custom.config.js`)
|
||||
|
||||
expect(await readOutputFile('main.css')).toIncludeCss(
|
||||
css`
|
||||
.font-bold {
|
||||
font-weight: BOLD;
|
||||
}
|
||||
`
|
||||
)
|
||||
})
|
||||
|
||||
test('--help', async () => {
|
||||
let { combined } = await $(`${EXECUTABLE} --help`)
|
||||
|
||||
expect(combined).toMatchInlineSnapshot(`
|
||||
"
|
||||
tailwindcss v2.1.2
|
||||
|
||||
Usage:
|
||||
tailwindcss build [options]
|
||||
|
||||
Options:
|
||||
-i, --input Input file
|
||||
-o, --output Output file
|
||||
-w, --watch Watch for changes and rebuild as needed
|
||||
--jit Build using JIT mode
|
||||
--files Template files to scan for class names
|
||||
--postcss Load custom PostCSS configuration
|
||||
-m, --minify Minify the output
|
||||
-c, --config Path to a custom config file
|
||||
--no-autoprefixer Disable autoprefixer
|
||||
-h, --help Display usage information
|
||||
|
||||
"
|
||||
`)
|
||||
})
|
||||
})
|
||||
|
||||
describe('Init command', () => {
|
||||
test('--full', async () => {
|
||||
cleanupFile('full.config.js')
|
||||
|
||||
let { combined } = await $(`${EXECUTABLE} init full.config.js --full`)
|
||||
|
||||
expect(combined).toMatchInlineSnapshot(`
|
||||
"
|
||||
Created Tailwind CSS config file: full.config.js
|
||||
"
|
||||
`)
|
||||
|
||||
// Not a clean way to test this. We could require the file and verify that
|
||||
// multiple keys in `theme` exists. However it loads `tailwindcss/colors`
|
||||
// which doesn't exists in this context.
|
||||
expect((await readOutputFile('../full.config.js')).split('\n').length).toBeGreaterThan(50)
|
||||
})
|
||||
|
||||
test('--jit', async () => {
|
||||
cleanupFile('with-jit.config.js')
|
||||
|
||||
let { combined } = await $(`${EXECUTABLE} init with-jit.config.js --jit`)
|
||||
|
||||
expect(combined).toMatchInlineSnapshot(`
|
||||
"
|
||||
Created Tailwind CSS config file: with-jit.config.js
|
||||
"
|
||||
`)
|
||||
|
||||
expect(await readOutputFile('../with-jit.config.js')).toContain("mode: 'jit'")
|
||||
})
|
||||
|
||||
test('--full, --jit', async () => {
|
||||
cleanupFile('full-with-jit.config.js')
|
||||
|
||||
let { combined } = await $(`${EXECUTABLE} init full-with-jit.config.js --jit --full`)
|
||||
|
||||
expect(combined).toMatchInlineSnapshot(`
|
||||
"
|
||||
Created Tailwind CSS config file: full-with-jit.config.js
|
||||
"
|
||||
`)
|
||||
|
||||
expect(await readOutputFile('../full-with-jit.config.js')).toContain("mode: 'jit'")
|
||||
})
|
||||
|
||||
test('--postcss', async () => {
|
||||
expect(await fileExists('postcss.config.js')).toBe(true)
|
||||
await removeFile('postcss.config.js')
|
||||
expect(await fileExists('postcss.config.js')).toBe(false)
|
||||
|
||||
let { combined } = await $(`${EXECUTABLE} init --postcss`)
|
||||
|
||||
expect(await fileExists('postcss.config.js')).toBe(true)
|
||||
|
||||
expect(combined).toMatchInlineSnapshot(`
|
||||
"
|
||||
tailwind.config.js already exists.
|
||||
Created PostCSS config file: postcss.config.js
|
||||
"
|
||||
`)
|
||||
})
|
||||
|
||||
test('--help', async () => {
|
||||
let { combined } = await $(`${EXECUTABLE} init --help`)
|
||||
|
||||
expect(combined).toMatchInlineSnapshot(`
|
||||
"
|
||||
tailwindcss v2.1.2
|
||||
|
||||
Usage:
|
||||
tailwindcss init [options]
|
||||
|
||||
Options:
|
||||
--jit Initialize for JIT mode
|
||||
-f, --full Initialize a full \`tailwind.config.js\` file
|
||||
-p, --postcss Initialize a \`postcss.config.js\` file
|
||||
-h, --help Display usage information
|
||||
|
||||
"
|
||||
`)
|
||||
})
|
||||
})
|
||||
@ -1,5 +1,5 @@
|
||||
const prettier = require('prettier')
|
||||
const diff = require('jest-diff').default
|
||||
const { diff } = require('jest-diff')
|
||||
|
||||
function format(input) {
|
||||
return prettier.format(input, {
|
||||
|
||||
35
package-lock.json
generated
35
package-lock.json
generated
@ -5,7 +5,6 @@
|
||||
"requires": true,
|
||||
"packages": {
|
||||
"": {
|
||||
"name": "tailwindcss",
|
||||
"version": "2.1.2",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
@ -59,6 +58,7 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"jest": "^27.0.2",
|
||||
"jest-diff": "^27.0.2",
|
||||
"postcss": "^8.3.0",
|
||||
"postcss-cli": "^8.3.1",
|
||||
"prettier": "^2.3.0",
|
||||
@ -11761,7 +11761,8 @@
|
||||
"version": "0.2.1",
|
||||
"resolved": "https://registry.npmjs.org/@tailwindcss/aspect-ratio/-/aspect-ratio-0.2.1.tgz",
|
||||
"integrity": "sha512-aDFi80aHQ3JM3symJ5iKU70lm151ugIGFCI0yRZGpyjgQSDS+Fbe93QwypC1tCEllQE8p0S7TUu20ih1b9IKLA==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"@tootallnate/once": {
|
||||
"version": "1.1.2",
|
||||
@ -11917,7 +11918,8 @@
|
||||
"version": "5.3.1",
|
||||
"resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.1.tgz",
|
||||
"integrity": "sha512-K0Ptm/47OKfQRpNQ2J/oIN/3QYiK6FwW+eJbILhsdxh2WTLdl+30o8aGdTbm5JbffpFFAg/g+zi1E+jvJha5ng==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"acorn-node": {
|
||||
"version": "1.8.2",
|
||||
@ -12903,7 +12905,8 @@
|
||||
"cssnano-utils": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/cssnano-utils/-/cssnano-utils-2.0.1.tgz",
|
||||
"integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ=="
|
||||
"integrity": "sha512-i8vLRZTnEH9ubIyfdZCAdIdgnHAUeQeByEeQ2I7oTilvP9oHO6RScpeq3GsFUVqeB8uZgOQ9pw8utofNn32hhQ==",
|
||||
"requires": {}
|
||||
},
|
||||
"csso": {
|
||||
"version": "4.2.0",
|
||||
@ -13369,7 +13372,8 @@
|
||||
"version": "8.3.0",
|
||||
"resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.3.0.tgz",
|
||||
"integrity": "sha512-BgZuLUSeKzvlL/VUjx/Yb787VQ26RU3gGjA3iiFvdsp/2bMfVIWUVP7tjxtjS0e+HP409cPlPvNkQloz8C91ew==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"eslint-plugin-prettier": {
|
||||
"version": "3.4.0",
|
||||
@ -14998,7 +15002,8 @@
|
||||
"version": "1.2.2",
|
||||
"resolved": "https://registry.npmjs.org/jest-pnp-resolver/-/jest-pnp-resolver-1.2.2.tgz",
|
||||
"integrity": "sha512-olV41bKSMm8BdnuMsewT4jqlZ8+3TCARAXjZGT9jcoSnrfUnRCqnMoF9XEeoWjbzObpqF9dRhHQj0Xb9QdF6/w==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"jest-regex-util": {
|
||||
"version": "27.0.1",
|
||||
@ -16201,22 +16206,26 @@
|
||||
"postcss-discard-comments": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-comments/-/postcss-discard-comments-5.0.1.tgz",
|
||||
"integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg=="
|
||||
"integrity": "sha512-lgZBPTDvWrbAYY1v5GYEv8fEO/WhKOu/hmZqmCYfrpD6eyDWWzAOsl2rF29lpvziKO02Gc5GJQtlpkTmakwOWg==",
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-discard-duplicates": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-duplicates/-/postcss-discard-duplicates-5.0.1.tgz",
|
||||
"integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA=="
|
||||
"integrity": "sha512-svx747PWHKOGpAXXQkCc4k/DsWo+6bc5LsVrAsw+OU+Ibi7klFZCyX54gjYzX4TH+f2uzXjRviLARxkMurA2bA==",
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-discard-empty": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-empty/-/postcss-discard-empty-5.0.1.tgz",
|
||||
"integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw=="
|
||||
"integrity": "sha512-vfU8CxAQ6YpMxV2SvMcMIyF2LX1ZzWpy0lqHDsOdaKKLQVQGVP1pzhrI9JlsO65s66uQTfkQBKBD/A5gp9STFw==",
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-discard-overridden": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-discard-overridden/-/postcss-discard-overridden-5.0.1.tgz",
|
||||
"integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q=="
|
||||
"integrity": "sha512-Y28H7y93L2BpJhrdUR2SR2fnSsT+3TVx1NmVQLbcnZWwIUpJ7mfcTC6Za9M2PG6w8j7UQRfzxqn8jU2VwFxo3Q==",
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-js": {
|
||||
"version": "3.0.3",
|
||||
@ -16308,7 +16317,8 @@
|
||||
"postcss-normalize-charset": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/postcss-normalize-charset/-/postcss-normalize-charset-5.0.1.tgz",
|
||||
"integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg=="
|
||||
"integrity": "sha512-6J40l6LNYnBdPSk+BHZ8SF+HAkS4q2twe5jnocgd+xWpz/mx/5Sa32m3W1AA8uE8XaXN+eg8trIlfu8V9x61eg==",
|
||||
"requires": {}
|
||||
},
|
||||
"postcss-normalize-display-values": {
|
||||
"version": "5.0.1",
|
||||
@ -17867,7 +17877,8 @@
|
||||
"version": "7.4.6",
|
||||
"resolved": "https://registry.npmjs.org/ws/-/ws-7.4.6.tgz",
|
||||
"integrity": "sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A==",
|
||||
"dev": true
|
||||
"dev": true,
|
||||
"requires": {}
|
||||
},
|
||||
"xml-name-validator": {
|
||||
"version": "3.0.0",
|
||||
|
||||
@ -56,6 +56,7 @@
|
||||
"eslint-config-prettier": "^8.3.0",
|
||||
"eslint-plugin-prettier": "^3.4.0",
|
||||
"jest": "^27.0.2",
|
||||
"jest-diff": "^27.0.2",
|
||||
"postcss": "^8.3.0",
|
||||
"postcss-cli": "^8.3.1",
|
||||
"prettier": "^2.3.0",
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user