From f1591fb413aba649e45e77506c1dcdc0408a83ea Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Thu, 29 Feb 2024 15:42:31 +0100 Subject: [PATCH 01/10] feat: some errors --- examples/standalone/package.json | 2 +- packages/css2tailwind/package.json | 2 ++ packages/css2tailwind/src/build.ts | 6 ++++-- packages/css2tailwind/src/cli.ts | 12 +++++++----- packages/css2tailwind/src/compiler.ts | 23 +++-------------------- packages/css2tailwind/src/error.ts | 26 +++++++++++++++++++++++++- packages/css2tailwind/src/util.ts | 25 +++++++++++++++++++++++++ pnpm-lock.yaml | 15 +++++++++++++++ 8 files changed, 82 insertions(+), 29 deletions(-) create mode 100644 packages/css2tailwind/src/util.ts diff --git a/examples/standalone/package.json b/examples/standalone/package.json index cf9e669..ce8fb34 100644 --- a/examples/standalone/package.json +++ b/examples/standalone/package.json @@ -12,7 +12,7 @@ "lint": "eslint src", "lint:fix": "eslint --fix src", "types": "tsc", - "styles:build": "css2tailwind src/styles .styles", + "styles:build": "css2tailwind src/styles .styles -c ./blah.js", "styles:watch": "css2tailwind src/styles .styles --watch" }, "dependencies": { diff --git a/packages/css2tailwind/package.json b/packages/css2tailwind/package.json index 84e4129..5d8ebc3 100644 --- a/packages/css2tailwind/package.json +++ b/packages/css2tailwind/package.json @@ -28,6 +28,8 @@ }, "dependencies": { "bundle-require": "4.0.2", + "chalk": "5.3.0", + "colorette": "2.0.20", "postcss": "8.4.35", "postcss-import": "16.0.1", "postcss-js": "4.0.1", diff --git a/packages/css2tailwind/src/build.ts b/packages/css2tailwind/src/build.ts index c41ccb3..365e4b1 100644 --- a/packages/css2tailwind/src/build.ts +++ b/packages/css2tailwind/src/build.ts @@ -5,6 +5,8 @@ import type { CssInJs } from 'postcss-js'; import { compileStyleSheet } from './compiler'; +import type { Config } from 'tailwindcss'; + export async function readStyles(dir: string): Promise { const entries = await fsp.readdir(dir); const files = entries.map((entry) => path.join(dir, entry, `${entry}.css`)); @@ -14,11 +16,11 @@ export async function readStyles(dir: string): Promise { export async function parseStyles( dir: string, stylesDirectory: string, - tailwindConfigPath?: string, + tailwindConfig: Config, ): Promise { const contents = await readStyles(dir); const compiledStyles = await Promise.all( - contents.map(async (raw) => await compileStyleSheet(raw, stylesDirectory, tailwindConfigPath)), + contents.map(async (raw) => await compileStyleSheet(raw, stylesDirectory, tailwindConfig)), ); return compiledStyles.reduce((kind, style) => { diff --git a/packages/css2tailwind/src/cli.ts b/packages/css2tailwind/src/cli.ts index e57d096..3cde41b 100644 --- a/packages/css2tailwind/src/cli.ts +++ b/packages/css2tailwind/src/cli.ts @@ -7,7 +7,8 @@ import { hideBin } from 'yargs/helpers'; import { z } from 'zod'; import { bootstrapStyles, parseStyles, writeStyles } from './build'; -import { NoStylesDirectory } from './error'; +import { NoStylesDirectoryError } from './error'; +import { readTailwindConfig } from './util'; const { argv } = yargs(hideBin(process.argv)) .usage('tgp ') @@ -48,19 +49,20 @@ const args = schema.parse(argv); const cwd = process.cwd(); const stylesDirectory = path.join(cwd, args.stylesDirectory); const outputDirectory = path.join(cwd, args.outputDirectory); -const configPath = args.config ? path.join(cwd, args.config) : undefined; async function assertDirExists(dir: string) { try { await fsp.stat(dir); } catch (error) { - throw new NoStylesDirectory(`Styles Directory ${stylesDirectory} does not exist.`); + throw new NoStylesDirectoryError(`Styles Directory ${stylesDirectory} does not exist.`); } } async function main() { await assertDirExists(stylesDirectory); + const tailwindConfig = await readTailwindConfig(args.config && path.join(cwd, args.config)); + const dirents = await fsp.readdir(stylesDirectory, { withFileTypes: true }); const entries = dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name); @@ -70,7 +72,7 @@ async function main() { const styles = await parseStyles( path.join(stylesDirectory, entry), stylesDirectory, - configPath, + tailwindConfig, ); await writeStyles(outputDirectory, entry, styles); }), @@ -89,7 +91,7 @@ async function main() { const styles = await parseStyles( path.join(stylesDirectory, entry), stylesDirectory, - configPath, + tailwindConfig, ); await writeStyles(outputDirectory, entry, styles); })(); diff --git a/packages/css2tailwind/src/compiler.ts b/packages/css2tailwind/src/compiler.ts index 1b2365d..6bf53bb 100644 --- a/packages/css2tailwind/src/compiler.ts +++ b/packages/css2tailwind/src/compiler.ts @@ -1,28 +1,11 @@ import * as path from 'node:path'; -import { bundleRequire } from 'bundle-require'; import importPlugin from 'postcss-import'; import postcssJs, { type CssInJs } from 'postcss-js'; import nestingPlugin from 'tailwindcss/nesting'; import postcss, { type Root } from 'postcss'; -import type { Config } from 'tailwindcss'; -import tailwindPlugin from 'tailwindcss'; - -const defaultTailwindConfig: Config = { content: ['./**/*.css'] }; - -// TODO: logging, error handling -async function readTailwindConfig(path?: string): Promise { - if (!path) return defaultTailwindConfig; - const bundle = await bundleRequire({ - filepath: path, - }); - // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unused-vars, @typescript-eslint/no-unsafe-member-access - const config = (bundle.mod.default ?? {}) as Config; - // TODO: log a warning - if (Object.keys(config).length <= 0) return defaultTailwindConfig; - return config; -} +import tailwindPlugin, { type Config } from 'tailwindcss'; function resolveImport(stylesDirectory: string): (id: string) => string { return (id: string): string => { @@ -34,14 +17,14 @@ function resolveImport(stylesDirectory: string): (id: string) => string { export async function compileStyleSheet( raw: string, stylesDirectory: string, - tailwindConfigPath?: string, + tailwindConfig: Config, ): Promise { const ast = postcss.parse(raw); const processedAst = await postcss([ importPlugin({ resolve: resolveImport(stylesDirectory) }), nestingPlugin(), - tailwindPlugin(await readTailwindConfig(tailwindConfigPath)), + tailwindPlugin(tailwindConfig), ]).process(ast, { from: undefined, parser: postcss.parse, diff --git a/packages/css2tailwind/src/error.ts b/packages/css2tailwind/src/error.ts index c9c1f69..78dc467 100644 --- a/packages/css2tailwind/src/error.ts +++ b/packages/css2tailwind/src/error.ts @@ -1,5 +1,29 @@ -export class NoStylesDirectory extends Error { +import { bgRed, red } from 'colorette'; + +export class NoStylesDirectoryError extends Error { public constructor(message: string) { super(message); } } + +export class CompilationError extends Error { + public constructor(message: string) { + super(message); + } + + public override toString() { + return this.message; + } +} + +export class ResolveTailwindConfigError extends Error { + private readonly errorName = 'ERR_RESOLVE_TAILWIND_CONFIG'; + + public constructor(message: string) { + super(message); + } + + public override toString() { + return `${bgRed(` ${this.errorName} `)} ${red(this.message)}`; + } +} diff --git a/packages/css2tailwind/src/util.ts b/packages/css2tailwind/src/util.ts new file mode 100644 index 0000000..1cebff2 --- /dev/null +++ b/packages/css2tailwind/src/util.ts @@ -0,0 +1,25 @@ +import { bundleRequire } from 'bundle-require'; + +import { ResolveTailwindConfigError } from './error'; + +import type { Config } from 'tailwindcss'; + +const defaultTailwindConfig: Config = { content: ['./**/*.css'] }; + +// TODO: logging, error handling +export async function readTailwindConfig(path?: string): Promise { + if (!path) return defaultTailwindConfig; + try { + const bundle = await bundleRequire({ + filepath: path, + preserveTemporaryFile: false, + }); + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment, @typescript-eslint/no-unused-vars, @typescript-eslint/no-unsafe-member-access + const config = (bundle.mod.default ?? {}) as Config; + // TODO: log a warning + if (Object.keys(config).length <= 0) throw new Error(); + return config; + } catch { + throw new ResolveTailwindConfigError(`Failed to read tailwind configuration at ${path}.`); + } +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index b0fdee6..af2e633 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -81,6 +81,12 @@ importers: bundle-require: specifier: 4.0.2 version: 4.0.2(esbuild@0.19.12) + chalk: + specifier: 5.3.0 + version: 5.3.0 + colorette: + specifier: 2.0.20 + version: 2.0.20 postcss: specifier: 8.4.35 version: 8.4.35 @@ -1692,6 +1698,11 @@ packages: supports-color: 7.2.0 dev: true + /chalk@5.3.0: + resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} + engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} + dev: false + /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true @@ -1755,6 +1766,10 @@ packages: /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} + /colorette@2.0.20: + resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} + dev: false + /commander@4.1.1: resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==} engines: {node: '>= 6'} From fbaaa2cf73b2133237f7a0d13958d78c4d194eda Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Thu, 29 Feb 2024 17:13:49 +0100 Subject: [PATCH 02/10] feat: compilation error using code frames --- examples/standalone/package.json | 2 +- .../src/styles/components/button/button.css | 2 +- packages/css2tailwind/package.json | 2 + packages/css2tailwind/src/build.ts | 40 +++++++++++++--- packages/css2tailwind/src/cli.ts | 26 +++++++++-- packages/css2tailwind/src/compiler.ts | 35 +++++++++----- packages/css2tailwind/src/error.ts | 46 ++++++++++++++++++- packages/css2tailwind/src/util.ts | 29 ++++++++++++ pnpm-lock.yaml | 20 ++++---- 9 files changed, 165 insertions(+), 37 deletions(-) diff --git a/examples/standalone/package.json b/examples/standalone/package.json index ce8fb34..cf9e669 100644 --- a/examples/standalone/package.json +++ b/examples/standalone/package.json @@ -12,7 +12,7 @@ "lint": "eslint src", "lint:fix": "eslint --fix src", "types": "tsc", - "styles:build": "css2tailwind src/styles .styles -c ./blah.js", + "styles:build": "css2tailwind src/styles .styles", "styles:watch": "css2tailwind src/styles .styles --watch" }, "dependencies": { diff --git a/examples/standalone/src/styles/components/button/button.css b/examples/standalone/src/styles/components/button/button.css index f274ba5..554572e 100644 --- a/examples/standalone/src/styles/components/button/button.css +++ b/examples/standalone/src/styles/components/button/button.css @@ -3,7 +3,7 @@ .button { color: black; - @apply bg-red-400 bg-my-color; + hello there @apply bg-red-400 bg-my-color; &-primary { @apply bg-green-500; diff --git a/packages/css2tailwind/package.json b/packages/css2tailwind/package.json index 5d8ebc3..0c620e4 100644 --- a/packages/css2tailwind/package.json +++ b/packages/css2tailwind/package.json @@ -27,6 +27,7 @@ "release:ci": "pnpm build && pnpm publish --access public --provenance" }, "dependencies": { + "@babel/code-frame": "7.23.5", "bundle-require": "4.0.2", "chalk": "5.3.0", "colorette": "2.0.20", @@ -38,6 +39,7 @@ "zod": "3.22.4" }, "devDependencies": { + "@types/babel__code-frame": "7.0.6", "@types/node": "20.11.20", "@types/postcss-import": "14.0.3", "@types/postcss-js": "4.0.4", diff --git a/packages/css2tailwind/src/build.ts b/packages/css2tailwind/src/build.ts index 365e4b1..d2ad595 100644 --- a/packages/css2tailwind/src/build.ts +++ b/packages/css2tailwind/src/build.ts @@ -4,6 +4,8 @@ import * as path from 'node:path'; import type { CssInJs } from 'postcss-js'; import { compileStyleSheet } from './compiler'; +import { isSyntaxError, type SyntaxError } from './error'; +import { err, ok, type Result } from './util'; import type { Config } from 'tailwindcss'; @@ -17,15 +19,39 @@ export async function parseStyles( dir: string, stylesDirectory: string, tailwindConfig: Config, -): Promise { +): Promise> { const contents = await readStyles(dir); - const compiledStyles = await Promise.all( - contents.map(async (raw) => await compileStyleSheet(raw, stylesDirectory, tailwindConfig)), - ); + try { + const compiledStyleResults = await Promise.allSettled( + contents.map(async (raw) => await compileStyleSheet(raw, stylesDirectory, tailwindConfig)), + ); - return compiledStyles.reduce((kind, style) => { - return { ...kind, ...style }; - }, {}); + const errors = compiledStyleResults + .filter(function (result): result is PromiseRejectedResult { + return result.status === 'rejected'; + }) + .map((result) => result.reason as unknown); + + if (errors.length) { + const syntaxErrors = errors.filter(isSyntaxError); + + return err(syntaxErrors); + } + + const values = compiledStyleResults + .filter(function (result): result is PromiseFulfilledResult { + return result.status === 'rejected'; + }) + .map((result) => result.value); + + return ok( + values.reduce((kind, style) => { + return { ...kind, ...style }; + }, {}), + ); + } catch (error) { + return err(new Error('unknown')); + } } export async function writeStyles(dir: string, entry: string, style: CssInJs) { diff --git a/packages/css2tailwind/src/cli.ts b/packages/css2tailwind/src/cli.ts index 3cde41b..95238ac 100644 --- a/packages/css2tailwind/src/cli.ts +++ b/packages/css2tailwind/src/cli.ts @@ -7,8 +7,8 @@ import { hideBin } from 'yargs/helpers'; import { z } from 'zod'; import { bootstrapStyles, parseStyles, writeStyles } from './build'; -import { NoStylesDirectoryError } from './error'; -import { readTailwindConfig } from './util'; +import { dedupeSyntaxErrors, isSyntaxError, NoStylesDirectoryError, SyntaxError } from './error'; +import { isPromiseRejected, mapPromiseRejectedResultToReason, readTailwindConfig } from './util'; const { argv } = yargs(hideBin(process.argv)) .usage('tgp ') @@ -66,18 +66,34 @@ async function main() { const dirents = await fsp.readdir(stylesDirectory, { withFileTypes: true }); const entries = dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name); - await Promise.all( + const buildResult = await Promise.allSettled( entries.map(async (entry) => { await bootstrapStyles(outputDirectory, entry); - const styles = await parseStyles( + const stylesResult = await parseStyles( path.join(stylesDirectory, entry), stylesDirectory, tailwindConfig, ); - await writeStyles(outputDirectory, entry, styles); + + if (stylesResult.ok) { + await writeStyles(outputDirectory, entry, stylesResult.value); + } else { + // eslint-disable-next-line @typescript-eslint/no-throw-literal + throw stylesResult.error; + } }), ); + const buildErrors = buildResult + .filter(isPromiseRejected) + .flatMap(mapPromiseRejectedResultToReason); + + const syntaxErrors = dedupeSyntaxErrors(buildErrors.filter(isSyntaxError)); + + for (const syntaxError of syntaxErrors) { + console.log(syntaxError.toString()); + } + if (!args.watch) process.exit(0); for (const entry of entries) { diff --git a/packages/css2tailwind/src/compiler.ts b/packages/css2tailwind/src/compiler.ts index 6bf53bb..8af053c 100644 --- a/packages/css2tailwind/src/compiler.ts +++ b/packages/css2tailwind/src/compiler.ts @@ -4,7 +4,9 @@ import importPlugin from 'postcss-import'; import postcssJs, { type CssInJs } from 'postcss-js'; import nestingPlugin from 'tailwindcss/nesting'; -import postcss, { type Root } from 'postcss'; +import { SyntaxError } from './error'; + +import postcss, { CssSyntaxError, type Root } from 'postcss'; import tailwindPlugin, { type Config } from 'tailwindcss'; function resolveImport(stylesDirectory: string): (id: string) => string { @@ -19,18 +21,27 @@ export async function compileStyleSheet( stylesDirectory: string, tailwindConfig: Config, ): Promise { - const ast = postcss.parse(raw); + try { + const ast = postcss.parse(raw); + const processedAst = await postcss([ + importPlugin({ resolve: resolveImport(stylesDirectory) }), + nestingPlugin(), + tailwindPlugin(tailwindConfig), + ]).process(ast, { + // TODO: this should always be set + from: undefined, + parser: postcss.parse, + }); - const processedAst = await postcss([ - importPlugin({ resolve: resolveImport(stylesDirectory) }), - nestingPlugin(), - tailwindPlugin(tailwindConfig), - ]).process(ast, { - from: undefined, - parser: postcss.parse, - }); + const cssInJs = postcssJs.objectify(processedAst.root as Root); - const cssInJs = postcssJs.objectify(processedAst.root as Root); + return cssInJs; + } catch (error) { + if (error instanceof CssSyntaxError) { + throw new SyntaxError(error); + } - return cssInJs; + // TODO: are there any other errors? + throw new Error('unknown error'); + } } diff --git a/packages/css2tailwind/src/error.ts b/packages/css2tailwind/src/error.ts index 78dc467..00c41ab 100644 --- a/packages/css2tailwind/src/error.ts +++ b/packages/css2tailwind/src/error.ts @@ -1,4 +1,6 @@ -import { bgRed, red } from 'colorette'; +import { bgRed, black, red } from 'colorette'; + +import type { CssSyntaxError } from 'postcss'; export class NoStylesDirectoryError extends Error { public constructor(message: string) { @@ -27,3 +29,45 @@ export class ResolveTailwindConfigError extends Error { return `${bgRed(` ${this.errorName} `)} ${red(this.message)}`; } } + +export class SyntaxError extends Error { + private readonly errorName = 'ERR_CSS_SYNTAX'; + + public constructor(public readonly error: CssSyntaxError) { + super(error.message); + } + + public override toString() { + return `${bgRed(` ${black(this.errorName)} `)} ${this.error.reason} at ${this.error.input?.file ?? this.error.file}:${this.error.input?.line}:${this.error.input?.column} + +${this.error.showSourceCode()} +`; + } + + public static dedupe(...errors: SyntaxError[]) { + console.debug(errors.map((error) => error)); + } + + public key() { + return `${this.error.line}:${this.error.column}:${this.error.reason}`; + } +} + +export function isSyntaxError(error: unknown): error is SyntaxError { + return error instanceof SyntaxError; +} + +export function dedupeSyntaxErrors(errors: SyntaxError[]) { + const blah = new Map(); + + for (const error of errors) { + const key = error.key(); + // if an error has a file path, prefer it over the other one(s) + const hasFile = Boolean(error.error.input?.file ?? error.error.file); + if (!blah.has(key) || hasFile) { + blah.set(key, error); + } + } + + return Array.from(blah.values()); +} diff --git a/packages/css2tailwind/src/util.ts b/packages/css2tailwind/src/util.ts index 1cebff2..c81a60e 100644 --- a/packages/css2tailwind/src/util.ts +++ b/packages/css2tailwind/src/util.ts @@ -23,3 +23,32 @@ export async function readTailwindConfig(path?: string): Promise { throw new ResolveTailwindConfigError(`Failed to read tailwind configuration at ${path}.`); } } + +export type Result = + | { ok: true; value: TValue } + | { ok: false; error: TError }; + +export function ok(value: TValue) { + return { ok: true, value } as const; +} +export function err(error: TError) { + return { ok: false, error } as const; +} + +export function isPromiseFulfilled( + promiseResult: PromiseSettledResult, +): promiseResult is PromiseFulfilledResult { + return promiseResult.status === 'fulfilled'; +} + +export function isPromiseRejected( + promiseResult: PromiseSettledResult, +): promiseResult is PromiseRejectedResult { + return promiseResult.status === 'rejected'; +} + +export function mapPromiseRejectedResultToReason( + promiseRejectedResult: PromiseRejectedResult, +): unknown { + return promiseRejectedResult.reason; +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index af2e633..04cfc21 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -78,6 +78,9 @@ importers: packages/css2tailwind: dependencies: + '@babel/code-frame': + specifier: 7.23.5 + version: 7.23.5 bundle-require: specifier: 4.0.2 version: 4.0.2(esbuild@0.19.12) @@ -106,6 +109,9 @@ importers: specifier: 3.22.4 version: 3.22.4 devDependencies: + '@types/babel__code-frame': + specifier: 7.0.6 + version: 7.0.6 '@types/node': specifier: 20.11.20 version: 20.11.20 @@ -150,7 +156,6 @@ packages: dependencies: '@babel/highlight': 7.23.4 chalk: 2.4.2 - dev: true /@babel/compat-data@7.23.5: resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} @@ -264,7 +269,6 @@ packages: /@babel/helper-validator-identifier@7.22.20: resolution: {integrity: sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==} engines: {node: '>=6.9.0'} - dev: true /@babel/helper-validator-option@7.23.5: resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} @@ -289,7 +293,6 @@ packages: '@babel/helper-validator-identifier': 7.22.20 chalk: 2.4.2 js-tokens: 4.0.0 - dev: true /@babel/parser@7.23.9: resolution: {integrity: sha512-9tcKgqKbs3xGJ+NtKF2ndOBBLVwPjl1SHxPQkd36r3Dlirw3xWUeGaTbqr7uGZcTaxkVNwc+03SVP7aCdWrTlA==} @@ -1161,6 +1164,10 @@ packages: resolution: {integrity: sha512-T0DQ96c3FdPXNhgc15AnCT/ATZFk2iX4gEFvS4tXVy0zLRR5PmlmoWCdjO+mxKUTyOaxBDnJ2dejcZNKkHzBgg==} dev: true + /@types/babel__code-frame@7.0.6: + resolution: {integrity: sha512-Anitqkl3+KrzcW2k77lRlg/GfLZLWXBuNgbEcIOU6M92yw42vsd3xV/Z/yAHEj8m+KUjL6bWOVOFqX8PFPJ4LA==} + dev: true + /@types/estree@1.0.5: resolution: {integrity: sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw==} dev: true @@ -1422,7 +1429,6 @@ packages: engines: {node: '>=4'} dependencies: color-convert: 1.9.3 - dev: true /ansi-styles@4.3.0: resolution: {integrity: sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==} @@ -1688,7 +1694,6 @@ packages: ansi-styles: 3.2.1 escape-string-regexp: 1.0.5 supports-color: 5.5.0 - dev: true /chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -1751,7 +1756,6 @@ packages: resolution: {integrity: sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==} dependencies: color-name: 1.1.3 - dev: true /color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -1761,7 +1765,6 @@ packages: /color-name@1.1.3: resolution: {integrity: sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==} - dev: true /color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} @@ -2111,7 +2114,6 @@ packages: /escape-string-regexp@1.0.5: resolution: {integrity: sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==} engines: {node: '>=0.8.0'} - dev: true /escape-string-regexp@4.0.0: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} @@ -2601,7 +2603,6 @@ packages: /has-flag@3.0.0: resolution: {integrity: sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==} engines: {node: '>=4'} - dev: true /has-flag@4.0.0: resolution: {integrity: sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==} @@ -4141,7 +4142,6 @@ packages: engines: {node: '>=4'} dependencies: has-flag: 3.0.0 - dev: true /supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} From 6ddfbaf27bcd418c0f13f9d0cff61eddb6a2d0b9 Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Thu, 29 Feb 2024 17:15:21 +0100 Subject: [PATCH 03/10] fix: name --- packages/css2tailwind/package.json | 1 - packages/css2tailwind/src/error.ts | 8 ++++---- pnpm-lock.yaml | 8 -------- 3 files changed, 4 insertions(+), 13 deletions(-) diff --git a/packages/css2tailwind/package.json b/packages/css2tailwind/package.json index 0c620e4..bbcbb45 100644 --- a/packages/css2tailwind/package.json +++ b/packages/css2tailwind/package.json @@ -29,7 +29,6 @@ "dependencies": { "@babel/code-frame": "7.23.5", "bundle-require": "4.0.2", - "chalk": "5.3.0", "colorette": "2.0.20", "postcss": "8.4.35", "postcss-import": "16.0.1", diff --git a/packages/css2tailwind/src/error.ts b/packages/css2tailwind/src/error.ts index 00c41ab..918f90f 100644 --- a/packages/css2tailwind/src/error.ts +++ b/packages/css2tailwind/src/error.ts @@ -58,16 +58,16 @@ export function isSyntaxError(error: unknown): error is SyntaxError { } export function dedupeSyntaxErrors(errors: SyntaxError[]) { - const blah = new Map(); + const uniqueErrors = new Map(); for (const error of errors) { const key = error.key(); // if an error has a file path, prefer it over the other one(s) const hasFile = Boolean(error.error.input?.file ?? error.error.file); - if (!blah.has(key) || hasFile) { - blah.set(key, error); + if (!uniqueErrors.has(key) || hasFile) { + uniqueErrors.set(key, error); } } - return Array.from(blah.values()); + return Array.from(uniqueErrors.values()); } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 04cfc21..55779ed 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -84,9 +84,6 @@ importers: bundle-require: specifier: 4.0.2 version: 4.0.2(esbuild@0.19.12) - chalk: - specifier: 5.3.0 - version: 5.3.0 colorette: specifier: 2.0.20 version: 2.0.20 @@ -1703,11 +1700,6 @@ packages: supports-color: 7.2.0 dev: true - /chalk@5.3.0: - resolution: {integrity: sha512-dLitG79d+GV1Nb/VYcCDFivJeK1hiukt9QjRNVOsUtTy1rR1YJsmpGGTZ3qJos+uw7WmWF4wUwBd9jxjocFC2w==} - engines: {node: ^12.17.0 || ^14.13 || >=16.0.0} - dev: false - /chardet@0.7.0: resolution: {integrity: sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA==} dev: true From 995f88d1e32c2dcdb6da1c1e3435cb2d34410f14 Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Thu, 29 Feb 2024 17:41:02 +0100 Subject: [PATCH 04/10] feat: log multiple deduped compilation errors --- .../src/styles/components/button/button.css | 2 +- .../src/styles/components/tag/tag.css | 3 +- packages/css2tailwind/src/build.ts | 8 +- packages/css2tailwind/src/cli.ts | 73 ++++++++++++------- packages/css2tailwind/src/error.ts | 4 - packages/css2tailwind/src/util.ts | 32 +++++++- 6 files changed, 81 insertions(+), 41 deletions(-) diff --git a/examples/standalone/src/styles/components/button/button.css b/examples/standalone/src/styles/components/button/button.css index 554572e..9f88ed1 100644 --- a/examples/standalone/src/styles/components/button/button.css +++ b/examples/standalone/src/styles/components/button/button.css @@ -3,7 +3,7 @@ .button { color: black; - hello there @apply bg-red-400 bg-my-color; + hwllo there @apply bg-red-400 bg-my-color; &-primary { @apply bg-green-500; diff --git a/examples/standalone/src/styles/components/tag/tag.css b/examples/standalone/src/styles/components/tag/tag.css index 6079cb3..b9dfb76 100644 --- a/examples/standalone/src/styles/components/tag/tag.css +++ b/examples/standalone/src/styles/components/tag/tag.css @@ -1,5 +1,6 @@ @import 'components/button'; .tag { - @apply button; + hello there @apply button; + yo what the fuck is this shit } diff --git a/packages/css2tailwind/src/build.ts b/packages/css2tailwind/src/build.ts index d2ad595..42ca186 100644 --- a/packages/css2tailwind/src/build.ts +++ b/packages/css2tailwind/src/build.ts @@ -5,7 +5,7 @@ import type { CssInJs } from 'postcss-js'; import { compileStyleSheet } from './compiler'; import { isSyntaxError, type SyntaxError } from './error'; -import { err, ok, type Result } from './util'; +import { err, isPromiseFulfilled, mapPromiseFulfilledResultToValue, ok, type Result } from './util'; import type { Config } from 'tailwindcss'; @@ -39,10 +39,8 @@ export async function parseStyles( } const values = compiledStyleResults - .filter(function (result): result is PromiseFulfilledResult { - return result.status === 'rejected'; - }) - .map((result) => result.value); + .filter(isPromiseFulfilled) + .map(mapPromiseFulfilledResultToValue); return ok( values.reduce((kind, style) => { diff --git a/packages/css2tailwind/src/cli.ts b/packages/css2tailwind/src/cli.ts index 95238ac..0df0074 100644 --- a/packages/css2tailwind/src/cli.ts +++ b/packages/css2tailwind/src/cli.ts @@ -8,7 +8,20 @@ import { z } from 'zod'; import { bootstrapStyles, parseStyles, writeStyles } from './build'; import { dedupeSyntaxErrors, isSyntaxError, NoStylesDirectoryError, SyntaxError } from './error'; -import { isPromiseRejected, mapPromiseRejectedResultToReason, readTailwindConfig } from './util'; +import { + err, + isErr, + isPromiseFulfilled, + isPromiseRejected, + mapErrResultToError, + mapPromiseFulfilledResultToValue, + mapPromiseRejectedResultToReason, + ok, + readTailwindConfig, + Result, +} from './util'; + +import { Config } from 'tailwindcss'; const { argv } = yargs(hideBin(process.argv)) .usage('tgp ') @@ -58,6 +71,10 @@ async function assertDirExists(dir: string) { } } +function exitIf(exit: boolean, code: number) { + if (exit) process.exit(code); +} + async function main() { await assertDirExists(stylesDirectory); @@ -66,27 +83,11 @@ async function main() { const dirents = await fsp.readdir(stylesDirectory, { withFileTypes: true }); const entries = dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name); - const buildResult = await Promise.allSettled( - entries.map(async (entry) => { - await bootstrapStyles(outputDirectory, entry); - const stylesResult = await parseStyles( - path.join(stylesDirectory, entry), - stylesDirectory, - tailwindConfig, - ); - - if (stylesResult.ok) { - await writeStyles(outputDirectory, entry, stylesResult.value); - } else { - // eslint-disable-next-line @typescript-eslint/no-throw-literal - throw stylesResult.error; - } - }), + const buildResults = await Promise.all( + entries.map(async (entry) => doTheThing(entry, tailwindConfig)), ); - const buildErrors = buildResult - .filter(isPromiseRejected) - .flatMap(mapPromiseRejectedResultToReason); + const buildErrors = buildResults.filter(isErr).flatMap(mapErrResultToError); const syntaxErrors = dedupeSyntaxErrors(buildErrors.filter(isSyntaxError)); @@ -94,6 +95,8 @@ async function main() { console.log(syntaxError.toString()); } + exitIf(!args.watch && !!buildErrors.length, 1); + if (!args.watch) process.exit(0); for (const entry of entries) { @@ -103,16 +106,32 @@ async function main() { }); watcher.on('change', () => { void (async () => { - await bootstrapStyles(outputDirectory, entry); - const styles = await parseStyles( - path.join(stylesDirectory, entry), - stylesDirectory, - tailwindConfig, - ); - await writeStyles(outputDirectory, entry, styles); + await doTheThing(entry, tailwindConfig); })(); }); } } void main(); + +async function doTheThing( + entry: string, + tailwindConfig: Config, +): Promise> { + try { + await bootstrapStyles(outputDirectory, entry); + const stylesResult = await parseStyles( + path.join(stylesDirectory, entry), + stylesDirectory, + tailwindConfig, + ); + + if (stylesResult.ok) { + await writeStyles(outputDirectory, entry, stylesResult.value); + return ok(undefined); + } + return err(stylesResult.error); + } catch (error) { + return err(new Error('unknown')); + } +} diff --git a/packages/css2tailwind/src/error.ts b/packages/css2tailwind/src/error.ts index 918f90f..974053a 100644 --- a/packages/css2tailwind/src/error.ts +++ b/packages/css2tailwind/src/error.ts @@ -44,10 +44,6 @@ ${this.error.showSourceCode()} `; } - public static dedupe(...errors: SyntaxError[]) { - console.debug(errors.map((error) => error)); - } - public key() { return `${this.error.line}:${this.error.column}:${this.error.reason}`; } diff --git a/packages/css2tailwind/src/util.ts b/packages/css2tailwind/src/util.ts index c81a60e..e74458a 100644 --- a/packages/css2tailwind/src/util.ts +++ b/packages/css2tailwind/src/util.ts @@ -24,9 +24,9 @@ export async function readTailwindConfig(path?: string): Promise { } } -export type Result = - | { ok: true; value: TValue } - | { ok: false; error: TError }; +export type ResultOk = { ok: true; value: TValue }; +export type ResultErr = { ok: false; error: TError }; +export type Result = ResultOk | ResultErr; export function ok(value: TValue) { return { ok: true, value } as const; @@ -35,6 +35,26 @@ export function err(error: TError) { return { ok: false, error } as const; } +export function isOk( + result: Result, +): result is { ok: true; value: TValue } { + return result.ok; +} + +export function isErr( + result: Result, +): result is { ok: false; error: TError } { + return !result.ok; +} + +export function mapOkResultToValue(result: ResultOk) { + return result.value; +} + +export function mapErrResultToError(result: ResultErr) { + return result.error; +} + export function isPromiseFulfilled( promiseResult: PromiseSettledResult, ): promiseResult is PromiseFulfilledResult { @@ -52,3 +72,9 @@ export function mapPromiseRejectedResultToReason( ): unknown { return promiseRejectedResult.reason; } + +export function mapPromiseFulfilledResultToValue( + promiseRejectedResult: PromiseFulfilledResult, +): TValue { + return promiseRejectedResult.value; +} From 4886f02ea8d1f02c539e81fd1171e0917a1eb936 Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Thu, 29 Feb 2024 17:59:34 +0100 Subject: [PATCH 05/10] feat: log errors on dev --- examples/standalone/package.json | 2 +- .../standalone/src/styles/components/button/button.css | 2 +- examples/standalone/src/styles/components/tag/tag.css | 3 +-- packages/css2tailwind/src/cli.ts | 10 +++++++++- 4 files changed, 12 insertions(+), 5 deletions(-) diff --git a/examples/standalone/package.json b/examples/standalone/package.json index cf9e669..81b63d0 100644 --- a/examples/standalone/package.json +++ b/examples/standalone/package.json @@ -3,7 +3,7 @@ "type": "module", "private": true, "scripts": { - "dev": "run-p vite styles:watch", + "dev": "run-p styles:watch vite", "vite": "vite", "build": "tsc && vite build", "preview": "vite preview", diff --git a/examples/standalone/src/styles/components/button/button.css b/examples/standalone/src/styles/components/button/button.css index 9f88ed1..f274ba5 100644 --- a/examples/standalone/src/styles/components/button/button.css +++ b/examples/standalone/src/styles/components/button/button.css @@ -3,7 +3,7 @@ .button { color: black; - hwllo there @apply bg-red-400 bg-my-color; + @apply bg-red-400 bg-my-color; &-primary { @apply bg-green-500; diff --git a/examples/standalone/src/styles/components/tag/tag.css b/examples/standalone/src/styles/components/tag/tag.css index b9dfb76..6079cb3 100644 --- a/examples/standalone/src/styles/components/tag/tag.css +++ b/examples/standalone/src/styles/components/tag/tag.css @@ -1,6 +1,5 @@ @import 'components/button'; .tag { - hello there @apply button; - yo what the fuck is this shit + @apply button; } diff --git a/packages/css2tailwind/src/cli.ts b/packages/css2tailwind/src/cli.ts index 0df0074..59ca5e9 100644 --- a/packages/css2tailwind/src/cli.ts +++ b/packages/css2tailwind/src/cli.ts @@ -106,7 +106,15 @@ async function main() { }); watcher.on('change', () => { void (async () => { - await doTheThing(entry, tailwindConfig); + const buildResult = await doTheThing(entry, tailwindConfig); + if (!buildResult.ok) { + const error = buildResult.error; + if (Array.isArray(error)) { + for (const syntaxError of syntaxErrors) { + console.log(syntaxError.toString()); + } + } + } })(); }); } From 1d46fd16754a77c9e5c901c2447f9dcc82aae634 Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Mon, 4 Mar 2024 14:57:52 +0100 Subject: [PATCH 06/10] fix: display correct errors in watch mode --- packages/css2tailwind/src/cli.ts | 27 +++++++++++---------------- 1 file changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/css2tailwind/src/cli.ts b/packages/css2tailwind/src/cli.ts index 59ca5e9..56eb4c8 100644 --- a/packages/css2tailwind/src/cli.ts +++ b/packages/css2tailwind/src/cli.ts @@ -7,21 +7,15 @@ import { hideBin } from 'yargs/helpers'; import { z } from 'zod'; import { bootstrapStyles, parseStyles, writeStyles } from './build'; -import { dedupeSyntaxErrors, isSyntaxError, NoStylesDirectoryError, SyntaxError } from './error'; import { - err, - isErr, - isPromiseFulfilled, - isPromiseRejected, - mapErrResultToError, - mapPromiseFulfilledResultToValue, - mapPromiseRejectedResultToReason, - ok, - readTailwindConfig, - Result, -} from './util'; - -import { Config } from 'tailwindcss'; + dedupeSyntaxErrors, + isSyntaxError, + NoStylesDirectoryError, + type SyntaxError, +} from './error'; +import { err, isErr, mapErrResultToError, ok, readTailwindConfig, type Result } from './util'; + +import type { Config } from 'tailwindcss'; const { argv } = yargs(hideBin(process.argv)) .usage('tgp ') @@ -96,8 +90,7 @@ async function main() { } exitIf(!args.watch && !!buildErrors.length, 1); - - if (!args.watch) process.exit(0); + exitIf(!args.watch, 0); for (const entry of entries) { const entryPath = path.join(stylesDirectory, entry); @@ -107,9 +100,11 @@ async function main() { watcher.on('change', () => { void (async () => { const buildResult = await doTheThing(entry, tailwindConfig); + if (!buildResult.ok) { const error = buildResult.error; if (Array.isArray(error)) { + const syntaxErrors = dedupeSyntaxErrors(error.filter(isSyntaxError)); for (const syntaxError of syntaxErrors) { console.log(syntaxError.toString()); } From 371e925f9db9a9708fd0b8a7ea3004ceef98b456 Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Mon, 4 Mar 2024 15:22:43 +0100 Subject: [PATCH 07/10] feat: more errors --- .changeset/angry-kangaroos-relate.md | 5 ++ packages/css2tailwind/src/cli.ts | 75 ++++++++++++++++------------ packages/css2tailwind/src/error.ts | 12 +++++ 3 files changed, 60 insertions(+), 32 deletions(-) create mode 100644 .changeset/angry-kangaroos-relate.md diff --git a/.changeset/angry-kangaroos-relate.md b/.changeset/angry-kangaroos-relate.md new file mode 100644 index 0000000..4cc2b0c --- /dev/null +++ b/.changeset/angry-kangaroos-relate.md @@ -0,0 +1,5 @@ +--- +"@titanom/css2tailwind": patch +--- + +add error handling diff --git a/packages/css2tailwind/src/cli.ts b/packages/css2tailwind/src/cli.ts index 56eb4c8..7d47b1b 100644 --- a/packages/css2tailwind/src/cli.ts +++ b/packages/css2tailwind/src/cli.ts @@ -1,13 +1,14 @@ import * as fsp from 'node:fs/promises'; import * as path from 'node:path'; -import { watch } from 'chokidar'; +import { FSWatcher, watch } from 'chokidar'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { z } from 'zod'; import { bootstrapStyles, parseStyles, writeStyles } from './build'; import { + CloseWatcherError, dedupeSyntaxErrors, isSyntaxError, NoStylesDirectoryError, @@ -69,60 +70,70 @@ function exitIf(exit: boolean, code: number) { if (exit) process.exit(code); } +async function buildAndHandleErrors(entry: string, tailwindConfig: Config) { + const buildResult = await compileAndWriteStyles(entry, tailwindConfig); + if (!buildResult.ok) handleBuildErrors(buildResult.error); + return buildResult; +} + +function handleBuildErrors(error: unknown) { + if (Array.isArray(error)) { + const syntaxErrors = dedupeSyntaxErrors(error.filter(isSyntaxError)); + syntaxErrors.forEach((error) => console.log(error.toString())); + } +} + +function setupWatcher(entryPath: string, entry: string, tailwindConfig: Config): FSWatcher { + const watcher = watch(`${entryPath}/*/*.css`, { + awaitWriteFinish: { stabilityThreshold: 10, pollInterval: 10 }, + }); + watcher.on('change', () => void buildAndHandleErrors(entry, tailwindConfig)); + return watcher; +} + +let watchers: FSWatcher[] = []; + +function gracefullyShutdown() { + Promise.all(watchers.map(async (watcher) => await watcher.close())) + .then(() => process.exit(0)) + .catch(() => { + console.error(new CloseWatcherError('').toString()); + process.exit(1); + }); +} + async function main() { await assertDirExists(stylesDirectory); const tailwindConfig = await readTailwindConfig(args.config && path.join(cwd, args.config)); - const dirents = await fsp.readdir(stylesDirectory, { withFileTypes: true }); const entries = dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name); const buildResults = await Promise.all( - entries.map(async (entry) => doTheThing(entry, tailwindConfig)), + entries.map(async (entry) => buildAndHandleErrors(entry, tailwindConfig)), ); const buildErrors = buildResults.filter(isErr).flatMap(mapErrResultToError); - const syntaxErrors = dedupeSyntaxErrors(buildErrors.filter(isSyntaxError)); - - for (const syntaxError of syntaxErrors) { - console.log(syntaxError.toString()); - } - - exitIf(!args.watch && !!buildErrors.length, 1); + exitIf(!args.watch && buildErrors.length > 0, 1); exitIf(!args.watch, 0); - for (const entry of entries) { + entries.forEach((entry) => { const entryPath = path.join(stylesDirectory, entry); - const watcher = watch(`${entryPath}/*/*.css`, { - awaitWriteFinish: { stabilityThreshold: 10, pollInterval: 10 }, - }); - watcher.on('change', () => { - void (async () => { - const buildResult = await doTheThing(entry, tailwindConfig); - - if (!buildResult.ok) { - const error = buildResult.error; - if (Array.isArray(error)) { - const syntaxErrors = dedupeSyntaxErrors(error.filter(isSyntaxError)); - for (const syntaxError of syntaxErrors) { - console.log(syntaxError.toString()); - } - } - } - })(); - }); - } + setupWatcher(entryPath, entry, tailwindConfig); + }); + + process.on('SIGINT', gracefullyShutdown); + process.on('SIGTERM', gracefullyShutdown); } void main(); -async function doTheThing( +async function compileAndWriteStyles( entry: string, tailwindConfig: Config, ): Promise> { try { - await bootstrapStyles(outputDirectory, entry); const stylesResult = await parseStyles( path.join(stylesDirectory, entry), stylesDirectory, diff --git a/packages/css2tailwind/src/error.ts b/packages/css2tailwind/src/error.ts index 974053a..009bb6b 100644 --- a/packages/css2tailwind/src/error.ts +++ b/packages/css2tailwind/src/error.ts @@ -8,6 +8,18 @@ export class NoStylesDirectoryError extends Error { } } +export class CloseWatcherError extends Error { + private readonly errorName = 'ERR_CLOSE_WATCHER'; + + public constructor(message: string) { + super(message); + } + + public override toString() { + return `${bgRed(` ${black(this.errorName)} `)} Failed to close watcher`; + } +} + export class CompilationError extends Error { public constructor(message: string) { super(message); From bd1fa18d3e1a611c08ee3743ccae629556d67a7b Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Mon, 4 Mar 2024 15:24:59 +0100 Subject: [PATCH 08/10] fix: bootstrap styles --- packages/css2tailwind/src/cli.ts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/packages/css2tailwind/src/cli.ts b/packages/css2tailwind/src/cli.ts index 7d47b1b..948d469 100644 --- a/packages/css2tailwind/src/cli.ts +++ b/packages/css2tailwind/src/cli.ts @@ -1,7 +1,7 @@ import * as fsp from 'node:fs/promises'; import * as path from 'node:path'; -import { FSWatcher, watch } from 'chokidar'; +import { watch, type FSWatcher } from 'chokidar'; import yargs from 'yargs'; import { hideBin } from 'yargs/helpers'; import { z } from 'zod'; @@ -110,7 +110,10 @@ async function main() { const entries = dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name); const buildResults = await Promise.all( - entries.map(async (entry) => buildAndHandleErrors(entry, tailwindConfig)), + entries.map(async (entry) => { + await bootstrapStyles(outputDirectory, entry); + return await buildAndHandleErrors(entry, tailwindConfig); + }), ); const buildErrors = buildResults.filter(isErr).flatMap(mapErrResultToError); From 3330aeeac881523c205fc473e0cd4319018b7777 Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Mon, 4 Mar 2024 15:33:25 +0100 Subject: [PATCH 09/10] test: log error --- packages/css2tailwind/src/util.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/css2tailwind/src/util.ts b/packages/css2tailwind/src/util.ts index e74458a..f584bfd 100644 --- a/packages/css2tailwind/src/util.ts +++ b/packages/css2tailwind/src/util.ts @@ -19,7 +19,8 @@ export async function readTailwindConfig(path?: string): Promise { // TODO: log a warning if (Object.keys(config).length <= 0) throw new Error(); return config; - } catch { + } catch (error) { + console.log('--------------------------------------', error); throw new ResolveTailwindConfigError(`Failed to read tailwind configuration at ${path}.`); } } From cd7b05b0c38116f391da37b6b578a6584ee6620b Mon Sep 17 00:00:00 2001 From: Jannis Morgenstern Date: Mon, 4 Mar 2024 15:37:05 +0100 Subject: [PATCH 10/10] fix: bootstrap styles before resolving tailwind config --- packages/css2tailwind/src/cli.ts | 6 ++++-- packages/css2tailwind/src/util.ts | 3 +-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/css2tailwind/src/cli.ts b/packages/css2tailwind/src/cli.ts index 948d469..026c005 100644 --- a/packages/css2tailwind/src/cli.ts +++ b/packages/css2tailwind/src/cli.ts @@ -105,13 +105,15 @@ function gracefullyShutdown() { async function main() { await assertDirExists(stylesDirectory); - const tailwindConfig = await readTailwindConfig(args.config && path.join(cwd, args.config)); const dirents = await fsp.readdir(stylesDirectory, { withFileTypes: true }); const entries = dirents.filter((dirent) => dirent.isDirectory()).map((dirent) => dirent.name); + await Promise.all(entries.map(async (entry) => bootstrapStyles(outputDirectory, entry))); + + const tailwindConfig = await readTailwindConfig(args.config && path.join(cwd, args.config)); + const buildResults = await Promise.all( entries.map(async (entry) => { - await bootstrapStyles(outputDirectory, entry); return await buildAndHandleErrors(entry, tailwindConfig); }), ); diff --git a/packages/css2tailwind/src/util.ts b/packages/css2tailwind/src/util.ts index f584bfd..e74458a 100644 --- a/packages/css2tailwind/src/util.ts +++ b/packages/css2tailwind/src/util.ts @@ -19,8 +19,7 @@ export async function readTailwindConfig(path?: string): Promise { // TODO: log a warning if (Object.keys(config).length <= 0) throw new Error(); return config; - } catch (error) { - console.log('--------------------------------------', error); + } catch { throw new ResolveTailwindConfigError(`Failed to read tailwind configuration at ${path}.`); } }