Skip to content
Permalink
Browse files

feat: Initial implementation of @css-blocks/cli.

The CLI is useful for testing css-blocks or doing basic compilation to
BEM classes.
  • Loading branch information...
chriseppstein committed Apr 26, 2019
1 parent b44f68e commit 8fb561a4ad7743a6d18d4492693be327ad928c89
@@ -36,6 +36,9 @@
},
{
"path": "packages/@css-blocks/test-utils"
},
{
"path": "packages/@css-blocks/cli"
}
],
"settings": {
@@ -0,0 +1 @@
# Change Log
@@ -0,0 +1,32 @@
# CSS Blocks Command Line Interface

The css-blocks Rewriter and Analyzer for Glimmer templates.

## Installation

```
yarn add @css-blocks/cli
```

or run without installation:

```
npx @css-blocks/cli --validate blocks/*.block.css
```

## Usage

```
css-blocks [options] <block directory/file>...
```

### Options:

| Option | Description |
|--------|-------------|
| `--output-file` | a file to output all compiled css blocks into. Overwrites any existing file. |
| `--output-dir` | Output a css file per block to this directory. |
| `--check` | Check syntax only. Do not output any files. |
| `--preprocessors <preprocessor js>` | A JS file that exports an object that maps extensions to a [preprocessor function][preprocessor_type] for that type. |

[preprocessor_type]: https://github.com/linkedin/css-blocks/blob/2f93f994f7ffc72c14728740e49227f7bd30c98b/packages/%40css-blocks/core/src/BlockParser/preprocessing.ts#L44
@@ -0,0 +1,4 @@
#!/usr/bin/env node
const { CLI } = require("../dist/src/index");
let cli = new CLI();
cli.run(process.argv.slice(2));
@@ -0,0 +1,58 @@
{
"name": "@css-blocks/cli",
"version": "0.21.0",
"description": "Command line interface to css-blocks.",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"dist",
"src",
"README.md",
"types-local"
],
"scripts": {
"test": "yarn run test:runner",
"test:runner": "mocha --opts test/mocha.opts dist/test",
"compile": "tsc --build",
"pretest": "yarn run compile",
"posttest": "yarn run lint",
"prepublish": "rm -rf dist && yarn run compile && yarn run lintall",
"lint": "tslint -t msbuild --project . -c tslint.cli.json",
"lintall": "tslint -t msbuild --project . -c tslint.release.json",
"lintfix": "tslint -t msbuild --project . -c tslint.cli.json --fix",
"coverage": "istanbul cover -i dist/src/**/*.js --dir ./build/coverage node_modules/mocha/bin/_mocha -- dist/test --opts test/mocha.opts",
"remap": "remap-istanbul -i build/coverage/coverage.json -o coverage -t html",
"watch": "watch 'yarn run test' src test --wait=1"
},
"keywords": [
"css-blocks",
"css",
"cli"
],
"author": "Chris Eppstein <chris@eppsteins.net>",
"license": "BSD-2-Clause",
"bugs": {
"url": "https://github.com/linkedin/css-blocks/issues"
},
"engines": {
"node": "6.* || 8.* || >= 10.*"
},
"repository": "https://github.com/linkedin/css-blocks/tree/master/packages/%40css-blocks/cli",
"homepage": "https://github.com/linkedin/css-blocks/tree/master/packages/@css-blocks/cli#readme",
"publishConfig": {
"access": "public"
},
"devDependencies": {
"@css-blocks/code-style": "^0.21.0",
"@types/yargs": "^13.0.0",
"typescript": "~3.4.4",
"watch": "^1.0.2"
},
"dependencies": {
"@css-blocks/core": "^0.21.0",
"chalk": "^2.4.2",
"debug": "^2.6.8",
"fs-extra": "^6.0.1",
"yargs": "^13.2.2"
}
}
@@ -0,0 +1,107 @@
import { BlockFactory, CssBlockError } from "@css-blocks/core";
import chalk = require("chalk");
import fs = require("fs");
import fse = require("fs-extra");
import path = require("path");
import util = require("util");
import yargs = require("yargs");

const writeFile = util.promisify(fs.writeFile);

interface GlobalArgs {
preprocessors: string | undefined;
[k: string]: unknown;
}

interface ValidateArgs extends GlobalArgs {
blocks: unknown;
}

interface ValidateOptions {
preprocessors?: string;
}

export class CLI {
constructor() {
}

run(args: Array<string>): Promise<void> {
let argv = this.argumentParser().parse(args);
if (argv.promise) {
return argv.promise as Promise<void>;
} else {
return Promise.resolve();
}
}

get chalk() {
return chalk.default;
}

argumentParser() {
return yargs
.scriptName("css-blocks")
.usage("$0 <cmd> [options] block-dir-or-file...")
.version()
.strict()
.option("preprocessors", {
type: "string",
global: true,
description: "A JS file that exports an object that maps extensions to a preprocessor function for that type.",
nargs: 1,
})
.command<ValidateArgs>(
"validate <blocks..>",
"Validate block file syntax.", (y) =>
y.positional("blocks", {
description: "files or directories containing css blocks.",
}),
(argv: ValidateArgs) => {
let { preprocessors } = argv;
argv.promise = this.validate(argv.blocks as Array<string>, {
preprocessors,
});
},
)
.demandCommand(1, "No command was provided.")
.help();
}

async validate(blockFiles: Array<string>, options: ValidateOptions) {
let preprocessors = options.preprocessors ? require(options.preprocessors) : {};
let factory = new BlockFactory({preprocessors});
let errorCount = 0;
for (let blockFile of blockFiles) {
try {
await factory.getBlockFromPath(path.resolve(blockFile));
this.println(`${this.chalk.green("ok")}\t${blockFile}`);
} catch (e) {
errorCount++;
if (e instanceof CssBlockError) {
let loc = e.location;
if (loc) {
this.println(`${this.chalk.red("error")}\t${this.chalk.whiteBright(blockFile)}:${loc.line}:${loc.column} ${e.origMessage}`);
} else {
this.println(`${this.chalk.red("error")}\t${this.chalk.whiteBright(blockFile)} ${e.origMessage}`);
}
} else {
console.error(e);
}
}
}
this.exit(errorCount);
}

println(...args: Array<string>) {
console.log(...args);
}

async writeFile(filename: string, contents: string): Promise<void> {
await fse.mkdirp(path.dirname(filename));
return writeFile(filename, contents, "utf8");
}

exit(code = 0) {
process.exit(code);
}
}
@@ -0,0 +1,22 @@
import { CLI } from "../src/index";

export class TestCLI extends CLI {
output: string;
exitCode: number | undefined;
constructor() {
super();
this.output = "";
this.chalk.enabled = false;
}
println(text: string) {
this.output += text + "\n";
}
argumentParser() {
let parser = super.argumentParser();
parser.exitProcess(false);
return parser;
}
exit(code: number) {
this.exitCode = code;
}
}
@@ -0,0 +1,23 @@
import assert = require("assert");
import path = require("path");

import { TestCLI as CLI } from "./TestCLI";

function fixture(...relativePathSegments: Array<string>): string {
return path.resolve(__dirname, "..", "..", "test", "fixtures", ...relativePathSegments);
}

describe("CLI", () => {
it("can check syntax for a valid block file", async () => {
let cli = new CLI();
await cli.run(["validate", fixture("simple.block.css")]);
assert.equal(cli.output, `ok\t${fixture("simple.block.css")}\n`);
assert.equal(cli.exitCode, 0);
});
it("can check syntax for a bad block file", async () => {
let cli = new CLI();
await cli.run(["validate", fixture("error.block.css")]);
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`);
assert.equal(cli.exitCode, 1);
});
});
@@ -0,0 +1,3 @@
.foo.bar {
color: red;
}
@@ -0,0 +1,3 @@
:scope {
color: red;
}
@@ -0,0 +1,4 @@
--reporter spec
--require source-map-support/register
--inline-diffs
dist/test/*.js
@@ -0,0 +1,19 @@
{
"extends": "../../../tsconfig.json",
"compilerOptions": {
"composite": true,
"outDir": "dist",
"baseUrl": "dist"
},
"references": [
{"path": "../core"}
],
"include": [
"src",
"test"
],
"exclude": [
"dist",
"node_modules"
]
}
@@ -0,0 +1,4 @@
{
"$schema": "http://json.schemastore.org/tslint",
"extends": "@css-blocks/code-style/configs/tslint.cli.json"
}
@@ -0,0 +1,3 @@
{
"extends": "@css-blocks/code-style"
}
@@ -0,0 +1,4 @@
{
"$schema": "http://json.schemastore.org/tslint",
"extends": "@css-blocks/code-style/configs/tslint.release.json"
}

0 comments on commit 8fb561a

Please sign in to comment.
You can’t perform that action at this time.