Skip to content

Commit 547d8c3

Browse files
authored
Merge pull request #179 from abendi/add-option-listDifferent
feat: add new CLI option --listDifferent
2 parents 86a1a30 + 8f124b4 commit 547d8c3

File tree

7 files changed

+128
-1
lines changed

7 files changed

+128
-1
lines changed

README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,11 @@ tcm -p 'src/**/*.icss' .
8888

8989
With `-w` or `--watch`, this CLI watches files in the input directory.
9090

91+
#### validating type files
92+
93+
With `-l` or `--listDifferent`, list any files that are different than those that would be generated.
94+
If any are different, exit with a status code 1.
95+
9196
#### camelize CSS token
9297

9398
With `-c` or `--camelCase`, kebab-cased CSS classes(such as `.my-class {...}`) are exported as camelized TypeScript varibale name(`export const myClass: string`).

src/cli.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,12 @@ const yarg = yargs
1616
.alias('o', 'outDir')
1717
.describe('o', 'Output directory')
1818
.string('o')
19+
.alias('l', 'listDifferent')
20+
.describe(
21+
'l',
22+
'List any files that are different than those that would be generated. If any are different, exit with a status code 1.',
23+
)
24+
.boolean('l')
1925
.alias('w', 'watch')
2026
.describe('w', "Watch input directory's css files or pattern")
2127
.boolean('w')
@@ -63,5 +69,6 @@ async function main(): Promise<void> {
6369
namedExports: argv.e,
6470
dropExtension: argv.d,
6571
silent: argv.s,
72+
listDifferent: argv.l,
6673
});
6774
}

src/dts-content.ts

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import isThere from 'is-there';
55
import * as mkdirp from 'mkdirp';
66
import * as util from 'util';
77
import camelcase from 'camelcase';
8+
import chalk from 'chalk';
89

910
const writeFile = util.promisify(fs.writeFile);
1011
const readFile = util.promisify(fs.readFile);
@@ -90,6 +91,26 @@ export class DtsContent {
9091
return path.join(this.rootDir, this.searchDir, this.rInputPath);
9192
}
9293

94+
public get relativeInputFilePath(): string {
95+
return path.join(this.searchDir, this.rInputPath);
96+
}
97+
98+
public async checkFile(postprocessor = (formatted: string) => formatted): Promise<boolean> {
99+
if (!isThere(this.outputFilePath)) {
100+
console.error(chalk.red(`[ERROR] Type file needs to be generated for '${this.relativeInputFilePath}'`));
101+
return false;
102+
}
103+
104+
const finalOutput = postprocessor(this.formatted);
105+
const fileContent = (await readFile(this.outputFilePath)).toString();
106+
107+
if (fileContent !== finalOutput) {
108+
console.error(chalk.red(`[ERROR] Check type definitions for '${this.outputFilePath}'`));
109+
return false;
110+
}
111+
return true;
112+
}
113+
93114
public async writeFile(postprocessor = (formatted: string) => formatted): Promise<void> {
94115
const finalOutput = postprocessor(this.formatted);
95116

src/run.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ interface RunOptions {
1616
namedExports?: boolean;
1717
dropExtension?: boolean;
1818
silent?: boolean;
19+
listDifferent?: boolean;
1920
}
2021

2122
export async function run(searchDir: string, options: RunOptions = {}): Promise<void> {
@@ -30,6 +31,16 @@ export async function run(searchDir: string, options: RunOptions = {}): Promise<
3031
dropExtension: options.dropExtension,
3132
});
3233

34+
const checkFile = async (f: string): Promise<boolean> => {
35+
try {
36+
const content: DtsContent = await creator.create(f, undefined, false);
37+
return await content.checkFile();
38+
} catch (error) {
39+
console.error(chalk.red(`[ERROR] An error occurred checking '${f}':\n${error}`));
40+
return false;
41+
}
42+
};
43+
3344
const writeFile = async (f: string): Promise<void> => {
3445
try {
3546
const content: DtsContent = await creator.create(f, undefined, !!options.watch);
@@ -43,6 +54,15 @@ export async function run(searchDir: string, options: RunOptions = {}): Promise<
4354
}
4455
};
4556

57+
if (options.listDifferent) {
58+
const files = await glob(filesPattern);
59+
const hasErrors = (await Promise.all(files.map(checkFile))).includes(false);
60+
if (hasErrors) {
61+
process.exit(1);
62+
}
63+
return;
64+
}
65+
4666
if (!options.watch) {
4767
const files = await glob(filesPattern);
4868
await Promise.all(files.map(writeFile));

test/different.css

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
.myClass {color: red;}

test/different.css.d.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
declare const styles: {
2+
readonly "differentClass": string;
3+
};
4+
export = styles;

test/dts-creator.spec.ts

Lines changed: 70 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@
33
import * as path from 'path';
44

55
import * as assert from 'assert';
6-
import * as os from 'os';
76
import { DtsCreator } from '../src/dts-creator';
7+
import SpyInstance = jest.SpyInstance;
88

99
describe('DtsCreator', () => {
1010
var creator = new DtsCreator();
@@ -91,6 +91,15 @@ describe('DtsContent', () => {
9191
});
9292
});
9393

94+
describe('#relativeInputFilePath', () => {
95+
it('returns relative original CSS file name', done => {
96+
new DtsCreator().create(path.normalize('test/testStyle.css')).then(content => {
97+
assert.equal(content.relativeInputFilePath, 'test/testStyle.css');
98+
done();
99+
});
100+
});
101+
});
102+
94103
describe('#outputFilePath', () => {
95104
it('adds d.ts to the original filename', done => {
96105
new DtsCreator().create(path.normalize('test/testStyle.css')).then(content => {
@@ -210,6 +219,66 @@ export = styles;
210219
});
211220
});
212221

222+
describe('#checkFile', () => {
223+
let mockExit: SpyInstance;
224+
let mockConsoleLog: SpyInstance;
225+
let mockConsoleError: SpyInstance;
226+
227+
beforeAll(() => {
228+
mockExit = jest.spyOn(process, 'exit').mockImplementation(exitCode => {
229+
throw new Error(`process.exit: ${exitCode}`);
230+
});
231+
mockConsoleLog = jest.spyOn(console, 'log').mockImplementation();
232+
mockConsoleError = jest.spyOn(console, 'error').mockImplementation();
233+
});
234+
235+
beforeEach(() => {
236+
jest.clearAllMocks();
237+
});
238+
239+
afterAll(() => {
240+
mockExit.mockRestore();
241+
mockConsoleLog.mockRestore();
242+
mockConsoleError.mockRestore();
243+
});
244+
245+
it('return false if type file is missing', done => {
246+
new DtsCreator()
247+
.create('test/empty.css')
248+
.then(content => {
249+
return content.checkFile();
250+
})
251+
.then(result => {
252+
assert.equal(result, false);
253+
done();
254+
});
255+
});
256+
257+
it('returns false if type file content is different', done => {
258+
new DtsCreator()
259+
.create('test/different.css')
260+
.then(content => {
261+
return content.checkFile();
262+
})
263+
.then(result => {
264+
assert.equal(result, false);
265+
done();
266+
});
267+
});
268+
269+
it('returns true if type files match', done => {
270+
new DtsCreator()
271+
.create('test/testStyle.css')
272+
.then(content => {
273+
return content.checkFile();
274+
})
275+
.then(result => {
276+
assert.equal(result, true);
277+
done();
278+
});
279+
});
280+
});
281+
213282
describe('#writeFile', () => {
214283
it('accepts a postprocessor function', done => {
215284
new DtsCreator()

0 commit comments

Comments
 (0)