From 182e3dc480d10911ae719bcb64912e7046918e0f Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Mon, 11 Jul 2022 12:44:18 -0400 Subject: [PATCH 01/10] Update lockfile --- .../tailwindcss-cli/package-lock.json | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/integrations/tailwindcss-cli/package-lock.json b/integrations/tailwindcss-cli/package-lock.json index e3602894561f..1bfff83a777d 100644 --- a/integrations/tailwindcss-cli/package-lock.json +++ b/integrations/tailwindcss-cli/package-lock.json @@ -12,13 +12,13 @@ } }, "../..": { - "version": "3.0.24", + "version": "3.1.5", "license": "MIT", "dependencies": { - "arg": "^5.0.1", + "arg": "^5.0.2", "chokidar": "^3.5.3", "color-name": "^1.1.4", - "detective": "^5.2.0", + "detective": "^5.2.1", "didyoumean": "^1.2.2", "dlv": "^1.1.3", "fast-glob": "^3.2.11", @@ -36,26 +36,26 @@ "postcss-selector-parser": "^6.0.10", "postcss-value-parser": "^4.2.0", "quick-lru": "^5.1.1", - "resolve": "^1.22.0" + "resolve": "^1.22.1" }, "bin": { "tailwind": "lib/cli.js", "tailwindcss": "lib/cli.js" }, "devDependencies": { + "@parcel/css": "^1.11.2", "@swc/cli": "^0.1.57", - "@swc/core": "^1.2.160", + "@swc/core": "^1.2.196", "@swc/jest": "^0.2.21", "@swc/register": "^0.1.10", "autoprefixer": "^10.4.7", - "cssnano": "^5.1.9", - "esbuild": "^0.14.39", - "eslint": "^8.15.0", + "esbuild": "^0.14.48", + "eslint": "^8.18.0", "eslint-config-prettier": "^8.5.0", - "eslint-plugin-prettier": "^4.0.0", - "jest": "^28.0.3", - "jest-diff": "^28.1.0", - "prettier": "^2.6.2", + "eslint-plugin-prettier": "^4.2.1", + "jest": "^28.1.2", + "jest-diff": "^28.1.1", + "prettier": "^2.7.1", "prettier-plugin-tailwindcss": "^0.1.11", "rimraf": "^3.0.0", "source-map-js": "^1.0.2" @@ -76,27 +76,27 @@ "tailwindcss": { "version": "file:../..", "requires": { + "@parcel/css": "^1.11.2", "@swc/cli": "^0.1.57", - "@swc/core": "^1.2.160", + "@swc/core": "^1.2.196", "@swc/jest": "^0.2.21", "@swc/register": "^0.1.10", - "arg": "^5.0.1", + "arg": "^5.0.2", "autoprefixer": "^10.4.7", "chokidar": "^3.5.3", "color-name": "^1.1.4", - "cssnano": "^5.1.9", - "detective": "^5.2.0", + "detective": "^5.2.1", "didyoumean": "^1.2.2", "dlv": "^1.1.3", - "esbuild": "^0.14.39", - "eslint": "^8.15.0", + "esbuild": "^0.14.48", + "eslint": "^8.18.0", "eslint-config-prettier": "^8.5.0", - "eslint-plugin-prettier": "^4.0.0", + "eslint-plugin-prettier": "^4.2.1", "fast-glob": "^3.2.11", "glob-parent": "^6.0.2", "is-glob": "^4.0.3", - "jest": "^28.0.3", - "jest-diff": "^28.1.0", + "jest": "^28.1.2", + "jest-diff": "^28.1.1", "lilconfig": "^2.0.5", "normalize-path": "^3.0.0", "object-hash": "^3.0.0", @@ -108,10 +108,10 @@ "postcss-nested": "5.0.6", "postcss-selector-parser": "^6.0.10", "postcss-value-parser": "^4.2.0", - "prettier": "^2.6.2", + "prettier": "^2.7.1", "prettier-plugin-tailwindcss": "^0.1.11", "quick-lru": "^5.1.1", - "resolve": "^1.22.0", + "resolve": "^1.22.1", "rimraf": "^3.0.0", "source-map-js": "^1.0.2" } From a3b42e4fec9ba916bd1bc58f617c26b65c7647c5 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 15 Sep 2022 13:55:11 -0400 Subject: [PATCH 02/10] Tweak formatting --- src/featureFlags.js | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/featureFlags.js b/src/featureFlags.js index 5e9d73583499..bd75108ea29b 100644 --- a/src/featureFlags.js +++ b/src/featureFlags.js @@ -11,7 +11,11 @@ let featureFlags = { 'respectDefaultRingColorOpacity', 'disableColorOpacityUtilitiesByDefault', ], - experimental: ['optimizeUniversalDefaults', 'matchVariant' /* , 'variantGrouping' */], + experimental: [ + 'optimizeUniversalDefaults', + 'matchVariant', + // 'variantGrouping', + ], } export function flagEnabled(config, flag) { From e8534bb4a767982b227f1db7ec8a6e143545ed5e Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 22 Sep 2022 12:59:46 -0400 Subject: [PATCH 03/10] Refactor content path parsing --- src/cli.js | 12 +-- src/lib/content.js | 162 ++++++++++++++++++++++++++++++++ src/lib/setupTrackingContext.js | 49 +--------- src/util/parseDependency.js | 79 ++++++++-------- src/util/parseGlob.js | 24 +++++ 5 files changed, 231 insertions(+), 95 deletions(-) create mode 100644 src/lib/content.js create mode 100644 src/util/parseGlob.js diff --git a/src/cli.js b/src/cli.js index 2851a79f1ec3..b217454917d0 100644 --- a/src/cli.js +++ b/src/cli.js @@ -19,6 +19,7 @@ import packageJson from '../package.json' import normalizePath from 'normalize-path' import micromatch from 'micromatch' import { validateConfig } from './util/validateConfig.js' +import { parseCandidateFiles } from './lib/content.js' let env = { DEBUG: process.env.DEBUG !== undefined && process.env.DEBUG !== '0', @@ -551,14 +552,9 @@ async function build() { } function extractFileGlobs(config) { - return config.content.files - .filter((file) => { - // Strings in this case are files / globs. If it is something else, - // like an object it's probably a raw content object. But this object - // is not watchable, so let's remove it. - return typeof file === 'string' - }) - .map((glob) => normalizePath(glob)) + let contentPaths = parseCandidateFiles(config) + + return contentPaths.map((contentPath) => contentPath.pattern) } function extractRawContent(config) { diff --git a/src/lib/content.js b/src/lib/content.js new file mode 100644 index 000000000000..efa6421d5987 --- /dev/null +++ b/src/lib/content.js @@ -0,0 +1,162 @@ +// @ts-check + +import fs from 'fs' +import path from 'path' +import isGlob from 'is-glob' +import fastGlob from 'fast-glob' +import normalizePath from 'normalize-path' +import { parseGlob } from '../util/parseGlob' +import { env } from './sharedState' + +/** @typedef {import('../../types/config.js').RawFile} RawFile */ +/** @typedef {import('../../types/config.js').FilePath} FilePath */ + +/** + * @typedef {object} ContentPath + * @property {string} original + * @property {string} base + * @property {string | null} glob + * @property {boolean} ignore + * @property {string} pattern + */ + +/** + * Turn a list of content paths (absolute or not; glob or not) into a list of + * absolute file paths that exist on the filesystem + * + * If there are symlinks in the path then multiple paths will be returned + * one for the symlink and one for the actual file + * + * @param {import('tailwindcss').Config} tailwindConfig + * @returns {ContentPath[]} + */ +export function parseCandidateFiles(tailwindConfig) { + let files = tailwindConfig.content.files + + // Normalize the file globs + files = files.filter((filePath) => typeof filePath === 'string') + files = files.map(normalizePath) + + // Split into included and excluded globs + let tasks = fastGlob.generateTasks(files) + + /** @type {ContentPath[]} */ + let included = [] + + /** @type {ContentPath[]} */ + let excluded = [] + + for (const task of tasks) { + included.push(...task.positive.map((filePath) => parseFilePath(filePath, false))) + excluded.push(...task.negative.map((filePath) => parseFilePath(filePath, true))) + } + + let paths = [...included, ...excluded] + + // Resolve paths relative to the config file or cwd + paths = resolveRelativePaths(paths) + + // Update cached patterns + paths = paths.map(resolveGlobPattern) + + return paths +} + +/** + * + * @param {string} filePath + * @param {boolean} ignore + * @returns {ContentPath} + */ +function parseFilePath(filePath, ignore) { + let contentPath = { + original: filePath, + base: filePath, + ignore, + pattern: filePath, + glob: null, + } + + if (isGlob(filePath)) { + Object.assign(contentPath, parseGlob(filePath)) + } + + return contentPath +} + +/** + * + * @param {ContentPath} contentPath + * @returns {ContentPath} + */ +function resolveGlobPattern(contentPath) { + contentPath.pattern = contentPath.glob + ? `${contentPath.base}/${contentPath.glob}` + : contentPath.base + + contentPath.pattern = contentPath.ignore ? `!${contentPath.pattern}` : contentPath.pattern + + return contentPath +} + +/** + * Resolve each path relative to the config file (when possible) if the experimental flag is enabled + * Otherwise, resolve relative to the current working directory + * + * @param {ContentPath[]} contentPaths + * @returns {ContentPath[]} + */ +function resolveRelativePaths(contentPaths) { + let resolveFrom = [] + + return contentPaths.map((contentPath) => { + contentPath.base = path.resolve(...resolveFrom, contentPath.base) + + return contentPath + }) +} + +/** + * @param {any} context + * @param {ContentPath[]} candidateFiles + * @param {Map} fileModifiedMap + * @returns {{ content: string, extension: string }[]} + */ +export function resolvedChangedContent(context, candidateFiles, fileModifiedMap) { + let changedContent = context.tailwindConfig.content.files + .filter((item) => typeof item.raw === 'string') + .map(({ raw, extension = 'html' }) => ({ content: raw, extension })) + + for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)) { + let content = fs.readFileSync(changedFile, 'utf8') + let extension = path.extname(changedFile).slice(1) + changedContent.push({ content, extension }) + } + + return changedContent +} + +/** + * + * @param {ContentPath[]} candidateFiles + * @param {Map} fileModifiedMap + * @returns {Set} + */ +function resolveChangedFiles(candidateFiles, fileModifiedMap) { + let paths = candidateFiles.map((contentPath) => contentPath.pattern) + + let changedFiles = new Set() + env.DEBUG && console.time('Finding changed files') + let files = fastGlob.sync(paths, { absolute: true }) + for (let file of files) { + let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity + let modified = fs.statSync(file).mtimeMs + + if (modified > prevModified) { + changedFiles.add(file) + fileModifiedMap.set(file, modified) + } + } + env.DEBUG && console.timeEnd('Finding changed files') + return changedFiles +} diff --git a/src/lib/setupTrackingContext.js b/src/lib/setupTrackingContext.js index 37b42cdbae20..e3ce8c3596d6 100644 --- a/src/lib/setupTrackingContext.js +++ b/src/lib/setupTrackingContext.js @@ -1,22 +1,14 @@ import fs from 'fs' -import path from 'path' - -import fastGlob from 'fast-glob' import LRU from 'quick-lru' -import normalizePath from 'normalize-path' import hash from '../util/hashConfig' import getModuleDependencies from '../lib/getModuleDependencies' - import resolveConfig from '../public/resolve-config' - import resolveConfigPath from '../util/resolveConfigPath' - -import { env } from './sharedState' - import { getContext, getFileModifiedMap } from './setupContextUtils' import parseDependency from '../util/parseDependency' import { validateConfig } from '../util/validateConfig.js' +import { parseCandidateFiles, resolvedChangedContent } from './content.js' let configPathCache = new LRU({ maxSize: 100 }) @@ -27,9 +19,7 @@ function getCandidateFiles(context, tailwindConfig) { return candidateFilesCache.get(context) } - let candidateFiles = tailwindConfig.content.files - .filter((item) => typeof item === 'string') - .map((contentPath) => normalizePath(contentPath)) + let candidateFiles = parseCandidateFiles(tailwindConfig) return candidateFilesCache.set(context, candidateFiles).get(context) } @@ -80,36 +70,6 @@ function getTailwindConfig(configOrPath) { return [newConfig, null, hash(newConfig), []] } -function resolvedChangedContent(context, candidateFiles, fileModifiedMap) { - let changedContent = context.tailwindConfig.content.files - .filter((item) => typeof item.raw === 'string') - .map(({ raw, extension = 'html' }) => ({ content: raw, extension })) - - for (let changedFile of resolveChangedFiles(candidateFiles, fileModifiedMap)) { - let content = fs.readFileSync(changedFile, 'utf8') - let extension = path.extname(changedFile).slice(1) - changedContent.push({ content, extension }) - } - return changedContent -} - -function resolveChangedFiles(candidateFiles, fileModifiedMap) { - let changedFiles = new Set() - env.DEBUG && console.time('Finding changed files') - let files = fastGlob.sync(candidateFiles, { absolute: true }) - for (let file of files) { - let prevModified = fileModifiedMap.has(file) ? fileModifiedMap.get(file) : -Infinity - let modified = fs.statSync(file).mtimeMs - - if (modified > prevModified) { - changedFiles.add(file) - fileModifiedMap.set(file, modified) - } - } - env.DEBUG && console.timeEnd('Finding changed files') - return changedFiles -} - // DISABLE_TOUCH = TRUE // Retrieve an existing context from cache if possible (since contexts are unique per @@ -161,9 +121,8 @@ export default function setupTrackingContext(configOrPath) { let fileModifiedMap = getFileModifiedMap(context) // Add template paths as postcss dependencies. - for (let fileOrGlob of candidateFiles) { - let dependency = parseDependency(fileOrGlob) - if (dependency) { + for (let contentPath of candidateFiles) { + for (let dependency of parseDependency(contentPath)) { registerDependency(dependency) } } diff --git a/src/util/parseDependency.js b/src/util/parseDependency.js index 1051df473357..f26eb1a29b15 100644 --- a/src/util/parseDependency.js +++ b/src/util/parseDependency.js @@ -1,49 +1,44 @@ -import isGlob from 'is-glob' -import globParent from 'glob-parent' -import path from 'path' - -// Based on `glob-base` -// https://github.com/micromatch/glob-base/blob/master/index.js -function parseGlob(pattern) { - let glob = pattern - let base = globParent(pattern) - - if (base !== '.') { - glob = pattern.substr(base.length) - if (glob.charAt(0) === '/') { - glob = glob.substr(1) - } - } - - if (glob.substr(0, 2) === './') { - glob = glob.substr(2) - } - if (glob.charAt(0) === '/') { - glob = glob.substr(1) +// @ts-check + +/** + * @typedef {{type: 'dependency', file: string} | {type: 'dir-dependency', dir: string, glob: string}} Dependency + */ + +/** + * + * @param {import('../lib/content.js').ContentPath} contentPath + * @returns {Dependency[]} + */ +export default function parseDependency(contentPath) { + if (contentPath.ignore) { + return [] } - return { base, glob } -} - -export default function parseDependency(normalizedFileOrGlob) { - if (normalizedFileOrGlob.startsWith('!')) { - return null - } - - let message - - if (isGlob(normalizedFileOrGlob)) { - let { base, glob } = parseGlob(normalizedFileOrGlob) - message = { type: 'dir-dependency', dir: path.resolve(base), glob } - } else { - message = { type: 'dependency', file: path.resolve(normalizedFileOrGlob) } + if (!contentPath.glob) { + return [ + { + type: 'dependency', + file: contentPath.base, + }, + ] } - // rollup-plugin-postcss does not support dir-dependency messages - // but directories can be watched in the same way as files - if (message.type === 'dir-dependency' && process.env.ROLLUP_WATCH === 'true') { - message = { type: 'dependency', file: message.dir } + if (process.env.ROLLUP_WATCH === 'true') { + // rollup-plugin-postcss does not support dir-dependency messages + // but directories can be watched in the same way as files + return [ + { + type: 'dependency', + file: contentPath.base, + }, + ] } - return message + return [ + { + type: 'dir-dependency', + dir: contentPath.base, + glob: contentPath.glob, + }, + ] } diff --git a/src/util/parseGlob.js b/src/util/parseGlob.js new file mode 100644 index 000000000000..5c03f413d7e2 --- /dev/null +++ b/src/util/parseGlob.js @@ -0,0 +1,24 @@ +import globParent from 'glob-parent' + +// Based on `glob-base` +// https://github.com/micromatch/glob-base/blob/master/index.js +export function parseGlob(pattern) { + let glob = pattern + let base = globParent(pattern) + + if (base !== '.') { + glob = pattern.substr(base.length) + if (glob.charAt(0) === '/') { + glob = glob.substr(1) + } + } + + if (glob.substr(0, 2) === './') { + glob = glob.substr(2) + } + if (glob.charAt(0) === '/') { + glob = glob.substr(1) + } + + return { base, glob } +} From 5ec5c45ad4c3cb119ae4f5ffbb4c7fcdc3fe5e1e Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 22 Sep 2022 12:59:48 -0400 Subject: [PATCH 04/10] Allow resolving content paths relative to the config file --- src/cli.js | 7 ++++++- src/featureFlags.js | 1 + src/lib/content.js | 13 ++++++++++--- src/lib/setupContextUtils.js | 4 ++++ src/lib/setupTrackingContext.js | 2 +- src/util/normalizeConfig.js | 24 ++++++++++++++++++++++-- tests/normalize-config.test.js | 2 ++ types/config.d.ts | 1 + 8 files changed, 47 insertions(+), 7 deletions(-) diff --git a/src/cli.js b/src/cli.js index b217454917d0..54d03743cabf 100644 --- a/src/cli.js +++ b/src/cli.js @@ -552,7 +552,12 @@ async function build() { } function extractFileGlobs(config) { - let contentPaths = parseCandidateFiles(config) + let context = { + tailwindConfig: config, + userConfigPath: configPath, + } + + let contentPaths = parseCandidateFiles(context, config) return contentPaths.map((contentPath) => contentPath.pattern) } diff --git a/src/featureFlags.js b/src/featureFlags.js index bd75108ea29b..a03916c5b0f8 100644 --- a/src/featureFlags.js +++ b/src/featureFlags.js @@ -10,6 +10,7 @@ let featureFlags = { 'hoverOnlyWhenSupported', 'respectDefaultRingColorOpacity', 'disableColorOpacityUtilitiesByDefault', + 'relativeContentPathsByDefault', ], experimental: [ 'optimizeUniversalDefaults', diff --git a/src/lib/content.js b/src/lib/content.js index efa6421d5987..4aa861561a49 100644 --- a/src/lib/content.js +++ b/src/lib/content.js @@ -27,10 +27,11 @@ import { env } from './sharedState' * If there are symlinks in the path then multiple paths will be returned * one for the symlink and one for the actual file * + * @param {*} context * @param {import('tailwindcss').Config} tailwindConfig * @returns {ContentPath[]} */ -export function parseCandidateFiles(tailwindConfig) { +export function parseCandidateFiles(context, tailwindConfig) { let files = tailwindConfig.content.files // Normalize the file globs @@ -54,7 +55,7 @@ export function parseCandidateFiles(tailwindConfig) { let paths = [...included, ...excluded] // Resolve paths relative to the config file or cwd - paths = resolveRelativePaths(paths) + paths = resolveRelativePaths(context, paths) // Update cached patterns paths = paths.map(resolveGlobPattern) @@ -103,12 +104,18 @@ function resolveGlobPattern(contentPath) { * Resolve each path relative to the config file (when possible) if the experimental flag is enabled * Otherwise, resolve relative to the current working directory * + * @param {any} context * @param {ContentPath[]} contentPaths * @returns {ContentPath[]} */ -function resolveRelativePaths(contentPaths) { +function resolveRelativePaths(context, contentPaths) { let resolveFrom = [] + // Resolve base paths relative to the config file (when possible) if the experimental flag is enabled + if (context.userConfigPath && context.tailwindConfig.content.relative) { + resolveFrom = [path.dirname(context.userConfigPath)] + } + return contentPaths.map((contentPath) => { contentPath.base = path.resolve(...resolveFrom, contentPath.base) diff --git a/src/lib/setupContextUtils.js b/src/lib/setupContextUtils.js index 2b8c095022ec..d15e8db8c70c 100644 --- a/src/lib/setupContextUtils.js +++ b/src/lib/setupContextUtils.js @@ -1000,6 +1000,10 @@ export function getContext( let context = createContext(tailwindConfig, [], root) + Object.assign(context, { + userConfigPath, + }) + trackModified([...contextDependencies], getFileModifiedMap(context)) // --- diff --git a/src/lib/setupTrackingContext.js b/src/lib/setupTrackingContext.js index e3ce8c3596d6..3e51bd9e15d6 100644 --- a/src/lib/setupTrackingContext.js +++ b/src/lib/setupTrackingContext.js @@ -19,7 +19,7 @@ function getCandidateFiles(context, tailwindConfig) { return candidateFilesCache.get(context) } - let candidateFiles = parseCandidateFiles(tailwindConfig) + let candidateFiles = parseCandidateFiles(context, tailwindConfig) return candidateFilesCache.set(context, candidateFiles).get(context) } diff --git a/src/util/normalizeConfig.js b/src/util/normalizeConfig.js index 2d23a864d505..baee48394d56 100644 --- a/src/util/normalizeConfig.js +++ b/src/util/normalizeConfig.js @@ -56,9 +56,11 @@ export function normalizeConfig(config) { // When `config.content` is an object if (typeof config.content === 'object' && config.content !== null) { - // Only `files`, `extract` and `transform` can exist in `config.content` + // Only `files`, `relative`, `extract`, and `transform` can exist in `config.content` if ( - Object.keys(config.content).some((key) => !['files', 'extract', 'transform'].includes(key)) + Object.keys(config.content).some( + (key) => !['files', 'relative', 'extract', 'transform'].includes(key) + ) ) { return false } @@ -112,6 +114,14 @@ export function normalizeConfig(config) { ) { return false } + + // `config.content.relative` is optional and can be a boolean + if ( + typeof config.content.relative !== 'boolean' && + typeof config.content.relative !== 'undefined' + ) { + return false + } } return true @@ -154,6 +164,16 @@ export function normalizeConfig(config) { // Normalize the `content` config.content = { + relative: (() => { + let { content } = config + + if (content?.relative) { + return content.relative + } + + return config.future?.relativeContentPathsByDefault ?? false + })(), + files: (() => { let { content, purge } = config diff --git a/tests/normalize-config.test.js b/tests/normalize-config.test.js index 71f304ea84ba..1b422a4bd4c6 100644 --- a/tests/normalize-config.test.js +++ b/tests/normalize-config.test.js @@ -105,6 +105,7 @@ it('should keep content files with globs', () => { expect(normalizeConfig(resolveConfig(config)).content).toEqual({ files: ['./example-folder/**/*.{html,js}'], + relative: false, extract: {}, transform: {}, }) @@ -129,6 +130,7 @@ it('should warn when we detect invalid globs with incorrect brace expansion', () './{example-folder}/**/*.{html}', './example-folder/**/*.{html}', ], + relative: false, extract: {}, transform: {}, }) diff --git a/types/config.d.ts b/types/config.d.ts index 855b9bb02199..61cd9546f2c9 100644 --- a/types/config.d.ts +++ b/types/config.d.ts @@ -31,6 +31,7 @@ type ContentConfig = | (FilePath | RawFile)[] | { files: (FilePath | RawFile)[] + relative?: boolean extract?: ExtractorFn | { [extension: string]: ExtractorFn } transform?: TransformerFn | { [extension: string]: TransformerFn } } From 9d617ecdc0d99e034b7208ee1396462293600d4d Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 22 Sep 2022 13:00:41 -0400 Subject: [PATCH 05/10] Include resolved symlinks as additional content paths --- src/lib/content.js | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/src/lib/content.js b/src/lib/content.js index 4aa861561a49..475706e0b47d 100644 --- a/src/lib/content.js +++ b/src/lib/content.js @@ -57,6 +57,9 @@ export function parseCandidateFiles(context, tailwindConfig) { // Resolve paths relative to the config file or cwd paths = resolveRelativePaths(context, paths) + // Resolve symlinks if possible + paths = paths.flatMap(resolvePathSymlinks) + // Update cached patterns paths = paths.map(resolveGlobPattern) @@ -123,6 +126,33 @@ function resolveRelativePaths(context, contentPaths) { }) } +/** + * Resolve the symlink for the base directory / file in each path + * These are added as additional dependencies to watch for changes because + * some tools (like webpack) will only watch the actual file or directory + * but not the symlink itself even in projects that use monorepos. + * + * @param {ContentPath} contentPath + * @returns {ContentPath[]} + */ +function resolvePathSymlinks(contentPath) { + let paths = [contentPath] + + try { + let resolvedPath = fs.realpathSync(contentPath.base) + if (resolvedPath !== contentPath.base) { + paths.push({ + ...contentPath, + base: resolvedPath, + }) + } + } catch { + // TODO: log this? + } + + return paths +} + /** * @param {any} context * @param {ContentPath[]} candidateFiles From 51e2222c422c9c6b9e47a3783d78ac74c0377e90 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 22 Sep 2022 13:20:29 -0400 Subject: [PATCH 06/10] Update changelog --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 22209b0ec786..9a11b1ee15f1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Add negative value support for `outline-offset` ([#9136](https://github.com/tailwindlabs/tailwindcss/pull/9136)) - Allow negating utilities using min/max/clamp ([#9237](https://github.com/tailwindlabs/tailwindcss/pull/9237)) - Add new `collapse` utility for `visibility: collapse` ([#9181](https://github.com/tailwindlabs/tailwindcss/pull/9181)) +- Allow resolving content paths relative to the config file ([#9396](https://github.com/tailwindlabs/tailwindcss/pull/9396)) ### Fixed From c2a6d2e1b0555a6ba75f90a04a53e36827aeebb9 Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Thu, 22 Sep 2022 17:33:18 -0400 Subject: [PATCH 07/10] Work on suite of tests for content resolution --- tests/relative-resolution/.gitignore | 1 + tests/relative-resolution/config.js | 49 ++++ tests/relative-resolution/content.test.js | 266 ++++++++++++++++++ tests/relative-resolution/cwd.js | 24 ++ tests/relative-resolution/files/real/no-1.js | 1 + tests/relative-resolution/files/real/no.js | 1 + tests/relative-resolution/files/real/yes.html | 1 + tests/relative-resolution/files/real/yes.js | 1 + .../files/resolved/no-1.js | 1 + .../relative-resolution/files/resolved/no.js | 1 + .../files/resolved/yes.html | 1 + .../relative-resolution/files/resolved/yes.js | 1 + 12 files changed, 348 insertions(+) create mode 100644 tests/relative-resolution/.gitignore create mode 100644 tests/relative-resolution/config.js create mode 100644 tests/relative-resolution/content.test.js create mode 100644 tests/relative-resolution/cwd.js create mode 100644 tests/relative-resolution/files/real/no-1.js create mode 100644 tests/relative-resolution/files/real/no.js create mode 100644 tests/relative-resolution/files/real/yes.html create mode 100644 tests/relative-resolution/files/real/yes.js create mode 100644 tests/relative-resolution/files/resolved/no-1.js create mode 100644 tests/relative-resolution/files/resolved/no.js create mode 100644 tests/relative-resolution/files/resolved/yes.html create mode 100644 tests/relative-resolution/files/resolved/yes.js diff --git a/tests/relative-resolution/.gitignore b/tests/relative-resolution/.gitignore new file mode 100644 index 000000000000..3c0e7734cfb3 --- /dev/null +++ b/tests/relative-resolution/.gitignore @@ -0,0 +1 @@ +*.tailwind.config.js diff --git a/tests/relative-resolution/config.js b/tests/relative-resolution/config.js new file mode 100644 index 000000000000..e3a4d4112eb8 --- /dev/null +++ b/tests/relative-resolution/config.js @@ -0,0 +1,49 @@ +// @ts-config + +import fs from 'fs' +import path from 'path' + +export async function writeConfigs({ both = {}, inRoot = {}, inDir = {} } = {}) { + let configs = [ + { + path: './content.tailwind.config.js', + config: { + ...both, + ...inRoot, + content: { + files: [], + ...both.content, + ...inRoot.content, + }, + }, + }, + { + path: './files/content.tailwind.config.js', + config: { + ...both, + ...inDir, + content: { + files: [], + ...both.content, + ...inDir.content, + }, + }, + }, + ] + + let defaultConfig = { + corePlugins: { preflight: false }, + } + + for (const config of configs) { + await fs.promises.writeFile( + path.resolve(__dirname, config.path), + `module.exports = ${JSON.stringify({ ...defaultConfig, ...config.config })};` + ) + } +} + +export async function destroyConfigs() { + await fs.promises.unlink(path.resolve(__dirname, './content.tailwind.config.js')) + await fs.promises.unlink(path.resolve(__dirname, './files/content.tailwind.config.js')) +} diff --git a/tests/relative-resolution/content.test.js b/tests/relative-resolution/content.test.js new file mode 100644 index 000000000000..d437d93828fd --- /dev/null +++ b/tests/relative-resolution/content.test.js @@ -0,0 +1,266 @@ +import path from 'path' +import { css, run } from '../util/run.js' +import { cwd } from './cwd.js' +import { writeConfigs, destroyConfigs } from './config.js' +import fs from 'fs' + +// Write default configs before running tests and remove them afterwards +beforeAll(() => writeConfigs()) +afterAll(() => destroyConfigs()) + +// Create a symlink at ./files/link that points to ./files/resolved and remove it afterwards +beforeAll(() => + fs.promises.symlink( + path.resolve(__dirname, './files/resolved'), + path.resolve(__dirname, './files/link') + ) +) +afterAll(() => fs.promises.unlink(path.resolve(__dirname, './files/link'))) + +// If we've changed directories reset the cwd back to what it was before running these tests +afterEach(() => cwd.unwind()) + +async function build({ cwd: cwdPath } = {}) { + await cwd.switch(cwdPath) + + // Note that ./content.tailwind.config.js is hardcoded on purpose here + // It represents a config but one that could be in different places + return await run('@tailwind utilities', './content.tailwind.config.js') +} + +fit('looks in the CWD by default', async () => { + await writeConfigs({ + both: { + content: { + files: ['./files/real/yes.html'], + }, + }, + }) + + let result = await build({ cwd: __dirname }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + `) + + result = await build({ cwd: path.resolve(__dirname, './files') }) + + expect(result.css).toMatchCss(``) +}) + +it('looks in the CWD for non-config-relative paths', async () => { + await writeConfigs({ + both: { + // Turn it on by default (eventual v4 behavior) + experimental: { relativeContentPathsByDefault: true }, + + // But then disable it anyway + content: { + relative: false, + files: ['./files/real/yes.html'], + }, + }, + }) + + let result = await build({ cwd: __dirname }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + `) + + result = await build({ cwd: path.resolve(__dirname, './files') }) + + expect(result.css).toMatchCss(``) +}) + +it('can look for content files relative to the config', async () => { + await writeConfigs({ + both: { + content: { + relative: true, + files: ['./real/yes.html'], + }, + }, + }) + + // Here `./real` doesn't exist next to the config in the root directory + let result = await build({ cwd: __dirname }) + + expect(result.css).toMatchCss(css``) + + // But here it `./real` does exist next to the config in the `./files` directory! + result = await build({ cwd: path.resolve(__dirname, './files') }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + `) +}) + +// it('it handles ignored globs correctly when not relative to the config', async () => { +// await writeConfigs({ +// both: { +// content: { +// relative: false, +// files: [ +// './files/real/yes.html', // Scanned + static +// './files/real/*.js', // Scanned + dynamic +// '!./files/real/no.js', // Ignored + static +// '!./files/real/no-*.js', // Ignored + dynamic +// ], +// }, +// }, +// }) + +// let result = await build({ cwd: __dirname }) + +// expect(result.css).toMatchCss(css` +// .content-\[real-static-positive\] { +// --tw-content: real-static-positive; +// content: var(--tw-content); +// } +// .content-\[real-dynamic-positive\] { +// --tw-content: real-dynamic-positive; +// content: var(--tw-content); +// } +// `) + +// // But here it `./real` does exist next to the config in the `./files` directory! +// result = await build({ cwd: path.resolve(__dirname, './files') }) + +// expect(result.css).toMatchCss(``) +// }) + +// it('it handles ignored globs correctly when relative to the config', async () => { +// await writeConfigs({ +// both: { +// content: { +// relative: true, +// files: [ +// './real/yes.html', // Scanned + static +// './real/*.js', // Scanned + dynamic +// '!./real/no.js', // Ignored + static +// '!./real/no-*.js', // Ignored + dynamic +// ], +// }, +// }, +// }) + +// let result = await build({ cwd: __dirname }) + +// expect(result.css).toMatchCss(``) + +// // But here it `./real` does exist next to the config in the `./files` directory! +// result = await build({ cwd: path.resolve(__dirname, './files') }) + +// expect(result.css).toMatchCss(css` +// .content-\[real-static-positive\] { +// --tw-content: real-static-positive; +// content: var(--tw-content); +// } +// .content-\[real-dynamic-positive\] { +// --tw-content: real-dynamic-positive; +// content: var(--tw-content); +// } +// `) +// }) + +// it('it can resolve symlinks for files when not relative to the config', async () => { +// await writeConfigs({ +// both: { +// content: { +// relative: false, +// files: [ +// './files/real/yes.html', // Scanned + static +// './files/real/*.js', // Scanned + dynamic +// './files/link/yes.html', // Scanned + static + symlinked +// './files/link/*.js', // Scanned + dynamic + symlinked +// '!./files/real/no.js', // Ignored + static +// '!./files/real/no-*.js', // Ignored + dynamic +// '!./files/link/no.js', // Ignored + static + symlinked +// '!./files/link/no-*.js', // Ignored + dynamic + symlinked +// ], +// }, +// }, +// }) + +// let result = await build({ cwd: __dirname }) + +// expect(result.css).toMatchCss(css` +// .content-\[real-static-positive\] { +// --tw-content: real-static-positive; +// content: var(--tw-content); +// } +// .content-\[resolved-static-positive\] { +// --tw-content: resolved-static-positive; +// content: var(--tw-content); +// } +// .content-\[real-dynamic-positive\] { +// --tw-content: real-dynamic-positive; +// content: var(--tw-content); +// } +// .content-\[resolved-dynamic-positive\] { +// --tw-content: resolved-dynamic-positive; +// content: var(--tw-content); +// } +// `) + +// // But here it `./real` does exist next to the config in the `./files` directory! +// result = await build({ cwd: path.resolve(__dirname, './files') }) + +// expect(result.css).toMatchCss(``) +// }) + +// it('it can resolve symlinks for files when relative to the config', async () => { +// await writeConfigs({ +// both: { +// content: { +// relative: true, +// files: [ +// './real/yes.html', // Scanned + static +// './real/*.js', // Scanned + dynamic +// './link/yes.html', // Scanned + static + symlinked +// './link/*.js', // Scanned + dynamic + symlinked +// '!./real/no.js', // Ignored + static +// '!./real/no-*.js', // Ignored + dynamic +// '!./link/no.js', // Ignored + static + symlinked +// '!./link/no-*.js', // Ignored + dynamic + symlinked +// ], +// }, +// }, +// }) + +// let result = await build({ cwd: __dirname }) + +// expect(result.css).toMatchCss(``) + +// // But here it `./real` does exist next to the config in the `./files` directory! +// result = await build({ cwd: path.resolve(__dirname, './files') }) + +// expect(result.css).toMatchCss(css` +// .content-\[real-static-positive\] { +// --tw-content: real-static-positive; +// content: var(--tw-content); +// } +// .content-\[resolved-static-positive\] { +// --tw-content: resolved-static-positive; +// content: var(--tw-content); +// } +// .content-\[real-dynamic-positive\] { +// --tw-content: real-dynamic-positive; +// content: var(--tw-content); +// } +// .content-\[resolved-dynamic-positive\] { +// --tw-content: resolved-dynamic-positive; +// content: var(--tw-content); +// } +// `) +// }) diff --git a/tests/relative-resolution/cwd.js b/tests/relative-resolution/cwd.js new file mode 100644 index 000000000000..ebfa4ffa324b --- /dev/null +++ b/tests/relative-resolution/cwd.js @@ -0,0 +1,24 @@ +// @ts-config + +let stack = [] + +export const cwd = { + get current() { + return process.cwd() + }, + + async switch(dir) { + stack.push(process.cwd()) + process.chdir(dir) + }, + + async restore() { + process.chdir(stack.pop()) + }, + + async unwind() { + while (stack.length) { + this.restore() + } + }, +} diff --git a/tests/relative-resolution/files/real/no-1.js b/tests/relative-resolution/files/real/no-1.js new file mode 100644 index 000000000000..7953cad0c416 --- /dev/null +++ b/tests/relative-resolution/files/real/no-1.js @@ -0,0 +1 @@ +// content-[real-static-negative] diff --git a/tests/relative-resolution/files/real/no.js b/tests/relative-resolution/files/real/no.js new file mode 100644 index 000000000000..76e0cb00bae4 --- /dev/null +++ b/tests/relative-resolution/files/real/no.js @@ -0,0 +1 @@ +// content-[real-dynamic-negative] diff --git a/tests/relative-resolution/files/real/yes.html b/tests/relative-resolution/files/real/yes.html new file mode 100644 index 000000000000..48acef1f7f39 --- /dev/null +++ b/tests/relative-resolution/files/real/yes.html @@ -0,0 +1 @@ + diff --git a/tests/relative-resolution/files/real/yes.js b/tests/relative-resolution/files/real/yes.js new file mode 100644 index 000000000000..8fc42639113b --- /dev/null +++ b/tests/relative-resolution/files/real/yes.js @@ -0,0 +1 @@ +// content-[real-dynamic-positive] diff --git a/tests/relative-resolution/files/resolved/no-1.js b/tests/relative-resolution/files/resolved/no-1.js new file mode 100644 index 000000000000..af079f767673 --- /dev/null +++ b/tests/relative-resolution/files/resolved/no-1.js @@ -0,0 +1 @@ +// content-[resolved-static-negative] diff --git a/tests/relative-resolution/files/resolved/no.js b/tests/relative-resolution/files/resolved/no.js new file mode 100644 index 000000000000..20ab72ef4ec6 --- /dev/null +++ b/tests/relative-resolution/files/resolved/no.js @@ -0,0 +1 @@ +// content-[resolved-dynamic-negative] diff --git a/tests/relative-resolution/files/resolved/yes.html b/tests/relative-resolution/files/resolved/yes.html new file mode 100644 index 000000000000..e4abd8e82dec --- /dev/null +++ b/tests/relative-resolution/files/resolved/yes.html @@ -0,0 +1 @@ + diff --git a/tests/relative-resolution/files/resolved/yes.js b/tests/relative-resolution/files/resolved/yes.js new file mode 100644 index 000000000000..b33de482b4b3 --- /dev/null +++ b/tests/relative-resolution/files/resolved/yes.js @@ -0,0 +1 @@ +// content-[resolved-dynamic-positive] From 34446039ce7275ac918a48e8840ca28e94b23d8a Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 23 Sep 2022 07:56:29 -0400 Subject: [PATCH 08/10] reformat integration test list --- .github/workflows/integration-tests.yml | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 4c2812b6196a..78bb9aba5204 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -15,7 +15,15 @@ jobs: strategy: matrix: - integration: [parcel, postcss-cli, rollup, rollup-sass, tailwindcss-cli, vite, webpack-4, webpack-5] + integration: + - parcel + - postcss-cli + - rollup + - rollup-sass + - tailwindcss-cli + - vite + - webpack-4 + - webpack-5 node-version: [16] steps: From c9ec1bd6da73ef2b83c06b07bf0ab5707d5a973a Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 23 Sep 2022 07:56:53 -0400 Subject: [PATCH 09/10] Move content resolution tests to integration --- .github/workflows/integration-tests.yml | 1 + .../content-resolution}/.gitignore | 0 .../content-resolution/package-lock.json | 1369 +++++++++++++++++ integrations/content-resolution/package.json | 20 + .../content-resolution/postcss.config.js | 5 + integrations/content-resolution/src/index.css | 1 + .../content-resolution/src}/real/no-1.js | 0 .../content-resolution/src}/real/no.js | 0 .../content-resolution/src}/real/yes.html | 0 .../content-resolution/src}/real/yes.js | 0 .../content-resolution/src}/resolved/no-1.js | 0 .../content-resolution/src}/resolved/no.js | 0 .../content-resolution/src}/resolved/yes.html | 0 .../content-resolution/src}/resolved/yes.js | 0 .../content-resolution/tests}/config.js | 20 +- .../content-resolution/tests/content.test.js | 287 ++++ .../content-resolution/tests}/cwd.js | 2 +- integrations/execute.js | 5 +- tests/relative-resolution/content.test.js | 266 ---- 19 files changed, 1699 insertions(+), 277 deletions(-) rename {tests/relative-resolution => integrations/content-resolution}/.gitignore (100%) create mode 100644 integrations/content-resolution/package-lock.json create mode 100644 integrations/content-resolution/package.json create mode 100644 integrations/content-resolution/postcss.config.js create mode 100644 integrations/content-resolution/src/index.css rename {tests/relative-resolution/files => integrations/content-resolution/src}/real/no-1.js (100%) rename {tests/relative-resolution/files => integrations/content-resolution/src}/real/no.js (100%) rename {tests/relative-resolution/files => integrations/content-resolution/src}/real/yes.html (100%) rename {tests/relative-resolution/files => integrations/content-resolution/src}/real/yes.js (100%) rename {tests/relative-resolution/files => integrations/content-resolution/src}/resolved/no-1.js (100%) rename {tests/relative-resolution/files => integrations/content-resolution/src}/resolved/no.js (100%) rename {tests/relative-resolution/files => integrations/content-resolution/src}/resolved/yes.html (100%) rename {tests/relative-resolution/files => integrations/content-resolution/src}/resolved/yes.js (100%) rename {tests/relative-resolution => integrations/content-resolution/tests}/config.js (59%) create mode 100644 integrations/content-resolution/tests/content.test.js rename {tests/relative-resolution => integrations/content-resolution/tests}/cwd.js (92%) delete mode 100644 tests/relative-resolution/content.test.js diff --git a/.github/workflows/integration-tests.yml b/.github/workflows/integration-tests.yml index 78bb9aba5204..9d813ad5bed8 100644 --- a/.github/workflows/integration-tests.yml +++ b/.github/workflows/integration-tests.yml @@ -16,6 +16,7 @@ jobs: strategy: matrix: integration: + - content-resolution - parcel - postcss-cli - rollup diff --git a/tests/relative-resolution/.gitignore b/integrations/content-resolution/.gitignore similarity index 100% rename from tests/relative-resolution/.gitignore rename to integrations/content-resolution/.gitignore diff --git a/integrations/content-resolution/package-lock.json b/integrations/content-resolution/package-lock.json new file mode 100644 index 000000000000..8f720899b1e7 --- /dev/null +++ b/integrations/content-resolution/package-lock.json @@ -0,0 +1,1369 @@ +{ + "name": "postcss-cli", + "version": "0.0.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "postcss-cli", + "version": "0.0.0", + "devDependencies": { + "postcss": "^8.4.14", + "postcss-cli": "^9.1.0" + } + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "node_modules/dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true, + "engines": { + "node": ">= 0.6.0" + } + }, + "node_modules/dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "dependencies": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/globby": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", + "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "dev": true, + "dependencies": { + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.9", + "merge2": "^1.4.1", + "slash": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "dependencies": { + "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" + } + }, + "node_modules/lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true, + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + } + ], + "dependencies": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-cli": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-9.1.0.tgz", + "integrity": "sha512-zvDN2ADbWfza42sAnj+O2uUWyL0eRL1V+6giM2vi4SqTR3gTYy8XzcpfwccayF2szcUif0HMmXiEaDv9iEhcpw==", + "dev": true, + "dependencies": { + "chokidar": "^3.3.0", + "dependency-graph": "^0.11.0", + "fs-extra": "^10.0.0", + "get-stdin": "^9.0.0", + "globby": "^12.0.0", + "picocolors": "^1.0.0", + "postcss-load-config": "^3.0.0", + "postcss-reporter": "^7.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "slash": "^4.0.0", + "yargs": "^17.0.0" + }, + "bin": { + "postcss": "index.js" + }, + "engines": { + "node": ">=12" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + }, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-reporter": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.0.5.tgz", + "integrity": "sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==", + "dev": true, + "dependencies": { + "picocolors": "^1.0.0", + "thenby": "^1.3.4" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/thenby": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", + "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", + "dev": true + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true, + "engines": { + "node": ">= 10.0.0" + } + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "dependencies": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true, + "engines": { + "node": ">=12" + } + } + }, + "dependencies": { + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "requires": { + "color-convert": "^2.0.1" + } + }, + "anymatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.2.tgz", + "integrity": "sha512-P43ePfOAIupkguHUycrc4qJ9kz8ZiuOUijaETwX7THt0Y/GNK7v0aa8rY816xWjZ7rJdA5XdMcpVFTKMq+RvWg==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "array-union": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-3.0.1.tgz", + "integrity": "sha512-1OvF9IbWwaeiM9VhzYXVQacMibxpXOMYVNIvMtKRyX9SImBXpKcFr8XvFDeEslCyuH/t6KRt7HEO94AlP8Iatw==", + "dev": true + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + } + }, + "cliui": { + "version": "7.0.4", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz", + "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==", + "dev": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^7.0.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true + }, + "dependency-graph": { + "version": "0.11.0", + "resolved": "https://registry.npmjs.org/dependency-graph/-/dependency-graph-0.11.0.tgz", + "integrity": "sha512-JeMq7fEshyepOWDfcfHK06N3MhyPhz++vtqWhMT5O9A3K42rdsEDpfdVqjaqaAhsw6a+ZqeDvQVtD0hFHQWrzg==", + "dev": true + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "fast-glob": { + "version": "3.2.12", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.12.tgz", + "integrity": "sha512-DVj4CQIYYow0BlaelwK1pHl5n5cRSJfM60UA0zK891sVInoPri2Ekj7+e1CT3/3qxXenpI+nBBmQAcJPJgaj4w==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + } + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "fs-extra": { + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-10.1.0.tgz", + "integrity": "sha512-oRXApq54ETRj4eMiFzGnHWGy+zo5raudjuxN0b8H7s/RU2oW0Wvsx9O0ACRN/kRq9E8Vu/ReskGB5o3ji+FzHQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^6.0.1", + "universalify": "^2.0.0" + } + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "dev": true + }, + "get-stdin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-9.0.0.tgz", + "integrity": "sha512-dVKBjfWisLAicarI2Sf+JuBE/DghV4UzNAVe9yhEJuzeREd3JhOTE9cUaJTeSa77fsbQUK3pcOpJfM59+VKZaA==", + "dev": true + }, + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "globby": { + "version": "12.2.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-12.2.0.tgz", + "integrity": "sha512-wiSuFQLZ+urS9x2gGPl1H5drc5twabmm4m2gTR27XDFyjUHJUNsS8o/2aKyIF6IoBaR630atdher0XJ5g6OMmA==", + "dev": true, + "requires": { + "array-union": "^3.0.1", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.7", + "ignore": "^5.1.9", + "merge2": "^1.4.1", + "slash": "^4.0.0" + } + }, + "graceful-fs": { + "version": "4.2.10", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.10.tgz", + "integrity": "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA==", + "dev": true + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "dev": true + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "dev": true + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "jsonfile": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", + "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6", + "universalify": "^2.0.0" + } + }, + "lilconfig": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.0.6.tgz", + "integrity": "sha512-9JROoBW7pobfsx+Sq2JsASvCo6Pfo6WWoUW79HuB1BCoBXD4PLWJPqDF6fNj67pqBYTbAHkE57M1kS/+L1neOg==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "nanoid": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.4.tgz", + "integrity": "sha512-MqBkQh/OHTS2egovRtLk45wEyNXwF+cokD+1YPf9u5VfJiRdAiRwB2froX5Co9Rh20xs4siNPm8naNotSD6RBw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "postcss": { + "version": "8.4.16", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.16.tgz", + "integrity": "sha512-ipHE1XBvKzm5xI7hiHCZJCSugxvsdq2mPnsq5+UF+VHCjiBvtDrlxJfMBToWaP9D5XlgNmcFGqoHmUn0EYEaRQ==", + "dev": true, + "requires": { + "nanoid": "^3.3.4", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-cli": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/postcss-cli/-/postcss-cli-9.1.0.tgz", + "integrity": "sha512-zvDN2ADbWfza42sAnj+O2uUWyL0eRL1V+6giM2vi4SqTR3gTYy8XzcpfwccayF2szcUif0HMmXiEaDv9iEhcpw==", + "dev": true, + "requires": { + "chokidar": "^3.3.0", + "dependency-graph": "^0.11.0", + "fs-extra": "^10.0.0", + "get-stdin": "^9.0.0", + "globby": "^12.0.0", + "picocolors": "^1.0.0", + "postcss-load-config": "^3.0.0", + "postcss-reporter": "^7.0.0", + "pretty-hrtime": "^1.0.3", + "read-cache": "^1.0.0", + "slash": "^4.0.0", + "yargs": "^17.0.0" + } + }, + "postcss-load-config": { + "version": "3.1.4", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-3.1.4.tgz", + "integrity": "sha512-6DiM4E7v4coTE4uzA8U//WhtPwyhiim3eyjEMFCnUpzbrkK9wJHgKDT2mR+HbtSrd/NubVaYTOpSpjUl8NQeRg==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^1.10.2" + } + }, + "postcss-reporter": { + "version": "7.0.5", + "resolved": "https://registry.npmjs.org/postcss-reporter/-/postcss-reporter-7.0.5.tgz", + "integrity": "sha512-glWg7VZBilooZGOFPhN9msJ3FQs19Hie7l5a/eE6WglzYqVeH3ong3ShFcp9kDWJT1g2Y/wd59cocf9XxBtkWA==", + "dev": true, + "requires": { + "picocolors": "^1.0.0", + "thenby": "^1.3.4" + } + }, + "pretty-hrtime": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/pretty-hrtime/-/pretty-hrtime-1.0.3.tgz", + "integrity": "sha512-66hKPCr+72mlfiSjlEB1+45IjXSqvVAIy6mocupoww4tBFE9R9IhwwUGoI4G++Tc9Aq+2rxOt0RFU6gPcrte0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "slash": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-4.0.0.tgz", + "integrity": "sha512-3dOsAHXXUkQTpOYcoAxLIorMTp4gIQr5IW3iVb7A7lFIp0VHhnynm9izx6TssdrIcVIESAlVjtnO2K8bg+Coew==", + "dev": true + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "dev": true + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "dev": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "thenby": { + "version": "1.3.4", + "resolved": "https://registry.npmjs.org/thenby/-/thenby-1.3.4.tgz", + "integrity": "sha512-89Gi5raiWA3QZ4b2ePcEwswC3me9JIg+ToSgtE0JWeCynLnLxNr/f9G+xfo9K+Oj4AFdom8YNJjibIARTJmapQ==", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "universalify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", + "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", + "dev": true + }, + "wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "dev": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "dev": true + }, + "yaml": { + "version": "1.10.2", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-1.10.2.tgz", + "integrity": "sha512-r3vXyErRCYJ7wg28yvBY5VSoAF8ZvlcW9/BwUzEtUsjvX/DKs24dIkuwjtuprwJJHsbyUbLApepYTR1BN4uHrg==", + "dev": true + }, + "yargs": { + "version": "17.5.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.5.1.tgz", + "integrity": "sha512-t6YAJcxDkNX7NFYiVtKvWUz8l+PaKTLiL63mJYWR2GnHq2gjEWISzsLp9wg3aY36dY1j+gfIEL3pIF+XlJJfbA==", + "dev": true, + "requires": { + "cliui": "^7.0.2", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.0.0" + } + }, + "yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "dev": true + } + } +} diff --git a/integrations/content-resolution/package.json b/integrations/content-resolution/package.json new file mode 100644 index 000000000000..32923b2fcccf --- /dev/null +++ b/integrations/content-resolution/package.json @@ -0,0 +1,20 @@ +{ + "name": "postcss-cli", + "private": true, + "version": "0.0.0", + "scripts": { + "build": "NODE_ENV=production postcss ./src/index.css -o ./dist/main.css --verbose", + "test": "jest --runInBand --forceExit" + }, + "jest": { + "testTimeout": 10000, + "displayName": "Content Resolution", + "setupFilesAfterEnv": [ + "/../../jest/customMatchers.js" + ] + }, + "devDependencies": { + "postcss": "^8.4.14", + "postcss-cli": "^9.1.0" + } +} diff --git a/integrations/content-resolution/postcss.config.js b/integrations/content-resolution/postcss.config.js new file mode 100644 index 000000000000..4c5efe72893b --- /dev/null +++ b/integrations/content-resolution/postcss.config.js @@ -0,0 +1,5 @@ +let path = require('path') + +module.exports = { + plugins: [require(path.resolve(__dirname, '..', '..'))], +} diff --git a/integrations/content-resolution/src/index.css b/integrations/content-resolution/src/index.css new file mode 100644 index 000000000000..65dd5f63a7df --- /dev/null +++ b/integrations/content-resolution/src/index.css @@ -0,0 +1 @@ +@tailwind utilities; diff --git a/tests/relative-resolution/files/real/no-1.js b/integrations/content-resolution/src/real/no-1.js similarity index 100% rename from tests/relative-resolution/files/real/no-1.js rename to integrations/content-resolution/src/real/no-1.js diff --git a/tests/relative-resolution/files/real/no.js b/integrations/content-resolution/src/real/no.js similarity index 100% rename from tests/relative-resolution/files/real/no.js rename to integrations/content-resolution/src/real/no.js diff --git a/tests/relative-resolution/files/real/yes.html b/integrations/content-resolution/src/real/yes.html similarity index 100% rename from tests/relative-resolution/files/real/yes.html rename to integrations/content-resolution/src/real/yes.html diff --git a/tests/relative-resolution/files/real/yes.js b/integrations/content-resolution/src/real/yes.js similarity index 100% rename from tests/relative-resolution/files/real/yes.js rename to integrations/content-resolution/src/real/yes.js diff --git a/tests/relative-resolution/files/resolved/no-1.js b/integrations/content-resolution/src/resolved/no-1.js similarity index 100% rename from tests/relative-resolution/files/resolved/no-1.js rename to integrations/content-resolution/src/resolved/no-1.js diff --git a/tests/relative-resolution/files/resolved/no.js b/integrations/content-resolution/src/resolved/no.js similarity index 100% rename from tests/relative-resolution/files/resolved/no.js rename to integrations/content-resolution/src/resolved/no.js diff --git a/tests/relative-resolution/files/resolved/yes.html b/integrations/content-resolution/src/resolved/yes.html similarity index 100% rename from tests/relative-resolution/files/resolved/yes.html rename to integrations/content-resolution/src/resolved/yes.html diff --git a/tests/relative-resolution/files/resolved/yes.js b/integrations/content-resolution/src/resolved/yes.js similarity index 100% rename from tests/relative-resolution/files/resolved/yes.js rename to integrations/content-resolution/src/resolved/yes.js diff --git a/tests/relative-resolution/config.js b/integrations/content-resolution/tests/config.js similarity index 59% rename from tests/relative-resolution/config.js rename to integrations/content-resolution/tests/config.js index e3a4d4112eb8..6f707d292a72 100644 --- a/tests/relative-resolution/config.js +++ b/integrations/content-resolution/tests/config.js @@ -1,12 +1,16 @@ // @ts-config -import fs from 'fs' -import path from 'path' +let fs = require('fs') +let path = require('path') -export async function writeConfigs({ both = {}, inRoot = {}, inDir = {} } = {}) { +module.exports.writeConfigs = async function writeConfigs({ + both = {}, + inRoot = {}, + inDir = {}, +} = {}) { let configs = [ { - path: './content.tailwind.config.js', + path: '../tailwind.config.js', config: { ...both, ...inRoot, @@ -18,7 +22,7 @@ export async function writeConfigs({ both = {}, inRoot = {}, inDir = {} } = {}) }, }, { - path: './files/content.tailwind.config.js', + path: '../src/tailwind.config.js', config: { ...both, ...inDir, @@ -43,7 +47,7 @@ export async function writeConfigs({ both = {}, inRoot = {}, inDir = {} } = {}) } } -export async function destroyConfigs() { - await fs.promises.unlink(path.resolve(__dirname, './content.tailwind.config.js')) - await fs.promises.unlink(path.resolve(__dirname, './files/content.tailwind.config.js')) +module.exports.destroyConfigs = async function destroyConfigs() { + await fs.promises.unlink(path.resolve(__dirname, '../tailwind.config.js')) + await fs.promises.unlink(path.resolve(__dirname, '../src/tailwind.config.js')) } diff --git a/integrations/content-resolution/tests/content.test.js b/integrations/content-resolution/tests/content.test.js new file mode 100644 index 000000000000..5af090827b3b --- /dev/null +++ b/integrations/content-resolution/tests/content.test.js @@ -0,0 +1,287 @@ +let fs = require('fs') +let path = require('path') +let { cwd } = require('./cwd.js') +let { writeConfigs, destroyConfigs } = require('./config.js') + +let $ = require('../../execute') +let { css } = require('../../syntax') + +let { readOutputFile } = require('../../io')({ + output: 'dist', + input: '.', +}) + +// Write default configs before running tests and remove them afterwards +beforeAll(() => writeConfigs()) +afterAll(() => destroyConfigs()) + +// Create a symlink at ./src/link that points to ./src/resolved and remove it afterwards +beforeAll(() => + fs.promises.symlink( + path.resolve(__dirname, '../src/resolved'), + path.resolve(__dirname, '../src/link') + ) +) +afterAll(async () => { + try { + await fs.promises.unlink(path.resolve(__dirname, '../src/link')) + } catch {} +}) + +// If we've changed directories reset the cwd back to what it was before running these tests +afterEach(() => cwd.unwind()) + +async function build({ cwd: cwdPath } = {}) { + let inputPath = path.resolve(__dirname, '../src/index.css') + let outputPath = path.resolve(__dirname, '../dist/main.css') + + await cwd.switch(cwdPath) + + // Note that ./tailwind.config.js is hardcoded on purpose here + // It represents a config but one that could be in different places + await $(`postcss ${inputPath} -o ${outputPath}`, { + env: { NODE_ENV: 'production' }, + cwd: cwdPath, + }) + + return { + css: await readOutputFile('main.css'), + } +} + +it('looks in the CWD by default', async () => { + await writeConfigs({ + both: { + content: { + files: ['./src/real/yes.html'], + }, + }, + }) + + let result = await build({ cwd: path.resolve(__dirname, '..') }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + `) + + result = await build({ cwd: path.resolve(__dirname, '../src') }) + + expect(result.css).toMatchCss(``) +}) + +it('looks in the CWD for non-config-relative paths', async () => { + await writeConfigs({ + both: { + // Turn it on by default (eventual v4 behavior) + experimental: { relativeContentPathsByDefault: true }, + + // But then disable it anyway + content: { + relative: false, + files: ['./src/real/yes.html'], + }, + }, + }) + + let result = await build({ cwd: path.resolve(__dirname, '..') }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + `) + + result = await build({ cwd: path.resolve(__dirname, '../src') }) + + expect(result.css).toMatchCss(``) +}) + +it('can look for content files relative to the config', async () => { + await writeConfigs({ + both: { + content: { + relative: true, + files: ['./real/yes.html'], + }, + }, + }) + + // Here `./real` doesn't exist next to the config in the root directory + let result = await build({ cwd: path.resolve(__dirname, '..') }) + + expect(result.css).toMatchCss(css``) + + // But here it `./real` does exist next to the config in the `./src` directory! + result = await build({ cwd: path.resolve(__dirname, '../src') }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + `) +}) + +it('it handles ignored globs correctly when not relative to the config', async () => { + await writeConfigs({ + both: { + content: { + relative: false, + files: [ + './src/real/yes.html', // Scanned + static + './src/real/*.js', // Scanned + dynamic + '!./src/real/no.js', // Ignored + static + '!./src/real/no-*.js', // Ignored + dynamic + ], + }, + }, + }) + + let result = await build({ cwd: path.resolve(__dirname, '..') }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + .content-\[real-dynamic-positive\] { + --tw-content: real-dynamic-positive; + content: var(--tw-content); + } + `) + + // But here it `./real` does exist next to the config in the `./src` directory! + result = await build({ cwd: path.resolve(__dirname, '../src') }) + + expect(result.css).toMatchCss(``) +}) + +it('it handles ignored globs correctly when relative to the config', async () => { + await writeConfigs({ + both: { + content: { + relative: true, + files: [ + './real/yes.html', // Scanned + static + './real/*.js', // Scanned + dynamic + '!./real/no.js', // Ignored + static + '!./real/no-*.js', // Ignored + dynamic + ], + }, + }, + }) + + let result = await build({ cwd: path.resolve(__dirname, '..') }) + + expect(result.css).toMatchCss(``) + + // But here it `./real` does exist next to the config in the `./src` directory! + result = await build({ cwd: path.resolve(__dirname, '../src') }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + .content-\[real-dynamic-positive\] { + --tw-content: real-dynamic-positive; + content: var(--tw-content); + } + `) +}) + +it('it can resolve symlinks for files when not relative to the config', async () => { + await writeConfigs({ + both: { + content: { + relative: false, + files: [ + './src/real/yes.html', // Scanned + static + './src/real/*.js', // Scanned + dynamic + './src/link/yes.html', // Scanned + static + symlinked + './src/link/*.js', // Scanned + dynamic + symlinked + '!./src/real/no.js', // Ignored + static + '!./src/real/no-*.js', // Ignored + dynamic + '!./src/link/no.js', // Ignored + static + symlinked + '!./src/link/no-*.js', // Ignored + dynamic + symlinked + ], + }, + }, + }) + + let result = await build({ cwd: path.resolve(__dirname, '..') }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + .content-\[resolved-static-positive\] { + --tw-content: resolved-static-positive; + content: var(--tw-content); + } + .content-\[real-dynamic-positive\] { + --tw-content: real-dynamic-positive; + content: var(--tw-content); + } + .content-\[resolved-dynamic-positive\] { + --tw-content: resolved-dynamic-positive; + content: var(--tw-content); + } + `) + + // But here it `./real` does exist next to the config in the `./src` directory! + result = await build({ cwd: path.resolve(__dirname, '../src') }) + + expect(result.css).toMatchCss(``) +}) + +it('it can resolve symlinks for files when relative to the config', async () => { + await writeConfigs({ + both: { + content: { + relative: true, + files: [ + './real/yes.html', // Scanned + static + './real/*.js', // Scanned + dynamic + './link/yes.html', // Scanned + static + symlinked + './link/*.js', // Scanned + dynamic + symlinked + '!./real/no.js', // Ignored + static + '!./real/no-*.js', // Ignored + dynamic + '!./link/no.js', // Ignored + static + symlinked + '!./link/no-*.js', // Ignored + dynamic + symlinked + ], + }, + }, + }) + + let result = await build({ cwd: path.resolve(__dirname, '..') }) + + expect(result.css).toMatchCss(``) + + // But here it `./real` does exist next to the config in the `./src` directory! + result = await build({ cwd: path.resolve(__dirname, '../src') }) + + expect(result.css).toMatchCss(css` + .content-\[real-static-positive\] { + --tw-content: real-static-positive; + content: var(--tw-content); + } + .content-\[resolved-static-positive\] { + --tw-content: resolved-static-positive; + content: var(--tw-content); + } + .content-\[real-dynamic-positive\] { + --tw-content: real-dynamic-positive; + content: var(--tw-content); + } + .content-\[resolved-dynamic-positive\] { + --tw-content: resolved-dynamic-positive; + content: var(--tw-content); + } + `) +}) diff --git a/tests/relative-resolution/cwd.js b/integrations/content-resolution/tests/cwd.js similarity index 92% rename from tests/relative-resolution/cwd.js rename to integrations/content-resolution/tests/cwd.js index ebfa4ffa324b..aeab638acb0e 100644 --- a/tests/relative-resolution/cwd.js +++ b/integrations/content-resolution/tests/cwd.js @@ -2,7 +2,7 @@ let stack = [] -export const cwd = { +module.exports.cwd = { get current() { return process.cwd() }, diff --git a/integrations/execute.js b/integrations/execute.js index 7b0f7e799c23..3931151404f5 100644 --- a/integrations/execute.js +++ b/integrations/execute.js @@ -19,14 +19,15 @@ function debounce(fn, ms) { module.exports = function $(command, options = {}) { let abortController = new AbortController() - let cwd = resolveToolRoot() + let root = resolveToolRoot() + let cwd = options.cwd ?? root let args = options.shell ? [command] : (() => { let args = command.split(' ') command = args.shift() - command = command === 'node' ? command : path.resolve(cwd, 'node_modules', '.bin', command) + command = command === 'node' ? command : path.resolve(root, 'node_modules', '.bin', command) return [command, args] })() diff --git a/tests/relative-resolution/content.test.js b/tests/relative-resolution/content.test.js deleted file mode 100644 index d437d93828fd..000000000000 --- a/tests/relative-resolution/content.test.js +++ /dev/null @@ -1,266 +0,0 @@ -import path from 'path' -import { css, run } from '../util/run.js' -import { cwd } from './cwd.js' -import { writeConfigs, destroyConfigs } from './config.js' -import fs from 'fs' - -// Write default configs before running tests and remove them afterwards -beforeAll(() => writeConfigs()) -afterAll(() => destroyConfigs()) - -// Create a symlink at ./files/link that points to ./files/resolved and remove it afterwards -beforeAll(() => - fs.promises.symlink( - path.resolve(__dirname, './files/resolved'), - path.resolve(__dirname, './files/link') - ) -) -afterAll(() => fs.promises.unlink(path.resolve(__dirname, './files/link'))) - -// If we've changed directories reset the cwd back to what it was before running these tests -afterEach(() => cwd.unwind()) - -async function build({ cwd: cwdPath } = {}) { - await cwd.switch(cwdPath) - - // Note that ./content.tailwind.config.js is hardcoded on purpose here - // It represents a config but one that could be in different places - return await run('@tailwind utilities', './content.tailwind.config.js') -} - -fit('looks in the CWD by default', async () => { - await writeConfigs({ - both: { - content: { - files: ['./files/real/yes.html'], - }, - }, - }) - - let result = await build({ cwd: __dirname }) - - expect(result.css).toMatchCss(css` - .content-\[real-static-positive\] { - --tw-content: real-static-positive; - content: var(--tw-content); - } - `) - - result = await build({ cwd: path.resolve(__dirname, './files') }) - - expect(result.css).toMatchCss(``) -}) - -it('looks in the CWD for non-config-relative paths', async () => { - await writeConfigs({ - both: { - // Turn it on by default (eventual v4 behavior) - experimental: { relativeContentPathsByDefault: true }, - - // But then disable it anyway - content: { - relative: false, - files: ['./files/real/yes.html'], - }, - }, - }) - - let result = await build({ cwd: __dirname }) - - expect(result.css).toMatchCss(css` - .content-\[real-static-positive\] { - --tw-content: real-static-positive; - content: var(--tw-content); - } - `) - - result = await build({ cwd: path.resolve(__dirname, './files') }) - - expect(result.css).toMatchCss(``) -}) - -it('can look for content files relative to the config', async () => { - await writeConfigs({ - both: { - content: { - relative: true, - files: ['./real/yes.html'], - }, - }, - }) - - // Here `./real` doesn't exist next to the config in the root directory - let result = await build({ cwd: __dirname }) - - expect(result.css).toMatchCss(css``) - - // But here it `./real` does exist next to the config in the `./files` directory! - result = await build({ cwd: path.resolve(__dirname, './files') }) - - expect(result.css).toMatchCss(css` - .content-\[real-static-positive\] { - --tw-content: real-static-positive; - content: var(--tw-content); - } - `) -}) - -// it('it handles ignored globs correctly when not relative to the config', async () => { -// await writeConfigs({ -// both: { -// content: { -// relative: false, -// files: [ -// './files/real/yes.html', // Scanned + static -// './files/real/*.js', // Scanned + dynamic -// '!./files/real/no.js', // Ignored + static -// '!./files/real/no-*.js', // Ignored + dynamic -// ], -// }, -// }, -// }) - -// let result = await build({ cwd: __dirname }) - -// expect(result.css).toMatchCss(css` -// .content-\[real-static-positive\] { -// --tw-content: real-static-positive; -// content: var(--tw-content); -// } -// .content-\[real-dynamic-positive\] { -// --tw-content: real-dynamic-positive; -// content: var(--tw-content); -// } -// `) - -// // But here it `./real` does exist next to the config in the `./files` directory! -// result = await build({ cwd: path.resolve(__dirname, './files') }) - -// expect(result.css).toMatchCss(``) -// }) - -// it('it handles ignored globs correctly when relative to the config', async () => { -// await writeConfigs({ -// both: { -// content: { -// relative: true, -// files: [ -// './real/yes.html', // Scanned + static -// './real/*.js', // Scanned + dynamic -// '!./real/no.js', // Ignored + static -// '!./real/no-*.js', // Ignored + dynamic -// ], -// }, -// }, -// }) - -// let result = await build({ cwd: __dirname }) - -// expect(result.css).toMatchCss(``) - -// // But here it `./real` does exist next to the config in the `./files` directory! -// result = await build({ cwd: path.resolve(__dirname, './files') }) - -// expect(result.css).toMatchCss(css` -// .content-\[real-static-positive\] { -// --tw-content: real-static-positive; -// content: var(--tw-content); -// } -// .content-\[real-dynamic-positive\] { -// --tw-content: real-dynamic-positive; -// content: var(--tw-content); -// } -// `) -// }) - -// it('it can resolve symlinks for files when not relative to the config', async () => { -// await writeConfigs({ -// both: { -// content: { -// relative: false, -// files: [ -// './files/real/yes.html', // Scanned + static -// './files/real/*.js', // Scanned + dynamic -// './files/link/yes.html', // Scanned + static + symlinked -// './files/link/*.js', // Scanned + dynamic + symlinked -// '!./files/real/no.js', // Ignored + static -// '!./files/real/no-*.js', // Ignored + dynamic -// '!./files/link/no.js', // Ignored + static + symlinked -// '!./files/link/no-*.js', // Ignored + dynamic + symlinked -// ], -// }, -// }, -// }) - -// let result = await build({ cwd: __dirname }) - -// expect(result.css).toMatchCss(css` -// .content-\[real-static-positive\] { -// --tw-content: real-static-positive; -// content: var(--tw-content); -// } -// .content-\[resolved-static-positive\] { -// --tw-content: resolved-static-positive; -// content: var(--tw-content); -// } -// .content-\[real-dynamic-positive\] { -// --tw-content: real-dynamic-positive; -// content: var(--tw-content); -// } -// .content-\[resolved-dynamic-positive\] { -// --tw-content: resolved-dynamic-positive; -// content: var(--tw-content); -// } -// `) - -// // But here it `./real` does exist next to the config in the `./files` directory! -// result = await build({ cwd: path.resolve(__dirname, './files') }) - -// expect(result.css).toMatchCss(``) -// }) - -// it('it can resolve symlinks for files when relative to the config', async () => { -// await writeConfigs({ -// both: { -// content: { -// relative: true, -// files: [ -// './real/yes.html', // Scanned + static -// './real/*.js', // Scanned + dynamic -// './link/yes.html', // Scanned + static + symlinked -// './link/*.js', // Scanned + dynamic + symlinked -// '!./real/no.js', // Ignored + static -// '!./real/no-*.js', // Ignored + dynamic -// '!./link/no.js', // Ignored + static + symlinked -// '!./link/no-*.js', // Ignored + dynamic + symlinked -// ], -// }, -// }, -// }) - -// let result = await build({ cwd: __dirname }) - -// expect(result.css).toMatchCss(``) - -// // But here it `./real` does exist next to the config in the `./files` directory! -// result = await build({ cwd: path.resolve(__dirname, './files') }) - -// expect(result.css).toMatchCss(css` -// .content-\[real-static-positive\] { -// --tw-content: real-static-positive; -// content: var(--tw-content); -// } -// .content-\[resolved-static-positive\] { -// --tw-content: resolved-static-positive; -// content: var(--tw-content); -// } -// .content-\[real-dynamic-positive\] { -// --tw-content: real-dynamic-positive; -// content: var(--tw-content); -// } -// .content-\[resolved-dynamic-positive\] { -// --tw-content: resolved-dynamic-positive; -// content: var(--tw-content); -// } -// `) -// }) From 1f5dfc59269b0eff065c6a385137b47de53be97b Mon Sep 17 00:00:00 2001 From: Jordan Pittman Date: Fri, 23 Sep 2022 08:05:19 -0400 Subject: [PATCH 10/10] Update future and experimental types --- types/config.d.ts | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/types/config.d.ts b/types/config.d.ts index 61cd9546f2c9..806cde42ab9e 100644 --- a/types/config.d.ts +++ b/types/config.d.ts @@ -57,20 +57,24 @@ type SafelistConfig = type PresetsConfig = Config[] // Future related config -type FutureConfigValues = never // Replace with 'future-feature-1' | 'future-feature-2' +type FutureConfigValues = + | 'hoverOnlyWhenSupported' + | 'respectDefaultRingColorOpacity' + | 'disableColorOpacityUtilitiesByDefault' + | 'relativeContentPathsByDefault' type FutureConfig = Expand<'all' | Partial>> | [] // Experimental related config -type ExperimentalConfigValues = 'optimizeUniversalDefaults' // Replace with 'experimental-feature-1' | 'experimental-feature-2' +type ExperimentalConfigValues = 'optimizeUniversalDefaults' | 'matchVariant' type ExperimentalConfig = Expand<'all' | Partial>> | [] // DarkMode related config type DarkModeConfig = // Use the `media` query strategy. | 'media' - // Use the `class` stategy, which requires a `.dark` class on the `html`. + // Use the `class` strategy, which requires a `.dark` class on the `html`. | 'class' - // Use the `class` stategy with a custom class instead of `.dark`. + // Use the `class` strategy with a custom class instead of `.dark`. | ['class', string] type Screen = { raw: string } | { min: string } | { max: string } | { min: string; max: string }