From df712bc0ecc8aea15d7ef18c055d4ad0d5fac96d Mon Sep 17 00:00:00 2001 From: TZ Date: Sat, 31 Mar 2018 22:02:11 +0800 Subject: [PATCH] feat: provides source map support for stack traces --- .eslintignore | 2 + .gitignore | 4 +- README.md | 2 + lib/cmd/start.js | 4 +- lib/command.js | 37 ++++++ package.json | 2 + test/fixtures/ts-pkg/app/controller/home.ts | 15 +++ test/fixtures/ts-pkg/app/router.js | 6 + test/fixtures/ts-pkg/config/config.default.js | 3 + test/fixtures/ts-pkg/package.json | 13 ++ test/fixtures/ts-pkg/tsconfig.json | 29 +++++ test/fixtures/ts/app/controller/home.ts | 15 +++ test/fixtures/ts/app/router.js | 6 + test/fixtures/ts/config/config.default.js | 3 + test/fixtures/ts/package.json | 10 ++ test/fixtures/ts/tsconfig.json | 29 +++++ test/ts.test.js | 114 ++++++++++++++++++ 17 files changed, 292 insertions(+), 2 deletions(-) create mode 100644 test/fixtures/ts-pkg/app/controller/home.ts create mode 100644 test/fixtures/ts-pkg/app/router.js create mode 100644 test/fixtures/ts-pkg/config/config.default.js create mode 100644 test/fixtures/ts-pkg/package.json create mode 100644 test/fixtures/ts-pkg/tsconfig.json create mode 100644 test/fixtures/ts/app/controller/home.ts create mode 100644 test/fixtures/ts/app/router.js create mode 100644 test/fixtures/ts/config/config.default.js create mode 100644 test/fixtures/ts/package.json create mode 100644 test/fixtures/ts/tsconfig.json create mode 100644 test/ts.test.js diff --git a/.eslintignore b/.eslintignore index 4ebc8ae..8cc505f 100644 --- a/.eslintignore +++ b/.eslintignore @@ -1 +1,3 @@ coverage +test/fixtures/ts/app/controller/home.js +test/fixtures/ts-pkg/app/controller/home.js \ No newline at end of file diff --git a/.gitignore b/.gitignore index 446bbe1..125fe6e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,8 +1,10 @@ logs/ npm-debug.log -/node_modules +node_modules coverage/ .idea/ run/ .DS_Store *.swp +test/fixtures/ts/app/controller/home.js +test/fixtures/ts-pkg/app/controller/home.js \ No newline at end of file diff --git a/README.md b/README.md index e2e9775..5bf8a17 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Add `eggctl` to `package.json` scripts: ``` Then run as: + - `npm start` - `npm stop` @@ -55,6 +56,7 @@ $ eggctl start [options] [baseDir] - `stderr` - customize stderr file, default to `$HOME/logs/master-stderr.log`. - `timeout` - the maximum timeout when app starts, default to 300s. - `ignore-stderr` - whether ignore stderr when app starts. + - `sourcemap` / `typescript` / `ts` - provides source map support for stack traces. ### stop diff --git a/lib/cmd/start.js b/lib/cmd/start.js index 6286043..5d562d6 100644 --- a/lib/cmd/start.js +++ b/lib/cmd/start.js @@ -133,7 +133,9 @@ class StartCommand extends Command { // remove unused properties from stringify, alias had been remove by `removeAlias` const ignoreKeys = [ '_', '$0', 'env', 'daemon', 'stdout', 'stderr', 'timeout', 'ignore-stderr' ]; - const eggArgs = [ this.serverBin, stringify(argv, ignoreKeys), `--title=${argv.title}` ]; + const clusterOptions = stringify(argv, ignoreKeys); + // Note: `spawn` is not like `fork`, had to pass `execArgv` youself + const eggArgs = [ ...(execArgv || []), this.serverBin, clusterOptions, `--title=${argv.title}` ]; this.logger.info('Run node %s', eggArgs.join(' ')); // whether run in the background. diff --git a/lib/command.js b/lib/command.js index 77f1a8e..5869e1d 100644 --- a/lib/command.js +++ b/lib/command.js @@ -1,5 +1,7 @@ 'use strict'; +const fs = require('fs'); +const path = require('path'); const BaseCommand = require('common-bin'); const Logger = require('zlogger'); const helper = require('./helper'); @@ -16,11 +18,46 @@ class Command extends BaseCommand { execArgv: true, }; + // common-bin setter, don't care about override at sub class + // https://github.com/node-modules/common-bin/blob/master/lib/command.js#L158 + this.options = { + sourcemap: { + description: 'whether enable sourcemap support, will load `source-map-support` etc', + type: 'boolean', + alias: [ 'ts', 'typescript' ], + }, + }; + this.logger = new Logger({ prefix: '[egg-scripts] ', time: false, }); } + + get context() { + const context = super.context; + const { argv, execArgvObj, cwd } = context; + + // read `egg.typescript` from package.json + const baseDir = argv._[0] || cwd; + const pkgFile = path.join(baseDir, 'package.json'); + if (fs.existsSync(pkgFile)) { + const pkgInfo = require(pkgFile); + if (pkgInfo && pkgInfo.egg && pkgInfo.egg.typescript) { + argv.sourcemap = true; + } + } + + // execArgv + if (argv.sourcemap) { + execArgvObj.require = execArgvObj.require || []; + execArgvObj.require.push(require.resolve('source-map-support/register')); + } + + argv.sourcemap = argv.typescript = argv.ts = undefined; + + return context; + } } module.exports = Command; diff --git a/package.json b/package.json index cdd963c..f888b83 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "mz-modules": "^2.0.0", "node-homedir": "^1.1.0", "runscript": "^1.3.0", + "source-map-support": "^0.5.4", "zlogger": "^1.1.0" }, "devDependencies": { @@ -26,6 +27,7 @@ "eslint": "^4.11.0", "eslint-config-egg": "^5.1.1", "mm": "^2.2.0", + "typescript": "^2.8.1", "urllib": "^2.25.1", "webstorm-disable-index": "^1.2.0" }, diff --git a/test/fixtures/ts-pkg/app/controller/home.ts b/test/fixtures/ts-pkg/app/controller/home.ts new file mode 100644 index 0000000..8e631d9 --- /dev/null +++ b/test/fixtures/ts-pkg/app/controller/home.ts @@ -0,0 +1,15 @@ +import { Controller } from 'egg'; + +export default class AppController extends Controller { + public async index() { + try { + throw new Error('some err'); + } catch (err) { + this.ctx.logger.error(err); + this.ctx.body = { + msg: err.message, + stack: err.stack, + }; + } + } +}; diff --git a/test/fixtures/ts-pkg/app/router.js b/test/fixtures/ts-pkg/app/router.js new file mode 100644 index 0000000..bece6e7 --- /dev/null +++ b/test/fixtures/ts-pkg/app/router.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = app => { + const { router, controller } = app; + router.get('/', controller.home.index); +}; diff --git a/test/fixtures/ts-pkg/config/config.default.js b/test/fixtures/ts-pkg/config/config.default.js new file mode 100644 index 0000000..c997e00 --- /dev/null +++ b/test/fixtures/ts-pkg/config/config.default.js @@ -0,0 +1,3 @@ +'use strict'; + +exports.keys = '123456'; diff --git a/test/fixtures/ts-pkg/package.json b/test/fixtures/ts-pkg/package.json new file mode 100644 index 0000000..818511f --- /dev/null +++ b/test/fixtures/ts-pkg/package.json @@ -0,0 +1,13 @@ +{ + "name": "ts-pkg", + "version": "1.0.0", + "dependencies": { + "egg": "^1.0.0" + }, + "egg": { + "typescript": true + }, + "scripts": { + "build": "node ../../../node_modules/.bin/tsc" + } +} diff --git a/test/fixtures/ts-pkg/tsconfig.json b/test/fixtures/ts-pkg/tsconfig.json new file mode 100644 index 0000000..f5bd4ba --- /dev/null +++ b/test/fixtures/ts-pkg/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "strict": true, + "noImplicitAny": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "charset": "utf8", + "allowJs": false, + "pretty": true, + "noEmitOnError": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "strictPropertyInitialization": false, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "inlineSourceMap": true, + "importHelpers": true + }, + "exclude": [ + "app/public", + "app/views" + ] +} \ No newline at end of file diff --git a/test/fixtures/ts/app/controller/home.ts b/test/fixtures/ts/app/controller/home.ts new file mode 100644 index 0000000..8e631d9 --- /dev/null +++ b/test/fixtures/ts/app/controller/home.ts @@ -0,0 +1,15 @@ +import { Controller } from 'egg'; + +export default class AppController extends Controller { + public async index() { + try { + throw new Error('some err'); + } catch (err) { + this.ctx.logger.error(err); + this.ctx.body = { + msg: err.message, + stack: err.stack, + }; + } + } +}; diff --git a/test/fixtures/ts/app/router.js b/test/fixtures/ts/app/router.js new file mode 100644 index 0000000..bece6e7 --- /dev/null +++ b/test/fixtures/ts/app/router.js @@ -0,0 +1,6 @@ +'use strict'; + +module.exports = app => { + const { router, controller } = app; + router.get('/', controller.home.index); +}; diff --git a/test/fixtures/ts/config/config.default.js b/test/fixtures/ts/config/config.default.js new file mode 100644 index 0000000..c997e00 --- /dev/null +++ b/test/fixtures/ts/config/config.default.js @@ -0,0 +1,3 @@ +'use strict'; + +exports.keys = '123456'; diff --git a/test/fixtures/ts/package.json b/test/fixtures/ts/package.json new file mode 100644 index 0000000..5c422dd --- /dev/null +++ b/test/fixtures/ts/package.json @@ -0,0 +1,10 @@ +{ + "name": "ts", + "version": "1.0.0", + "dependencies": { + "egg": "^1.0.0" + }, + "scripts": { + "build": "node ../../../node_modules/.bin/tsc" + } +} diff --git a/test/fixtures/ts/tsconfig.json b/test/fixtures/ts/tsconfig.json new file mode 100644 index 0000000..f5bd4ba --- /dev/null +++ b/test/fixtures/ts/tsconfig.json @@ -0,0 +1,29 @@ +{ + "compileOnSave": true, + "compilerOptions": { + "target": "es2017", + "module": "commonjs", + "strict": true, + "noImplicitAny": false, + "experimentalDecorators": true, + "emitDecoratorMetadata": true, + "charset": "utf8", + "allowJs": false, + "pretty": true, + "noEmitOnError": false, + "noUnusedLocals": true, + "noUnusedParameters": true, + "allowUnreachableCode": false, + "allowUnusedLabels": false, + "strictPropertyInitialization": false, + "noFallthroughCasesInSwitch": true, + "skipLibCheck": true, + "skipDefaultLibCheck": true, + "inlineSourceMap": true, + "importHelpers": true + }, + "exclude": [ + "app/public", + "app/views" + ] +} \ No newline at end of file diff --git a/test/ts.test.js b/test/ts.test.js new file mode 100644 index 0000000..79f87ae --- /dev/null +++ b/test/ts.test.js @@ -0,0 +1,114 @@ +'use strict'; + +const path = require('path'); +const assert = require('assert'); +const cp = require('child_process'); +const sleep = require('mz-modules/sleep'); +const rimraf = require('mz-modules/rimraf'); +const mkdirp = require('mz-modules/mkdirp'); +const coffee = require('coffee'); +const httpclient = require('urllib'); +const mm = require('mm'); +const utils = require('./utils'); + +describe('test/ts.test.js', () => { + const eggBin = require.resolve('../bin/egg-scripts.js'); + const homePath = path.join(__dirname, 'fixtures/home'); + const waitTime = '10s'; + let fixturePath; + + beforeEach(() => mm(process.env, 'MOCK_HOME_DIR', homePath)); + afterEach(mm.restore); + + before(() => mkdirp(homePath)); + after(() => rimraf(homePath)); + + describe('should display correct stack traces', () => { + let app; + beforeEach(function* () { + fixturePath = path.join(__dirname, 'fixtures/ts'); + yield utils.cleanup(fixturePath); + const result = cp.spawnSync('npm', [ 'run', 'build' ], { cwd: fixturePath }); + assert(!result.stderr.toString()); + }); + + afterEach(function* () { + app.proc.kill('SIGTERM'); + yield utils.cleanup(fixturePath); + }); + + it('--ts', function* () { + app = coffee.fork(eggBin, [ 'start', '--workers=1', '--ts', fixturePath ]); + // app.debug(); + app.expect('code', 0); + + yield sleep(waitTime); + + assert(app.stderr === ''); + assert(app.stdout.match(/egg started on http:\/\/127\.0\.0\.1:7001/)); + const result = yield httpclient.request('http://127.0.0.1:7001', { dataType: 'json' }); + // console.log(result.data); + assert(result.data.stack.includes('app/controller/home.ts:6:13')); + }); + + it('--typescript', function* () { + app = coffee.fork(eggBin, [ 'start', '--workers=1', '--typescript', fixturePath ]); + // app.debug(); + app.expect('code', 0); + + yield sleep(waitTime); + + assert(app.stderr === ''); + assert(app.stdout.match(/egg started on http:\/\/127\.0\.0\.1:7001/)); + const result = yield httpclient.request('http://127.0.0.1:7001', { dataType: 'json' }); + // console.log(result.data); + assert(result.data.stack.includes('app/controller/home.ts:6:13')); + }); + + it('--sourcemap', function* () { + app = coffee.fork(eggBin, [ 'start', '--workers=1', '--sourcemap', fixturePath ]); + // app.debug(); + app.expect('code', 0); + + yield sleep(waitTime); + + assert(app.stderr === ''); + assert(app.stdout.match(/egg started on http:\/\/127\.0\.0\.1:7001/)); + const result = yield httpclient.request('http://127.0.0.1:7001', { dataType: 'json' }); + // console.log(result.data); + assert(result.data.stack.includes('app/controller/home.ts:6:13')); + }); + }); + + describe('pkg.egg.typescript', () => { + let app; + beforeEach(function* () { + fixturePath = path.join(__dirname, 'fixtures/ts-pkg'); + yield utils.cleanup(fixturePath); + const result = cp.spawnSync('npm', [ 'run', 'build' ], { cwd: fixturePath }); + assert(!result.stderr.toString()); + }); + + afterEach(function* () { + app.proc.kill('SIGTERM'); + yield utils.cleanup(fixturePath); + }); + + it('should got correct stack', function* () { + app = coffee.fork(eggBin, [ 'start', '--workers=1', fixturePath ]); + app.debug(); + app.expect('code', 0); + + yield sleep(waitTime); + + assert(app.stderr === ''); + assert(app.stdout.match(/egg started on http:\/\/127\.0\.0\.1:7001/)); + const result = yield httpclient.request('http://127.0.0.1:7001', { dataType: 'json' }); + // console.log(result.data); + assert(result.data.stack.includes('app/controller/home.ts:6:13')); + }); + }); + + // TODO: add fixture for pkg egg.typescript +}); +