Skip to content

Commit

Permalink
Additional release 1.6 Changes:
Browse files Browse the repository at this point in the history
- Support `--project` and `tsconfig.json` for specifying most configuration options.
- Change generate API to integrate `sendMessage` into configuration object
- Add support for `--verbose` logging and add additional logging
- Rename `excludes` to `exclude` to align with `tsc`
- Add more testing
- Refactor CLI to behave better
- Add support for `moduleResolution`
  • Loading branch information
kitsonk committed Nov 3, 2015
1 parent 2240235 commit c8e0bdd
Show file tree
Hide file tree
Showing 11 changed files with 258 additions and 90 deletions.
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
*.js
*.js.map
node_modules
tmp
!tasks/*.js
13 changes: 12 additions & 1 deletion bin/dts-generator
Original file line number Diff line number Diff line change
@@ -1,2 +1,13 @@
#!/usr/bin/env node
require('./dts-generator.js');
var dtsGenerator = require('./dts-generator.js');
/* istanbul ignore if: we use the module interface in testing */
if (!module.parent) {
dtsGenerator(process.argv.slice(2)).then(function (code) {
return process.exit(code || 0);
}, function (err) {
throw err;
});
}
else {
module.exports = dtsGenerator;
}
109 changes: 63 additions & 46 deletions bin/dts-generator.ts
Original file line number Diff line number Diff line change
@@ -1,59 +1,76 @@
import dts = require('../index');

const kwArgs: {
[key: string]: any;
excludes?: string[];
externs?: string[];
files: string[];
} = { files: [] };

for (let i = 2, j = process.argv.length; i < j; ++i) {
const arg = process.argv[i];

if (arg.charAt(0) === '-') {
const key = process.argv[i].replace(/^-+/, '');
const value = process.argv[i + 1];
++i;

if (key === 'exclude') {
if (!kwArgs.excludes) {
kwArgs.excludes = [];
}
import generate from '../index';

kwArgs.excludes.push(value);
}
else if (key === 'extern') {
if (!kwArgs.externs) {
kwArgs.externs = [];
export = function main(argv: string[]): Promise<number | void> {
const kwArgs: {
[key: string]: any;
baseDir?: string;
exclude?: string[];
externs?: string[];
files: string[];
project?: string;
sendMessage?: (message: any, ...optionalParams: any[]) => void;
verbose?: boolean;
} = {
files: [],
sendMessage: console.log.bind(console)
};

for (let i = 0; i < argv.length; ++i) {
const arg = argv[i];

if (arg.charAt(0) === '-') {
const key = argv[i].replace(/^-+/, '');
const value = argv[i + 1];
++i;

if (key === 'exclude') {
if (!kwArgs.exclude) {
kwArgs.exclude = [];
}

kwArgs.exclude.push(value);
}
else if (key === 'extern') {
if (!kwArgs.externs) {
kwArgs.externs = [];
}

kwArgs.externs.push(value);
kwArgs.externs.push(value);
}
else if (key === 'verbose') {
kwArgs.verbose = true;
/* decrement counter, because vebose does not take a value */
--i;
}
else {
kwArgs[key] = value;
}
}
else {
kwArgs[key] = value;
kwArgs.files.push(argv[i]);
}
}
else {
kwArgs.files.push(process.argv[i]);

[ 'name', 'out' ].forEach(function (key) {
if (!kwArgs[key]) {
console.error(`Missing required argument "${key}"`);
process.exit(1);
}
});

if (!kwArgs.baseDir && !kwArgs.project) {
console.error(`Missing required argument of "baseDir" or "project"`);
process.exit(1);
}
}

[ 'baseDir', 'name', 'out' ].forEach(function (key) {
if (!kwArgs[key]) {
console.error('Missing required argument "' + key + '"');
if (!kwArgs.project && kwArgs.files.length === 0) {
console.error('Missing files');
process.exit(1);
}
});

if (kwArgs.files.length === 0) {
console.error('Missing files');
process.exit(1);
}
console.log('Starting');

console.log('Starting');
dts.generate(<any> kwArgs, console.log.bind(console)).then(function () {
console.log('Done!');
}, function (error: Error) {
console.error(error);
process.exit(1);
});
return generate(<any> kwArgs).then(function () {
console.log('Done!');
});
}
75 changes: 66 additions & 9 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,18 +12,22 @@ interface StringLiteralTypeNode extends ts.TypeNode {
}

interface Options {
baseDir: string;
files: string[];
excludes?: string[];
baseDir?: string;
project?: string;
files?: string[];
exclude?: string[];
externs?: string[];
eol?: string;
includes?: string[];
indent?: string;
main?: string;
moduleResolution?: ts.ModuleResolutionKind;
name: string;
out: string;
outDir?: string;
target?: ts.ScriptTarget;
sendMessage?: (message: any, ...optionalParams: any[]) => void;
verbose?: boolean;
}

const filenameToMid: (filename: string) => string = (function () {
Expand Down Expand Up @@ -100,36 +104,87 @@ function processTree(sourceFile: ts.SourceFile, replacer: (node: ts.Node) => str
return code;
}

export function generate(options: Options, sendMessage: (message: string) => void = function () {}) {
const baseDir = pathUtil.resolve(options.baseDir);
function getTSConfig(options: Options, fileName: string): Options {
const configText = fs.readFileSync(fileName, { encoding: 'utf8' });
const result = ts.parseConfigFileText(fileName, configText);
const configObject = result.config;
const configParseResult = ts.parseConfigFile(configObject, ts.sys, pathUtil.dirname(fileName));
options.target = configParseResult.options.target;
if (configParseResult.options.outDir) {
options.outDir = configParseResult.options.outDir;
}
if (configParseResult.options.moduleResolution) {
options.moduleResolution = configParseResult.options.moduleResolution;
}
options.files = configParseResult.fileNames;
return;
}

export default function generate(options: Options): Promise<void> {

const noop = function (message?: any, ...optionalParams: any[]): void {};
const sendMessage = options.sendMessage || noop;
const verboseMessage = options.verbose ? sendMessage : noop;

/* following tsc behaviour, if a project is speicified, or if no files are specified then
* attempt to load tsconfig.json */
if (options.project || !options.files || options.files.length === 0) {
verboseMessage(`project = "${options.project || options.baseDir}"`);
const tsconfigFilename = pathUtil.join(options.project || options.baseDir, 'tsconfig.json');
if (fs.existsSync(tsconfigFilename)) {
verboseMessage(` parsing "${tsconfigFilename}"`);
getTSConfig(options, tsconfigFilename);
}
else {
sendMessage(`No "tsconfig.json" found at "${tsconfigFilename}"!`);
return new Promise<void>(function (resolve, reject) {
reject(new SyntaxError('Unable to resolve configuration.'));
});
}
}

const baseDir = pathUtil.resolve(options.project || options.baseDir);
verboseMessage(`baseDir = "${baseDir}"`);
const eol = options.eol || os.EOL;
const nonEmptyLineStart = new RegExp(eol + '(?!' + eol + '|$)', 'g');
const indent = options.indent === undefined ? '\t' : options.indent;
const target = options.target || ts.ScriptTarget.Latest;
verboseMessage(`taget = ${target}`);
const compilerOptions: ts.CompilerOptions = {
declaration: true,
module: ts.ModuleKind.CommonJS,
target: target
};
if (options.outDir) {
verboseMessage(`outDir = ${options.outDir}`);
compilerOptions.outDir = options.outDir;
}
if (options.moduleResolution) {
verboseMessage(`moduleResolution = ${options.moduleResolution}`);
compilerOptions.moduleResolution = options.moduleResolution;
}

const filenames = getFilenames(baseDir, options.files);
verboseMessage('filenames:');
filenames.forEach(name => { verboseMessage(' ' + name); });
const excludesMap: { [filename: string]: boolean; } = {};

options.excludes = options.excludes || [ 'node_modules/**/*.d.ts' ];
options.exclude = options.exclude || [ 'node_modules/**/*.d.ts' ];

options.excludes && options.excludes.forEach(function (filename) {
options.exclude && options.exclude.forEach(function (filename) {
glob.sync(filename).forEach(function(globFileName) {
excludesMap[filenameToMid(pathUtil.resolve(baseDir, globFileName))] = true;
});
});
if (options.exclude) {
verboseMessage('exclude:');
options.exclude.forEach(name => { verboseMessage(' ' + name); });
}

mkdirp.sync(pathUtil.dirname(options.out));
/* node.js typings are missing the optional mode in createWriteStream options and therefore
* in TS 1.6 the strict object literal checking is throwing, therefore a hammer to the nut */
const output = (<any> fs).createWriteStream(options.out, { mode: parseInt('644', 8) });
const output = fs.createWriteStream(options.out, <any> { mode: parseInt('644', 8) });

const host = ts.createCompilerHost(compilerOptions);
const program = ts.createProgram(filenames, compilerOptions, host);
Expand All @@ -154,6 +209,7 @@ export function generate(options: Options, sendMessage: (message: string) => voi
});
}

sendMessage('processing:');
program.getSourceFiles().some(function (sourceFile) {
// Source file is a default library, or other dependency from another project, that should not be included in
// our bundled output
Expand All @@ -165,7 +221,7 @@ export function generate(options: Options, sendMessage: (message: string) => voi
return;
}

sendMessage(`Processing ${sourceFile.fileName}`);
sendMessage(` ${sourceFile.fileName}`);

// Source file is already a declaration file so should does not need to be pre-processed by the emitter
if (sourceFile.fileName.slice(-5) === '.d.ts') {
Expand Down Expand Up @@ -194,6 +250,7 @@ export function generate(options: Options, sendMessage: (message: string) => voi
sendMessage(`Aliased main module ${options.name} to ${options.main}`);
}

sendMessage(`output to "${options.out}"`);
output.end();
});

Expand Down
6 changes: 5 additions & 1 deletion tests/run.sh
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
#!/usr/bin/env bash
set -e
cd "$(dirname $0)/.."
echo "Building modules..."
node_modules/.bin/tsc --module umd --target es5 node_modules/intern/typings/intern/intern.d.ts typings/tsd.d.ts tests/typings/dts-generator/dts-generator.d.ts tests/intern.ts tests/unit/all.ts
node_modules/.bin/tsc --module commonjs --target es5 typings/tsd.d.ts index.ts bin/dts-generator.ts
node_modules/.bin/tsc --module commonjs --target es5 --sourcemap typings/tsd.d.ts index.ts bin/dts-generator.ts
echo "Running intern..."
node_modules/.bin/intern-client config=tests/intern reporters=Console
echo "Cleanup..."
rm -rf tmp
16 changes: 16 additions & 0 deletions tests/support/foo/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"version": "1.6.2",
"compilerOptions": {
"target": "es5",
"module": "umd",
"declaration": false,
"noImplicitAny": true
},
"filesGlob": [
"./*.ts"
],
"files": [
"./index.ts",
"./Bar.ts"
]
}
10 changes: 10 additions & 0 deletions tests/typings/dts-generator/dts-generator.d.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
/// <reference path="../../../node_modules/intern/typings/intern/intern.d.ts" />

declare module 'intern/dojo/node!../../index' {
let dtsGenerator: any;
export default dtsGenerator;
}

declare module 'intern/dojo/node!../../../bin/dts-generator' {
let dtsGenerator: any;
export = dtsGenerator;
}

declare module 'intern/dojo/node!fs' {
import * as fs from 'fs';
export = fs;
}
1 change: 1 addition & 0 deletions tests/unit/all.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
import './bin/dts-generator';
import './index';
21 changes: 21 additions & 0 deletions tests/unit/bin/dts-generator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import * as registerSuite from 'intern!object';
import * as assert from 'intern/chai!assert';
import * as dtsGenerator from 'intern/dojo/node!../../../bin/dts-generator';

registerSuite({
name: 'bin/dts-generator',
api: function () {
assert.isFunction(dtsGenerator, 'dtsGenerator should be a function');
assert.strictEqual(Object.keys(dtsGenerator).length, 0, 'There should be no other keys');
},
basic: function () {
return dtsGenerator([
'-name',
'foo',
'-project',
'tests/support/foo',
'-out',
'tmp/foo.cli.d.ts'
]);
}
});
Loading

0 comments on commit c8e0bdd

Please sign in to comment.