Skip to content

Commit f947ed1

Browse files
committed
Finalize CLI
1 parent 6faf848 commit f947ed1

16 files changed

+881
-102
lines changed

bin/tst.mjs

+9-1
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,10 @@
11
#!/usr/bin/env node
2-
import '../lib/CLI.js';
2+
3+
import CLI from '../lib/CLI.js';
4+
5+
CLI
6+
.run(process.argv.slice(1))
7+
.catch(error => {
8+
console.error(error.toString());
9+
process.exit(1);
10+
});

lib/CLI.d.ts

+19-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,19 @@
1-
export {};
1+
import System from './System.js';
2+
export declare class CLI {
3+
constructor(argv: Array<string>, system: typeof System);
4+
readonly argv: Array<string>;
5+
readonly source: string;
6+
readonly system: typeof System;
7+
run(): Promise<void>;
8+
}
9+
export declare namespace CLI {
10+
interface Args extends Partial<Record<string, (boolean | string)>> {
11+
help?: boolean;
12+
source?: string;
13+
version?: boolean;
14+
}
15+
const VERSION: string;
16+
const HELP: string[];
17+
function run(argv: Array<string>): Promise<void>;
18+
}
19+
export default CLI;

lib/CLI.js

+143-11
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,144 @@
1-
import { hideBin } from 'yargs/helpers';
2-
import Yargs from 'yargs';
3-
Yargs(hideBin(process.argv))
4-
.command('* [testfolder]', false, function (yargs) {
5-
return yargs.positional('testfolder', {
6-
default: 'tests/',
7-
description: 'Path to the tests'
1+
/* *
2+
*
3+
* Imports
4+
*
5+
* */
6+
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
7+
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
8+
return new (P || (P = Promise))(function (resolve, reject) {
9+
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
10+
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
11+
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
12+
step((generator = generator.apply(thisArg, _arguments || [])).next());
813
});
9-
}, function (argv) {
10-
console.log('CLI', argv);
11-
})
12-
.parse();
14+
};
15+
import * as Path from 'path';
16+
import System from './System.js';
17+
import Tester from './Tester.js';
18+
import TSConfig from './TSConfig.js';
19+
/* *
20+
*
21+
* Class
22+
*
23+
* */
24+
export class CLI {
25+
/* *
26+
*
27+
* Constructor
28+
*
29+
* */
30+
constructor(argv, system) {
31+
this.argv = argv;
32+
this.source = argv[argv.length - 1] || './';
33+
this.system = system;
34+
}
35+
/* *
36+
*
37+
* Functions
38+
*
39+
* */
40+
run() {
41+
return __awaiter(this, void 0, void 0, function* () {
42+
const system = this.system;
43+
if (this.argv.includes('-h') ||
44+
this.argv.includes('--help')) {
45+
console.info(CLI.HELP.join(system.EOL));
46+
return;
47+
}
48+
if (this.argv.includes('-v') ||
49+
this.argv.includes('--version')) {
50+
console.info(CLI.VERSION);
51+
return;
52+
}
53+
let source = this.source;
54+
if (!source.endsWith('.json')) {
55+
source = Path.join(source, 'tsconfig.json');
56+
}
57+
if (!system.fileExits(source)) {
58+
throw new Error(`TSConfig not found. (${source})`);
59+
}
60+
const target = TSConfig.extractOutDir(source) || source;
61+
try {
62+
console.info(`Testing ${this.source}...`);
63+
const result = yield System.exec(`npx tsc -p ${source}`);
64+
console.info(result);
65+
const files = System.getFiles(target, /\.js$/);
66+
for (const file of files) {
67+
yield import(System.join(System.CWD, target, file));
68+
}
69+
const tester = Tester.default;
70+
yield tester.start();
71+
for (const success of tester.successes) {
72+
console.log('✅', success);
73+
}
74+
for (const error of tester.errors) {
75+
console.log('🛑', error[0]);
76+
console.error(error[1]);
77+
}
78+
}
79+
catch (error) {
80+
console.error(error);
81+
process.exit(1);
82+
}
83+
});
84+
}
85+
}
86+
/* *
87+
*
88+
* Class Namespace
89+
*
90+
* */
91+
(function (CLI) {
92+
/* *
93+
*
94+
* Declarations
95+
*
96+
* */
97+
/* *
98+
*
99+
* Constants
100+
*
101+
* */
102+
CLI.VERSION = `Version ${System.extractPackageVersion()}`;
103+
CLI.HELP = [
104+
`tst: The TypeScript Tester - ${CLI.VERSION}`,
105+
'',
106+
`tst [options] [source]`,
107+
'',
108+
'ARGUMENTS:',
109+
'',
110+
' [options] Optional flags explained in the section below.',
111+
'',
112+
' [source] Source folder with TypeScript tests.',
113+
'',
114+
'OPTIONS:',
115+
'',
116+
' --help, -h Prints this help text.',
117+
'',
118+
' --verbose Prints this help text.',
119+
'',
120+
' --version, -v Prints the version string.',
121+
'',
122+
'EXAMPLES:',
123+
'',
124+
' tst tests/',
125+
' Compiles, loads and runs assertion tests in the "tests" folder.',
126+
];
127+
/* *
128+
*
129+
* Functions
130+
*
131+
* */
132+
function run(argv) {
133+
return __awaiter(this, void 0, void 0, function* () {
134+
return new CLI(argv, System).run();
135+
});
136+
}
137+
CLI.run = run;
138+
})(CLI || (CLI = {}));
139+
/* *
140+
*
141+
* Default Export
142+
*
143+
* */
144+
export default CLI;

lib/System.d.ts

+21
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
declare function dirname(path: string): string;
2+
declare function exec(command: string): Promise<string>;
3+
declare function extractPackageVersion(packagePath?: string): string;
4+
declare function fileExits(filePath: string): boolean;
5+
declare function getFiles(folderPath: string, positivePattern?: RegExp, negativePattern?: RegExp): Array<string>;
6+
declare function join(...paths: Array<string>): string;
7+
declare function pathFromURL(fileURL: string): string;
8+
export declare const System: {
9+
CWD: string;
10+
EOL: string;
11+
PATH: string;
12+
VERSION: string;
13+
dirname: typeof dirname;
14+
exec: typeof exec;
15+
extractPackageVersion: typeof extractPackageVersion;
16+
fileExits: typeof fileExits;
17+
getFiles: typeof getFiles;
18+
join: typeof join;
19+
pathFromURL: typeof pathFromURL;
20+
};
21+
export default System;

lib/System.js

+94
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/* *
2+
*
3+
* Imports
4+
*
5+
* */
6+
import * as ChildProcess from 'child_process';
7+
import * as FS from 'fs';
8+
import * as OS from 'os';
9+
import * as Path from 'path';
10+
import * as URL from 'url';
11+
/* *
12+
*
13+
* Constants
14+
*
15+
* */
16+
const CWD = process.cwd();
17+
const EOL = OS.EOL;
18+
const PATH = join(dirname(pathFromURL(import.meta.url)), '..');
19+
const VERSION = extractPackageVersion();
20+
/* *
21+
*
22+
* Functions
23+
*
24+
* */
25+
function dirname(path) {
26+
return Path.dirname(path);
27+
}
28+
function exec(command) {
29+
return new Promise((resolve, reject) => {
30+
ChildProcess.exec(command, (error, stdout, stderr) => {
31+
if (error) {
32+
reject(stderr || stdout || error);
33+
}
34+
else {
35+
resolve(stdout);
36+
}
37+
});
38+
});
39+
}
40+
function extractPackageVersion(packagePath = Path.join(PATH, 'package.json')) {
41+
const packageJSON = JSON.parse(FS.readFileSync(packagePath).toString());
42+
return ((packageJSON === null || packageJSON === void 0 ? void 0 : packageJSON.version) || '0.0.0');
43+
}
44+
function fileExits(filePath) {
45+
return FS.lstatSync(filePath).isFile();
46+
}
47+
function getFiles(folderPath, positivePattern, negativePattern) {
48+
const items = FS.readdirSync(folderPath, { withFileTypes: true }).sort();
49+
const files = [];
50+
let name;
51+
for (const item of items) {
52+
name = item.name;
53+
if (item.isFile()) {
54+
if (positivePattern &&
55+
!positivePattern.test(name) ||
56+
negativePattern &&
57+
negativePattern.test(name)) {
58+
continue;
59+
}
60+
files.push(name);
61+
continue;
62+
}
63+
const subFiles = getFiles(name);
64+
for (const file of subFiles) {
65+
files.push(join(name, file));
66+
}
67+
}
68+
return files;
69+
}
70+
function join(...paths) {
71+
return Path.join(...paths);
72+
}
73+
function pathFromURL(fileURL) {
74+
return URL.fileURLToPath(fileURL);
75+
}
76+
/* *
77+
*
78+
* Default Export
79+
*
80+
* */
81+
export const System = {
82+
CWD,
83+
EOL,
84+
PATH,
85+
VERSION,
86+
dirname,
87+
exec,
88+
extractPackageVersion,
89+
fileExits,
90+
getFiles,
91+
join,
92+
pathFromURL,
93+
};
94+
export default System;

lib/TSConfig.d.ts

+5
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
declare function extractOutDir(tsConfigPath: string): string;
2+
export declare const TSConfig: {
3+
extractOutDir: typeof extractOutDir;
4+
};
5+
export default TSConfig;

lib/TSConfig.js

+41
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
/* *
2+
*
3+
* Imports
4+
*
5+
* */
6+
import * as FS from 'fs';
7+
import * as Path from 'path';
8+
/* *
9+
*
10+
* Constants
11+
*
12+
* */
13+
const EXTENDS_REGEXP = /^\s*"extends"\s*:\s*"([^"]*)"/mu;
14+
const OUT_DIR_REGEXP = /^\s*"outDir"\s*:\s*"([^"]*)"/mu;
15+
/* *
16+
*
17+
* Functions
18+
*
19+
* */
20+
function extractOutDir(tsConfigPath) {
21+
const relative = Path.dirname(tsConfigPath);
22+
const tsConfig = FS.readFileSync(tsConfigPath).toString();
23+
let match = tsConfig.match(OUT_DIR_REGEXP);
24+
if (match && match[1]) {
25+
return Path.join(relative, match[1]);
26+
}
27+
match = tsConfig.match(EXTENDS_REGEXP);
28+
if (match && match[1]) {
29+
return extractOutDir(Path.join(relative, match[1]));
30+
}
31+
return '';
32+
}
33+
/* *
34+
*
35+
* Default Export
36+
*
37+
* */
38+
export const TSConfig = {
39+
extractOutDir,
40+
};
41+
export default TSConfig;

lib/Tester.d.ts

+9-6
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,16 @@
11
/// <reference types="node" />
2-
import * as Assert from 'assert';
3-
export declare class Tester<T = unknown> {
4-
static readonly defaultSession: Tester<typeof Assert>;
2+
import { strict as Assert } from 'assert';
3+
export declare class Tester<T = any> {
4+
static readonly default: Tester<Tester.DefaultAssert>;
55
constructor(assert: T);
66
readonly assert: T;
7-
readonly errors: Array<[string, unknown]>;
7+
readonly errors: Array<[string, any]>;
88
readonly successes: Array<string>;
99
readonly tests: Array<[string, Function]>;
10-
start(verbose?: boolean): void;
11-
test(description: string, testCode: (assert: T) => unknown): void;
10+
start(): Promise<void>;
11+
test(description: string, testCode: (assert: T) => any): asserts testCode;
12+
}
13+
export declare namespace Tester {
14+
type DefaultAssert = typeof Assert;
1215
}
1316
export default Tester;

0 commit comments

Comments
 (0)