Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
288 changes: 129 additions & 159 deletions index.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
#!/usr/bin/env node

import path from 'path'

import path from 'node:path'
import { text } from 'node:stream/consumers'
import prettyHrtime from 'pretty-hrtime'
import { text } from 'stream/consumers'
import read from 'read-cache'
import pc from 'picocolors'
import { glob } from 'tinyglobby'
Expand All @@ -27,35 +26,6 @@ const { dir, output } = argv

if (argv.map) argv.map = { inline: false }

let cliConfig

async function buildCliConfig() {
cliConfig = {
options: {
map: argv.map !== undefined ? argv.map : { inline: true },
parser: argv.parser ? await import(argv.parser) : undefined,
syntax: argv.syntax ? await import(argv.syntax) : undefined,
stringifier: argv.stringifier
? await import(argv.stringifier)
: undefined,
},
plugins: argv.use
? await Promise.all(
argv.use.map(async (plugin) => {
try {
return (await import(plugin)).default()
} catch (e) {
const msg = e.message || `Unknown error in '${plugin}'`
let prefix = msg.includes(plugin) ? '' : ` (${plugin})`
if (e.name && e.name !== 'Error') prefix += `: ${e.name}`
error(`Plugin Error${prefix}: ${msg}`)
}
}),
)
: [],
}
}

let configFile
let argvConfigSet = false

Expand All @@ -81,89 +51,103 @@ if (parseInt(postcss().version) < 8) {
error('Please install PostCSS 8 or above')
}

buildCliConfig()
.then(() => {
if (argv.watch && !(argv.output || argv.replace || argv.dir)) {
error('Cannot write to stdout in watch mode')
}

if (input && input.length) {
return glob(
input.map((i) => slash(String(i))),
{ dot: argv.includeDotfiles },
const cliConfig = {
options: {
map: argv.map !== undefined ? argv.map : { inline: true },
parser: argv.parser ? await import(argv.parser) : undefined,
syntax: argv.syntax ? await import(argv.syntax) : undefined,
stringifier: argv.stringifier ? await import(argv.stringifier) : undefined,
Comment thread
RyanZim marked this conversation as resolved.
},
plugins: argv.use
? await Promise.all(
argv.use.map(async (plugin) => {
try {
return (await import(plugin)).default()
} catch (e) {
const msg = e.message || `Unknown error in '${plugin}'`
let prefix = msg.includes(plugin) ? '' : ` (${plugin})`
if (e.name && e.name !== 'Error') prefix += `: ${e.name}`
error(`Plugin Error${prefix}: ${msg}`)
}
}),
)
}
: [],
}

if (argv.replace || argv.dir) {
error(
'Input Error: Cannot use --dir or --replace when reading from stdin',
)
}
if (argv.watch && !(argv.output || argv.replace || argv.dir)) {
error('Cannot write to stdout in watch mode')
}

if (argv.watch) {
error('Input Error: Cannot run in watch mode when reading from stdin')
}
if (input && input.length) {
input = await glob(
input.map((i) => slash(String(i))),
{ dot: argv.includeDotfiles },
)
if (!input.length) {
error('Input Error: You must pass a valid list of files to parse')
}

return ['stdin']
})
.then((i) => {
if (!i || !i.length) {
error('Input Error: You must pass a valid list of files to parse')
}
if (input.length > 1 && !argv.dir && !argv.replace) {
error('Input Error: Must use --dir or --replace with multiple input files')
}

if (i.length > 1 && !argv.dir && !argv.replace) {
error(
'Input Error: Must use --dir or --replace with multiple input files',
)
}
input = input.map((i) => path.resolve(i))
} else {
if (argv.replace || argv.dir) {
error('Input Error: Cannot use --dir or --replace when reading from stdin')
}

if (i[0] !== 'stdin') i = i.map((i) => path.resolve(i))
if (argv.watch) {
error('Input Error: Cannot run in watch mode when reading from stdin')
}

input = i
input = ['stdin']
}

return files(input)
})
.then((results) => {
if (argv.watch) {
const printMessage = () =>
printVerbose(pc.dim('\nWaiting for file changes...'))
const watcher = chokidar.watch(input.concat(dependencies(results)), {
usePolling: argv.poll,
interval: argv.poll && typeof argv.poll === 'number' ? argv.poll : 100,
awaitWriteFinish: {
stabilityThreshold: 50,
pollInterval: 10,
},
})
try {
const results = await files(input)

if (argv.watch) {
const printMessage = () =>
printVerbose(pc.dim('\nWaiting for file changes...'))
const watcher = chokidar.watch(input.concat(dependencies(results)), {
usePolling: argv.poll,
interval: argv.poll && typeof argv.poll === 'number' ? argv.poll : 100,
awaitWriteFinish: {
stabilityThreshold: 50,
pollInterval: 10,
},
})

if (configFile) watcher.add(configFile)
if (configFile) watcher.add(configFile)

watcher.on('ready', printMessage).on('change', (file) => {
let recompile = []
watcher.on('ready', printMessage).on('change', (file) => {
let recompile = []

if (input.includes(file)) recompile.push(file)
if (input.includes(file)) recompile.push(file)

const dependants = depGraph
.dependantsOf(file)
.concat(getAncestorDirs(file).flatMap(depGraph.dependantsOf))
const dependants = depGraph
.dependantsOf(file)
.concat(getAncestorDirs(file).flatMap(depGraph.dependantsOf))

recompile = recompile.concat(
dependants.filter((file) => input.includes(file)),
)
recompile = recompile.concat(
dependants.filter((file) => input.includes(file)),
)

if (!recompile.length) recompile = input
if (!recompile.length) recompile = input

return files([...new Set(recompile)])
.then((results) => watcher.add(dependencies(results)))
.then(printMessage)
.catch((err) => {
// Watch mode shouldn't exit on file processing error
error(err, argv.watch)
})
})
}
})
.catch(error)
return files([...new Set(recompile)])
.then((results) => watcher.add(dependencies(results)))
.then(printMessage)
.catch((err) => {
// Watch mode shouldn't exit on file processing error
error(err, argv.watch)
})
})
}
} catch (err) {
error(err)
}

function rc(ctx, path) {
if (argv.use) return Promise.resolve(cliConfig)
Expand All @@ -187,8 +171,6 @@ function rc(ctx, path) {
}

function files(files) {
if (typeof files === 'string') files = [files]

return Promise.all(
files.map((file) => {
if (file === 'stdin') {
Expand All @@ -203,7 +185,7 @@ function files(files) {
)
}

function css(css, file) {
async function css(css, file) {
const ctx = { options: cliConfig.options }

if (file !== 'stdin') {
Expand All @@ -225,69 +207,57 @@ function css(css, file) {

printVerbose(pc.cyan(`Processing ${pc.bold(relativePath)}...`))

return rc(ctx, argv.config)
.then((config) => {
config = config || cliConfig
const options = { ...config.options }
const config = (await rc(ctx, argv.config)) || cliConfig
const options = { ...config.options }

if (file === 'stdin' && output) file = output
if (file === 'stdin' && output) file = output

// TODO: Unit test this
options.from = file === 'stdin' ? path.join(process.cwd(), 'stdin') : file
// TODO: Unit test this
options.from = file === 'stdin' ? path.join(process.cwd(), 'stdin') : file

if (output || dir || argv.replace) {
const base = argv.base
? file.replace(path.resolve(argv.base), '')
: path.basename(file)
options.to = output || (argv.replace ? file : path.join(dir, base))
if (output || dir || argv.replace) {
const base = argv.base
? file.replace(path.resolve(argv.base), '')
: path.basename(file)
options.to = output || (argv.replace ? file : path.join(dir, base))

if (argv.ext) {
options.to = options.to.replace(path.extname(options.to), argv.ext)
}
if (argv.ext) {
options.to = options.to.replace(path.extname(options.to), argv.ext)
}

options.to = path.resolve(options.to)
}
options.to = path.resolve(options.to)
}

if (!options.to && config.options.map && !config.options.map.inline) {
error(
'Output Error: Cannot output external sourcemaps when writing to STDOUT',
)
}
if (!options.to && config.options.map && !config.options.map.inline) {
error(
'Output Error: Cannot output external sourcemaps when writing to STDOUT',
)
}

return postcss(config.plugins)
.process(css, options)
.then((result) => {
const tasks = []

if (options.to) {
tasks.push(outputFile(options.to, result.css))

if (result.map) {
const mapfile = getMapfile(options)
tasks.push(outputFile(mapfile, result.map.toString()))
}
} else process.stdout.write(result.css, 'utf8')

return Promise.all(tasks).then(() => {
const prettyTime = prettyHrtime(process.hrtime(time))
printVerbose(
pc.green(
`Finished ${pc.bold(relativePath)} in ${pc.bold(prettyTime)}`,
),
)

const messages = result.warnings()
if (messages.length) {
console.warn(reporter({ ...result, messages }))
}

return result
})
})
})
.catch((err) => {
throw err
})
const result = await postcss(config.plugins).process(css, options)
const tasks = []

if (options.to) {
tasks.push(outputFile(options.to, result.css))

if (result.map) {
const mapfile = getMapfile(options)
tasks.push(outputFile(mapfile, result.map.toString()))
}
} else process.stdout.write(result.css, 'utf8')

await Promise.all(tasks)
const prettyTime = prettyHrtime(process.hrtime(time))
printVerbose(
pc.green(`Finished ${pc.bold(relativePath)} in ${pc.bold(prettyTime)}`),
)

const messages = result.warnings()
if (messages.length) {
console.warn(reporter({ ...result, messages }))
}

return result
}

function dependencies(results) {
Expand Down
2 changes: 1 addition & 1 deletion lib/DependencyGraph.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import path from 'path'
import path from 'node:path'
import { DepGraph } from 'dependency-graph'

export default function createDependencyGraph() {
Expand Down
2 changes: 1 addition & 1 deletion lib/DependencyGraph.test.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from 'ava'
import path from 'path'
import path from 'node:path'
import createDependencyGraph from './DependencyGraph.js'

function resolveArray(arr) {
Expand Down
2 changes: 1 addition & 1 deletion lib/getMapfile.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import path from 'path'
import path from 'node:path'
export default function getMapfile(options) {
if (options.map && typeof options.map.annotation === 'string') {
return `${path.dirname(options.to)}/${options.map.annotation}`
Expand Down
2 changes: 1 addition & 1 deletion test/base.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from 'ava'
import path from 'path'
import path from 'node:path'

import cli from './helpers/cli.js'
import tmp from './helpers/tmp.js'
Expand Down
2 changes: 1 addition & 1 deletion test/config.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from 'ava'
import path from 'path'
import path from 'node:path'

import ENV from './helpers/env.js'

Expand Down
2 changes: 1 addition & 1 deletion test/dir.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import test from 'ava'
import path from 'path'
import path from 'node:path'

import cli from './helpers/cli.js'
import tmp from './helpers/tmp.js'
Expand Down
Loading