├── .gitignore ├── tsconfig.build.json ├── .eslintrc ├── src ├── index.ts ├── types.ts ├── getStackTrace.test.ts ├── serializeStackTrace.test.ts ├── serializeStackTrace.ts └── getStackTrace.ts ├── .releaserc ├── tsconfig.json ├── .github └── workflows │ ├── feature.yaml │ └── main.yaml ├── README.md ├── package.json └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | dist 2 | node_modules 3 | *.log 4 | .* 5 | !.eslintrc 6 | !.github 7 | !.gitignore 8 | !.releaserc -------------------------------------------------------------------------------- /tsconfig.build.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "noEmit": false, 4 | "outDir": "dist" 5 | }, 6 | "extends": "./tsconfig.json" 7 | } -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "canonical/auto", 3 | "ignorePatterns": [ 4 | "dist", 5 | "package-lock.json" 6 | ], 7 | "root": true 8 | } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export { getStackTrace } from './getStackTrace'; 2 | export { serializeStackTrace } from './serializeStackTrace'; 3 | export { StackFrame, StackTrace } from './types'; 4 | -------------------------------------------------------------------------------- /.releaserc: -------------------------------------------------------------------------------- 1 | { 2 | "branches": [ 3 | "main" 4 | ], 5 | "plugins": [ 6 | "@semantic-release/commit-analyzer", 7 | "@semantic-release/github", 8 | "@semantic-release/npm" 9 | ] 10 | } -------------------------------------------------------------------------------- /src/types.ts: -------------------------------------------------------------------------------- 1 | export type StackFrame = { 2 | arguments: readonly string[]; 3 | columnNumber: number | null; 4 | fileName: string | null; 5 | functionName: string | null; 6 | lineNumber: number | null; 7 | }; 8 | 9 | export type StackTrace = StackFrame[]; 10 | -------------------------------------------------------------------------------- /src/getStackTrace.test.ts: -------------------------------------------------------------------------------- 1 | import { getStackTrace } from './getStackTrace'; 2 | import { expect, test } from 'vitest'; 3 | 4 | test('gets stack trace', () => { 5 | const stackTrace = getStackTrace(); 6 | 7 | expect(stackTrace[0].fileName).toMatch(/getStackTrace/u); 8 | expect(stackTrace[0].lineNumber).toBe(5); 9 | expect(stackTrace[0].columnNumber).toBe(22); 10 | }); 11 | -------------------------------------------------------------------------------- /src/serializeStackTrace.test.ts: -------------------------------------------------------------------------------- 1 | import { getStackTrace } from './getStackTrace'; 2 | import { serializeStackTrace } from './serializeStackTrace'; 3 | import { expect, test } from 'vitest'; 4 | 5 | test('serializes stack traces', () => { 6 | const actual = serializeStackTrace('Error', 'bar', getStackTrace()); 7 | const expected = String(new Error('bar').stack); 8 | 9 | expect(actual.split('\n')[0]).toBe(expected.split('\n')[0]); 10 | }); 11 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowSyntheticDefaultImports": true, 4 | "declaration": true, 5 | "declarationMap": true, 6 | "esModuleInterop": true, 7 | "lib": [ 8 | "es2021" 9 | ], 10 | "module": "commonjs", 11 | "moduleResolution": "node", 12 | "noEmit": true, 13 | "noImplicitAny": false, 14 | "noImplicitReturns": true, 15 | "skipLibCheck": true, 16 | "sourceMap": true, 17 | "strict": true, 18 | "target": "es2018", 19 | "useUnknownInCatchVariables": false 20 | }, 21 | "include": [ 22 | "src" 23 | ] 24 | } -------------------------------------------------------------------------------- /.github/workflows/feature.yaml: -------------------------------------------------------------------------------- 1 | jobs: 2 | test: 3 | runs-on: ubuntu-latest 4 | environment: release 5 | name: Test 6 | steps: 7 | - name: setup repository 8 | uses: actions/checkout@v3 9 | with: 10 | fetch-depth: 0 11 | - name: setup node.js 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: "18" 15 | - run: npm ci 16 | - run: npm run lint 17 | - run: npm run test 18 | - run: npm run build 19 | timeout-minutes: 10 20 | name: Test and build 21 | on: 22 | pull_request: 23 | branches: 24 | - main 25 | types: 26 | - opened 27 | - synchronize 28 | - reopened 29 | - ready_for_review 30 | -------------------------------------------------------------------------------- /.github/workflows/main.yaml: -------------------------------------------------------------------------------- 1 | jobs: 2 | test: 3 | runs-on: ubuntu-latest 4 | environment: release 5 | name: Test 6 | steps: 7 | - name: setup repository 8 | uses: actions/checkout@v3 9 | with: 10 | fetch-depth: 0 11 | - name: setup node.js 12 | uses: actions/setup-node@v3 13 | with: 14 | node-version: "18" 15 | - run: npm ci 16 | - run: npm run lint 17 | - run: npm run test 18 | - run: npm run build 19 | - run: npx semantic-release 20 | env: 21 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 22 | NPM_TOKEN: ${{ secrets.NPM_TOKEN }} 23 | name: Test, build and release 24 | on: 25 | push: 26 | branches: 27 | - main 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # get-stack-trace 2 | 3 | [![NPM version](http://img.shields.io/npm/v/get-stack-trace.svg?style=flat-square)](https://www.npmjs.org/package/get-stack-trace) 4 | [![Canonical Code Style](https://img.shields.io/badge/code%20style-canonical-blue.svg?style=flat-square)](https://github.com/gajus/canonical) 5 | [![Twitter Follow](https://img.shields.io/twitter/follow/kuizinas.svg?style=social&label=Follow)](https://twitter.com/kuizinas) 6 | 7 | Stack traces as an array of stack frames with source maps support. 8 | 9 | ## Usage 10 | 11 | ```js 12 | import { 13 | getStackTrace, 14 | serializeStackTrace, 15 | } from 'get-stack-trace'; 16 | 17 | const stackTrace = getStackTrace(); 18 | 19 | serializeStackTrace('Error', 'Hello, World!', stackTrace); 20 | ``` 21 | -------------------------------------------------------------------------------- /src/serializeStackTrace.ts: -------------------------------------------------------------------------------- 1 | import { type StackTrace } from './types'; 2 | 3 | export const serializeStackTrace = ( 4 | constructorName: string, 5 | errorMessage: string, 6 | stackTrace: StackTrace, 7 | ) => { 8 | return ( 9 | constructorName + 10 | ': ' + 11 | errorMessage + 12 | '\n' + 13 | stackTrace 14 | .map((stackFrame) => { 15 | const { columnNumber, fileName, functionName, lineNumber } = stackFrame; 16 | 17 | if (!functionName) { 18 | return ' at ' + fileName + ':' + lineNumber + ':' + columnNumber; 19 | } 20 | 21 | // eslint-disable-next-line prettier/prettier 22 | return ' at ' + functionName + ' (' + fileName + ':' + lineNumber + ':' + columnNumber + ')'; 23 | }) 24 | .join('\n') 25 | ); 26 | }; 27 | -------------------------------------------------------------------------------- /src/getStackTrace.ts: -------------------------------------------------------------------------------- 1 | import { type StackTrace } from './types'; 2 | import { parse as parseStackTrace } from 'stacktrace-parser'; 3 | 4 | export const getStackTrace = (): StackTrace => { 5 | // The reason we are parsing the stack trace rather than 6 | // using captureStackTrace is because captureStackTrace 7 | // does not resolve source maps, i.e. the stack trace 8 | // will contain the compiled code references rather than 9 | // the original source code references. 10 | // 11 | // eslint-disable-next-line unicorn/error-message 12 | const stackTrace = new Error().stack; 13 | 14 | if (!stackTrace) { 15 | throw new Error('Could not get stack trace'); 16 | } 17 | 18 | return parseStackTrace(stackTrace) 19 | .map((stackFrame) => { 20 | return { 21 | arguments: stackFrame.arguments, 22 | columnNumber: stackFrame.column, 23 | fileName: stackFrame.file, 24 | functionName: stackFrame.methodName, 25 | lineNumber: stackFrame.lineNumber, 26 | }; 27 | }) 28 | .slice(1); 29 | }; 30 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "author": { 3 | "email": "gajus@gajus.com", 4 | "name": "Gajus Kuizinas", 5 | "url": "https://gajus.com" 6 | }, 7 | "dependencies": { 8 | "stacktrace-parser": "^0.1.10" 9 | }, 10 | "description": "Stack traces as an array of stack frames with source maps support.", 11 | "devDependencies": { 12 | "@semantic-release/commit-analyzer": "^11.0.0", 13 | "@semantic-release/github": "^9.1.0", 14 | "@semantic-release/npm": "^11.0.0", 15 | "eslint": "^8.50.0", 16 | "eslint-config-canonical": "^41.2.3", 17 | "vitest": "^0.34.5" 18 | }, 19 | "engines": { 20 | "node": ">=18.0" 21 | }, 22 | "files": [ 23 | "dist" 24 | ], 25 | "keywords": [ 26 | "stack trace" 27 | ], 28 | "main": "./dist/index.js", 29 | "name": "get-stack-trace", 30 | "repository": { 31 | "type": "git", 32 | "url": "git@github.com:gajus/get-stack-trace.git" 33 | }, 34 | "scripts": { 35 | "build": "rm -fr ./dist && tsc --project tsconfig.build.json", 36 | "lint": "eslint . && tsc --noEmit", 37 | "test": "vitest" 38 | }, 39 | "types": "./dist/index.d.ts", 40 | "version": "0.0.0" 41 | } 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2023, Gajus Kuizinas (https://gajus.com/) 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the Gajus Kuizinas (https://gajus.com/) nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL GAJUS KUIZINAS BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | --------------------------------------------------------------------------------