Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,11 @@ tcm -p 'src/**/*.icss' .

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

#### validating type files

With `-l` or `--listDifferent`, list any files that are different than those that would be generated.
If any are different, exit with a status code 1.

#### camelize CSS token

With `-c` or `--camelCase`, kebab-cased CSS classes(such as `.my-class {...}`) are exported as camelized TypeScript varibale name(`export const myClass: string`).
Expand Down
7 changes: 7 additions & 0 deletions src/cli.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ const yarg = yargs
.alias('o', 'outDir')
.describe('o', 'Output directory')
.string('o')
.alias('l', 'listDifferent')
.describe(
'l',
'List any files that are different than those that would be generated. If any are different, exit with a status code 1.',
)
.boolean('l')
.alias('w', 'watch')
.describe('w', "Watch input directory's css files or pattern")
.boolean('w')
Expand Down Expand Up @@ -63,5 +69,6 @@ async function main(): Promise<void> {
namedExports: argv.e,
dropExtension: argv.d,
silent: argv.s,
listDifferent: argv.l,
});
}
21 changes: 21 additions & 0 deletions src/dts-content.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import isThere from 'is-there';
import * as mkdirp from 'mkdirp';
import * as util from 'util';
import camelcase from 'camelcase';
import chalk from 'chalk';

const writeFile = util.promisify(fs.writeFile);
const readFile = util.promisify(fs.readFile);
Expand Down Expand Up @@ -90,6 +91,26 @@ export class DtsContent {
return path.join(this.rootDir, this.searchDir, this.rInputPath);
}

public get relativeInputFilePath(): string {
return path.join(this.searchDir, this.rInputPath);
}

public async checkFile(postprocessor = (formatted: string) => formatted): Promise<boolean> {
if (!isThere(this.outputFilePath)) {
console.error(chalk.red(`[ERROR] Type file needs to be generated for '${this.relativeInputFilePath}'`));
return false;
}

const finalOutput = postprocessor(this.formatted);
const fileContent = (await readFile(this.outputFilePath)).toString();

if (fileContent !== finalOutput) {
console.error(chalk.red(`[ERROR] Check type definitions for '${this.outputFilePath}'`));
return false;
}
return true;
}

public async writeFile(postprocessor = (formatted: string) => formatted): Promise<void> {
const finalOutput = postprocessor(this.formatted);

Expand Down
20 changes: 20 additions & 0 deletions src/run.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ interface RunOptions {
namedExports?: boolean;
dropExtension?: boolean;
silent?: boolean;
listDifferent?: boolean;
}

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

const checkFile = async (f: string): Promise<boolean> => {
try {
const content: DtsContent = await creator.create(f, undefined, false);
return await content.checkFile();
} catch (error) {
console.error(chalk.red(`[ERROR] An error occurred checking '${f}':\n${error}`));
return false;
}
};

const writeFile = async (f: string): Promise<void> => {
try {
const content: DtsContent = await creator.create(f, undefined, !!options.watch);
Expand All @@ -43,6 +54,15 @@ export async function run(searchDir: string, options: RunOptions = {}): Promise<
}
};

if (options.listDifferent) {
const files = await glob(filesPattern);
const hasErrors = (await Promise.all(files.map(checkFile))).includes(false);
if (hasErrors) {
process.exit(1);
}
return;
}

if (!options.watch) {
const files = await glob(filesPattern);
await Promise.all(files.map(writeFile));
Expand Down
1 change: 1 addition & 0 deletions test/different.css
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
.myClass {color: red;}
4 changes: 4 additions & 0 deletions test/different.css.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare const styles: {
readonly "differentClass": string;
};
export = styles;
71 changes: 70 additions & 1 deletion test/dts-creator.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@
import * as path from 'path';

import * as assert from 'assert';
import * as os from 'os';
import { DtsCreator } from '../src/dts-creator';
import SpyInstance = jest.SpyInstance;

describe('DtsCreator', () => {
var creator = new DtsCreator();
Expand Down Expand Up @@ -91,6 +91,15 @@ describe('DtsContent', () => {
});
});

describe('#relativeInputFilePath', () => {
it('returns relative original CSS file name', done => {
new DtsCreator().create(path.normalize('test/testStyle.css')).then(content => {
assert.equal(content.relativeInputFilePath, 'test/testStyle.css');
done();
});
});
});

describe('#outputFilePath', () => {
it('adds d.ts to the original filename', done => {
new DtsCreator().create(path.normalize('test/testStyle.css')).then(content => {
Expand Down Expand Up @@ -210,6 +219,66 @@ export = styles;
});
});

describe('#checkFile', () => {
let mockExit: SpyInstance;
let mockConsoleLog: SpyInstance;
let mockConsoleError: SpyInstance;

beforeAll(() => {
mockExit = jest.spyOn(process, 'exit').mockImplementation(exitCode => {
throw new Error(`process.exit: ${exitCode}`);
});
mockConsoleLog = jest.spyOn(console, 'log').mockImplementation();
mockConsoleError = jest.spyOn(console, 'error').mockImplementation();
});

beforeEach(() => {
jest.clearAllMocks();
});

afterAll(() => {
mockExit.mockRestore();
mockConsoleLog.mockRestore();
mockConsoleError.mockRestore();
});

it('return false if type file is missing', done => {
new DtsCreator()
.create('test/empty.css')
.then(content => {
return content.checkFile();
})
.then(result => {
assert.equal(result, false);
done();
});
});

it('returns false if type file content is different', done => {
new DtsCreator()
.create('test/different.css')
.then(content => {
return content.checkFile();
})
.then(result => {
assert.equal(result, false);
done();
});
});

it('returns true if type files match', done => {
new DtsCreator()
.create('test/testStyle.css')
.then(content => {
return content.checkFile();
})
.then(result => {
assert.equal(result, true);
done();
});
});
});

describe('#writeFile', () => {
it('accepts a postprocessor function', done => {
new DtsCreator()
Expand Down