Skip to content

Commit

Permalink
feat: provides source map support for stack traces
Browse files Browse the repository at this point in the history
  • Loading branch information
atian25 committed Apr 1, 2018
1 parent 0016e28 commit fe1b61e
Show file tree
Hide file tree
Showing 17 changed files with 292 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .eslintignore
@@ -1 +1,3 @@
coverage
test/fixtures/ts/app/controller/home.js
test/fixtures/ts-pkg/app/controller/home.js
4 changes: 3 additions & 1 deletion .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
2 changes: 2 additions & 0 deletions README.md
Expand Up @@ -24,6 +24,7 @@ Add `eggctl` to `package.json` scripts:
```

Then run as:

- `npm start`
- `npm stop`

Expand Down Expand Up @@ -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

Expand Down
4 changes: 3 additions & 1 deletion lib/cmd/start.js
Expand Up @@ -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.
Expand Down
37 changes: 37 additions & 0 deletions 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');
Expand All @@ -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;
2 changes: 2 additions & 0 deletions package.json
Expand Up @@ -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": {
Expand All @@ -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"
},
Expand Down
15 changes: 15 additions & 0 deletions 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,
};
}
}
};
6 changes: 6 additions & 0 deletions 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);
};
3 changes: 3 additions & 0 deletions test/fixtures/ts-pkg/config/config.default.js
@@ -0,0 +1,3 @@
'use strict';

exports.keys = '123456';
13 changes: 13 additions & 0 deletions 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"
}
}
29 changes: 29 additions & 0 deletions 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"
]
}
15 changes: 15 additions & 0 deletions 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,
};
}
}
};
6 changes: 6 additions & 0 deletions test/fixtures/ts/app/router.js
@@ -0,0 +1,6 @@
'use strict';

module.exports = app => {
const { router, controller } = app;
router.get('/', controller.home.index);
};
3 changes: 3 additions & 0 deletions test/fixtures/ts/config/config.default.js
@@ -0,0 +1,3 @@
'use strict';

exports.keys = '123456';
10 changes: 10 additions & 0 deletions 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"
}
}
29 changes: 29 additions & 0 deletions 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"
]
}
114 changes: 114 additions & 0 deletions 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 = '5s';
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
});

0 comments on commit fe1b61e

Please sign in to comment.