From 775b0b320814f4819b99b3908f6aa864b7a06ccc Mon Sep 17 00:00:00 2001 From: Connor Sullivan Date: Mon, 29 Jul 2024 22:52:45 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=92=A5=20Improve=20CLI=20DX=20(#16)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Adds Commander to improve CLI developer experience. Commander out-of-the-box features delivers a help system, error handling, and (most notably to css-typed users) better argument/option handling. (You can finally pass `--dashes` before the pattern.) Closes #5 BREAKING CHANGE: Removes specific exit codes. Upgrades glob dependency given breaking change already. +semver:minor (0.x semver) --- .github/workflows/pipeline.yaml | 9 +++- README.md | 2 +- package-lock.json | 85 +++++++++++++++++++-------------- package.json | 8 ++-- src/logic.js | 2 +- src/logic.test.js | 5 +- src/main.js | 64 +++++++++++++------------ src/version.js | 2 + 8 files changed, 102 insertions(+), 75 deletions(-) create mode 100644 src/version.js diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index ccb3d84..1a1237b 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -78,6 +78,7 @@ jobs: - name: Run css-typed (the test) # `node dist/main.js` is executing local `css-typed` as if installed (same as `bin`) # Use `-I '//.*'` to ignore the first line (comment) which has generated path and timestamp + # Test --dashes in both positions run: | cp src/fixtures/kebab-case/kebab-case.css "$RUNNER_TEMP/kebab-case.css" @@ -87,6 +88,9 @@ jobs: node dist/main.js "$RUNNER_TEMP/*.css" --dashes diff --strip-trailing-cr -uI '//.*' src/fixtures/kebab-case/kebab-case-dashes.d.css.ts "$RUNNER_TEMP/kebab-case.d.css.ts" + node dist/main.js --dashes "$RUNNER_TEMP/*.css" + diff --strip-trailing-cr -uI '//.*' src/fixtures/kebab-case/kebab-case-dashes.d.css.ts "$RUNNER_TEMP/kebab-case.d.css.ts" + Publish: if: ${{ github.ref == 'refs/heads/main' }} name: Publish @@ -109,7 +113,10 @@ jobs: registry-url: https://registry.npmjs.org - name: Set version - run: sed -i 's/0.0.0-gitversion/${{ env.GitVersion_SemVer }}/g' package.json + run: | + expr='s/0.0.0-gitversion/${{ env.GitVersion_SemVer }}/g' + sed -i "$expr" package.json + sed -i "$expr" src/version.js - name: Install run: npm install --omit=dev diff --git a/README.md b/README.md index 08dfbfa..48119a1 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # css-typed -Basic TypeScript declaration generator for CSS files. +TypeScript declaration generator for CSS files. **Table of contents** diff --git a/package-lock.json b/package-lock.json index f3d2af4..cc360fa 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9,8 +9,10 @@ "version": "0.0.0-gitversion", "license": "MIT", "dependencies": { + "@commander-js/extra-typings": "^12.1.0", + "commander": "^12.1.0", "css-tree": "^2.3.1", - "glob": "^10.3.14" + "glob": "^11.0.0" }, "bin": { "css-typed": "dist/main.js" @@ -150,6 +152,14 @@ "node": ">=4" } }, + "node_modules/@commander-js/extra-typings": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/@commander-js/extra-typings/-/extra-typings-12.1.0.tgz", + "integrity": "sha512-wf/lwQvWAA0goIghcb91dQYpkLBcyhOhQNqG/VgWhnKzgt+UOMvra7EX/2fv70arm5RW+PUHoQHHDa6/p77Eqg==", + "peerDependencies": { + "commander": "~12.1.0" + } + }, "node_modules/@esbuild/aix-ppc64": { "version": "0.21.5", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", @@ -1888,7 +1898,6 @@ "version": "12.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", - "dev": true, "engines": { "node": ">=18" } @@ -3292,21 +3301,22 @@ } }, "node_modules/glob": { - "version": "10.3.14", - "resolved": "https://registry.npmjs.org/glob/-/glob-10.3.14.tgz", - "integrity": "sha512-4fkAqu93xe9Mk7le9v0y3VrPDqLKHarNi2s4Pv7f2yOvfhWfhc7hRPHC/JyqMqb8B/Dt/eGS4n7ykwf3fOsl8g==", + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/glob/-/glob-11.0.0.tgz", + "integrity": "sha512-9UiX/Bl6J2yaBbxKoEBRm4Cipxgok8kQYcOPEhScPwebu2I0HoQOuYdIO6S3hLuWoZgpDpwQZMzTFxgpkyT76g==", "dependencies": { "foreground-child": "^3.1.0", - "jackspeak": "^2.3.6", - "minimatch": "^9.0.1", - "minipass": "^7.0.4", - "path-scurry": "^1.11.0" + "jackspeak": "^4.0.1", + "minimatch": "^10.0.0", + "minipass": "^7.1.2", + "package-json-from-dist": "^1.0.0", + "path-scurry": "^2.0.0" }, "bin": { "glob": "dist/esm/bin.mjs" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -3333,14 +3343,14 @@ } }, "node_modules/glob/node_modules/minimatch": { - "version": "9.0.4", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.4.tgz", - "integrity": "sha512-KqWh+VchfxcMNRAJjj2tnsSJdNbHsVgnkBhTNrW7AjVo6OvLtxw8zfT9oLw1JSohlFzJ8jCoTgaoXvJ+kHt6fw==", + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.0.1.tgz", + "integrity": "sha512-ethXTt3SGGR+95gudmqJ1eNhRO7eGEGIgYA9vnPatK4/etz2MEVDno5GMCibdMTuBMyElzIlgxMna3K94XDIDQ==", "dependencies": { "brace-expansion": "^2.0.1" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4094,14 +4104,14 @@ } }, "node_modules/jackspeak": { - "version": "2.3.6", - "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-2.3.6.tgz", - "integrity": "sha512-N3yCS/NegsOBokc8GAdM8UcmfsKiSS8cipheD/nivzr700H+nsMOxJjQnvwOcRYVuFkdH0wGUvW2WbXGmrZGbQ==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jackspeak/-/jackspeak-4.0.1.tgz", + "integrity": "sha512-cub8rahkh0Q/bw1+GxP7aeSe29hHHn2V4m29nnDlvCdlgU+3UGxkZp7Z53jLUdpX3jdTO0nJZUDl3xvbWc2Xog==", "dependencies": { "@isaacs/cliui": "^8.0.2" }, "engines": { - "node": ">=14" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" @@ -4604,6 +4614,14 @@ "get-func-name": "^2.0.1" } }, + "node_modules/lru-cache": { + "version": "11.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.0.0.tgz", + "integrity": "sha512-Qv32eSV1RSCfhY3fpPE2GNZ8jgM9X7rdAfemLWqTUxwiyIC4jJ6Sy0fZ8H+oLWevO6i4/bizg7c8d8i6bxrzbA==", + "engines": { + "node": "20 || >=22" + } + }, "node_modules/magic-string": { "version": "0.30.11", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.11.tgz", @@ -4710,9 +4728,9 @@ } }, "node_modules/minipass": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.1.tgz", - "integrity": "sha512-UZ7eQ+h8ywIRAW1hIEl2AqdwzJucU/Kp59+8kkZeSvafXhZjul247BvIJjEVFVeON6d7lM46XX1HXCduKAS8VA==", + "version": "7.1.2", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.2.tgz", + "integrity": "sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==", "engines": { "node": ">=16 || 14 >=14.17" } @@ -5186,6 +5204,11 @@ "node": ">=6" } }, + "node_modules/package-json-from-dist": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz", + "integrity": "sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw==" + }, "node_modules/parent-module": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", @@ -5249,28 +5272,20 @@ "dev": true }, "node_modules/path-scurry": { - "version": "1.11.0", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-1.11.0.tgz", - "integrity": "sha512-LNHTaVkzaYaLGlO+0u3rQTz7QrHTFOuKyba9JMTQutkmtNew8dw8wOD7mTU/5fCPZzCWpfW0XnQKzY61P0aTaw==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.0.tgz", + "integrity": "sha512-ypGJsmGtdXUOeM5u93TyeIEfEhM6s+ljAhrk5vAvSx8uyY/02OvrZnA0YNGUrPXfpJMgI1ODd3nwz8Npx4O4cg==", "dependencies": { - "lru-cache": "^10.2.0", - "minipass": "^5.0.0 || ^6.0.2 || ^7.0.0" + "lru-cache": "^11.0.0", + "minipass": "^7.1.2" }, "engines": { - "node": ">=16 || 14 >=14.17" + "node": "20 || >=22" }, "funding": { "url": "https://github.com/sponsors/isaacs" } }, - "node_modules/path-scurry/node_modules/lru-cache": { - "version": "10.2.2", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.2.2.tgz", - "integrity": "sha512-9hp3Vp2/hFQUiIwKo8XCeFVnrg8Pk3TYNPIR7tJADKi5YfcF7vEaK7avFHTlSy3kOKYaJQaalfEo6YuXdceBOQ==", - "engines": { - "node": "14 || >=16.14" - } - }, "node_modules/path-type": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", diff --git a/package.json b/package.json index 666cb1b..75cfc05 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "css-typed", "version": "0.0.0-gitversion", - "description": "Basic TypeScript declaration generator for CSS files", + "description": "TypeScript declaration generator for CSS files", "keywords": [ "CSS", "modules", @@ -33,7 +33,7 @@ "dist" ], "scripts": { - "build": "rm -rf dist && mkdir -p dist && cp src/main.js src/logic.js dist", + "build": "rm -rf dist && mkdir -p dist && cp src/main.js src/logic.js src/version.js dist", "ci-build": "npm-run-all -l -p build eslint prettier test", "eslint": "eslint -f pretty .", "eslint:fix": "npm run eslint -- --fix", @@ -44,8 +44,10 @@ }, "engineStrict": true, "dependencies": { + "@commander-js/extra-typings": "^12.1.0", + "commander": "^12.1.0", "css-tree": "^2.3.1", - "glob": "^10.3.14" + "glob": "^11.0.0" }, "devDependencies": { "@types/css-tree": "^2.3.8", diff --git a/src/logic.js b/src/logic.js index 13373a9..34fc6c7 100644 --- a/src/logic.js +++ b/src/logic.js @@ -16,7 +16,7 @@ import { parse as parseCss, walk } from "css-tree"; * @returns {Promise} TypeScript declaration file content or * `undefined` if no declarations to write. */ -export async function generateDeclaration(stylesheetPath, time, options) { +export async function generateDeclaration(stylesheetPath, time, options = {}) { // Handle case where the file got deleted by the time we got here if (!existsSync(stylesheetPath)) return undefined; diff --git a/src/logic.test.js b/src/logic.test.js index 71746be..4943605 100644 --- a/src/logic.test.js +++ b/src/logic.test.js @@ -1,13 +1,10 @@ import { readFileSync } from "node:fs"; import path from "node:path"; -import { fileURLToPath } from "node:url"; import { describe, expect, it } from "vitest"; import { dtsPath, generateDeclaration } from "./logic.js"; -const __dirname = path.dirname(fileURLToPath(import.meta.url)); - describe(`css-typed`, () => { it(`should not generate an empty declaration file [#9]`, async () => { const path = fixtureFile(`no-declaration-file.css`); @@ -46,5 +43,5 @@ describe(`css-typed`, () => { }); function fixtureFile(filename) { - return path.join(__dirname, `fixtures`, filename); + return path.join(import.meta.dirname, `fixtures`, filename); } diff --git a/src/main.js b/src/main.js index 0a298fb..3a22e35 100755 --- a/src/main.js +++ b/src/main.js @@ -2,40 +2,44 @@ import { writeFile } from "node:fs/promises"; +import { Command } from "@commander-js/extra-typings"; import { glob } from "glob"; import { dtsPath, generateDeclaration } from "./logic.js"; - -/* globals process -- Node/CLI tool */ -await main(process.argv[2], process.argv[3] === `--dashes`); -// See https://github.com/connorjs/css-typed/issues/5 for "proper" CLI arg handling - -async function main(pattern, dashesEnabled) { - if (!pattern) { - console.error(`Expected glob pattern`); - process.exit(2); - } - const options = dashesEnabled ? { localsConvention: `dashes` } : {}; - - const files = await glob(pattern); - - const time = new Date().toISOString(); - const results = await Promise.all( - files.map((file) => - generateDeclaration(file, time, options).then((ts) => - writeDeclarationFile(file, ts), +import { version } from "./version.js"; + +await new Command() + .name(`css-typed`) + .description(`TypeScript declaration generator for CSS files.`) + .version(version) + .argument(``, `Glob path for CSS files to target.`) + .option( + `--dashes`, + `Transform kebab-case classes (dashed names) to camelCase.`, + false, + ) + .action(async function (pattern, options, program) { + const declarationOptions = options.dashes + ? { localsConvention: `dashes` } + : {}; + + const files = await glob(pattern); + + const time = new Date().toISOString(); + const results = await Promise.all( + files.map((file) => + generateDeclaration(file, time, declarationOptions).then((ts) => + writeDeclarationFile(file, ts), + ), ), - ), - ); - - const errors = results.filter(Boolean); - if (errors.length > 0) { - console.error(`Errors encountered`, errors); - process.exit(3); - } - - process.exit(0); -} + ); + + const errors = results.filter(Boolean); + if (errors.length > 0) { + program.error(`Errors encountered: ${errors}`); + } + }) + .parseAsync(); /** * Writes the TypeScript declaration content to file. Handles the output path. diff --git a/src/version.js b/src/version.js new file mode 100644 index 0000000..9ab616a --- /dev/null +++ b/src/version.js @@ -0,0 +1,2 @@ +// Replaced with actual version in Publish (see `pipeline.yaml`) +export const version = `0.0.0-gitversion`;