diff --git a/.github/workflows/pipeline.yaml b/.github/workflows/pipeline.yaml index 396dbad..62c49f2 100644 --- a/.github/workflows/pipeline.yaml +++ b/.github/workflows/pipeline.yaml @@ -77,21 +77,58 @@ jobs: - name: Build run: npm run build - - 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 `--localsConvention` in both positions + # Run tests + # + # - Run from $RUNNER_TEMP for auto-cleanup. + # - `./dist/main.js` is executing local `css-typed` as if installed (same as `bin`). + # But it is `$GITHUB_WORKSPACE/dist/main.js` b/c we `cd $RUNNER_TEMP`. + # - Use `diff` to compare the files. + # Use `-I '//.*'` to ignore the first line (comment) which has generated path and timestamp. + + - name: "Test 1: Default case" run: | - cp src/fixtures/casing/casing.css "$RUNNER_TEMP/casing.css" + cp src/fixtures/casing/casing.css $RUNNER_TEMP/casing.css + cp src/fixtures/casing/dashesOnly.d.css.ts $RUNNER_TEMP/expected.d.css.ts - node dist/main.js "$RUNNER_TEMP/*.css" - diff --strip-trailing-cr -uI '//.*' src/fixtures/casing/dashesOnly.d.css.ts "$RUNNER_TEMP/casing.d.css.ts" + cd $RUNNER_TEMP + $GITHUB_WORKSPACE/dist/main.js '*.css' + diff --strip-trailing-cr -uI '//.*' expected.d.css.ts casing.d.css.ts - node dist/main.js "$RUNNER_TEMP/*.css" --localsConvention camelCaseOnly - diff --strip-trailing-cr -uI '//.*' src/fixtures/casing/camelCaseOnly.d.css.ts "$RUNNER_TEMP/casing.d.css.ts" + - name: "Test 2: localsConvention, second position" + run: | + cp src/fixtures/casing/casing.css $RUNNER_TEMP/casing.css + cp src/fixtures/casing/camelCaseOnly.d.css.ts $RUNNER_TEMP/expected.d.css.ts + + cd $RUNNER_TEMP + $GITHUB_WORKSPACE/dist/main.js '*.css' --localsConvention camelCaseOnly + diff --strip-trailing-cr -uI '//.*' expected.d.css.ts casing.d.css.ts + + - name: "Test 3: localsConvention, first position" + run: | + cp src/fixtures/casing/casing.css $RUNNER_TEMP/casing.css + cp src/fixtures/casing/camelCaseOnly.d.css.ts $RUNNER_TEMP/expected.d.css.ts + + cd $RUNNER_TEMP + $GITHUB_WORKSPACE/dist/main.js --localsConvention camelCaseOnly '*.css' + diff --strip-trailing-cr -uI '//.*' expected.d.css.ts casing.d.css.ts + + - name: "Test 4: relative outdir" + run: | + cp src/fixtures/casing/casing.css $RUNNER_TEMP/casing.css + cp src/fixtures/casing/dashesOnly.d.css.ts $RUNNER_TEMP/expected.d.css.ts + + cd $RUNNER_TEMP + $GITHUB_WORKSPACE/dist/main.js '*.css' --outdir generated + diff --strip-trailing-cr -uI '//.*' expected.d.css.ts generated/casing.d.css.ts + + - name: "Test 5: absolute outdir" + run: | + cp src/fixtures/casing/casing.css $RUNNER_TEMP/casing.css + cp src/fixtures/casing/dashesOnly.d.css.ts $RUNNER_TEMP/expected.d.css.ts - node dist/main.js --localsConvention camelCaseOnly "$RUNNER_TEMP/*.css" - diff --strip-trailing-cr -uI '//.*' src/fixtures/casing/camelCaseOnly.d.css.ts "$RUNNER_TEMP/casing.d.css.ts" + cd $RUNNER_TEMP + $GITHUB_WORKSPACE/dist/main.js '*.css' -o $GITHUB_WORKSPACE/generated + diff --strip-trailing-cr -uI '//.*' expected.d.css.ts $GITHUB_WORKSPACE/generated/casing.d.css.ts Publish: if: ${{ github.ref == 'refs/heads/main' }} diff --git a/src/logic.test.ts b/src/logic.test.ts index e7d008b..9737a02 100644 --- a/src/logic.test.ts +++ b/src/logic.test.ts @@ -1,5 +1,6 @@ import { readFileSync } from "node:fs"; import path from "node:path"; +import * as process from "node:process"; import { describe, expect, it } from "vitest"; @@ -37,13 +38,21 @@ describe(`css-typed`, () => { }); }); + const cwd = process.cwd(); + const cwdParent = path.resolve(cwd, `..`); describe(`dtsPath`, () => { it.each([ - [`foo.css`, `foo.d.css.ts`], - [`foo.module.css`, `foo.module.d.css.ts`], - ])(`%s should create file %s`, (input, expected) => { - expect(dtsPath(input)).toStrictEqual(expected); - }); + [`src/foo.css`, `${cwd}/src/foo.d.css.ts`, undefined], + [`src/foo.module.css`, `${cwd}/src/foo.module.d.css.ts`, ``], + [`src/foo.css`, `${cwd}/generated/src/foo.d.css.ts`, `generated`], + [`src/foo.css`, `/absolute/src/foo.d.css.ts`, `/absolute`], + [`src/foo.css`, `${cwdParent}/relative/src/foo.d.css.ts`, `../relative`], + ])( + `[%s] should create file [%s] with outdir [%s]`, + (input, expected, outdir) => { + expect(path.join(...dtsPath(input, outdir))).toStrictEqual(expected); + }, + ); }); }); diff --git a/src/logic.ts b/src/logic.ts index 390dc19..8ae360e 100644 --- a/src/logic.ts +++ b/src/logic.ts @@ -154,7 +154,19 @@ function dashesCamelCase(s: string) { ); } -export function dtsPath(stylesheetPath: string) { - const { dir, name, ext } = path.parse(stylesheetPath); - return path.join(dir, `${name}.d${ext}.ts`); +export function dtsPath( + stylesheetPath: string, + outdir: string | undefined, +): [string, string] { + const { dir: originalDirectory, name, ext } = path.parse(stylesheetPath); + + let directory; + if (outdir) { + const relative = path.relative(process.cwd(), originalDirectory); + directory = path.join(outdir, relative); + } else { + directory = originalDirectory; + } + + return [path.resolve(directory), `${name}.d${ext}.ts`]; } diff --git a/src/main.ts b/src/main.ts index 923fe3c..1aff532 100755 --- a/src/main.ts +++ b/src/main.ts @@ -1,6 +1,8 @@ #!/usr/bin/env node -import { writeFile } from "node:fs/promises"; +import { existsSync } from "node:fs"; +import { mkdir, writeFile } from "node:fs/promises"; +import path from "node:path"; import { Command, Option } from "@commander-js/extra-typings"; import { glob } from "glob"; @@ -24,6 +26,10 @@ await new Command() .choices(localsConventionChoices) .default(`dashesOnly` as const), ) + .option( + `-o, --outdir `, + `Root directory for generated CSS declaration files.`, + ) .action(async function (pattern, options) { const files = await glob(pattern); @@ -31,7 +37,7 @@ await new Command() await Promise.all( files.map((file) => generateDeclaration(file, time, options).then((ts) => - writeDeclarationFile(file, ts), + writeDeclarationFile(file, options.outdir, ts), ), ), ); @@ -41,12 +47,25 @@ await new Command() /** * Writes the TypeScript declaration content to file. Handles the output path. * - * @param path - Path to the original stylesheet file. NOT the path to write. + * @param file - Path to the original stylesheet file. NOT the path to write. + * @param outdir - Output directory to which to write. * @param ts - The TypeScript declaration content to write. * @returns Empty promise indicating when writing has completed. */ -async function writeDeclarationFile(path: string, ts: string | undefined) { - if (!ts) return undefined; - await writeFile(dtsPath(path), ts, `utf8`); - return undefined; +async function writeDeclarationFile( + file: string, + outdir: string | undefined, + ts: string | undefined, +) { + if (!ts) { + return undefined; + } + + const [directoryToWrite, fileToWrite] = dtsPath(file, outdir); + if (!existsSync(directoryToWrite)) { + await mkdir(directoryToWrite, { recursive: true }); + } + + const pathToWrite = path.join(directoryToWrite, fileToWrite); + await writeFile(pathToWrite, ts, { encoding: `utf8` }); }