Skip to content

Commit 8fb561a

Browse files
committed
feat: Initial implementation of @css-blocks/cli.
The CLI is useful for testing css-blocks or doing basic compilation to BEM classes.
1 parent b44f68e commit 8fb561a

16 files changed

+466
-24
lines changed

css-blocks.code-workspace

+3
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,9 @@
3636
},
3737
{
3838
"path": "packages/@css-blocks/test-utils"
39+
},
40+
{
41+
"path": "packages/@css-blocks/cli"
3942
}
4043
],
4144
"settings": {

packages/@css-blocks/cli/CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
# Change Log

packages/@css-blocks/cli/README.md

+32
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
# CSS Blocks Command Line Interface
2+
3+
The css-blocks Rewriter and Analyzer for Glimmer templates.
4+
5+
## Installation
6+
7+
```
8+
yarn add @css-blocks/cli
9+
```
10+
11+
or run without installation:
12+
13+
```
14+
npx @css-blocks/cli --validate blocks/*.block.css
15+
```
16+
17+
## Usage
18+
19+
```
20+
css-blocks [options] <block directory/file>...
21+
```
22+
23+
### Options:
24+
25+
| Option | Description |
26+
|--------|-------------|
27+
| `--output-file` | a file to output all compiled css blocks into. Overwrites any existing file. |
28+
| `--output-dir` | Output a css file per block to this directory. |
29+
| `--check` | Check syntax only. Do not output any files. |
30+
| `--preprocessors <preprocessor js>` | A JS file that exports an object that maps extensions to a [preprocessor function][preprocessor_type] for that type. |
31+
32+
[preprocessor_type]: https://github.com/linkedin/css-blocks/blob/2f93f994f7ffc72c14728740e49227f7bd30c98b/packages/%40css-blocks/core/src/BlockParser/preprocessing.ts#L44
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
#!/usr/bin/env node
2+
const { CLI } = require("../dist/src/index");
3+
let cli = new CLI();
4+
cli.run(process.argv.slice(2));

packages/@css-blocks/cli/package.json

+58
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
{
2+
"name": "@css-blocks/cli",
3+
"version": "0.21.0",
4+
"description": "Command line interface to css-blocks.",
5+
"main": "dist/index.js",
6+
"types": "dist/index.d.ts",
7+
"files": [
8+
"dist",
9+
"src",
10+
"README.md",
11+
"types-local"
12+
],
13+
"scripts": {
14+
"test": "yarn run test:runner",
15+
"test:runner": "mocha --opts test/mocha.opts dist/test",
16+
"compile": "tsc --build",
17+
"pretest": "yarn run compile",
18+
"posttest": "yarn run lint",
19+
"prepublish": "rm -rf dist && yarn run compile && yarn run lintall",
20+
"lint": "tslint -t msbuild --project . -c tslint.cli.json",
21+
"lintall": "tslint -t msbuild --project . -c tslint.release.json",
22+
"lintfix": "tslint -t msbuild --project . -c tslint.cli.json --fix",
23+
"coverage": "istanbul cover -i dist/src/**/*.js --dir ./build/coverage node_modules/mocha/bin/_mocha -- dist/test --opts test/mocha.opts",
24+
"remap": "remap-istanbul -i build/coverage/coverage.json -o coverage -t html",
25+
"watch": "watch 'yarn run test' src test --wait=1"
26+
},
27+
"keywords": [
28+
"css-blocks",
29+
"css",
30+
"cli"
31+
],
32+
"author": "Chris Eppstein <[email protected]>",
33+
"license": "BSD-2-Clause",
34+
"bugs": {
35+
"url": "https://github.com/linkedin/css-blocks/issues"
36+
},
37+
"engines": {
38+
"node": "6.* || 8.* || >= 10.*"
39+
},
40+
"repository": "https://github.com/linkedin/css-blocks/tree/master/packages/%40css-blocks/cli",
41+
"homepage": "https://github.com/linkedin/css-blocks/tree/master/packages/@css-blocks/cli#readme",
42+
"publishConfig": {
43+
"access": "public"
44+
},
45+
"devDependencies": {
46+
"@css-blocks/code-style": "^0.21.0",
47+
"@types/yargs": "^13.0.0",
48+
"typescript": "~3.4.4",
49+
"watch": "^1.0.2"
50+
},
51+
"dependencies": {
52+
"@css-blocks/core": "^0.21.0",
53+
"chalk": "^2.4.2",
54+
"debug": "^2.6.8",
55+
"fs-extra": "^6.0.1",
56+
"yargs": "^13.2.2"
57+
}
58+
}

packages/@css-blocks/cli/src/index.ts

+107
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,107 @@
1+
import { BlockFactory, CssBlockError } from "@css-blocks/core";
2+
import chalk = require("chalk");
3+
import fs = require("fs");
4+
import fse = require("fs-extra");
5+
import path = require("path");
6+
import util = require("util");
7+
import yargs = require("yargs");
8+
9+
const writeFile = util.promisify(fs.writeFile);
10+
11+
interface GlobalArgs {
12+
preprocessors: string | undefined;
13+
[k: string]: unknown;
14+
}
15+
16+
interface ValidateArgs extends GlobalArgs {
17+
blocks: unknown;
18+
}
19+
20+
interface ValidateOptions {
21+
preprocessors?: string;
22+
}
23+
24+
export class CLI {
25+
constructor() {
26+
}
27+
28+
run(args: Array<string>): Promise<void> {
29+
let argv = this.argumentParser().parse(args);
30+
if (argv.promise) {
31+
return argv.promise as Promise<void>;
32+
} else {
33+
return Promise.resolve();
34+
}
35+
}
36+
37+
get chalk() {
38+
return chalk.default;
39+
}
40+
41+
argumentParser() {
42+
return yargs
43+
.scriptName("css-blocks")
44+
.usage("$0 <cmd> [options] block-dir-or-file...")
45+
.version()
46+
.strict()
47+
.option("preprocessors", {
48+
type: "string",
49+
global: true,
50+
description: "A JS file that exports an object that maps extensions to a preprocessor function for that type.",
51+
nargs: 1,
52+
})
53+
.command<ValidateArgs>(
54+
"validate <blocks..>",
55+
"Validate block file syntax.", (y) =>
56+
y.positional("blocks", {
57+
description: "files or directories containing css blocks.",
58+
}),
59+
(argv: ValidateArgs) => {
60+
let { preprocessors } = argv;
61+
argv.promise = this.validate(argv.blocks as Array<string>, {
62+
preprocessors,
63+
});
64+
},
65+
)
66+
.demandCommand(1, "No command was provided.")
67+
.help();
68+
}
69+
70+
async validate(blockFiles: Array<string>, options: ValidateOptions) {
71+
let preprocessors = options.preprocessors ? require(options.preprocessors) : {};
72+
let factory = new BlockFactory({preprocessors});
73+
let errorCount = 0;
74+
for (let blockFile of blockFiles) {
75+
try {
76+
await factory.getBlockFromPath(path.resolve(blockFile));
77+
this.println(`${this.chalk.green("ok")}\t${blockFile}`);
78+
} catch (e) {
79+
errorCount++;
80+
if (e instanceof CssBlockError) {
81+
let loc = e.location;
82+
if (loc) {
83+
this.println(`${this.chalk.red("error")}\t${this.chalk.whiteBright(blockFile)}:${loc.line}:${loc.column} ${e.origMessage}`);
84+
} else {
85+
this.println(`${this.chalk.red("error")}\t${this.chalk.whiteBright(blockFile)} ${e.origMessage}`);
86+
}
87+
} else {
88+
console.error(e);
89+
}
90+
}
91+
}
92+
this.exit(errorCount);
93+
}
94+
95+
println(...args: Array<string>) {
96+
console.log(...args);
97+
}
98+
99+
async writeFile(filename: string, contents: string): Promise<void> {
100+
await fse.mkdirp(path.dirname(filename));
101+
return writeFile(filename, contents, "utf8");
102+
}
103+
104+
exit(code = 0) {
105+
process.exit(code);
106+
}
107+
}
+22
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
import { CLI } from "../src/index";
2+
3+
export class TestCLI extends CLI {
4+
output: string;
5+
exitCode: number | undefined;
6+
constructor() {
7+
super();
8+
this.output = "";
9+
this.chalk.enabled = false;
10+
}
11+
println(text: string) {
12+
this.output += text + "\n";
13+
}
14+
argumentParser() {
15+
let parser = super.argumentParser();
16+
parser.exitProcess(false);
17+
return parser;
18+
}
19+
exit(code: number) {
20+
this.exitCode = code;
21+
}
22+
}
+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
import assert = require("assert");
2+
import path = require("path");
3+
4+
import { TestCLI as CLI } from "./TestCLI";
5+
6+
function fixture(...relativePathSegments: Array<string>): string {
7+
return path.resolve(__dirname, "..", "..", "test", "fixtures", ...relativePathSegments);
8+
}
9+
10+
describe("CLI", () => {
11+
it("can check syntax for a valid block file", async () => {
12+
let cli = new CLI();
13+
await cli.run(["validate", fixture("simple.block.css")]);
14+
assert.equal(cli.output, `ok\t${fixture("simple.block.css")}\n`);
15+
assert.equal(cli.exitCode, 0);
16+
});
17+
it("can check syntax for a bad block file", async () => {
18+
let cli = new CLI();
19+
await cli.run(["validate", fixture("error.block.css")]);
20+
assert.equal(cli.output, `error\t${fixture("error.block.css")}:1:5 Two distinct classes cannot be selected on the same element: .foo.bar\n`);
21+
assert.equal(cli.exitCode, 1);
22+
});
23+
});
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
.foo.bar {
2+
color: red;
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:scope {
2+
color: red;
3+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
--reporter spec
2+
--require source-map-support/register
3+
--inline-diffs
4+
dist/test/*.js
+19
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
{
2+
"extends": "../../../tsconfig.json",
3+
"compilerOptions": {
4+
"composite": true,
5+
"outDir": "dist",
6+
"baseUrl": "dist"
7+
},
8+
"references": [
9+
{"path": "../core"}
10+
],
11+
"include": [
12+
"src",
13+
"test"
14+
],
15+
"exclude": [
16+
"dist",
17+
"node_modules"
18+
]
19+
}
+4
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "http://json.schemastore.org/tslint",
3+
"extends": "@css-blocks/code-style/configs/tslint.cli.json"
4+
}

packages/@css-blocks/cli/tslint.json

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"extends": "@css-blocks/code-style"
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
{
2+
"$schema": "http://json.schemastore.org/tslint",
3+
"extends": "@css-blocks/code-style/configs/tslint.release.json"
4+
}

0 commit comments

Comments
 (0)