Skip to content

Commit 620ac9a

Browse files
committed
feat: add basic cli
1 parent cc3f33f commit 620ac9a

9 files changed

Lines changed: 188 additions & 15 deletions

File tree

README.md

Lines changed: 61 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,21 +2,74 @@
22

33
css-codemod is a toolkit for running codemods (a.k.a. transforms) over many CSS files.
44

5-
## Install
5+
## Usage
66

7-
TODO
7+
There are two ways to use css-codemod.
88

9-
## Usage
9+
First, using [npx](https://www.npmjs.com/package/npx) to execute the transform without need to explicitly install `css-codemod`.
10+
11+
```bash
12+
npx css-codemod ./src/**/*.css -t ./transform.ts
13+
```
14+
15+
Second, install `css-codemod` as a dependency and execute with your package manager of choice.
16+
17+
```bash
18+
# Install and execute css-codemod with npm
19+
npm i -D css-codemod
20+
./node_modules/.bin/css-codemod ./src/**/*.css -t ./transform.ts
21+
22+
# Or, install and execute css-codemod with yarn
23+
yarn add -D css-codemod
24+
yarn css-codemod ./src/**/*.css -t ./transform.ts
25+
```
26+
27+
## Transform
28+
29+
The transform file defines the transformations to define. The transform can be written in either JavaScript or TypeScript.
30+
31+
```ts
32+
// transform.ts
33+
34+
// Import the `Transform` type to provide type-safety when
35+
// using and creating a transform function.
36+
import { Transform } from 'css-codemod';
37+
38+
// Define a named `transform` export.
39+
// Note: it's important the function is named `transform` because that's
40+
// what the tool expects.
41+
export const transform: Transform = (file, api) => {
42+
// Implement the transform.
43+
};
44+
```
45+
46+
## CLI
47+
48+
```bash
49+
Usage:
50+
$ css-codemod [files]
51+
52+
Commands:
53+
[files] File path to transform. Glob patterns are supported.
1054

11-
TODO
55+
For more info, run any command with the `--help` flag:
56+
$ css-codemod --help
1257

13-
### CLI
58+
Options:
59+
-t, --transform <transform> Path to the transform file (default: ./transform.ts)
60+
-h, --help Display this message
61+
-v, --version Display version number
1462

15-
TODO
63+
Examples:
64+
css-codemod ./a.css
65+
css-codemod ./src/a.css
66+
css-codemod ./src/**/*.css
67+
css-codemod ./**/*.css
68+
```
1669

17-
### API
70+
## API
1871

19-
TODO
72+
TODO(Document transform function API)
2073

2174
### PostCSS
2275

package.json

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@
2323
"prepare": "yarn build",
2424
"size": "size-limit",
2525
"analyze": "size-limit --why",
26-
"clean": "rimraf dist"
26+
"clean": "rimraf dist",
27+
"css-codemod": "yarn build && ./dist/cli.js"
2728
},
2829
"husky": {
2930
"hooks": {
@@ -48,6 +49,7 @@
4849
],
4950
"devDependencies": {
5051
"@size-limit/preset-small-lib": "^7.0.5",
52+
"@types/node": "^17.0.13",
5153
"dripip": "^0.10.0",
5254
"husky": "^7.0.4",
5355
"rimraf": "^3.0.2",
@@ -58,6 +60,10 @@
5860
"typescript": "^4.5.5"
5961
},
6062
"dependencies": {
63+
"bundle-require": "^3.0.2",
64+
"cac": "^6.7.12",
65+
"esbuild": "^0.14.14",
66+
"glob": "^7.2.0",
6167
"postcss": "^8.4.5"
6268
}
6369
}

src/cli.ts

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,38 @@
11
#!/usr/bin/env node
22

3-
console.log('hello world.');
3+
import { cac } from 'cac';
4+
import { readFileSync } from 'fs';
5+
import { join } from 'path';
6+
import { processTransform } from './process-transform';
7+
8+
const PACKAGE_PATH = join(__dirname, '../package.json');
9+
const PACKAGE_JSON = JSON.parse(readFileSync(PACKAGE_PATH, 'utf8'));
10+
const NAME = PACKAGE_JSON.name;
11+
const VERSION = PACKAGE_JSON.version;
12+
13+
const run = async () => {
14+
const cli = cac(`${NAME}`);
15+
16+
cli
17+
.command('[files]', 'File path to transform. Glob patterns are supported.')
18+
.example(`${NAME} ./a.css`)
19+
.example(`${NAME} ./src/a.css`)
20+
.example(`${NAME} ./src/**/*.css`)
21+
.example(`${NAME} ./**/*.css`)
22+
.option('-t, --transform <transform>', 'Path to the transform file', {
23+
default: './transform.ts',
24+
})
25+
.action(async (files: string[], flags) => {
26+
const { transform } = flags;
27+
28+
await processTransform({ files, transform });
29+
});
30+
31+
cli.help();
32+
cli.version(VERSION);
33+
cli.parse(process.argv, { run: false });
34+
35+
await cli.runMatchedCommand();
36+
};
37+
38+
run();

src/load-transform.ts

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
import { bundleRequire } from 'bundle-require';
2+
import { Transform } from './transform';
3+
4+
const MINIMUM_EXAMPLE_TRANSFORM = `
5+
import { Transform } from "css-codemod";
6+
7+
export const transform: Transform = (file, api) => {};
8+
`;
9+
10+
/**
11+
* Validate the general structure of the transform to catch simple errors.
12+
*/
13+
const validateTransform = (transform: unknown): Transform => {
14+
if (typeof transform === 'function') {
15+
return transform as Transform;
16+
} else {
17+
console.error(
18+
`Transform file must export a valid transform. For example:\n${MINIMUM_EXAMPLE_TRANSFORM}`
19+
);
20+
process.exit(1);
21+
}
22+
};
23+
24+
/**
25+
* Load and validate the transform file given the filepath.
26+
*/
27+
export const loadTransform = async (filepath: string): Promise<Transform> => {
28+
try {
29+
const { mod } = await bundleRequire({ filepath });
30+
const transform = validateTransform(mod.transform || mod.default);
31+
return transform;
32+
} catch (err) {
33+
console.error(
34+
`An error occurred loading the transform file. Verify "${filepath}" exists.`
35+
);
36+
process.exit(1);
37+
}
38+
};

src/process-transform.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
import { loadTransform } from './load-transform';
2+
3+
interface ProcessTransformOptions {
4+
/**
5+
* The list of file paths to process and run through the transform.
6+
*/
7+
files: string[];
8+
9+
/**
10+
* The transform path to run each file through.
11+
*/
12+
transform: string;
13+
}
14+
15+
export const processTransform = async (options: ProcessTransformOptions) => {
16+
console.log(options);
17+
const transform = await loadTransform(options.transform);
18+
19+
transform();
20+
};

src/transform.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
interface TransformFileInfo {}
2+
3+
interface TransformAPI {}
4+
5+
export type Transform = (
6+
fileInfo: TransformFileInfo,
7+
api: TransformAPI
8+
) => null | string;

test/transforms/color-to-red.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
export const t = () => {
2+
console.log('HELLO WORLD TRANSFORM.');
3+
};

tsconfig.json

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,6 @@
2121
"noUnusedParameters": true,
2222
// use Node's module resolution algorithm, instead of the legacy TS one
2323
"moduleResolution": "node",
24-
// transpile JSX to React.createElement
25-
"jsx": "react",
2624
// interop between ESM and CJS modules. Recommended by TS
2725
"esModuleInterop": true,
2826
// significant perf increase by skipping checking .d.ts files, particularly those in node_modules. Recommended by TS

yarn.lock

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1529,7 +1529,7 @@
15291529
resolved "https://registry.yarnpkg.com/@types/ms/-/ms-0.7.31.tgz#31b7ca6407128a3d2bbc27fe2d21b345397f6197"
15301530
integrity sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==
15311531

1532-
"@types/node@*":
1532+
"@types/node@*", "@types/node@^17.0.13":
15331533
version "17.0.13"
15341534
resolved "https://registry.yarnpkg.com/@types/node/-/node-17.0.13.tgz#5ed7ed7c662948335fcad6c412bb42d99ea754e3"
15351535
integrity sha512-Y86MAxASe25hNzlDbsviXl8jQHb0RDvKt4c40ZJQ1Don0AAL0STLZSs4N+6gLEO55pedy7r2cLwS+ZDxPm/2Bw==
@@ -2135,6 +2135,13 @@ bundle-require@^2.1.8:
21352135
resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-2.3.0.tgz#2d4d4968dd29d2de4b9ac1db98be495c73f5b2b8"
21362136
integrity sha512-kH8vyERJv0Td4Odu2KQyooYyeXDx2FbhGwSfkEGdyHmkTCahvwVI8w/pE2stbsp6G/W5/3kIC7ErdBg/30OAkw==
21372137

2138+
bundle-require@^3.0.2:
2139+
version "3.0.2"
2140+
resolved "https://registry.yarnpkg.com/bundle-require/-/bundle-require-3.0.2.tgz#900fbd84d84db6799e674fbebc74c4ba498651b5"
2141+
integrity sha512-WLS50LRdi8oAMnQfoxqjI3Fszi0xaI6dJumvtX909u0WREkYCuFGeE2UwMn5H8bSUXWtUB0XeBqNkgpVjMcYyQ==
2142+
dependencies:
2143+
load-tsconfig "^0.2.0"
2144+
21382145
bytes-iec@^3.1.1:
21392146
version "3.1.1"
21402147
resolved "https://registry.yarnpkg.com/bytes-iec/-/bytes-iec-3.1.1.tgz#94cd36bf95c2c22a82002c247df8772d1d591083"
@@ -2845,7 +2852,7 @@ esbuild-windows-arm64@0.14.14:
28452852
resolved "https://registry.yarnpkg.com/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.14.tgz#ca747ce4066d5b8a79dbe48fe6ecd92d202e5366"
28462853
integrity sha512-dCm1wTOm6HIisLanmybvRKvaXZZo4yEVrHh1dY0v582GThXJOzuXGja1HIQgV09RpSHYRL3m4KoUBL00l6SWEg==
28472854

2848-
esbuild@^0.14.2, esbuild@^0.14.8:
2855+
esbuild@^0.14.14, esbuild@^0.14.2, esbuild@^0.14.8:
28492856
version "0.14.14"
28502857
resolved "https://registry.yarnpkg.com/esbuild/-/esbuild-0.14.14.tgz#3b99f20d628013c3e2ae90e67687e03f1d6eb071"
28512858
integrity sha512-aiK4ddv+uui0k52OqSHu4xxu+SzOim7Rlz4i25pMEiC8rlnGU0HJ9r+ZMfdWL5bzifg+nhnn7x4NSWTeehYblg==
@@ -3561,7 +3568,7 @@ glob@7.1.6:
35613568
once "^1.3.0"
35623569
path-is-absolute "^1.0.0"
35633570

3564-
glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6:
3571+
glob@^7.0.0, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3, glob@^7.1.4, glob@^7.1.6, glob@^7.2.0:
35653572
version "7.2.0"
35663573
resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023"
35673574
integrity sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==
@@ -4790,6 +4797,11 @@ lines-and-columns@^1.1.6:
47904797
resolved "https://registry.yarnpkg.com/lines-and-columns/-/lines-and-columns-1.2.4.tgz#eca284f75d2965079309dc0ad9255abb2ebc1632"
47914798
integrity sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==
47924799

4800+
load-tsconfig@^0.2.0:
4801+
version "0.2.2"
4802+
resolved "https://registry.yarnpkg.com/load-tsconfig/-/load-tsconfig-0.2.2.tgz#1aaf97e37e34a847042598a7596f4a3163320e06"
4803+
integrity sha512-9B4XOMjNhphRmXg3YHFnpgEH5fmYKofXJ7M6sLkRcfJ5DcuPiStlQ1Or+1Rv/aML716kQ9Q+C9zJGUcfMYiq4Q==
4804+
47934805
locate-path@^2.0.0:
47944806
version "2.0.0"
47954807
resolved "https://registry.yarnpkg.com/locate-path/-/locate-path-2.0.0.tgz#2b568b265eec944c6d9c0de9c3dbbbca0354cd8e"

0 commit comments

Comments
 (0)