diff --git a/.gitignore b/.gitignore index 8ea1ff4..0f30646 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,5 @@ # Ignore npm modules and src folder node_modules -src -tsconfig.json +# src +# tsconfig.json test \ No newline at end of file diff --git a/README.md b/README.md index 665c177..8339eb1 100644 --- a/README.md +++ b/README.md @@ -65,3 +65,50 @@ Ignore files/folders by enlosing the names in braces. Eg `(general).less` ##### Compile specific files If you're watching an entire folder, you can compile specific files in that folder to a stanalone file. Example if you create a file named `[test].less` in your watch directory, in your distribution directory there will be an extra file named `test.css`. + +## Using a `lesscw.config.json` file + +You can use a `lesscw.config.json` file to add your files instead of using the CLI interface. For this to work your `lesscw.config.json` file must be located in the root directory of your project, and it must contain at least one `src` and one `dst` entry + +### Basic Config JSON + +```json +{ + "src": "./folder", + "dst": "./dist/less.css" +} +``` + +This works the same as running `npx lessc-watcher --src ./folder --dst ./dist/less.css` in your terminal. Instead you only need to run: + +```bash +npx lessc-watcher +``` + +### Using multiple sources and destinations + +```json +{ + "src": ["./folder", "./folder-2", "./folder/sub-folder/admin.less"], + "dst": ["./dist/less.css", "./dist/folder-2/less.css", "./dist/sub-folder.css"] +} +``` + +Like the CLI paradigm, the number of paths in the `src` array must match the `dst array`. And note that you can use multiple different folders with multiple different destinations. + +### Using a `srcDst` cofiguration + +ALternative to the `src` and `dst` paths, you can provide a `srcDst` key instead, this will contain an array of key-value pairs as follows + +```json +{ + "srcDst": [ + { + "src": "./folder", + "dst": "./folder-2" + } + ] +} +``` + +**_NOTE:_** If you provide a config file and still add `--src` and `--dst` arguments in your terminal, the terminal source and distributuion arguments will be ignored. diff --git a/dist/index.js b/dist/index.js index a930554..3f95c37 100644 --- a/dist/index.js +++ b/dist/index.js @@ -6,11 +6,50 @@ var __importDefault = (this && this.__importDefault) || function (mod) { Object.defineProperty(exports, "__esModule", { value: true }); const fs_1 = __importDefault(require("fs")); const child_process_1 = require("child_process"); +if (fs_1.default.existsSync("./lesscw.config.json")) { + if (process.argv.indexOf("--src") >= 0 || process.argv.indexOf("--dst") >= 0) { + try { + process.argv.splice(process.argv.indexOf("--src")); + process.argv.splice(process.argv.indexOf("--dst")); + } + catch (error) { } + } + try { + const configObject = JSON.parse(fs_1.default.readFileSync("./lesscw.config.json", "utf-8")); + if ((configObject === null || configObject === void 0 ? void 0 : configObject.src) && (configObject === null || configObject === void 0 ? void 0 : configObject.dst) && typeof configObject.src === "string" && typeof configObject.dst === "string") { + process.argv.push("--src", configObject.src); + process.argv.push("--dst", configObject.dst); + } + else if ((configObject === null || configObject === void 0 ? void 0 : configObject.src) && (configObject === null || configObject === void 0 ? void 0 : configObject.dst) && typeof configObject.src === "object" && typeof configObject.dst === "object" && Array.isArray(configObject.src) && Array.isArray(configObject.dst)) { + process.argv.push("--src", configObject.src.join(",")); + process.argv.push("--dst", configObject.dst.join(",")); + } + else if ((configObject === null || configObject === void 0 ? void 0 : configObject.srcDst) && Array.isArray(configObject.srcDst) && configObject.srcDst.length > 0) { + const srcDstArray = configObject.srcDst; + let srcArray = []; + let dstArray = []; + srcDstArray.forEach((item) => { + if ((item === null || item === void 0 ? void 0 : item.src) && (item === null || item === void 0 ? void 0 : item.dst) && typeof item.src === "string" && typeof item.dst === "string") { + srcArray.push(item.src); + dstArray.push(item.dst); + } + }); + if (srcArray.length && dstArray.length) { + process.argv.push("--src", srcArray.join(",")); + process.argv.push("--dst", dstArray.join(",")); + } + } + } + catch (error) { + console.log("ERROR in your config file =>", error.message); + process.exit(); + } +} const sourceFile = process.argv.indexOf("--src") >= 0 ? process.argv[process.argv.indexOf("--src") + 1] : null; const destinationFile = process.argv.indexOf("--dst") >= 0 ? process.argv[process.argv.indexOf("--dst") + 1] : null; console.log("\x1b[44mRunning Less compiler\x1b[0m ..."); if (!sourceFile || !destinationFile) { - console.log("ERROR => Missing source or destination file"); + console.log("\x1b[33mERROR:\x1b[0m => Missing source or destination file"); process.exit(); } const sourceFiles = sourceFile.split(","); @@ -18,11 +57,30 @@ const dstFiles = destinationFile.split(","); for (let i = 0; i < sourceFiles.length; i++) { const srcFolder = sourceFiles[i]; const dstFile = dstFiles[i]; - if ((srcFolder === null || srcFolder === void 0 ? void 0 : srcFolder.match(/\..{2,4}$/)) && !(srcFolder === null || srcFolder === void 0 ? void 0 : srcFolder.match(/\.less$/))) { + if ((srcFolder === null || srcFolder === void 0 ? void 0 : srcFolder.match(/\.[^\/]+$/)) && !(srcFolder === null || srcFolder === void 0 ? void 0 : srcFolder.match(/\.less$/))) { console.log("\x1b[33mERROR:\x1b[0m Source must be a folder or a .less file"); process.exit(); } compile(srcFolder, dstFile, null); + if (!fs_1.default.existsSync(srcFolder)) { + if (srcFolder === null || srcFolder === void 0 ? void 0 : srcFolder.match(/\.less$/)) { + fs_1.default.mkdirSync(srcFolder.replace(/\/[^\/]+\.less$/, ""), { recursive: true }); + fs_1.default.writeFileSync(srcFolder, "", "utf-8"); + } + else { + fs_1.default.mkdirSync(srcFolder.replace(/\/[^\/]+\.[^\/]+$/, ""), { recursive: true }); + } + } + if (!fs_1.default.existsSync(dstFile)) { + if (dstFile === null || dstFile === void 0 ? void 0 : dstFile.match(/\.css$/)) { + fs_1.default.mkdirSync(dstFile.replace(/\/[^\/]+\.css$/, ""), { recursive: true }); + fs_1.default.writeFileSync(dstFile, "", "utf-8"); + } + else { + fs_1.default.mkdirSync(dstFile.replace(/\/[^\/]+\.[^\/]+$/, ""), { recursive: true }); + fs_1.default.writeFileSync((dstFile + "/_main.css").replace(/\/\//g, ""), "", "utf-8"); + } + } fs_1.default.watch(srcFolder, { recursive: true }, (evtType, fileName) => { if (!fileName) return; diff --git a/package.json b/package.json index 41a07b5..5d8a31c 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lessc-watcher", - "version": "1.1.2", + "version": "1.1.3", "description": "A minimal package to watch less files and compile them to css", "main": "dist/index.js", "bin": { diff --git a/src/index.d.ts b/src/index.d.ts new file mode 100644 index 0000000..10f2d32 --- /dev/null +++ b/src/index.d.ts @@ -0,0 +1,8 @@ +export type LessCssWatcherConfigObject = { + src?: string | string[]; + dst?: string | string[]; + srcDst?: { + src?: string; + dst?: string; + }[]; +}; diff --git a/src/index.ts b/src/index.ts new file mode 100644 index 0000000..6debd38 --- /dev/null +++ b/src/index.ts @@ -0,0 +1,152 @@ +#! /usr/bin/env node + +import fs from "fs"; +import { exec } from "child_process"; +import { LessCssWatcherConfigObject } from "./index.d"; + +if (fs.existsSync("./lesscw.config.json")) { + if (process.argv.indexOf("--src") >= 0 || process.argv.indexOf("--dst") >= 0) { + try { + process.argv.splice(process.argv.indexOf("--src")); + process.argv.splice(process.argv.indexOf("--dst")); + } catch (error) {} + } + + try { + const configObject: LessCssWatcherConfigObject = JSON.parse(fs.readFileSync("./lesscw.config.json", "utf-8")); + + if (configObject?.src && configObject?.dst && typeof configObject.src === "string" && typeof configObject.dst === "string") { + process.argv.push("--src", configObject.src); + process.argv.push("--dst", configObject.dst); + } else if (configObject?.src && configObject?.dst && typeof configObject.src === "object" && typeof configObject.dst === "object" && Array.isArray(configObject.src) && Array.isArray(configObject.dst)) { + process.argv.push("--src", configObject.src.join(",")); + process.argv.push("--dst", configObject.dst.join(",")); + } else if (configObject?.srcDst && Array.isArray(configObject.srcDst) && configObject.srcDst.length > 0) { + const srcDstArray = configObject.srcDst; + + let srcArray: string[] = []; + let dstArray: string[] = []; + + srcDstArray.forEach((item) => { + if (item?.src && item?.dst && typeof item.src === "string" && typeof item.dst === "string") { + srcArray.push(item.src); + dstArray.push(item.dst); + } + }); + + if (srcArray.length && dstArray.length) { + process.argv.push("--src", srcArray.join(",")); + process.argv.push("--dst", dstArray.join(",")); + } + } + } catch (error: any) { + console.log("ERROR in your config file =>", error.message); + process.exit(); + } +} + +const sourceFile = process.argv.indexOf("--src") >= 0 ? process.argv[process.argv.indexOf("--src") + 1] : null; +const destinationFile = process.argv.indexOf("--dst") >= 0 ? process.argv[process.argv.indexOf("--dst") + 1] : null; + +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ +/** ****************************************************************************** */ + +console.log("\x1b[44mRunning Less compiler\x1b[0m ..."); + +if (!sourceFile || !destinationFile) { + console.log("\x1b[33mERROR:\x1b[0m => Missing source or destination file"); + process.exit(); +} + +const sourceFiles = sourceFile.split(","); +const dstFiles = destinationFile.split(","); + +/** + * Loop through source files and destination files and run the compile function + */ +for (let i = 0; i < sourceFiles.length; i++) { + const srcFolder = sourceFiles[i]; + const dstFile = dstFiles[i]; + + if (srcFolder?.match(/\.[^\/]+$/) && !srcFolder?.match(/\.less$/)) { + console.log("\x1b[33mERROR:\x1b[0m Source must be a folder or a .less file"); + process.exit(); + } + + compile(srcFolder, dstFile, null); + + if (!fs.existsSync(srcFolder)) { + if (srcFolder?.match(/\.less$/)) { + fs.mkdirSync(srcFolder.replace(/\/[^\/]+\.less$/, ""), { recursive: true }); + fs.writeFileSync(srcFolder, "", "utf-8"); + } else { + fs.mkdirSync(srcFolder.replace(/\/[^\/]+\.[^\/]+$/, ""), { recursive: true }); + } + } + + if (!fs.existsSync(dstFile)) { + if (dstFile?.match(/\.css$/)) { + fs.mkdirSync(dstFile.replace(/\/[^\/]+\.css$/, ""), { recursive: true }); + fs.writeFileSync(dstFile, "", "utf-8"); + } else { + fs.mkdirSync(dstFile.replace(/\/[^\/]+\.[^\/]+$/, ""), { recursive: true }); + fs.writeFileSync((dstFile + "/_main.css").replace(/\/\//g, ""), "", "utf-8"); + } + } + + fs.watch(srcFolder, { recursive: true }, (evtType, fileName) => { + if (!fileName) return; + + const filePathRoot = srcFolder?.match(/\.less$/) ? srcFolder : srcFolder + "/" + fileName; + + compile(filePathRoot, dstFile, evtType); + }); +} + +/** + * Compile less file to css function + * @param fileName - less file path or folder path + * @param dst - destination file path or folder path + * @param evtType - event type (change, rename, etc.) or null + * @returns + */ +function compile(fileName: string, dst: string, evtType: string | null) { + if (fileName?.match(/\(/) || (fileName.match(/\..{2,4}$/) && !fileName?.match(/\.less$/i))) { + return; + } + + let finalSrcPath = fileName?.match(/\.less$/) ? fileName : `${fileName}/main.less`; + let finalDstPath = dst; + + if (fileName?.match(/\[/)) { + const paths = fileName.split("/"); + const targetPathFull = paths[paths.length - 1]; + const targetPath = targetPathFull.replace(/\[|\]/g, "").replace(/\.less/, ""); + + const destinationFileParentFolder = dst.replace(/\/[^\/]+\.css$/, ""); + + const targetDstFilePath = `${destinationFileParentFolder}/${targetPath}.css`; + + finalSrcPath = `${fileName}/${targetPathFull}`; + finalDstPath = targetDstFilePath; + } + + exec(`lessc ${finalSrcPath} ${finalDstPath?.match(/\.css$/) ? finalDstPath : finalDstPath.replace(/\/$/, "") + "/_main.css"}`, (error, stdout, stderr) => { + /** @type {Error} */ + if (error) { + console.log("ERROR =>", error.message); + + if (!evtType?.match(/change/i) && fileName && fileName.match(/\[/)) { + fs.unlinkSync(finalDstPath); + } + + return; + } + + console.log("Less Compilation \x1b[32msuccessful\x1b[0m!"); + }); +} diff --git a/tsconfig.json b/tsconfig.json new file mode 100644 index 0000000..1479c8c --- /dev/null +++ b/tsconfig.json @@ -0,0 +1,13 @@ +{ + "compilerOptions": { + "target": "es2016", + "module": "commonjs", + "rootDir": "./src", + "outDir": "./dist", + "removeComments": true, + "esModuleInterop": true, + "forceConsistentCasingInFileNames": true, + "strict": true, + "skipLibCheck": true + } +}