diff --git a/.gitignore b/.gitignore index 784886a..a81ac10 100644 --- a/.gitignore +++ b/.gitignore @@ -2,4 +2,5 @@ node_modules jspm_packages bower_components .idea -.DS_STORE \ No newline at end of file +.DS_STORE +/yarn.lock diff --git a/package.json b/package.json index 66a4231..6a70f74 100644 --- a/package.json +++ b/package.json @@ -17,8 +17,19 @@ "type": "git", "url": "http://github.com/aurelia/tools" }, + "bin": { + "aurelia-tools": "src/cli.js" + }, "dependencies": { "breeze-dag": "^0.1.0", - "through2": "^2.0.0" + "rimraf": "^2.5.4", + "standard-version": "^4.0.0", + "through2": "^2.0.0", + "typedoc": "^0.5.1", + "yargs": "^6.3.0" + }, + "devDependencies": { + "@types/node": "^6.0.46", + "@types/yargs": "^6.3.1" } } diff --git a/src/cli-util.js b/src/cli-util.js new file mode 100644 index 0000000..d433db5 --- /dev/null +++ b/src/cli-util.js @@ -0,0 +1,27 @@ +module.exports = { + proxySpawned: function proxySpawned(spawned, outputPrefix, continueWhenFailed, onDone) { + if (outputPrefix) { + outputPrefix = '[' + outputPrefix + '] '; + } else { + outputPrefix = '' + } + spawned.stdout.on( 'data', data => { + const text = data.toString().trim(); + if (text) + console.log( outputPrefix + text ); + }); + + spawned.stderr.on( 'data', data => { + const text = data.toString().trim(); + if (text) + console.error( outputPrefix + text ); + }); + + spawned.on( 'close', code => { + if (onDone) onDone(code); + if (!continueWhenFailed && code > 0) { + process.exit(1); + } + }); + } +} diff --git a/src/cli.js b/src/cli.js new file mode 100644 index 0000000..6a56a63 --- /dev/null +++ b/src/cli.js @@ -0,0 +1,187 @@ +#!/usr/bin/env node +"use strict"; +process.title = 'aurelia-tools'; + +const path = require('path'); +const rimraf = require('rimraf').sync; +const spawn = require('child_process').spawn; +const proxySpawned = require('./cli-util').proxySpawned; +const projectDir = process.cwd(); +const docShape = require('./doc-shape'); +const docShapeDefs = require('./doc-shape-defs'); + +let tscPath; +try { + tscPath = require.resolve(path.join(projectDir, 'node_modules', 'typescript/bin/tsc')); +} catch (_) { + tscPath = require.resolve('typescript/bin/tsc'); +} + +const argv = require('yargs') + .command('ts-build-all', 'Build multiple versions of the project', { + project: { + alias: 'p', + describe: 'TypeScript project file or folder', + type: 'string', + default: 'tsconfig.build.json' + }, + outDir: { + alias: 'out', + describe: 'Output directory for compilations', + type: 'string', + default: 'dist' + }, + 'continue-when-failed': { + describe: 'Do not bail when one compilation fails', + type: 'boolean', + default: false + }, + 'clean-before': { + describe: 'Clean outdir before compiling', + type: 'boolean', + default: false + }, + 'variation': { + describe: 'Which variation to compile (or all if not defined)', + type: 'array' + } + }, + function(argv) { + if (argv.cleanBefore) { + rimraf(argv.outDir); + } + + let variations = [ + { module: "amd" }, + { module: "commonjs" }, + { module: "es2015", directory: "native-modules" }, + { module: "system" }, + { module: "es2015", target: "es2015" }, + { module: "es2015", target: "es2017" } + ]; + + if (argv.variation && argv.variation.length) { + variations = variations.filter(v => + (v.target && argv.variation.includes(v.target)) || + (v.directory && argv.variation.includes(v.directory)) || + argv.variation.includes(v.module) + ) + } + + variations.forEach(variation => { + const outDir = variation.directory || variation.target || variation.module; + const args = [ tscPath, '--project', argv.project, '--outDir', path.join(argv.outDir, outDir), '--module', variation.module ]; + if (variation.target) { + args.push('--target', variation.target); + } + console.log(`Running TypeScript compiler: ${args.join(' ')}`); + const tsc = spawn('node', args); + proxySpawned(tsc, outDir, argv.continueWhenFailed); + }); + }) + .command('doc-jsonshape', 'Shape docs', {}, function(argv) { + docShape.shape(argv._[1], argv._[2]); + }) + .command('doc-shape-defs', 'Shape doc defs', {}, function(argv) { + docShapeDefs.shapeDefs(argv._[1], argv._[2]); + }) + .command('doc-build', 'Creates a single .d.ts from TS project', { + project: { + alias: 'p', + describe: 'TypeScript project file or folder', + type: 'string', + default: 'tsconfig.build.json' + }, + outDir: { + alias: 'out', + describe: 'Output for compilation', + type: 'string', + default: 'dist/doc-temp' + }, + 'continue-when-failed': { + describe: 'Do not bail when one compilation fails', + type: 'boolean', + default: false + } + }, function(argv) { + const packageJsonPath = path.resolve(projectDir, 'package.json'); + try { + const packageName = require(packageJsonPath).name; + rimraf('dist/doc-temp/**'); + const tsc = spawn( 'node', [ './node_modules/typescript/bin/tsc', '--project', argv.project, '--outFile', path.join(argv.outDir, packageName + '.js') ] ); + proxySpawned(tsc, undefined, argv.continueWhenFailed); + } catch (e) { + console.error(e.message); + process.exit(1); + } + }) + .command('typedoc', 'Creates a typedoc file', { + inDir: { + alias: 'in', + describe: 'Input d.ts files directory', + type: 'string', + default: 'dist/doc-temp' + }, + outFile: { + alias: 'o', + describe: 'api.json output path', + type: 'string', + default: 'doc/api.json' + }, + cleanUpInDir: { + alias: 'clean', + describe: 'removes the outdir', + type: 'boolean', + default: true + }, + 'continue-when-failed': { + describe: 'Do not bail when one compilation fails', + type: 'boolean', + default: false + }, + project: { + alias: 'p', + describe: 'TypeScript project file', + type: 'string', + default: path.resolve(__dirname, '../tsc/tsconfig.json') + } + }, function(argv) { + const projectDir = process.cwd(); + const packageJsonPath = path.resolve(projectDir, 'package.json'); + try { + const packageName = require(packageJsonPath).name; + const typeDocPath = require.resolve('typedoc/bin/typedoc'); + rimraf('doc/api.json'); + + const spawn = require( 'child_process' ).spawn; + const typedoc = spawn( 'node', [ typeDocPath, '--json', argv.outFile, '--excludeExternals', '--includeDeclarations', '--mode', 'modules', '--target', 'ES6', '--name', packageName, '--ignoreCompilerErrors', '--tsconfig', argv.project, argv.inDir ] ); + proxySpawned(typedoc, undefined, argv.continueWhenFailed, function(code) { + if (code === 0) { + if (argv.cleanUpInDir) { + rimraf(argv.inDir); + } + } + }); + } catch (e) { + console.error(e.message); + process.exit(1); + } + }) + .command('changelog', 'Generate changelog from commits', { + 'first-release': { + alias: 'f', + describe: 'Is this the first release?', + type: 'boolean', + default: false + } + }, function(argv) { + const standardVersion = require('standard-version'); + standardVersion({ + infile: argv._[1] || path.resolve(process.cwd(), 'doc/CHANGELOG.md'), + message: 'chore(release): prepare release %s', + firstRelease: argv.firstRelease, + tagPrefix: "" + }, function (err) { + process.exit(1); + }); + }).argv; diff --git a/src/doc-shape-defs.js b/src/doc-shape-defs.js new file mode 100644 index 0000000..d58d516 --- /dev/null +++ b/src/doc-shape-defs.js @@ -0,0 +1,74 @@ +"use strict"; + +const path = require('path'); +const fs = require('fs'); + +module.exports = { + shapeDefs: function shapeDefs(directory, targetDir) { + if (!directory) { + directory = process.cwd(); + } + if (!targetDir) { + targetDir = 'dist/doc-temp'; + } + if (!path.isAbsolute(targetDir)) { + targetDir = path.resolve(directory, targetDir); + } + + const packageJsonPath = path.resolve(directory, 'package.json'); + let packageName; + try { + packageName = require(packageJsonPath).name; + } catch (e) { + console.error(`Unable to shape the find the package.json file.`); + console.error(e.message); + return process.exit(1); + } + try { + const dtsPath = path.join(targetDir, `${packageName}.d.ts`); + let defs = fs.readFileSync(dtsPath).toString(); + + // aggregate external imports + const packages = {}; + const importRegex = /^\s*import\s+\{([^}]+)\}\s*from\s*'([\w|-]+)'/gm; + let importMatch = importRegex.exec(defs); + while (importMatch) { + const packageName = importMatch[2]; + const imports = packages[packageName] || (packages[packageName] = []); + const bindings = importMatch[1].split(',').map(x => x.trim()); + for (let binding of bindings) { + if (imports.indexOf(binding) === -1) { + imports.push(binding); + } + } + importMatch = importRegex.exec(defs); + } + + // remove leading declare module + defs = defs.replace(/^declare module ".*" \{/, ''); + // remove "} declare module {" + defs = defs.replace(/\}\r?\ndeclare module ".*" \{/g, ''); + // remove closing "}" + defs = defs.replace(/\}\r?\n$/, ''); + // remove imports + defs = defs.replace(/^\s+import.*;$/gm, ''); + // remove "export *" + defs = defs.replace(/^\s+export \*.*;$/gm, ''); + + // write imports + for (let packageName in packages) { + if (packages.hasOwnProperty(packageName)) { + const imports = packages[packageName]; + defs = `import {${imports.sort()}} from '${packageName}';\n` + defs; + } + } + + fs.writeFileSync(dtsPath, defs); + console.log(`Shaped the ${packageName}.d.ts file.`); + } catch (e) { + console.error(`Unable to shape the ${packageName}.d.ts file.`); + console.error(e.message); + return process.exit(1); + } + } +} diff --git a/src/doc-shape.js b/src/doc-shape.js new file mode 100644 index 0000000..da04747 --- /dev/null +++ b/src/doc-shape.js @@ -0,0 +1,38 @@ +"use strict"; + +const path = require('path'); +const fs = require('fs'); + +module.exports = { + shape: function shape(apiJsonPath, directory) { + if (!directory) { + directory = process.cwd(); + } + if (!apiJsonPath) { + apiJsonPath = 'doc/api.json'; + } + if (!path.isAbsolute(apiJsonPath)) { + apiJsonPath = path.resolve(directory, apiJsonPath); + } + const packageJsonPath = path.resolve(directory, 'package.json'); + + try { + const packageName = require(packageJsonPath).name; + let json = require(apiJsonPath).children[0]; + + json = { + name: packageName, + children: json.children, + groups: json.groups + }; + + const str = JSON.stringify(json) + '\n'; + fs.writeFileSync(apiJsonPath, str); + console.log('Shaped the api.json file.'); + } catch (e) { + console.error('Unable to shape the api.json. The file probably has an incorrect format or doesn\'t exist.'); + console.error(e.message); + return process.exit(1); + } + } +} diff --git a/src/index.js b/src/index.js old mode 100644 new mode 100755 index 85336de..e7aea0d --- a/src/index.js +++ b/src/index.js @@ -1,6 +1,10 @@ -var doc = require('./doc'); -var dev = require('./dev'); -var build = require('./build'); +"use strict"; +const doc = require('./doc'); +const dev = require('./dev'); +const build = require('./build'); +const docShape = require('./doc-shape'); +const docShapeDefs = require('./doc-shape-defs'); + module.exports = { transformAPIModel:doc.transformAPIModel, @@ -10,5 +14,7 @@ module.exports = { extractImports:build.extractImports, createImportBlock:build.createImportBlock, sortFiles:build.sortFiles, - cleanGeneratedCode: build.cleanGeneratedCode + cleanGeneratedCode: build.cleanGeneratedCode, + docShapeDefs: docShapeDefs.shapeDefs, + docShape: docShape.shape }; diff --git a/tsc/tsconfig.json b/tsc/tsconfig.json new file mode 100644 index 0000000..f2d49b0 --- /dev/null +++ b/tsc/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compilerOptions": { + "module": "amd", + "moduleResolution": "node", + "target": "es5", + "lib": [ + "es2017", + "dom" + ], + "outDir": "dist/test", + "noImplicitAny": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noImplicitReturns": true, + "strictNullChecks": true, + "declaration": true, + "forceConsistentCasingInFileNames": true, + "experimentalDecorators": true, + "noEmitHelpers": false + }, + "exclude": [ + ".vscode", + "dist", + "doc", + "node_modules", + "build", + "gulpfile.js" + ] +}