diff --git a/.gitignore b/.gitignore
index 6704566..9e6c6a6 100644
--- a/.gitignore
+++ b/.gitignore
@@ -102,3 +102,108 @@ dist
# TernJS port file
.tern-port
+
+### WebStorm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### WebStorm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+# https://plugins.jetbrains.com/plugin/7973-sonarlint
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+# https://plugins.jetbrains.com/plugin/7238-sonarqube-community-plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+# https://plugins.jetbrains.com/plugin/7896-markdown-navigator-enhanced
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator-enh.xml
+.idea/**/markdown-navigator/
+
+# Cache file creation bug
+# See https://youtrack.jetbrains.com/issue/JBR-2257
+.idea/$CACHE_FILE$
+
+# CodeStream plugin
+# https://plugins.jetbrains.com/plugin/12206-codestream
+.idea/codestream.xml
+
+# End of https://www.toptal.com/developers/gitignore/api/webstorm
diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..5bcbb08
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/stacktracify.iml b/.idea/stacktracify.iml
new file mode 100644
index 0000000..24643cc
--- /dev/null
+++ b/.idea/stacktracify.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..94a25f7
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/README.md b/README.md
index 84e00d7..dcfffef 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,21 @@
# stacktracify
+**NOTICE:** This is a modified version of the excellent `stacktracify` CLI-tool, created by: [mifi/stacktracify](https://github.com/mifi/stacktracify)
+
+This modified version allows for passing in a path to a folder of source maps (which was previously limited to a single file).
+
+In addition to this, support for the following parameters have also been added:
+
+| Parameter name | Abbreviation | Description |
+|----------------|--------------|-----------------------------------------------------------------------------------------------------------|
+| `--legend` | `-l` | Prints a legend, indicating when unable to not find a source map, or resolve line from a found source map |
+| `--debug` | `-d` | Prints debug information, useful for determining lookup-logic for relative paths etc. |
+
+**WARNING:** This version has not been made available to be installed on [npm](https://www.npmjs.com/), and hence must be installed
+by cloning this repository, running `yarn install` and linking the index.js file as an executable script (or invoke directly)!
+
+## Original documentation
+
Have you ever been faced with a stacktrace that looks like this?
```
diff --git a/index.js b/index.js
index 1bf6720..1f31f0e 100644
--- a/index.js
+++ b/index.js
@@ -3,9 +3,114 @@
const meow = require('meow');
const stackTraceParser = require('stacktrace-parser');
const fs = require('fs-extra');
+const {basename, join, resolve} = require('path');
+const {lstat} = require('fs').promises;
const clipboardy = require('clipboardy');
const { SourceMapConsumer } = require('source-map');
+const WARNINGS = {
+ noPosition: '❓',
+ noSmc: '🌍'
+};
+
+function formatStackFrame(frame, decoration = null) {
+ const { file, methodName, lineNumber, column } = frame;
+ const parts = [];
+ if (decoration) {
+ parts.push(`[${decoration}] `);
+ }
+ parts.push('at ');
+ if (methodName) {
+ parts.push(methodName);
+ }
+ if (file) {
+ parts.push(' (');
+ parts.push(file);
+ if (lineNumber && column) {
+ parts.push(':');
+ parts.push(column);
+ parts.push(':');
+ parts.push(lineNumber);
+ }
+ parts.push(')');
+ }
+
+ return parts.join('');
+}
+
+class SourceMapRegistry {
+ // Map of "basename" -> "fullpath"
+ sourceMapFiles = new Map();
+ // Map of "basename" -> "source map consumer for source map file"
+ sourceMaps = new Map();
+
+ async getSourceMapConsumer(path) {
+ const key = basename(path) + '.map';
+ const fullPath = this.sourceMapFiles.get(key);
+
+ let smc = this.sourceMaps.get(key);
+ if (!smc && fullPath) {
+ // Acquire smc
+ const mapContent = JSON.parse(await fs.readFile(fullPath, 'utf-8'));
+ smc = await new SourceMapConsumer(mapContent);
+ this.sourceMaps.set(key, smc);
+ }
+ return smc;
+ }
+
+ async initialize(path) {
+ this.sourceMapFiles = new Map();
+ this.sourceMaps = new Map();
+
+ const stat = await fs.lstat(path);
+ if (stat.isFile()) {
+ this.sourceMapFiles.set(basename(path), path);
+ } else {
+ const found = await this.findFiles(path);
+ found.forEach(each => this.sourceMapFiles.set(basename(each), each));
+ }
+
+ if (debug) {
+ console.log('[DEBUG] Found the following files:');
+ for (var [key, value] of this.sourceMapFiles.entries()) {
+ console.log(`- ${value}`);
+ }
+ console.log('');
+ }
+ }
+
+ async findFiles(folder) {
+ const results = []
+
+ // Get all files from the folder
+ let items = await fs.readdir(folder);
+
+ // Loop through the results, possibly recurse
+ for (const item of items) {
+ try {
+ const fullPath = join(folder, item)
+
+ if (
+ fs.statSync(fullPath).isDirectory()) {
+
+ // Its a folder, recursively get the child folders' files
+ results.push(
+ ...(await this.findFiles(fullPath))
+ )
+ } else {
+ // Filter by the file name pattern, if there is one
+ if (item.search(new RegExp('.*\.js\.map', 'i')) > -1) {
+ results.push(resolve(fullPath))
+ }
+ }
+ } catch (error) {
+ // Ignore!
+ }
+ }
+
+ return results
+ }
+}
const cli = meow(`
Usage
@@ -13,58 +118,92 @@ const cli = meow(`
Options
--file, -f (default is read from clipboard)
+ --debug, -d (defaults to false)
+ --legend, -l (displays legend for parsing hints, eg ${Object.keys(WARNINGS).join(', ')} - disabled as default)
Examples
- $ stacktracify /path/to/js.map --file /path/to/my-stacktrace.txt
+ $ stacktracify /path/to/source-maps --file /path/to/my-stacktrace.txt --debug --legend
`, {
flags: {
file: {
type: 'string',
alias: 'f',
},
+ debug: {
+ type: 'boolean',
+ alias: 'd',
+ },
+ legend: {
+ type: 'boolean',
+ alias: 'l',
+ },
},
});
-const { file } = cli.flags;
+var { file, debug, legend } = cli.flags;
(async () => {
try {
+ // Determine path of source maps
const mapPath = cli.input[0];
if (!mapPath) cli.showHelp();
- const mapContent = JSON.parse(await fs.readFile(mapPath, 'utf-8'));
- // WTF? promise?
- const smc = await new SourceMapConsumer(mapContent);
+ // Create registry
+ const registry = new SourceMapRegistry();
+ await registry.initialize(mapPath);
+
+ // Acquire stacktrace
let str;
if (file !== undefined) {
str = await fs.readFile(file, 'utf-8');
} else {
str = await clipboardy.read();
}
+
+ // Parse stacktrace
const stack = stackTraceParser.parse(str);
if (stack.length === 0) throw new Error('No stack found');
+ // Print "header" (usually message of what went wrong, eg. message of Error)
const header = str.split('\n').find(line => line.trim().length > 0);
-
- if (header) console.log(header);
-
- stack.forEach(({ methodName, lineNumber, column }) => {
+ if (header && !header.includes(stack[0].file)) {
+ console.log(header);
+ }
+
+ // Translate stacktrace
+ const warnings = [];
+ for (const each of stack) {
+ const { file, methodName, lineNumber, column } = each;
try {
if (lineNumber == null || lineNumber < 1) {
console.log(` at ${methodName || ''}`);
} else {
- const pos = smc.originalPositionFor({ line: lineNumber, column });
- if (pos && pos.line != null) {
- console.log(` at ${pos.name || ''} (${pos.source}:${pos.line}:${pos.column})`);
+ const smc = await registry.getSourceMapConsumer(file);
+ if (smc && typeof smc.originalPositionFor === 'function') {
+ const pos = smc && smc.originalPositionFor({ line: lineNumber, column }) || undefined;
+ if (pos && pos.line != null) {
+ console.log(` at ${pos.name || ''} (${pos.source}:${pos.line}:${pos.column})`);
+ } else {
+ console.log(` ${formatStackFrame(each, legend && WARNINGS.noPosition)}`);
+ warnings.push(WARNINGS.noPosition);
+ }
+ } else {
+ console.log(` ${formatStackFrame(each, legend && WARNINGS.noSmc)}`);
+ warnings.push(WARNINGS.noSmc);
}
-
- // console.log('src', smc.sourceContentFor(pos.source));
+
}
} catch (err) {
- console.log(` at FAILED_TO_PARSE_LINE`);
+ console.log(` at FAILED_TO_PARSE_LINE`, err);
}
- });
+ }
+
+ if (warnings.length > 0) {
+ console.log('\nLegend:\n-------');
+ console.log(`[${WARNINGS.noPosition}] -> Indicates that a the particular stack frame could not be located in the source map`);
+ console.log(`[${WARNINGS.noSmc}] -> Indicates that a source map could not be located for the stack frame`);
+ }
} catch (err) {
console.error(err);
}