├── .eslintrc.json ├── .github └── workflows │ └── ci.yml ├── .gitignore ├── LICENSE ├── action.js ├── action.yml ├── dist ├── action │ ├── dot2js.g │ ├── index.js │ ├── index.js.map │ ├── service_table.tmpl.md │ ├── sourcemap-register.js │ └── summary.tmpl.md └── main.js ├── examples ├── example-screenshot.png └── workflows │ ├── after_deploy.yml │ ├── pagerduty.yml │ ├── snapshot.yml │ └── snapshot_to_issue.yml ├── main.js ├── package-lock.json ├── package.json ├── readme.md ├── snapshot ├── api.js ├── diff.js └── markdown.js └── template ├── service_table.tmpl.md └── summary.tmpl.md /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "jest": true, 6 | "node": true 7 | }, 8 | "ignorePatterns": ["dist/*.js"], 9 | "extends": "eslint:recommended", 10 | "globals": { 11 | "Atomics": "readonly", 12 | "SharedArrayBuffer": "readonly" 13 | }, 14 | "parserOptions": { 15 | "ecmaVersion": 2018 16 | }, 17 | "rules": { 18 | "array-bracket-spacing": [0], 19 | "consistent-return": [0], 20 | "func-names": [0], 21 | "guard-for-in": [0], 22 | "indent": [2, 4], 23 | "semi": ["error", "never"], 24 | "key-spacing": [2, { "align": "colon", "beforeColon": true, "afterColon": true }], 25 | "max-len": [2, 120, 4], 26 | "new-cap": [0], 27 | "no-trailing-spaces": "error", 28 | "no-continue": [0], 29 | "no-param-reassign": [0], 30 | "no-multi-spaces": [2, { "exceptions": { 31 | "AssignmentExpression": true, 32 | "VariableDeclarator": true 33 | }} ], 34 | "no-underscore-dangle": [0], 35 | "no-unused-vars": [2, { "vars": "all", "args" : "none" }], 36 | "no-use-before-define": [2, { "functions": false }], 37 | "no-console": [1], 38 | "object-shorthand" : [0], 39 | "prefer-const": [0], 40 | "prefer-rest-params" : [0], 41 | "spaced-comment": [0] 42 | } 43 | } -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: 'Continuous Integration' 2 | 3 | on: 4 | pull_request: 5 | 6 | jobs: 7 | test: 8 | name: Test 9 | runs-on: ubuntu-latest 10 | 11 | steps: 12 | - name: Checkout 13 | uses: actions/checkout@v2 14 | 15 | - name: Setup Node.js 12.x 16 | uses: actions/setup-node@v1 17 | with: 18 | node-version: '12.x' 19 | 20 | - name: Install 21 | run: npm clean-install 22 | 23 | - name: Verify 24 | run: | 25 | npm run build 26 | # Fail if "npm run build" generated new changes in dist 27 | git update-index --refresh dist/action/* && git diff-index --quiet HEAD dist/action -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | 3 | .DS_Store 4 | 5 | .lightstep.yml 6 | 7 | # Optional npm cache directory 8 | .npm 9 | 10 | # Optional eslint cache 11 | .eslintcache 12 | 13 | # Editors 14 | .vscode 15 | 16 | # Logs 17 | logs 18 | *.log 19 | npm-debug.log* 20 | yarn-debug.log* 21 | yarn-error.log* 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright [yyyy] [name of copyright owner] 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | -------------------------------------------------------------------------------- /action.js: -------------------------------------------------------------------------------- 1 | const core = require('@actions/core') 2 | const github = require('@actions/github') 3 | const { snapshotDiff } = require('./snapshot/diff') 4 | const { takeSnapshot } = require('./snapshot/api') 5 | const { LightstepConfig } = require('@lightstep/lightstep-api-js').action 6 | 7 | /** 8 | * Resolves input as an enviornment variable or action input 9 | * @param {*} name input name 10 | */ 11 | const resolveActionInput = (name, config = {}) => { 12 | if (typeof name !== 'string') { 13 | return null 14 | } 15 | const configName = name.replace('lightstep_', '') 16 | return process.env[name.toUpperCase()] || core.getInput(name) || config[configName] 17 | } 18 | 19 | /** 20 | * Fails action if input does not exist 21 | * @param {*} name input name 22 | */ 23 | const assertActionInput = (name, config) => { 24 | if (!resolveActionInput(name, config)) { 25 | core.setFailed( 26 | `Input ${name} must be set as an env var, passed as an action input`) 27 | } 28 | } 29 | 30 | module.exports.initAction = async () => { 31 | core.info('Starting action...') 32 | } 33 | 34 | module.exports.runAction = async () => { 35 | try { 36 | // Read `.lightstep.yml` file, if exists 37 | const config = new LightstepConfig(process.env.GITHUB_WORKSPACE || process.cwd()) 38 | 39 | assertActionInput('lightstep_api_key') 40 | 41 | const apiKey = resolveActionInput('lightstep_api_key') 42 | const lightstepOrg = resolveActionInput('lightstep_organization') || config.lightstepOrg() 43 | const lightstepProj = resolveActionInput('lightstep_project') || config.lightstepProject() 44 | 45 | // For creating a snapshot 46 | var lightstepQuery = resolveActionInput('lightstep_snapshot_query') 47 | 48 | // For displaying the diff between two snapshots 49 | var snapshotCompareId = resolveActionInput('lightstep_snapshot_compare_id') 50 | const snapshotId = resolveActionInput('lightstep_snapshot_id') 51 | const serviceFilter = resolveActionInput('lightstep_service_filter') 52 | const disableComment = resolveActionInput('disable_comment') 53 | 54 | core.info(`Using lightstep project: ${lightstepProj}...`) 55 | core.info(`Using lightstep organization: ${lightstepOrg}...`) 56 | core.setOutput('lightstep_project', lightstepProj) 57 | core.setOutput('lightstep_organization', lightstepProj) 58 | 59 | // Take a new snapshot 60 | if (lightstepQuery) { 61 | assertActionInput('lightstep_snapshot_query') 62 | // The API doesn't support annotations... 63 | // so we add a special no-op to the query ;) 64 | lightstepQuery = 65 | `${lightstepQuery} AND "ignore.github.repo" NOT IN ("${process.env.GITHUB_REPOSITORY}")` 66 | core.info(`Creating snapshot for project ${lightstepProj}...`) 67 | const newSnapshotId = await takeSnapshot({ apiKey, lightstepOrg, lightstepProj, lightstepQuery }) 68 | core.info(`Took snapshot ${newSnapshotId}...`) 69 | core.setOutput('lightstep_snapshot_id', newSnapshotId) 70 | return 71 | } 72 | 73 | if (!snapshotId) { 74 | core.setFailed('no input found: please specify a query to take a snapshot or snapshot id(s) to summarize') 75 | return 76 | } 77 | 78 | // Summarize existing snapshot(s) 79 | var snapshotAfterId 80 | var snapshotBeforeId 81 | 82 | if (snapshotId && snapshotCompareId) { 83 | snapshotAfterId = snapshotId 84 | snapshotBeforeId = snapshotCompareId 85 | core.info(`Analyzing difference between snapshots ${snapshotBeforeId} and ${snapshotAfterId}...`) 86 | const { markdown, graph, hasViolations } = await snapshotDiff( 87 | { apiKey, lightstepOrg, lightstepProj, snapshotBeforeId, snapshotAfterId, serviceFilter, config }) 88 | core.setOutput('lightstep_snapshot_md', markdown) 89 | core.setOutput('lightstep_snapshot_dotviz', graph) 90 | if (hasViolations) { 91 | core.setOutput('lightstep_snapshot_has_violations', hasViolations) 92 | } 93 | } else if (snapshotId) { 94 | snapshotAfterId = snapshotId 95 | snapshotBeforeId = snapshotId 96 | core.info(`Analyzing snapshot ${snapshotBeforeId}...`) 97 | } 98 | 99 | const { markdown, graph, hasViolations } = await snapshotDiff( 100 | { apiKey, lightstepOrg, lightstepProj, snapshotBeforeId, snapshotAfterId, serviceFilter, config }) 101 | core.setOutput('lightstep_snapshot_md', markdown) 102 | core.setOutput('lightstep_snapshot_dotviz', graph) 103 | if (hasViolations) { 104 | core.setOutput('lightstep_snapshot_has_violations', hasViolations) 105 | } 106 | 107 | // add pull request or issue comment 108 | if (disableComment !== 'true') { 109 | const token = resolveActionInput('github_token') 110 | var octokit 111 | try { 112 | octokit = github.getOctokit(token) 113 | } catch (e) { 114 | core.setFailed(`could not initialize github api client: ${e}`) 115 | return 116 | } 117 | 118 | const context = github.context 119 | if (context.issue && context.issue.number) { 120 | await octokit.issues.createComment({ 121 | issue_number : context.issue.number, 122 | owner : context.repo.owner, 123 | repo : context.repo.repo, 124 | body : markdown, 125 | }) 126 | } else if (context.sha) { 127 | core.info(`attempting to find pr: ${context.repo.owner}/${context.repo.repo}@${context.sha}...`) 128 | const pulls = await octokit.repos.listPullRequestsAssociatedWithCommit({ 129 | owner : context.repo.owner, 130 | repo : context.repo.repo, 131 | commit_sha : context.sha, 132 | }) 133 | if (pulls.data.length === 0) { 134 | core.info('could not find a pull request associated with the git sha') 135 | return 136 | } 137 | const num = pulls.data[0].number 138 | core.info(`commenting on pr #{num}...`) 139 | await octokit.issues.createComment({ 140 | issue_number : num, 141 | owner : context.repo.owner, 142 | repo : context.repo.repo, 143 | body : markdown 144 | }) 145 | } else { 146 | core.info('could not find a SHA or issue number') 147 | } 148 | } 149 | } catch (error) { 150 | // eslint-disable-next-line no-console 151 | console.log(error) 152 | core.setFailed(error.message) 153 | } 154 | } 155 | 156 | -------------------------------------------------------------------------------- /action.yml: -------------------------------------------------------------------------------- 1 | name: 'Lightstep Services Change Report' 2 | author: 'Lightstep, Inc.' 3 | description: 'Capture and analyze detailed behavior of your services using Lightstep' 4 | 5 | branding: 6 | icon: 'camera' 7 | color: 'green' 8 | 9 | inputs: 10 | lightstep_organization: 11 | description: 'The organization associated with your Lightstep account (usually your company name)' 12 | required: false 13 | 14 | lightstep_project: 15 | description: 'The Lightstep project associated with this repository' 16 | required: false 17 | 18 | lightstep_service_filter: 19 | description: 'Only show services in the snapshot from this comma-separated list' 20 | required: false 21 | 22 | lightstep_snapshot_query: 23 | description: 'The query to use when taking a snapshot' 24 | required: false 25 | 26 | lightstep_snapshot_id: 27 | description: 'The Lightstep snapshot id to summarize' 28 | required: false 29 | 30 | lightstep_snapshot_compare_id: 31 | description: 'The Lightstep snapshot id to compare with lightstep_snapshot_id' 32 | required: false 33 | 34 | lightstep_api_key: 35 | description: 'The key to access the Lightstep Public API' 36 | required: false 37 | 38 | github_token: 39 | description: 'Github API Token' 40 | required: false 41 | 42 | disable_comment: 43 | description: 'If set to true, will not add a comment to pull-requests' 44 | required: false 45 | 46 | outputs: 47 | lightstep_project: 48 | description: 'Lightstep Project' 49 | 50 | lightstep_organization: 51 | description: 'Lightstep Organization' 52 | 53 | lightstep_snapshot_id: 54 | description: 'Lightstep Snapshot ID' 55 | 56 | lightstep_snapshot_md: 57 | description: 'Lightstep Snapshot Markdown Summary' 58 | 59 | lightstep_snapshot_dotviz: 60 | description: 'Lightstep Snapshot dotviz output of service relationships' 61 | 62 | lightstep_snapshot_has_violations: 63 | description: 'Set if user-configured violations are found in the snapshot' 64 | 65 | runs: 66 | using: 'node12' 67 | main: 'dist/main.js' 68 | -------------------------------------------------------------------------------- /dist/action/dot2js.g: -------------------------------------------------------------------------------- 1 | // Copyright (c) 2010 Gregoire Lejeune 2 | // 3 | // This program is free software; you can redistribute it and/or modify 4 | // it under the terms of the GNU General Public License as published by 5 | // the Free Software Foundation; either version 2 of the License, or 6 | // (at your option) any later version. 7 | // 8 | // This program is distributed in the hope that it will be useful, 9 | // but WITHOUT ANY WARRANTY; without even the implied warranty of 10 | // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 11 | // GNU General Public License for more details. 12 | // 13 | // You should have received a copy of the GNU General Public License 14 | // along with this program; if not, write to the Free Software 15 | // Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 16 | // 17 | // Usage : 18 | // gvpr -f dot2js.g [-a ] 19 | 20 | BEGIN { 21 | int g_strict; int g_direct; 22 | graph_t cluster; 23 | node_t cnode; 24 | edge_t cedge; 25 | string attr; string attrv; 26 | graph_t subgraph; graph_t pgraph; 27 | graph_t ofgraph; 28 | 29 | string xOut; 30 | if( ARGC == 0 ) { 31 | xOut = "_"; 32 | } else { 33 | xOut = tolower(ARGV[0]); 34 | } 35 | 36 | printf( "// This code was generated by dot2js.g\n\n" ); 37 | 38 | string rubyfy( string s ) { 39 | string out; 40 | out = tolower( s ); 41 | out = gsub( out, " ", "__" ); 42 | out = gsub( out, "'", "" ); 43 | out = gsub( out, "-", "_" ); 44 | out = gsub( out, ".", "" ); 45 | out = gsub( out, "%", "u" ); 46 | out = gsub( out, "+", "" ); 47 | out = gsub( out, "/", "_" ); 48 | return( out ); 49 | } 50 | } 51 | 52 | BEG_G { 53 | printf( "var util = require('util'),\n graphviz = require('graphviz');\n\n"); 54 | // Directed 55 | g_direct = isDirect($); 56 | if( g_direct == 0 ) { 57 | printf( "var graph_%s = graphviz.graph( \"%s\" );\n", rubyfy($.name), rubyfy($.name) ); 58 | } else { 59 | printf( "var graph_%s = graphviz.digraph( \"%s\" );\n", rubyfy($.name), rubyfy($.name) ); 60 | } 61 | // Strict 62 | g_strict = isStrict($); 63 | if( g_strict != 0 ) { 64 | // printf( ", :strict => true" ); ///////////////////////// TODO 65 | } 66 | 67 | // Attributs of G 68 | attr = fstAttr($, "G"); 69 | while( attr != "" ) { 70 | attrv = aget( $, attr ); 71 | if( attrv != "" ) { 72 | printf( "graph_%s.set( \"%s\", \"%s\" );\n", rubyfy($.name), attr, attrv ); 73 | } 74 | attr = nxtAttr( $, "G", attr ); 75 | } 76 | 77 | // Subgraph 78 | subgraph = fstsubg( $ ); 79 | while( subgraph != NULL ) { 80 | pgraph = subgraph.parent; 81 | printf ( "var graph_%s = graph_%s.addCluster( \"%s\" )\n", rubyfy(subgraph.name), rubyfy(pgraph.name), rubyfy(subgraph.name) ); 82 | 83 | // ATTRS 84 | attr = fstAttr(subgraph, "G"); 85 | while( attr != "" ) { 86 | attrv = aget( subgraph, attr ); 87 | if( attrv != "" ) { 88 | printf( "graph_%s.set( \"%s\", \"%s\" );\n", rubyfy(subgraph.name), attr, attrv ); 89 | } 90 | attr = nxtAttr( subgraph, "G", attr ); 91 | } 92 | 93 | subgraph = nxtsubg( subgraph ); 94 | } 95 | } 96 | 97 | N { 98 | pgraph = $.root; 99 | ofgraph = pgraph; 100 | 101 | subgraph = fstsubg( pgraph ); 102 | while( subgraph != NULL ) { 103 | if( isSubnode( subgraph, $ ) != 0 ) { 104 | ofgraph = subgraph; 105 | } 106 | subgraph = nxtsubg( subgraph ); 107 | } 108 | 109 | printf( "var node_%s = graph_%s.addNode( \"%s\", {", rubyfy($.name), rubyfy(ofgraph.name), $.name ); 110 | 111 | // Attributs of N 112 | attr = fstAttr($G, "N"); 113 | while( attr != "" ) { 114 | attrv = aget( $, attr ); 115 | if( attrv != "" ) { 116 | printf( "\"%s\" : \"%s\", ", attr, gsub( attrv, "'", "\\'" ) ); 117 | // } else { 118 | // printf( "\"%s\" : \"\", ", attr ); 119 | } 120 | attr = nxtAttr( $G, "N", attr ); 121 | } 122 | 123 | printf( "} );\n" ); 124 | } 125 | 126 | E { 127 | pgraph = $.root; 128 | ofgraph = pgraph; 129 | 130 | subgraph = fstsubg( pgraph ); 131 | while( subgraph != NULL ) { 132 | if( isSubedge( subgraph, $ ) != 0 ) { 133 | ofgraph = subgraph; 134 | } 135 | subgraph = nxtsubg( subgraph ); 136 | } 137 | 138 | printf( "graph_%s.addEdge( \"%s\", \"%s\", {", rubyfy(ofgraph.name), $.tail.name, $.head.name ); 139 | 140 | // Attributs of E 141 | attr = fstAttr($G, "E"); 142 | while( attr != "" ) { 143 | attrv = aget( $, attr ); 144 | if( attrv != "" ) { 145 | printf( "\"%s\" : \"%s\", ", attr, gsub( attrv, "'", "\\'" ) ); 146 | // } else { 147 | // printf( "\"%s\" : \"\", ", attr ); 148 | } 149 | attr = nxtAttr( $G, "E", attr ); 150 | } 151 | 152 | printf( "} );\n" ); 153 | } 154 | 155 | END_G { 156 | printf( "__graph_eval = graph_%s;\n", rubyfy($.name) ); 157 | } 158 | -------------------------------------------------------------------------------- /dist/action/service_table.tmpl.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% if (integrations.pagerduty) {%><%}%> 5 | 6 | <%for (var s of inputTable) {%> 7 | 8 | 9 | 10 | 11 | <% if (integrations.pagerduty) {%><%}%> 12 | 13 | 14 | 15 | 64 | <%}%> 65 |
ServiceAverage LatencyError %PagerDuty Logo On-Call
<% if (s.new_service) { %>:new: <%}%><%=s.service%><%= helpers.latencyFormatter(s) %><%=helpers.percentFormatter(s)%><%= helpers.parseOnCall(s.pagerduty) %>
 <% if (s.violations && s.violations.length > 0) { %> 16 |

17 | :warning: Violations 18 |
    <% for (v of s.violations) { %> 19 |
  • <%=helpers.snapshotFilterLink(v.msg, lightstepProj, snapshotAfterId, s.service, v.violation.key)%>
  • <% } %> 20 |
21 |

<% } %><% if (s.snapshot.dependencies) { %> 22 |

23 | :arrow_down_small: Downstream Dependencies 24 |
    <% for (serviceDep in s.snapshot.dependencies) { %> 25 |
  • <% if (serviceDep.new_connection) { %>:new: <%}%><%=serviceDep%>
  • <% } %> 26 |
27 |

28 | <% } %>

29 | :gear: Service Operations 30 |
    <% for (op in s.snapshot.operations) { %> 31 |
  • <%= helpers.snapshotOperationLink(lightstepProj, snapshotAfterId, s.service, op) %>
  • <% } %> 32 |
33 |

34 |

35 | 🕵️‍♀️ What caused that change? 36 |
    37 |
  • 38 |

    Snapshot<%=snapshotBeforeId%>

    39 |
      40 |
    • <%=helpers.snapshotFilterLink(':octocat: View traces by GitHub SHA', lightstepProj, snapshotBeforeId, s.service, 'github.sha')%>
    • 41 | <% if (integrations.rollbar) {%>
    • <%=helpers.snapshotFilterLink(`Rollbar Logo View traces by Rollbar error`, lightstepProj, snapshotBeforeId, s.service, 'rollbar.error_uuid')%>
    • <% } %> 42 | 44 |
    45 |
  • <% if (snapshotCompare) {%> 46 |
  • Snapshot<%=snapshotAfterId%>

    47 |
      48 |
    • <%=helpers.snapshotFilterLink(':octocat: View traces by GitHub SHA', lightstepProj, snapshotAfterId, s.service, 'github.sha')%>
    • 49 | <% if (integrations.rollbar) {%>
    • <%=helpers.snapshotFilterLink(`Rollbar Logo View traces by Rollbar error`, lightstepProj, snapshotBeforeId, s.service, 'rollbar.error_uuid')%>
    • <% } %> 50 | 52 |
    53 |
  • <% } %> 54 |
55 |
56 |

<% if (helpers.parseOnCall(s.pagerduty) === ':question:') { %> 57 |

58 | 🗒️ Recommendations 59 |
    60 |
  • :pager: No on-call information found. Add a PagerDuty service to .lightstep.yml to see on-call information for this service.
  • 61 |
62 |

<% } %> 63 |
66 | -------------------------------------------------------------------------------- /dist/action/sourcemap-register.js: -------------------------------------------------------------------------------- 1 | module.exports = 2 | /******/ (function(modules, runtime) { // webpackBootstrap 3 | /******/ "use strict"; 4 | /******/ // The module cache 5 | /******/ var installedModules = {}; 6 | /******/ 7 | /******/ // The require function 8 | /******/ function __webpack_require__(moduleId) { 9 | /******/ 10 | /******/ // Check if module is in cache 11 | /******/ if(installedModules[moduleId]) { 12 | /******/ return installedModules[moduleId].exports; 13 | /******/ } 14 | /******/ // Create a new module (and put it into the cache) 15 | /******/ var module = installedModules[moduleId] = { 16 | /******/ i: moduleId, 17 | /******/ l: false, 18 | /******/ exports: {} 19 | /******/ }; 20 | /******/ 21 | /******/ // Execute the module function 22 | /******/ var threw = true; 23 | /******/ try { 24 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 25 | /******/ threw = false; 26 | /******/ } finally { 27 | /******/ if(threw) delete installedModules[moduleId]; 28 | /******/ } 29 | /******/ 30 | /******/ // Flag the module as loaded 31 | /******/ module.l = true; 32 | /******/ 33 | /******/ // Return the exports of the module 34 | /******/ return module.exports; 35 | /******/ } 36 | /******/ 37 | /******/ 38 | /******/ __webpack_require__.ab = __dirname + "/"; 39 | /******/ 40 | /******/ // the startup function 41 | /******/ function startup() { 42 | /******/ // Load entry module and return exports 43 | /******/ return __webpack_require__(645); 44 | /******/ }; 45 | /******/ 46 | /******/ // run startup 47 | /******/ return startup(); 48 | /******/ }) 49 | /************************************************************************/ 50 | /******/ ({ 51 | 52 | /***/ 164: 53 | /***/ (function(__unusedmodule, exports) { 54 | 55 | /* -*- Mode: js; js-indent-level: 2; -*- */ 56 | /* 57 | * Copyright 2011 Mozilla Foundation and contributors 58 | * Licensed under the New BSD license. See LICENSE or: 59 | * http://opensource.org/licenses/BSD-3-Clause 60 | */ 61 | 62 | exports.GREATEST_LOWER_BOUND = 1; 63 | exports.LEAST_UPPER_BOUND = 2; 64 | 65 | /** 66 | * Recursive implementation of binary search. 67 | * 68 | * @param aLow Indices here and lower do not contain the needle. 69 | * @param aHigh Indices here and higher do not contain the needle. 70 | * @param aNeedle The element being searched for. 71 | * @param aHaystack The non-empty array being searched. 72 | * @param aCompare Function which takes two elements and returns -1, 0, or 1. 73 | * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or 74 | * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the 75 | * closest element that is smaller than or greater than the one we are 76 | * searching for, respectively, if the exact element cannot be found. 77 | */ 78 | function recursiveSearch(aLow, aHigh, aNeedle, aHaystack, aCompare, aBias) { 79 | // This function terminates when one of the following is true: 80 | // 81 | // 1. We find the exact element we are looking for. 82 | // 83 | // 2. We did not find the exact element, but we can return the index of 84 | // the next-closest element. 85 | // 86 | // 3. We did not find the exact element, and there is no next-closest 87 | // element than the one we are searching for, so we return -1. 88 | var mid = Math.floor((aHigh - aLow) / 2) + aLow; 89 | var cmp = aCompare(aNeedle, aHaystack[mid], true); 90 | if (cmp === 0) { 91 | // Found the element we are looking for. 92 | return mid; 93 | } 94 | else if (cmp > 0) { 95 | // Our needle is greater than aHaystack[mid]. 96 | if (aHigh - mid > 1) { 97 | // The element is in the upper half. 98 | return recursiveSearch(mid, aHigh, aNeedle, aHaystack, aCompare, aBias); 99 | } 100 | 101 | // The exact needle element was not found in this haystack. Determine if 102 | // we are in termination case (3) or (2) and return the appropriate thing. 103 | if (aBias == exports.LEAST_UPPER_BOUND) { 104 | return aHigh < aHaystack.length ? aHigh : -1; 105 | } else { 106 | return mid; 107 | } 108 | } 109 | else { 110 | // Our needle is less than aHaystack[mid]. 111 | if (mid - aLow > 1) { 112 | // The element is in the lower half. 113 | return recursiveSearch(aLow, mid, aNeedle, aHaystack, aCompare, aBias); 114 | } 115 | 116 | // we are in termination case (3) or (2) and return the appropriate thing. 117 | if (aBias == exports.LEAST_UPPER_BOUND) { 118 | return mid; 119 | } else { 120 | return aLow < 0 ? -1 : aLow; 121 | } 122 | } 123 | } 124 | 125 | /** 126 | * This is an implementation of binary search which will always try and return 127 | * the index of the closest element if there is no exact hit. This is because 128 | * mappings between original and generated line/col pairs are single points, 129 | * and there is an implicit region between each of them, so a miss just means 130 | * that you aren't on the very start of a region. 131 | * 132 | * @param aNeedle The element you are looking for. 133 | * @param aHaystack The array that is being searched. 134 | * @param aCompare A function which takes the needle and an element in the 135 | * array and returns -1, 0, or 1 depending on whether the needle is less 136 | * than, equal to, or greater than the element, respectively. 137 | * @param aBias Either 'binarySearch.GREATEST_LOWER_BOUND' or 138 | * 'binarySearch.LEAST_UPPER_BOUND'. Specifies whether to return the 139 | * closest element that is smaller than or greater than the one we are 140 | * searching for, respectively, if the exact element cannot be found. 141 | * Defaults to 'binarySearch.GREATEST_LOWER_BOUND'. 142 | */ 143 | exports.search = function search(aNeedle, aHaystack, aCompare, aBias) { 144 | if (aHaystack.length === 0) { 145 | return -1; 146 | } 147 | 148 | var index = recursiveSearch(-1, aHaystack.length, aNeedle, aHaystack, 149 | aCompare, aBias || exports.GREATEST_LOWER_BOUND); 150 | if (index < 0) { 151 | return -1; 152 | } 153 | 154 | // We have found either the exact element, or the next-closest element than 155 | // the one we are searching for. However, there may be more than one such 156 | // element. Make sure we always return the smallest of these. 157 | while (index - 1 >= 0) { 158 | if (aCompare(aHaystack[index], aHaystack[index - 1], true) !== 0) { 159 | break; 160 | } 161 | --index; 162 | } 163 | 164 | return index; 165 | }; 166 | 167 | 168 | /***/ }), 169 | 170 | /***/ 215: 171 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 172 | 173 | /* -*- Mode: js; js-indent-level: 2; -*- */ 174 | /* 175 | * Copyright 2011 Mozilla Foundation and contributors 176 | * Licensed under the New BSD license. See LICENSE or: 177 | * http://opensource.org/licenses/BSD-3-Clause 178 | * 179 | * Based on the Base 64 VLQ implementation in Closure Compiler: 180 | * https://code.google.com/p/closure-compiler/source/browse/trunk/src/com/google/debugging/sourcemap/Base64VLQ.java 181 | * 182 | * Copyright 2011 The Closure Compiler Authors. All rights reserved. 183 | * Redistribution and use in source and binary forms, with or without 184 | * modification, are permitted provided that the following conditions are 185 | * met: 186 | * 187 | * * Redistributions of source code must retain the above copyright 188 | * notice, this list of conditions and the following disclaimer. 189 | * * Redistributions in binary form must reproduce the above 190 | * copyright notice, this list of conditions and the following 191 | * disclaimer in the documentation and/or other materials provided 192 | * with the distribution. 193 | * * Neither the name of Google Inc. nor the names of its 194 | * contributors may be used to endorse or promote products derived 195 | * from this software without specific prior written permission. 196 | * 197 | * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 198 | * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 199 | * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 200 | * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 201 | * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 202 | * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 203 | * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 204 | * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 205 | * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 206 | * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 207 | * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 208 | */ 209 | 210 | var base64 = __webpack_require__(537); 211 | 212 | // A single base 64 digit can contain 6 bits of data. For the base 64 variable 213 | // length quantities we use in the source map spec, the first bit is the sign, 214 | // the next four bits are the actual value, and the 6th bit is the 215 | // continuation bit. The continuation bit tells us whether there are more 216 | // digits in this value following this digit. 217 | // 218 | // Continuation 219 | // | Sign 220 | // | | 221 | // V V 222 | // 101011 223 | 224 | var VLQ_BASE_SHIFT = 5; 225 | 226 | // binary: 100000 227 | var VLQ_BASE = 1 << VLQ_BASE_SHIFT; 228 | 229 | // binary: 011111 230 | var VLQ_BASE_MASK = VLQ_BASE - 1; 231 | 232 | // binary: 100000 233 | var VLQ_CONTINUATION_BIT = VLQ_BASE; 234 | 235 | /** 236 | * Converts from a two-complement value to a value where the sign bit is 237 | * placed in the least significant bit. For example, as decimals: 238 | * 1 becomes 2 (10 binary), -1 becomes 3 (11 binary) 239 | * 2 becomes 4 (100 binary), -2 becomes 5 (101 binary) 240 | */ 241 | function toVLQSigned(aValue) { 242 | return aValue < 0 243 | ? ((-aValue) << 1) + 1 244 | : (aValue << 1) + 0; 245 | } 246 | 247 | /** 248 | * Converts to a two-complement value from a value where the sign bit is 249 | * placed in the least significant bit. For example, as decimals: 250 | * 2 (10 binary) becomes 1, 3 (11 binary) becomes -1 251 | * 4 (100 binary) becomes 2, 5 (101 binary) becomes -2 252 | */ 253 | function fromVLQSigned(aValue) { 254 | var isNegative = (aValue & 1) === 1; 255 | var shifted = aValue >> 1; 256 | return isNegative 257 | ? -shifted 258 | : shifted; 259 | } 260 | 261 | /** 262 | * Returns the base 64 VLQ encoded value. 263 | */ 264 | exports.encode = function base64VLQ_encode(aValue) { 265 | var encoded = ""; 266 | var digit; 267 | 268 | var vlq = toVLQSigned(aValue); 269 | 270 | do { 271 | digit = vlq & VLQ_BASE_MASK; 272 | vlq >>>= VLQ_BASE_SHIFT; 273 | if (vlq > 0) { 274 | // There are still more digits in this value, so we must make sure the 275 | // continuation bit is marked. 276 | digit |= VLQ_CONTINUATION_BIT; 277 | } 278 | encoded += base64.encode(digit); 279 | } while (vlq > 0); 280 | 281 | return encoded; 282 | }; 283 | 284 | /** 285 | * Decodes the next base 64 VLQ value from the given string and returns the 286 | * value and the rest of the string via the out parameter. 287 | */ 288 | exports.decode = function base64VLQ_decode(aStr, aIndex, aOutParam) { 289 | var strLen = aStr.length; 290 | var result = 0; 291 | var shift = 0; 292 | var continuation, digit; 293 | 294 | do { 295 | if (aIndex >= strLen) { 296 | throw new Error("Expected more digits in base 64 VLQ value."); 297 | } 298 | 299 | digit = base64.decode(aStr.charCodeAt(aIndex++)); 300 | if (digit === -1) { 301 | throw new Error("Invalid base64 digit: " + aStr.charAt(aIndex - 1)); 302 | } 303 | 304 | continuation = !!(digit & VLQ_CONTINUATION_BIT); 305 | digit &= VLQ_BASE_MASK; 306 | result = result + (digit << shift); 307 | shift += VLQ_BASE_SHIFT; 308 | } while (continuation); 309 | 310 | aOutParam.value = fromVLQSigned(result); 311 | aOutParam.rest = aIndex; 312 | }; 313 | 314 | 315 | /***/ }), 316 | 317 | /***/ 226: 318 | /***/ (function(__unusedmodule, exports) { 319 | 320 | /* -*- Mode: js; js-indent-level: 2; -*- */ 321 | /* 322 | * Copyright 2011 Mozilla Foundation and contributors 323 | * Licensed under the New BSD license. See LICENSE or: 324 | * http://opensource.org/licenses/BSD-3-Clause 325 | */ 326 | 327 | // It turns out that some (most?) JavaScript engines don't self-host 328 | // `Array.prototype.sort`. This makes sense because C++ will likely remain 329 | // faster than JS when doing raw CPU-intensive sorting. However, when using a 330 | // custom comparator function, calling back and forth between the VM's C++ and 331 | // JIT'd JS is rather slow *and* loses JIT type information, resulting in 332 | // worse generated code for the comparator function than would be optimal. In 333 | // fact, when sorting with a comparator, these costs outweigh the benefits of 334 | // sorting in C++. By using our own JS-implemented Quick Sort (below), we get 335 | // a ~3500ms mean speed-up in `bench/bench.html`. 336 | 337 | /** 338 | * Swap the elements indexed by `x` and `y` in the array `ary`. 339 | * 340 | * @param {Array} ary 341 | * The array. 342 | * @param {Number} x 343 | * The index of the first item. 344 | * @param {Number} y 345 | * The index of the second item. 346 | */ 347 | function swap(ary, x, y) { 348 | var temp = ary[x]; 349 | ary[x] = ary[y]; 350 | ary[y] = temp; 351 | } 352 | 353 | /** 354 | * Returns a random integer within the range `low .. high` inclusive. 355 | * 356 | * @param {Number} low 357 | * The lower bound on the range. 358 | * @param {Number} high 359 | * The upper bound on the range. 360 | */ 361 | function randomIntInRange(low, high) { 362 | return Math.round(low + (Math.random() * (high - low))); 363 | } 364 | 365 | /** 366 | * The Quick Sort algorithm. 367 | * 368 | * @param {Array} ary 369 | * An array to sort. 370 | * @param {function} comparator 371 | * Function to use to compare two items. 372 | * @param {Number} p 373 | * Start index of the array 374 | * @param {Number} r 375 | * End index of the array 376 | */ 377 | function doQuickSort(ary, comparator, p, r) { 378 | // If our lower bound is less than our upper bound, we (1) partition the 379 | // array into two pieces and (2) recurse on each half. If it is not, this is 380 | // the empty array and our base case. 381 | 382 | if (p < r) { 383 | // (1) Partitioning. 384 | // 385 | // The partitioning chooses a pivot between `p` and `r` and moves all 386 | // elements that are less than or equal to the pivot to the before it, and 387 | // all the elements that are greater than it after it. The effect is that 388 | // once partition is done, the pivot is in the exact place it will be when 389 | // the array is put in sorted order, and it will not need to be moved 390 | // again. This runs in O(n) time. 391 | 392 | // Always choose a random pivot so that an input array which is reverse 393 | // sorted does not cause O(n^2) running time. 394 | var pivotIndex = randomIntInRange(p, r); 395 | var i = p - 1; 396 | 397 | swap(ary, pivotIndex, r); 398 | var pivot = ary[r]; 399 | 400 | // Immediately after `j` is incremented in this loop, the following hold 401 | // true: 402 | // 403 | // * Every element in `ary[p .. i]` is less than or equal to the pivot. 404 | // 405 | // * Every element in `ary[i+1 .. j-1]` is greater than the pivot. 406 | for (var j = p; j < r; j++) { 407 | if (comparator(ary[j], pivot) <= 0) { 408 | i += 1; 409 | swap(ary, i, j); 410 | } 411 | } 412 | 413 | swap(ary, i + 1, j); 414 | var q = i + 1; 415 | 416 | // (2) Recurse on each half. 417 | 418 | doQuickSort(ary, comparator, p, q - 1); 419 | doQuickSort(ary, comparator, q + 1, r); 420 | } 421 | } 422 | 423 | /** 424 | * Sort the given array in-place with the given comparator function. 425 | * 426 | * @param {Array} ary 427 | * An array to sort. 428 | * @param {function} comparator 429 | * Function to use to compare two items. 430 | */ 431 | exports.quickSort = function (ary, comparator) { 432 | doQuickSort(ary, comparator, 0, ary.length - 1); 433 | }; 434 | 435 | 436 | /***/ }), 437 | 438 | /***/ 282: 439 | /***/ (function(module) { 440 | 441 | module.exports = require("module"); 442 | 443 | /***/ }), 444 | 445 | /***/ 284: 446 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 447 | 448 | var SourceMapConsumer = __webpack_require__(596).SourceMapConsumer; 449 | var path = __webpack_require__(622); 450 | 451 | var fs; 452 | try { 453 | fs = __webpack_require__(747); 454 | if (!fs.existsSync || !fs.readFileSync) { 455 | // fs doesn't have all methods we need 456 | fs = null; 457 | } 458 | } catch (err) { 459 | /* nop */ 460 | } 461 | 462 | var bufferFrom = __webpack_require__(650); 463 | 464 | // Only install once if called multiple times 465 | var errorFormatterInstalled = false; 466 | var uncaughtShimInstalled = false; 467 | 468 | // If true, the caches are reset before a stack trace formatting operation 469 | var emptyCacheBetweenOperations = false; 470 | 471 | // Supports {browser, node, auto} 472 | var environment = "auto"; 473 | 474 | // Maps a file path to a string containing the file contents 475 | var fileContentsCache = {}; 476 | 477 | // Maps a file path to a source map for that file 478 | var sourceMapCache = {}; 479 | 480 | // Regex for detecting source maps 481 | var reSourceMap = /^data:application\/json[^,]+base64,/; 482 | 483 | // Priority list of retrieve handlers 484 | var retrieveFileHandlers = []; 485 | var retrieveMapHandlers = []; 486 | 487 | function isInBrowser() { 488 | if (environment === "browser") 489 | return true; 490 | if (environment === "node") 491 | return false; 492 | return ((typeof window !== 'undefined') && (typeof XMLHttpRequest === 'function') && !(window.require && window.module && window.process && window.process.type === "renderer")); 493 | } 494 | 495 | function hasGlobalProcessEventEmitter() { 496 | return ((typeof process === 'object') && (process !== null) && (typeof process.on === 'function')); 497 | } 498 | 499 | function handlerExec(list) { 500 | return function(arg) { 501 | for (var i = 0; i < list.length; i++) { 502 | var ret = list[i](arg); 503 | if (ret) { 504 | return ret; 505 | } 506 | } 507 | return null; 508 | }; 509 | } 510 | 511 | var retrieveFile = handlerExec(retrieveFileHandlers); 512 | 513 | retrieveFileHandlers.push(function(path) { 514 | // Trim the path to make sure there is no extra whitespace. 515 | path = path.trim(); 516 | if (/^file:/.test(path)) { 517 | // existsSync/readFileSync can't handle file protocol, but once stripped, it works 518 | path = path.replace(/file:\/\/\/(\w:)?/, function(protocol, drive) { 519 | return drive ? 520 | '' : // file:///C:/dir/file -> C:/dir/file 521 | '/'; // file:///root-dir/file -> /root-dir/file 522 | }); 523 | } 524 | if (path in fileContentsCache) { 525 | return fileContentsCache[path]; 526 | } 527 | 528 | var contents = ''; 529 | try { 530 | if (!fs) { 531 | // Use SJAX if we are in the browser 532 | var xhr = new XMLHttpRequest(); 533 | xhr.open('GET', path, /** async */ false); 534 | xhr.send(null); 535 | if (xhr.readyState === 4 && xhr.status === 200) { 536 | contents = xhr.responseText; 537 | } 538 | } else if (fs.existsSync(path)) { 539 | // Otherwise, use the filesystem 540 | contents = fs.readFileSync(path, 'utf8'); 541 | } 542 | } catch (er) { 543 | /* ignore any errors */ 544 | } 545 | 546 | return fileContentsCache[path] = contents; 547 | }); 548 | 549 | // Support URLs relative to a directory, but be careful about a protocol prefix 550 | // in case we are in the browser (i.e. directories may start with "http://" or "file:///") 551 | function supportRelativeURL(file, url) { 552 | if (!file) return url; 553 | var dir = path.dirname(file); 554 | var match = /^\w+:\/\/[^\/]*/.exec(dir); 555 | var protocol = match ? match[0] : ''; 556 | var startPath = dir.slice(protocol.length); 557 | if (protocol && /^\/\w\:/.test(startPath)) { 558 | // handle file:///C:/ paths 559 | protocol += '/'; 560 | return protocol + path.resolve(dir.slice(protocol.length), url).replace(/\\/g, '/'); 561 | } 562 | return protocol + path.resolve(dir.slice(protocol.length), url); 563 | } 564 | 565 | function retrieveSourceMapURL(source) { 566 | var fileData; 567 | 568 | if (isInBrowser()) { 569 | try { 570 | var xhr = new XMLHttpRequest(); 571 | xhr.open('GET', source, false); 572 | xhr.send(null); 573 | fileData = xhr.readyState === 4 ? xhr.responseText : null; 574 | 575 | // Support providing a sourceMappingURL via the SourceMap header 576 | var sourceMapHeader = xhr.getResponseHeader("SourceMap") || 577 | xhr.getResponseHeader("X-SourceMap"); 578 | if (sourceMapHeader) { 579 | return sourceMapHeader; 580 | } 581 | } catch (e) { 582 | } 583 | } 584 | 585 | // Get the URL of the source map 586 | fileData = retrieveFile(source); 587 | var re = /(?:\/\/[@#][ \t]+sourceMappingURL=([^\s'"]+?)[ \t]*$)|(?:\/\*[@#][ \t]+sourceMappingURL=([^\*]+?)[ \t]*(?:\*\/)[ \t]*$)/mg; 588 | // Keep executing the search to find the *last* sourceMappingURL to avoid 589 | // picking up sourceMappingURLs from comments, strings, etc. 590 | var lastMatch, match; 591 | while (match = re.exec(fileData)) lastMatch = match; 592 | if (!lastMatch) return null; 593 | return lastMatch[1]; 594 | }; 595 | 596 | // Can be overridden by the retrieveSourceMap option to install. Takes a 597 | // generated source filename; returns a {map, optional url} object, or null if 598 | // there is no source map. The map field may be either a string or the parsed 599 | // JSON object (ie, it must be a valid argument to the SourceMapConsumer 600 | // constructor). 601 | var retrieveSourceMap = handlerExec(retrieveMapHandlers); 602 | retrieveMapHandlers.push(function(source) { 603 | var sourceMappingURL = retrieveSourceMapURL(source); 604 | if (!sourceMappingURL) return null; 605 | 606 | // Read the contents of the source map 607 | var sourceMapData; 608 | if (reSourceMap.test(sourceMappingURL)) { 609 | // Support source map URL as a data url 610 | var rawData = sourceMappingURL.slice(sourceMappingURL.indexOf(',') + 1); 611 | sourceMapData = bufferFrom(rawData, "base64").toString(); 612 | sourceMappingURL = source; 613 | } else { 614 | // Support source map URLs relative to the source URL 615 | sourceMappingURL = supportRelativeURL(source, sourceMappingURL); 616 | sourceMapData = retrieveFile(sourceMappingURL); 617 | } 618 | 619 | if (!sourceMapData) { 620 | return null; 621 | } 622 | 623 | return { 624 | url: sourceMappingURL, 625 | map: sourceMapData 626 | }; 627 | }); 628 | 629 | function mapSourcePosition(position) { 630 | var sourceMap = sourceMapCache[position.source]; 631 | if (!sourceMap) { 632 | // Call the (overrideable) retrieveSourceMap function to get the source map. 633 | var urlAndMap = retrieveSourceMap(position.source); 634 | if (urlAndMap) { 635 | sourceMap = sourceMapCache[position.source] = { 636 | url: urlAndMap.url, 637 | map: new SourceMapConsumer(urlAndMap.map) 638 | }; 639 | 640 | // Load all sources stored inline with the source map into the file cache 641 | // to pretend like they are already loaded. They may not exist on disk. 642 | if (sourceMap.map.sourcesContent) { 643 | sourceMap.map.sources.forEach(function(source, i) { 644 | var contents = sourceMap.map.sourcesContent[i]; 645 | if (contents) { 646 | var url = supportRelativeURL(sourceMap.url, source); 647 | fileContentsCache[url] = contents; 648 | } 649 | }); 650 | } 651 | } else { 652 | sourceMap = sourceMapCache[position.source] = { 653 | url: null, 654 | map: null 655 | }; 656 | } 657 | } 658 | 659 | // Resolve the source URL relative to the URL of the source map 660 | if (sourceMap && sourceMap.map) { 661 | var originalPosition = sourceMap.map.originalPositionFor(position); 662 | 663 | // Only return the original position if a matching line was found. If no 664 | // matching line is found then we return position instead, which will cause 665 | // the stack trace to print the path and line for the compiled file. It is 666 | // better to give a precise location in the compiled file than a vague 667 | // location in the original file. 668 | if (originalPosition.source !== null) { 669 | originalPosition.source = supportRelativeURL( 670 | sourceMap.url, originalPosition.source); 671 | return originalPosition; 672 | } 673 | } 674 | 675 | return position; 676 | } 677 | 678 | // Parses code generated by FormatEvalOrigin(), a function inside V8: 679 | // https://code.google.com/p/v8/source/browse/trunk/src/messages.js 680 | function mapEvalOrigin(origin) { 681 | // Most eval() calls are in this format 682 | var match = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/.exec(origin); 683 | if (match) { 684 | var position = mapSourcePosition({ 685 | source: match[2], 686 | line: +match[3], 687 | column: match[4] - 1 688 | }); 689 | return 'eval at ' + match[1] + ' (' + position.source + ':' + 690 | position.line + ':' + (position.column + 1) + ')'; 691 | } 692 | 693 | // Parse nested eval() calls using recursion 694 | match = /^eval at ([^(]+) \((.+)\)$/.exec(origin); 695 | if (match) { 696 | return 'eval at ' + match[1] + ' (' + mapEvalOrigin(match[2]) + ')'; 697 | } 698 | 699 | // Make sure we still return useful information if we didn't find anything 700 | return origin; 701 | } 702 | 703 | // This is copied almost verbatim from the V8 source code at 704 | // https://code.google.com/p/v8/source/browse/trunk/src/messages.js. The 705 | // implementation of wrapCallSite() used to just forward to the actual source 706 | // code of CallSite.prototype.toString but unfortunately a new release of V8 707 | // did something to the prototype chain and broke the shim. The only fix I 708 | // could find was copy/paste. 709 | function CallSiteToString() { 710 | var fileName; 711 | var fileLocation = ""; 712 | if (this.isNative()) { 713 | fileLocation = "native"; 714 | } else { 715 | fileName = this.getScriptNameOrSourceURL(); 716 | if (!fileName && this.isEval()) { 717 | fileLocation = this.getEvalOrigin(); 718 | fileLocation += ", "; // Expecting source position to follow. 719 | } 720 | 721 | if (fileName) { 722 | fileLocation += fileName; 723 | } else { 724 | // Source code does not originate from a file and is not native, but we 725 | // can still get the source position inside the source string, e.g. in 726 | // an eval string. 727 | fileLocation += ""; 728 | } 729 | var lineNumber = this.getLineNumber(); 730 | if (lineNumber != null) { 731 | fileLocation += ":" + lineNumber; 732 | var columnNumber = this.getColumnNumber(); 733 | if (columnNumber) { 734 | fileLocation += ":" + columnNumber; 735 | } 736 | } 737 | } 738 | 739 | var line = ""; 740 | var functionName = this.getFunctionName(); 741 | var addSuffix = true; 742 | var isConstructor = this.isConstructor(); 743 | var isMethodCall = !(this.isToplevel() || isConstructor); 744 | if (isMethodCall) { 745 | var typeName = this.getTypeName(); 746 | // Fixes shim to be backward compatable with Node v0 to v4 747 | if (typeName === "[object Object]") { 748 | typeName = "null"; 749 | } 750 | var methodName = this.getMethodName(); 751 | if (functionName) { 752 | if (typeName && functionName.indexOf(typeName) != 0) { 753 | line += typeName + "."; 754 | } 755 | line += functionName; 756 | if (methodName && functionName.indexOf("." + methodName) != functionName.length - methodName.length - 1) { 757 | line += " [as " + methodName + "]"; 758 | } 759 | } else { 760 | line += typeName + "." + (methodName || ""); 761 | } 762 | } else if (isConstructor) { 763 | line += "new " + (functionName || ""); 764 | } else if (functionName) { 765 | line += functionName; 766 | } else { 767 | line += fileLocation; 768 | addSuffix = false; 769 | } 770 | if (addSuffix) { 771 | line += " (" + fileLocation + ")"; 772 | } 773 | return line; 774 | } 775 | 776 | function cloneCallSite(frame) { 777 | var object = {}; 778 | Object.getOwnPropertyNames(Object.getPrototypeOf(frame)).forEach(function(name) { 779 | object[name] = /^(?:is|get)/.test(name) ? function() { return frame[name].call(frame); } : frame[name]; 780 | }); 781 | object.toString = CallSiteToString; 782 | return object; 783 | } 784 | 785 | function wrapCallSite(frame) { 786 | if(frame.isNative()) { 787 | return frame; 788 | } 789 | 790 | // Most call sites will return the source file from getFileName(), but code 791 | // passed to eval() ending in "//# sourceURL=..." will return the source file 792 | // from getScriptNameOrSourceURL() instead 793 | var source = frame.getFileName() || frame.getScriptNameOrSourceURL(); 794 | if (source) { 795 | var line = frame.getLineNumber(); 796 | var column = frame.getColumnNumber() - 1; 797 | 798 | // Fix position in Node where some (internal) code is prepended. 799 | // See https://github.com/evanw/node-source-map-support/issues/36 800 | var headerLength = 62; 801 | if (line === 1 && column > headerLength && !isInBrowser() && !frame.isEval()) { 802 | column -= headerLength; 803 | } 804 | 805 | var position = mapSourcePosition({ 806 | source: source, 807 | line: line, 808 | column: column 809 | }); 810 | frame = cloneCallSite(frame); 811 | var originalFunctionName = frame.getFunctionName; 812 | frame.getFunctionName = function() { return position.name || originalFunctionName(); }; 813 | frame.getFileName = function() { return position.source; }; 814 | frame.getLineNumber = function() { return position.line; }; 815 | frame.getColumnNumber = function() { return position.column + 1; }; 816 | frame.getScriptNameOrSourceURL = function() { return position.source; }; 817 | return frame; 818 | } 819 | 820 | // Code called using eval() needs special handling 821 | var origin = frame.isEval() && frame.getEvalOrigin(); 822 | if (origin) { 823 | origin = mapEvalOrigin(origin); 824 | frame = cloneCallSite(frame); 825 | frame.getEvalOrigin = function() { return origin; }; 826 | return frame; 827 | } 828 | 829 | // If we get here then we were unable to change the source position 830 | return frame; 831 | } 832 | 833 | // This function is part of the V8 stack trace API, for more info see: 834 | // http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi 835 | function prepareStackTrace(error, stack) { 836 | if (emptyCacheBetweenOperations) { 837 | fileContentsCache = {}; 838 | sourceMapCache = {}; 839 | } 840 | 841 | return error + stack.map(function(frame) { 842 | return '\n at ' + wrapCallSite(frame); 843 | }).join(''); 844 | } 845 | 846 | // Generate position and snippet of original source with pointer 847 | function getErrorSource(error) { 848 | var match = /\n at [^(]+ \((.*):(\d+):(\d+)\)/.exec(error.stack); 849 | if (match) { 850 | var source = match[1]; 851 | var line = +match[2]; 852 | var column = +match[3]; 853 | 854 | // Support the inline sourceContents inside the source map 855 | var contents = fileContentsCache[source]; 856 | 857 | // Support files on disk 858 | if (!contents && fs && fs.existsSync(source)) { 859 | try { 860 | contents = fs.readFileSync(source, 'utf8'); 861 | } catch (er) { 862 | contents = ''; 863 | } 864 | } 865 | 866 | // Format the line from the original source code like node does 867 | if (contents) { 868 | var code = contents.split(/(?:\r\n|\r|\n)/)[line - 1]; 869 | if (code) { 870 | return source + ':' + line + '\n' + code + '\n' + 871 | new Array(column).join(' ') + '^'; 872 | } 873 | } 874 | } 875 | return null; 876 | } 877 | 878 | function printErrorAndExit (error) { 879 | var source = getErrorSource(error); 880 | 881 | // Ensure error is printed synchronously and not truncated 882 | if (process.stderr._handle && process.stderr._handle.setBlocking) { 883 | process.stderr._handle.setBlocking(true); 884 | } 885 | 886 | if (source) { 887 | console.error(); 888 | console.error(source); 889 | } 890 | 891 | console.error(error.stack); 892 | process.exit(1); 893 | } 894 | 895 | function shimEmitUncaughtException () { 896 | var origEmit = process.emit; 897 | 898 | process.emit = function (type) { 899 | if (type === 'uncaughtException') { 900 | var hasStack = (arguments[1] && arguments[1].stack); 901 | var hasListeners = (this.listeners(type).length > 0); 902 | 903 | if (hasStack && !hasListeners) { 904 | return printErrorAndExit(arguments[1]); 905 | } 906 | } 907 | 908 | return origEmit.apply(this, arguments); 909 | }; 910 | } 911 | 912 | var originalRetrieveFileHandlers = retrieveFileHandlers.slice(0); 913 | var originalRetrieveMapHandlers = retrieveMapHandlers.slice(0); 914 | 915 | exports.wrapCallSite = wrapCallSite; 916 | exports.getErrorSource = getErrorSource; 917 | exports.mapSourcePosition = mapSourcePosition; 918 | exports.retrieveSourceMap = retrieveSourceMap; 919 | 920 | exports.install = function(options) { 921 | options = options || {}; 922 | 923 | if (options.environment) { 924 | environment = options.environment; 925 | if (["node", "browser", "auto"].indexOf(environment) === -1) { 926 | throw new Error("environment " + environment + " was unknown. Available options are {auto, browser, node}") 927 | } 928 | } 929 | 930 | // Allow sources to be found by methods other than reading the files 931 | // directly from disk. 932 | if (options.retrieveFile) { 933 | if (options.overrideRetrieveFile) { 934 | retrieveFileHandlers.length = 0; 935 | } 936 | 937 | retrieveFileHandlers.unshift(options.retrieveFile); 938 | } 939 | 940 | // Allow source maps to be found by methods other than reading the files 941 | // directly from disk. 942 | if (options.retrieveSourceMap) { 943 | if (options.overrideRetrieveSourceMap) { 944 | retrieveMapHandlers.length = 0; 945 | } 946 | 947 | retrieveMapHandlers.unshift(options.retrieveSourceMap); 948 | } 949 | 950 | // Support runtime transpilers that include inline source maps 951 | if (options.hookRequire && !isInBrowser()) { 952 | var Module; 953 | try { 954 | Module = __webpack_require__(282); 955 | } catch (err) { 956 | // NOP: Loading in catch block to convert webpack error to warning. 957 | } 958 | var $compile = Module.prototype._compile; 959 | 960 | if (!$compile.__sourceMapSupport) { 961 | Module.prototype._compile = function(content, filename) { 962 | fileContentsCache[filename] = content; 963 | sourceMapCache[filename] = undefined; 964 | return $compile.call(this, content, filename); 965 | }; 966 | 967 | Module.prototype._compile.__sourceMapSupport = true; 968 | } 969 | } 970 | 971 | // Configure options 972 | if (!emptyCacheBetweenOperations) { 973 | emptyCacheBetweenOperations = 'emptyCacheBetweenOperations' in options ? 974 | options.emptyCacheBetweenOperations : false; 975 | } 976 | 977 | // Install the error reformatter 978 | if (!errorFormatterInstalled) { 979 | errorFormatterInstalled = true; 980 | Error.prepareStackTrace = prepareStackTrace; 981 | } 982 | 983 | if (!uncaughtShimInstalled) { 984 | var installHandler = 'handleUncaughtExceptions' in options ? 985 | options.handleUncaughtExceptions : true; 986 | 987 | // Provide the option to not install the uncaught exception handler. This is 988 | // to support other uncaught exception handlers (in test frameworks, for 989 | // example). If this handler is not installed and there are no other uncaught 990 | // exception handlers, uncaught exceptions will be caught by node's built-in 991 | // exception handler and the process will still be terminated. However, the 992 | // generated JavaScript code will be shown above the stack trace instead of 993 | // the original source code. 994 | if (installHandler && hasGlobalProcessEventEmitter()) { 995 | uncaughtShimInstalled = true; 996 | shimEmitUncaughtException(); 997 | } 998 | } 999 | }; 1000 | 1001 | exports.resetRetrieveHandlers = function() { 1002 | retrieveFileHandlers.length = 0; 1003 | retrieveMapHandlers.length = 0; 1004 | 1005 | retrieveFileHandlers = originalRetrieveFileHandlers.slice(0); 1006 | retrieveMapHandlers = originalRetrieveMapHandlers.slice(0); 1007 | } 1008 | 1009 | 1010 | /***/ }), 1011 | 1012 | /***/ 327: 1013 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 1014 | 1015 | /* -*- Mode: js; js-indent-level: 2; -*- */ 1016 | /* 1017 | * Copyright 2011 Mozilla Foundation and contributors 1018 | * Licensed under the New BSD license. See LICENSE or: 1019 | * http://opensource.org/licenses/BSD-3-Clause 1020 | */ 1021 | 1022 | var util = __webpack_require__(983); 1023 | var binarySearch = __webpack_require__(164); 1024 | var ArraySet = __webpack_require__(837).ArraySet; 1025 | var base64VLQ = __webpack_require__(215); 1026 | var quickSort = __webpack_require__(226).quickSort; 1027 | 1028 | function SourceMapConsumer(aSourceMap, aSourceMapURL) { 1029 | var sourceMap = aSourceMap; 1030 | if (typeof aSourceMap === 'string') { 1031 | sourceMap = util.parseSourceMapInput(aSourceMap); 1032 | } 1033 | 1034 | return sourceMap.sections != null 1035 | ? new IndexedSourceMapConsumer(sourceMap, aSourceMapURL) 1036 | : new BasicSourceMapConsumer(sourceMap, aSourceMapURL); 1037 | } 1038 | 1039 | SourceMapConsumer.fromSourceMap = function(aSourceMap, aSourceMapURL) { 1040 | return BasicSourceMapConsumer.fromSourceMap(aSourceMap, aSourceMapURL); 1041 | } 1042 | 1043 | /** 1044 | * The version of the source mapping spec that we are consuming. 1045 | */ 1046 | SourceMapConsumer.prototype._version = 3; 1047 | 1048 | // `__generatedMappings` and `__originalMappings` are arrays that hold the 1049 | // parsed mapping coordinates from the source map's "mappings" attribute. They 1050 | // are lazily instantiated, accessed via the `_generatedMappings` and 1051 | // `_originalMappings` getters respectively, and we only parse the mappings 1052 | // and create these arrays once queried for a source location. We jump through 1053 | // these hoops because there can be many thousands of mappings, and parsing 1054 | // them is expensive, so we only want to do it if we must. 1055 | // 1056 | // Each object in the arrays is of the form: 1057 | // 1058 | // { 1059 | // generatedLine: The line number in the generated code, 1060 | // generatedColumn: The column number in the generated code, 1061 | // source: The path to the original source file that generated this 1062 | // chunk of code, 1063 | // originalLine: The line number in the original source that 1064 | // corresponds to this chunk of generated code, 1065 | // originalColumn: The column number in the original source that 1066 | // corresponds to this chunk of generated code, 1067 | // name: The name of the original symbol which generated this chunk of 1068 | // code. 1069 | // } 1070 | // 1071 | // All properties except for `generatedLine` and `generatedColumn` can be 1072 | // `null`. 1073 | // 1074 | // `_generatedMappings` is ordered by the generated positions. 1075 | // 1076 | // `_originalMappings` is ordered by the original positions. 1077 | 1078 | SourceMapConsumer.prototype.__generatedMappings = null; 1079 | Object.defineProperty(SourceMapConsumer.prototype, '_generatedMappings', { 1080 | configurable: true, 1081 | enumerable: true, 1082 | get: function () { 1083 | if (!this.__generatedMappings) { 1084 | this._parseMappings(this._mappings, this.sourceRoot); 1085 | } 1086 | 1087 | return this.__generatedMappings; 1088 | } 1089 | }); 1090 | 1091 | SourceMapConsumer.prototype.__originalMappings = null; 1092 | Object.defineProperty(SourceMapConsumer.prototype, '_originalMappings', { 1093 | configurable: true, 1094 | enumerable: true, 1095 | get: function () { 1096 | if (!this.__originalMappings) { 1097 | this._parseMappings(this._mappings, this.sourceRoot); 1098 | } 1099 | 1100 | return this.__originalMappings; 1101 | } 1102 | }); 1103 | 1104 | SourceMapConsumer.prototype._charIsMappingSeparator = 1105 | function SourceMapConsumer_charIsMappingSeparator(aStr, index) { 1106 | var c = aStr.charAt(index); 1107 | return c === ";" || c === ","; 1108 | }; 1109 | 1110 | /** 1111 | * Parse the mappings in a string in to a data structure which we can easily 1112 | * query (the ordered arrays in the `this.__generatedMappings` and 1113 | * `this.__originalMappings` properties). 1114 | */ 1115 | SourceMapConsumer.prototype._parseMappings = 1116 | function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { 1117 | throw new Error("Subclasses must implement _parseMappings"); 1118 | }; 1119 | 1120 | SourceMapConsumer.GENERATED_ORDER = 1; 1121 | SourceMapConsumer.ORIGINAL_ORDER = 2; 1122 | 1123 | SourceMapConsumer.GREATEST_LOWER_BOUND = 1; 1124 | SourceMapConsumer.LEAST_UPPER_BOUND = 2; 1125 | 1126 | /** 1127 | * Iterate over each mapping between an original source/line/column and a 1128 | * generated line/column in this source map. 1129 | * 1130 | * @param Function aCallback 1131 | * The function that is called with each mapping. 1132 | * @param Object aContext 1133 | * Optional. If specified, this object will be the value of `this` every 1134 | * time that `aCallback` is called. 1135 | * @param aOrder 1136 | * Either `SourceMapConsumer.GENERATED_ORDER` or 1137 | * `SourceMapConsumer.ORIGINAL_ORDER`. Specifies whether you want to 1138 | * iterate over the mappings sorted by the generated file's line/column 1139 | * order or the original's source/line/column order, respectively. Defaults to 1140 | * `SourceMapConsumer.GENERATED_ORDER`. 1141 | */ 1142 | SourceMapConsumer.prototype.eachMapping = 1143 | function SourceMapConsumer_eachMapping(aCallback, aContext, aOrder) { 1144 | var context = aContext || null; 1145 | var order = aOrder || SourceMapConsumer.GENERATED_ORDER; 1146 | 1147 | var mappings; 1148 | switch (order) { 1149 | case SourceMapConsumer.GENERATED_ORDER: 1150 | mappings = this._generatedMappings; 1151 | break; 1152 | case SourceMapConsumer.ORIGINAL_ORDER: 1153 | mappings = this._originalMappings; 1154 | break; 1155 | default: 1156 | throw new Error("Unknown order of iteration."); 1157 | } 1158 | 1159 | var sourceRoot = this.sourceRoot; 1160 | mappings.map(function (mapping) { 1161 | var source = mapping.source === null ? null : this._sources.at(mapping.source); 1162 | source = util.computeSourceURL(sourceRoot, source, this._sourceMapURL); 1163 | return { 1164 | source: source, 1165 | generatedLine: mapping.generatedLine, 1166 | generatedColumn: mapping.generatedColumn, 1167 | originalLine: mapping.originalLine, 1168 | originalColumn: mapping.originalColumn, 1169 | name: mapping.name === null ? null : this._names.at(mapping.name) 1170 | }; 1171 | }, this).forEach(aCallback, context); 1172 | }; 1173 | 1174 | /** 1175 | * Returns all generated line and column information for the original source, 1176 | * line, and column provided. If no column is provided, returns all mappings 1177 | * corresponding to a either the line we are searching for or the next 1178 | * closest line that has any mappings. Otherwise, returns all mappings 1179 | * corresponding to the given line and either the column we are searching for 1180 | * or the next closest column that has any offsets. 1181 | * 1182 | * The only argument is an object with the following properties: 1183 | * 1184 | * - source: The filename of the original source. 1185 | * - line: The line number in the original source. The line number is 1-based. 1186 | * - column: Optional. the column number in the original source. 1187 | * The column number is 0-based. 1188 | * 1189 | * and an array of objects is returned, each with the following properties: 1190 | * 1191 | * - line: The line number in the generated source, or null. The 1192 | * line number is 1-based. 1193 | * - column: The column number in the generated source, or null. 1194 | * The column number is 0-based. 1195 | */ 1196 | SourceMapConsumer.prototype.allGeneratedPositionsFor = 1197 | function SourceMapConsumer_allGeneratedPositionsFor(aArgs) { 1198 | var line = util.getArg(aArgs, 'line'); 1199 | 1200 | // When there is no exact match, BasicSourceMapConsumer.prototype._findMapping 1201 | // returns the index of the closest mapping less than the needle. By 1202 | // setting needle.originalColumn to 0, we thus find the last mapping for 1203 | // the given line, provided such a mapping exists. 1204 | var needle = { 1205 | source: util.getArg(aArgs, 'source'), 1206 | originalLine: line, 1207 | originalColumn: util.getArg(aArgs, 'column', 0) 1208 | }; 1209 | 1210 | needle.source = this._findSourceIndex(needle.source); 1211 | if (needle.source < 0) { 1212 | return []; 1213 | } 1214 | 1215 | var mappings = []; 1216 | 1217 | var index = this._findMapping(needle, 1218 | this._originalMappings, 1219 | "originalLine", 1220 | "originalColumn", 1221 | util.compareByOriginalPositions, 1222 | binarySearch.LEAST_UPPER_BOUND); 1223 | if (index >= 0) { 1224 | var mapping = this._originalMappings[index]; 1225 | 1226 | if (aArgs.column === undefined) { 1227 | var originalLine = mapping.originalLine; 1228 | 1229 | // Iterate until either we run out of mappings, or we run into 1230 | // a mapping for a different line than the one we found. Since 1231 | // mappings are sorted, this is guaranteed to find all mappings for 1232 | // the line we found. 1233 | while (mapping && mapping.originalLine === originalLine) { 1234 | mappings.push({ 1235 | line: util.getArg(mapping, 'generatedLine', null), 1236 | column: util.getArg(mapping, 'generatedColumn', null), 1237 | lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) 1238 | }); 1239 | 1240 | mapping = this._originalMappings[++index]; 1241 | } 1242 | } else { 1243 | var originalColumn = mapping.originalColumn; 1244 | 1245 | // Iterate until either we run out of mappings, or we run into 1246 | // a mapping for a different line than the one we were searching for. 1247 | // Since mappings are sorted, this is guaranteed to find all mappings for 1248 | // the line we are searching for. 1249 | while (mapping && 1250 | mapping.originalLine === line && 1251 | mapping.originalColumn == originalColumn) { 1252 | mappings.push({ 1253 | line: util.getArg(mapping, 'generatedLine', null), 1254 | column: util.getArg(mapping, 'generatedColumn', null), 1255 | lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) 1256 | }); 1257 | 1258 | mapping = this._originalMappings[++index]; 1259 | } 1260 | } 1261 | } 1262 | 1263 | return mappings; 1264 | }; 1265 | 1266 | exports.SourceMapConsumer = SourceMapConsumer; 1267 | 1268 | /** 1269 | * A BasicSourceMapConsumer instance represents a parsed source map which we can 1270 | * query for information about the original file positions by giving it a file 1271 | * position in the generated source. 1272 | * 1273 | * The first parameter is the raw source map (either as a JSON string, or 1274 | * already parsed to an object). According to the spec, source maps have the 1275 | * following attributes: 1276 | * 1277 | * - version: Which version of the source map spec this map is following. 1278 | * - sources: An array of URLs to the original source files. 1279 | * - names: An array of identifiers which can be referrenced by individual mappings. 1280 | * - sourceRoot: Optional. The URL root from which all sources are relative. 1281 | * - sourcesContent: Optional. An array of contents of the original source files. 1282 | * - mappings: A string of base64 VLQs which contain the actual mappings. 1283 | * - file: Optional. The generated file this source map is associated with. 1284 | * 1285 | * Here is an example source map, taken from the source map spec[0]: 1286 | * 1287 | * { 1288 | * version : 3, 1289 | * file: "out.js", 1290 | * sourceRoot : "", 1291 | * sources: ["foo.js", "bar.js"], 1292 | * names: ["src", "maps", "are", "fun"], 1293 | * mappings: "AA,AB;;ABCDE;" 1294 | * } 1295 | * 1296 | * The second parameter, if given, is a string whose value is the URL 1297 | * at which the source map was found. This URL is used to compute the 1298 | * sources array. 1299 | * 1300 | * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit?pli=1# 1301 | */ 1302 | function BasicSourceMapConsumer(aSourceMap, aSourceMapURL) { 1303 | var sourceMap = aSourceMap; 1304 | if (typeof aSourceMap === 'string') { 1305 | sourceMap = util.parseSourceMapInput(aSourceMap); 1306 | } 1307 | 1308 | var version = util.getArg(sourceMap, 'version'); 1309 | var sources = util.getArg(sourceMap, 'sources'); 1310 | // Sass 3.3 leaves out the 'names' array, so we deviate from the spec (which 1311 | // requires the array) to play nice here. 1312 | var names = util.getArg(sourceMap, 'names', []); 1313 | var sourceRoot = util.getArg(sourceMap, 'sourceRoot', null); 1314 | var sourcesContent = util.getArg(sourceMap, 'sourcesContent', null); 1315 | var mappings = util.getArg(sourceMap, 'mappings'); 1316 | var file = util.getArg(sourceMap, 'file', null); 1317 | 1318 | // Once again, Sass deviates from the spec and supplies the version as a 1319 | // string rather than a number, so we use loose equality checking here. 1320 | if (version != this._version) { 1321 | throw new Error('Unsupported version: ' + version); 1322 | } 1323 | 1324 | if (sourceRoot) { 1325 | sourceRoot = util.normalize(sourceRoot); 1326 | } 1327 | 1328 | sources = sources 1329 | .map(String) 1330 | // Some source maps produce relative source paths like "./foo.js" instead of 1331 | // "foo.js". Normalize these first so that future comparisons will succeed. 1332 | // See bugzil.la/1090768. 1333 | .map(util.normalize) 1334 | // Always ensure that absolute sources are internally stored relative to 1335 | // the source root, if the source root is absolute. Not doing this would 1336 | // be particularly problematic when the source root is a prefix of the 1337 | // source (valid, but why??). See github issue #199 and bugzil.la/1188982. 1338 | .map(function (source) { 1339 | return sourceRoot && util.isAbsolute(sourceRoot) && util.isAbsolute(source) 1340 | ? util.relative(sourceRoot, source) 1341 | : source; 1342 | }); 1343 | 1344 | // Pass `true` below to allow duplicate names and sources. While source maps 1345 | // are intended to be compressed and deduplicated, the TypeScript compiler 1346 | // sometimes generates source maps with duplicates in them. See Github issue 1347 | // #72 and bugzil.la/889492. 1348 | this._names = ArraySet.fromArray(names.map(String), true); 1349 | this._sources = ArraySet.fromArray(sources, true); 1350 | 1351 | this._absoluteSources = this._sources.toArray().map(function (s) { 1352 | return util.computeSourceURL(sourceRoot, s, aSourceMapURL); 1353 | }); 1354 | 1355 | this.sourceRoot = sourceRoot; 1356 | this.sourcesContent = sourcesContent; 1357 | this._mappings = mappings; 1358 | this._sourceMapURL = aSourceMapURL; 1359 | this.file = file; 1360 | } 1361 | 1362 | BasicSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); 1363 | BasicSourceMapConsumer.prototype.consumer = SourceMapConsumer; 1364 | 1365 | /** 1366 | * Utility function to find the index of a source. Returns -1 if not 1367 | * found. 1368 | */ 1369 | BasicSourceMapConsumer.prototype._findSourceIndex = function(aSource) { 1370 | var relativeSource = aSource; 1371 | if (this.sourceRoot != null) { 1372 | relativeSource = util.relative(this.sourceRoot, relativeSource); 1373 | } 1374 | 1375 | if (this._sources.has(relativeSource)) { 1376 | return this._sources.indexOf(relativeSource); 1377 | } 1378 | 1379 | // Maybe aSource is an absolute URL as returned by |sources|. In 1380 | // this case we can't simply undo the transform. 1381 | var i; 1382 | for (i = 0; i < this._absoluteSources.length; ++i) { 1383 | if (this._absoluteSources[i] == aSource) { 1384 | return i; 1385 | } 1386 | } 1387 | 1388 | return -1; 1389 | }; 1390 | 1391 | /** 1392 | * Create a BasicSourceMapConsumer from a SourceMapGenerator. 1393 | * 1394 | * @param SourceMapGenerator aSourceMap 1395 | * The source map that will be consumed. 1396 | * @param String aSourceMapURL 1397 | * The URL at which the source map can be found (optional) 1398 | * @returns BasicSourceMapConsumer 1399 | */ 1400 | BasicSourceMapConsumer.fromSourceMap = 1401 | function SourceMapConsumer_fromSourceMap(aSourceMap, aSourceMapURL) { 1402 | var smc = Object.create(BasicSourceMapConsumer.prototype); 1403 | 1404 | var names = smc._names = ArraySet.fromArray(aSourceMap._names.toArray(), true); 1405 | var sources = smc._sources = ArraySet.fromArray(aSourceMap._sources.toArray(), true); 1406 | smc.sourceRoot = aSourceMap._sourceRoot; 1407 | smc.sourcesContent = aSourceMap._generateSourcesContent(smc._sources.toArray(), 1408 | smc.sourceRoot); 1409 | smc.file = aSourceMap._file; 1410 | smc._sourceMapURL = aSourceMapURL; 1411 | smc._absoluteSources = smc._sources.toArray().map(function (s) { 1412 | return util.computeSourceURL(smc.sourceRoot, s, aSourceMapURL); 1413 | }); 1414 | 1415 | // Because we are modifying the entries (by converting string sources and 1416 | // names to indices into the sources and names ArraySets), we have to make 1417 | // a copy of the entry or else bad things happen. Shared mutable state 1418 | // strikes again! See github issue #191. 1419 | 1420 | var generatedMappings = aSourceMap._mappings.toArray().slice(); 1421 | var destGeneratedMappings = smc.__generatedMappings = []; 1422 | var destOriginalMappings = smc.__originalMappings = []; 1423 | 1424 | for (var i = 0, length = generatedMappings.length; i < length; i++) { 1425 | var srcMapping = generatedMappings[i]; 1426 | var destMapping = new Mapping; 1427 | destMapping.generatedLine = srcMapping.generatedLine; 1428 | destMapping.generatedColumn = srcMapping.generatedColumn; 1429 | 1430 | if (srcMapping.source) { 1431 | destMapping.source = sources.indexOf(srcMapping.source); 1432 | destMapping.originalLine = srcMapping.originalLine; 1433 | destMapping.originalColumn = srcMapping.originalColumn; 1434 | 1435 | if (srcMapping.name) { 1436 | destMapping.name = names.indexOf(srcMapping.name); 1437 | } 1438 | 1439 | destOriginalMappings.push(destMapping); 1440 | } 1441 | 1442 | destGeneratedMappings.push(destMapping); 1443 | } 1444 | 1445 | quickSort(smc.__originalMappings, util.compareByOriginalPositions); 1446 | 1447 | return smc; 1448 | }; 1449 | 1450 | /** 1451 | * The version of the source mapping spec that we are consuming. 1452 | */ 1453 | BasicSourceMapConsumer.prototype._version = 3; 1454 | 1455 | /** 1456 | * The list of original sources. 1457 | */ 1458 | Object.defineProperty(BasicSourceMapConsumer.prototype, 'sources', { 1459 | get: function () { 1460 | return this._absoluteSources.slice(); 1461 | } 1462 | }); 1463 | 1464 | /** 1465 | * Provide the JIT with a nice shape / hidden class. 1466 | */ 1467 | function Mapping() { 1468 | this.generatedLine = 0; 1469 | this.generatedColumn = 0; 1470 | this.source = null; 1471 | this.originalLine = null; 1472 | this.originalColumn = null; 1473 | this.name = null; 1474 | } 1475 | 1476 | /** 1477 | * Parse the mappings in a string in to a data structure which we can easily 1478 | * query (the ordered arrays in the `this.__generatedMappings` and 1479 | * `this.__originalMappings` properties). 1480 | */ 1481 | BasicSourceMapConsumer.prototype._parseMappings = 1482 | function SourceMapConsumer_parseMappings(aStr, aSourceRoot) { 1483 | var generatedLine = 1; 1484 | var previousGeneratedColumn = 0; 1485 | var previousOriginalLine = 0; 1486 | var previousOriginalColumn = 0; 1487 | var previousSource = 0; 1488 | var previousName = 0; 1489 | var length = aStr.length; 1490 | var index = 0; 1491 | var cachedSegments = {}; 1492 | var temp = {}; 1493 | var originalMappings = []; 1494 | var generatedMappings = []; 1495 | var mapping, str, segment, end, value; 1496 | 1497 | while (index < length) { 1498 | if (aStr.charAt(index) === ';') { 1499 | generatedLine++; 1500 | index++; 1501 | previousGeneratedColumn = 0; 1502 | } 1503 | else if (aStr.charAt(index) === ',') { 1504 | index++; 1505 | } 1506 | else { 1507 | mapping = new Mapping(); 1508 | mapping.generatedLine = generatedLine; 1509 | 1510 | // Because each offset is encoded relative to the previous one, 1511 | // many segments often have the same encoding. We can exploit this 1512 | // fact by caching the parsed variable length fields of each segment, 1513 | // allowing us to avoid a second parse if we encounter the same 1514 | // segment again. 1515 | for (end = index; end < length; end++) { 1516 | if (this._charIsMappingSeparator(aStr, end)) { 1517 | break; 1518 | } 1519 | } 1520 | str = aStr.slice(index, end); 1521 | 1522 | segment = cachedSegments[str]; 1523 | if (segment) { 1524 | index += str.length; 1525 | } else { 1526 | segment = []; 1527 | while (index < end) { 1528 | base64VLQ.decode(aStr, index, temp); 1529 | value = temp.value; 1530 | index = temp.rest; 1531 | segment.push(value); 1532 | } 1533 | 1534 | if (segment.length === 2) { 1535 | throw new Error('Found a source, but no line and column'); 1536 | } 1537 | 1538 | if (segment.length === 3) { 1539 | throw new Error('Found a source and line, but no column'); 1540 | } 1541 | 1542 | cachedSegments[str] = segment; 1543 | } 1544 | 1545 | // Generated column. 1546 | mapping.generatedColumn = previousGeneratedColumn + segment[0]; 1547 | previousGeneratedColumn = mapping.generatedColumn; 1548 | 1549 | if (segment.length > 1) { 1550 | // Original source. 1551 | mapping.source = previousSource + segment[1]; 1552 | previousSource += segment[1]; 1553 | 1554 | // Original line. 1555 | mapping.originalLine = previousOriginalLine + segment[2]; 1556 | previousOriginalLine = mapping.originalLine; 1557 | // Lines are stored 0-based 1558 | mapping.originalLine += 1; 1559 | 1560 | // Original column. 1561 | mapping.originalColumn = previousOriginalColumn + segment[3]; 1562 | previousOriginalColumn = mapping.originalColumn; 1563 | 1564 | if (segment.length > 4) { 1565 | // Original name. 1566 | mapping.name = previousName + segment[4]; 1567 | previousName += segment[4]; 1568 | } 1569 | } 1570 | 1571 | generatedMappings.push(mapping); 1572 | if (typeof mapping.originalLine === 'number') { 1573 | originalMappings.push(mapping); 1574 | } 1575 | } 1576 | } 1577 | 1578 | quickSort(generatedMappings, util.compareByGeneratedPositionsDeflated); 1579 | this.__generatedMappings = generatedMappings; 1580 | 1581 | quickSort(originalMappings, util.compareByOriginalPositions); 1582 | this.__originalMappings = originalMappings; 1583 | }; 1584 | 1585 | /** 1586 | * Find the mapping that best matches the hypothetical "needle" mapping that 1587 | * we are searching for in the given "haystack" of mappings. 1588 | */ 1589 | BasicSourceMapConsumer.prototype._findMapping = 1590 | function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, 1591 | aColumnName, aComparator, aBias) { 1592 | // To return the position we are searching for, we must first find the 1593 | // mapping for the given position and then return the opposite position it 1594 | // points to. Because the mappings are sorted, we can use binary search to 1595 | // find the best mapping. 1596 | 1597 | if (aNeedle[aLineName] <= 0) { 1598 | throw new TypeError('Line must be greater than or equal to 1, got ' 1599 | + aNeedle[aLineName]); 1600 | } 1601 | if (aNeedle[aColumnName] < 0) { 1602 | throw new TypeError('Column must be greater than or equal to 0, got ' 1603 | + aNeedle[aColumnName]); 1604 | } 1605 | 1606 | return binarySearch.search(aNeedle, aMappings, aComparator, aBias); 1607 | }; 1608 | 1609 | /** 1610 | * Compute the last column for each generated mapping. The last column is 1611 | * inclusive. 1612 | */ 1613 | BasicSourceMapConsumer.prototype.computeColumnSpans = 1614 | function SourceMapConsumer_computeColumnSpans() { 1615 | for (var index = 0; index < this._generatedMappings.length; ++index) { 1616 | var mapping = this._generatedMappings[index]; 1617 | 1618 | // Mappings do not contain a field for the last generated columnt. We 1619 | // can come up with an optimistic estimate, however, by assuming that 1620 | // mappings are contiguous (i.e. given two consecutive mappings, the 1621 | // first mapping ends where the second one starts). 1622 | if (index + 1 < this._generatedMappings.length) { 1623 | var nextMapping = this._generatedMappings[index + 1]; 1624 | 1625 | if (mapping.generatedLine === nextMapping.generatedLine) { 1626 | mapping.lastGeneratedColumn = nextMapping.generatedColumn - 1; 1627 | continue; 1628 | } 1629 | } 1630 | 1631 | // The last mapping for each line spans the entire line. 1632 | mapping.lastGeneratedColumn = Infinity; 1633 | } 1634 | }; 1635 | 1636 | /** 1637 | * Returns the original source, line, and column information for the generated 1638 | * source's line and column positions provided. The only argument is an object 1639 | * with the following properties: 1640 | * 1641 | * - line: The line number in the generated source. The line number 1642 | * is 1-based. 1643 | * - column: The column number in the generated source. The column 1644 | * number is 0-based. 1645 | * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or 1646 | * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the 1647 | * closest element that is smaller than or greater than the one we are 1648 | * searching for, respectively, if the exact element cannot be found. 1649 | * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. 1650 | * 1651 | * and an object is returned with the following properties: 1652 | * 1653 | * - source: The original source file, or null. 1654 | * - line: The line number in the original source, or null. The 1655 | * line number is 1-based. 1656 | * - column: The column number in the original source, or null. The 1657 | * column number is 0-based. 1658 | * - name: The original identifier, or null. 1659 | */ 1660 | BasicSourceMapConsumer.prototype.originalPositionFor = 1661 | function SourceMapConsumer_originalPositionFor(aArgs) { 1662 | var needle = { 1663 | generatedLine: util.getArg(aArgs, 'line'), 1664 | generatedColumn: util.getArg(aArgs, 'column') 1665 | }; 1666 | 1667 | var index = this._findMapping( 1668 | needle, 1669 | this._generatedMappings, 1670 | "generatedLine", 1671 | "generatedColumn", 1672 | util.compareByGeneratedPositionsDeflated, 1673 | util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) 1674 | ); 1675 | 1676 | if (index >= 0) { 1677 | var mapping = this._generatedMappings[index]; 1678 | 1679 | if (mapping.generatedLine === needle.generatedLine) { 1680 | var source = util.getArg(mapping, 'source', null); 1681 | if (source !== null) { 1682 | source = this._sources.at(source); 1683 | source = util.computeSourceURL(this.sourceRoot, source, this._sourceMapURL); 1684 | } 1685 | var name = util.getArg(mapping, 'name', null); 1686 | if (name !== null) { 1687 | name = this._names.at(name); 1688 | } 1689 | return { 1690 | source: source, 1691 | line: util.getArg(mapping, 'originalLine', null), 1692 | column: util.getArg(mapping, 'originalColumn', null), 1693 | name: name 1694 | }; 1695 | } 1696 | } 1697 | 1698 | return { 1699 | source: null, 1700 | line: null, 1701 | column: null, 1702 | name: null 1703 | }; 1704 | }; 1705 | 1706 | /** 1707 | * Return true if we have the source content for every source in the source 1708 | * map, false otherwise. 1709 | */ 1710 | BasicSourceMapConsumer.prototype.hasContentsOfAllSources = 1711 | function BasicSourceMapConsumer_hasContentsOfAllSources() { 1712 | if (!this.sourcesContent) { 1713 | return false; 1714 | } 1715 | return this.sourcesContent.length >= this._sources.size() && 1716 | !this.sourcesContent.some(function (sc) { return sc == null; }); 1717 | }; 1718 | 1719 | /** 1720 | * Returns the original source content. The only argument is the url of the 1721 | * original source file. Returns null if no original source content is 1722 | * available. 1723 | */ 1724 | BasicSourceMapConsumer.prototype.sourceContentFor = 1725 | function SourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { 1726 | if (!this.sourcesContent) { 1727 | return null; 1728 | } 1729 | 1730 | var index = this._findSourceIndex(aSource); 1731 | if (index >= 0) { 1732 | return this.sourcesContent[index]; 1733 | } 1734 | 1735 | var relativeSource = aSource; 1736 | if (this.sourceRoot != null) { 1737 | relativeSource = util.relative(this.sourceRoot, relativeSource); 1738 | } 1739 | 1740 | var url; 1741 | if (this.sourceRoot != null 1742 | && (url = util.urlParse(this.sourceRoot))) { 1743 | // XXX: file:// URIs and absolute paths lead to unexpected behavior for 1744 | // many users. We can help them out when they expect file:// URIs to 1745 | // behave like it would if they were running a local HTTP server. See 1746 | // https://bugzilla.mozilla.org/show_bug.cgi?id=885597. 1747 | var fileUriAbsPath = relativeSource.replace(/^file:\/\//, ""); 1748 | if (url.scheme == "file" 1749 | && this._sources.has(fileUriAbsPath)) { 1750 | return this.sourcesContent[this._sources.indexOf(fileUriAbsPath)] 1751 | } 1752 | 1753 | if ((!url.path || url.path == "/") 1754 | && this._sources.has("/" + relativeSource)) { 1755 | return this.sourcesContent[this._sources.indexOf("/" + relativeSource)]; 1756 | } 1757 | } 1758 | 1759 | // This function is used recursively from 1760 | // IndexedSourceMapConsumer.prototype.sourceContentFor. In that case, we 1761 | // don't want to throw if we can't find the source - we just want to 1762 | // return null, so we provide a flag to exit gracefully. 1763 | if (nullOnMissing) { 1764 | return null; 1765 | } 1766 | else { 1767 | throw new Error('"' + relativeSource + '" is not in the SourceMap.'); 1768 | } 1769 | }; 1770 | 1771 | /** 1772 | * Returns the generated line and column information for the original source, 1773 | * line, and column positions provided. The only argument is an object with 1774 | * the following properties: 1775 | * 1776 | * - source: The filename of the original source. 1777 | * - line: The line number in the original source. The line number 1778 | * is 1-based. 1779 | * - column: The column number in the original source. The column 1780 | * number is 0-based. 1781 | * - bias: Either 'SourceMapConsumer.GREATEST_LOWER_BOUND' or 1782 | * 'SourceMapConsumer.LEAST_UPPER_BOUND'. Specifies whether to return the 1783 | * closest element that is smaller than or greater than the one we are 1784 | * searching for, respectively, if the exact element cannot be found. 1785 | * Defaults to 'SourceMapConsumer.GREATEST_LOWER_BOUND'. 1786 | * 1787 | * and an object is returned with the following properties: 1788 | * 1789 | * - line: The line number in the generated source, or null. The 1790 | * line number is 1-based. 1791 | * - column: The column number in the generated source, or null. 1792 | * The column number is 0-based. 1793 | */ 1794 | BasicSourceMapConsumer.prototype.generatedPositionFor = 1795 | function SourceMapConsumer_generatedPositionFor(aArgs) { 1796 | var source = util.getArg(aArgs, 'source'); 1797 | source = this._findSourceIndex(source); 1798 | if (source < 0) { 1799 | return { 1800 | line: null, 1801 | column: null, 1802 | lastColumn: null 1803 | }; 1804 | } 1805 | 1806 | var needle = { 1807 | source: source, 1808 | originalLine: util.getArg(aArgs, 'line'), 1809 | originalColumn: util.getArg(aArgs, 'column') 1810 | }; 1811 | 1812 | var index = this._findMapping( 1813 | needle, 1814 | this._originalMappings, 1815 | "originalLine", 1816 | "originalColumn", 1817 | util.compareByOriginalPositions, 1818 | util.getArg(aArgs, 'bias', SourceMapConsumer.GREATEST_LOWER_BOUND) 1819 | ); 1820 | 1821 | if (index >= 0) { 1822 | var mapping = this._originalMappings[index]; 1823 | 1824 | if (mapping.source === needle.source) { 1825 | return { 1826 | line: util.getArg(mapping, 'generatedLine', null), 1827 | column: util.getArg(mapping, 'generatedColumn', null), 1828 | lastColumn: util.getArg(mapping, 'lastGeneratedColumn', null) 1829 | }; 1830 | } 1831 | } 1832 | 1833 | return { 1834 | line: null, 1835 | column: null, 1836 | lastColumn: null 1837 | }; 1838 | }; 1839 | 1840 | exports.BasicSourceMapConsumer = BasicSourceMapConsumer; 1841 | 1842 | /** 1843 | * An IndexedSourceMapConsumer instance represents a parsed source map which 1844 | * we can query for information. It differs from BasicSourceMapConsumer in 1845 | * that it takes "indexed" source maps (i.e. ones with a "sections" field) as 1846 | * input. 1847 | * 1848 | * The first parameter is a raw source map (either as a JSON string, or already 1849 | * parsed to an object). According to the spec for indexed source maps, they 1850 | * have the following attributes: 1851 | * 1852 | * - version: Which version of the source map spec this map is following. 1853 | * - file: Optional. The generated file this source map is associated with. 1854 | * - sections: A list of section definitions. 1855 | * 1856 | * Each value under the "sections" field has two fields: 1857 | * - offset: The offset into the original specified at which this section 1858 | * begins to apply, defined as an object with a "line" and "column" 1859 | * field. 1860 | * - map: A source map definition. This source map could also be indexed, 1861 | * but doesn't have to be. 1862 | * 1863 | * Instead of the "map" field, it's also possible to have a "url" field 1864 | * specifying a URL to retrieve a source map from, but that's currently 1865 | * unsupported. 1866 | * 1867 | * Here's an example source map, taken from the source map spec[0], but 1868 | * modified to omit a section which uses the "url" field. 1869 | * 1870 | * { 1871 | * version : 3, 1872 | * file: "app.js", 1873 | * sections: [{ 1874 | * offset: {line:100, column:10}, 1875 | * map: { 1876 | * version : 3, 1877 | * file: "section.js", 1878 | * sources: ["foo.js", "bar.js"], 1879 | * names: ["src", "maps", "are", "fun"], 1880 | * mappings: "AAAA,E;;ABCDE;" 1881 | * } 1882 | * }], 1883 | * } 1884 | * 1885 | * The second parameter, if given, is a string whose value is the URL 1886 | * at which the source map was found. This URL is used to compute the 1887 | * sources array. 1888 | * 1889 | * [0]: https://docs.google.com/document/d/1U1RGAehQwRypUTovF1KRlpiOFze0b-_2gc6fAH0KY0k/edit#heading=h.535es3xeprgt 1890 | */ 1891 | function IndexedSourceMapConsumer(aSourceMap, aSourceMapURL) { 1892 | var sourceMap = aSourceMap; 1893 | if (typeof aSourceMap === 'string') { 1894 | sourceMap = util.parseSourceMapInput(aSourceMap); 1895 | } 1896 | 1897 | var version = util.getArg(sourceMap, 'version'); 1898 | var sections = util.getArg(sourceMap, 'sections'); 1899 | 1900 | if (version != this._version) { 1901 | throw new Error('Unsupported version: ' + version); 1902 | } 1903 | 1904 | this._sources = new ArraySet(); 1905 | this._names = new ArraySet(); 1906 | 1907 | var lastOffset = { 1908 | line: -1, 1909 | column: 0 1910 | }; 1911 | this._sections = sections.map(function (s) { 1912 | if (s.url) { 1913 | // The url field will require support for asynchronicity. 1914 | // See https://github.com/mozilla/source-map/issues/16 1915 | throw new Error('Support for url field in sections not implemented.'); 1916 | } 1917 | var offset = util.getArg(s, 'offset'); 1918 | var offsetLine = util.getArg(offset, 'line'); 1919 | var offsetColumn = util.getArg(offset, 'column'); 1920 | 1921 | if (offsetLine < lastOffset.line || 1922 | (offsetLine === lastOffset.line && offsetColumn < lastOffset.column)) { 1923 | throw new Error('Section offsets must be ordered and non-overlapping.'); 1924 | } 1925 | lastOffset = offset; 1926 | 1927 | return { 1928 | generatedOffset: { 1929 | // The offset fields are 0-based, but we use 1-based indices when 1930 | // encoding/decoding from VLQ. 1931 | generatedLine: offsetLine + 1, 1932 | generatedColumn: offsetColumn + 1 1933 | }, 1934 | consumer: new SourceMapConsumer(util.getArg(s, 'map'), aSourceMapURL) 1935 | } 1936 | }); 1937 | } 1938 | 1939 | IndexedSourceMapConsumer.prototype = Object.create(SourceMapConsumer.prototype); 1940 | IndexedSourceMapConsumer.prototype.constructor = SourceMapConsumer; 1941 | 1942 | /** 1943 | * The version of the source mapping spec that we are consuming. 1944 | */ 1945 | IndexedSourceMapConsumer.prototype._version = 3; 1946 | 1947 | /** 1948 | * The list of original sources. 1949 | */ 1950 | Object.defineProperty(IndexedSourceMapConsumer.prototype, 'sources', { 1951 | get: function () { 1952 | var sources = []; 1953 | for (var i = 0; i < this._sections.length; i++) { 1954 | for (var j = 0; j < this._sections[i].consumer.sources.length; j++) { 1955 | sources.push(this._sections[i].consumer.sources[j]); 1956 | } 1957 | } 1958 | return sources; 1959 | } 1960 | }); 1961 | 1962 | /** 1963 | * Returns the original source, line, and column information for the generated 1964 | * source's line and column positions provided. The only argument is an object 1965 | * with the following properties: 1966 | * 1967 | * - line: The line number in the generated source. The line number 1968 | * is 1-based. 1969 | * - column: The column number in the generated source. The column 1970 | * number is 0-based. 1971 | * 1972 | * and an object is returned with the following properties: 1973 | * 1974 | * - source: The original source file, or null. 1975 | * - line: The line number in the original source, or null. The 1976 | * line number is 1-based. 1977 | * - column: The column number in the original source, or null. The 1978 | * column number is 0-based. 1979 | * - name: The original identifier, or null. 1980 | */ 1981 | IndexedSourceMapConsumer.prototype.originalPositionFor = 1982 | function IndexedSourceMapConsumer_originalPositionFor(aArgs) { 1983 | var needle = { 1984 | generatedLine: util.getArg(aArgs, 'line'), 1985 | generatedColumn: util.getArg(aArgs, 'column') 1986 | }; 1987 | 1988 | // Find the section containing the generated position we're trying to map 1989 | // to an original position. 1990 | var sectionIndex = binarySearch.search(needle, this._sections, 1991 | function(needle, section) { 1992 | var cmp = needle.generatedLine - section.generatedOffset.generatedLine; 1993 | if (cmp) { 1994 | return cmp; 1995 | } 1996 | 1997 | return (needle.generatedColumn - 1998 | section.generatedOffset.generatedColumn); 1999 | }); 2000 | var section = this._sections[sectionIndex]; 2001 | 2002 | if (!section) { 2003 | return { 2004 | source: null, 2005 | line: null, 2006 | column: null, 2007 | name: null 2008 | }; 2009 | } 2010 | 2011 | return section.consumer.originalPositionFor({ 2012 | line: needle.generatedLine - 2013 | (section.generatedOffset.generatedLine - 1), 2014 | column: needle.generatedColumn - 2015 | (section.generatedOffset.generatedLine === needle.generatedLine 2016 | ? section.generatedOffset.generatedColumn - 1 2017 | : 0), 2018 | bias: aArgs.bias 2019 | }); 2020 | }; 2021 | 2022 | /** 2023 | * Return true if we have the source content for every source in the source 2024 | * map, false otherwise. 2025 | */ 2026 | IndexedSourceMapConsumer.prototype.hasContentsOfAllSources = 2027 | function IndexedSourceMapConsumer_hasContentsOfAllSources() { 2028 | return this._sections.every(function (s) { 2029 | return s.consumer.hasContentsOfAllSources(); 2030 | }); 2031 | }; 2032 | 2033 | /** 2034 | * Returns the original source content. The only argument is the url of the 2035 | * original source file. Returns null if no original source content is 2036 | * available. 2037 | */ 2038 | IndexedSourceMapConsumer.prototype.sourceContentFor = 2039 | function IndexedSourceMapConsumer_sourceContentFor(aSource, nullOnMissing) { 2040 | for (var i = 0; i < this._sections.length; i++) { 2041 | var section = this._sections[i]; 2042 | 2043 | var content = section.consumer.sourceContentFor(aSource, true); 2044 | if (content) { 2045 | return content; 2046 | } 2047 | } 2048 | if (nullOnMissing) { 2049 | return null; 2050 | } 2051 | else { 2052 | throw new Error('"' + aSource + '" is not in the SourceMap.'); 2053 | } 2054 | }; 2055 | 2056 | /** 2057 | * Returns the generated line and column information for the original source, 2058 | * line, and column positions provided. The only argument is an object with 2059 | * the following properties: 2060 | * 2061 | * - source: The filename of the original source. 2062 | * - line: The line number in the original source. The line number 2063 | * is 1-based. 2064 | * - column: The column number in the original source. The column 2065 | * number is 0-based. 2066 | * 2067 | * and an object is returned with the following properties: 2068 | * 2069 | * - line: The line number in the generated source, or null. The 2070 | * line number is 1-based. 2071 | * - column: The column number in the generated source, or null. 2072 | * The column number is 0-based. 2073 | */ 2074 | IndexedSourceMapConsumer.prototype.generatedPositionFor = 2075 | function IndexedSourceMapConsumer_generatedPositionFor(aArgs) { 2076 | for (var i = 0; i < this._sections.length; i++) { 2077 | var section = this._sections[i]; 2078 | 2079 | // Only consider this section if the requested source is in the list of 2080 | // sources of the consumer. 2081 | if (section.consumer._findSourceIndex(util.getArg(aArgs, 'source')) === -1) { 2082 | continue; 2083 | } 2084 | var generatedPosition = section.consumer.generatedPositionFor(aArgs); 2085 | if (generatedPosition) { 2086 | var ret = { 2087 | line: generatedPosition.line + 2088 | (section.generatedOffset.generatedLine - 1), 2089 | column: generatedPosition.column + 2090 | (section.generatedOffset.generatedLine === generatedPosition.line 2091 | ? section.generatedOffset.generatedColumn - 1 2092 | : 0) 2093 | }; 2094 | return ret; 2095 | } 2096 | } 2097 | 2098 | return { 2099 | line: null, 2100 | column: null 2101 | }; 2102 | }; 2103 | 2104 | /** 2105 | * Parse the mappings in a string in to a data structure which we can easily 2106 | * query (the ordered arrays in the `this.__generatedMappings` and 2107 | * `this.__originalMappings` properties). 2108 | */ 2109 | IndexedSourceMapConsumer.prototype._parseMappings = 2110 | function IndexedSourceMapConsumer_parseMappings(aStr, aSourceRoot) { 2111 | this.__generatedMappings = []; 2112 | this.__originalMappings = []; 2113 | for (var i = 0; i < this._sections.length; i++) { 2114 | var section = this._sections[i]; 2115 | var sectionMappings = section.consumer._generatedMappings; 2116 | for (var j = 0; j < sectionMappings.length; j++) { 2117 | var mapping = sectionMappings[j]; 2118 | 2119 | var source = section.consumer._sources.at(mapping.source); 2120 | source = util.computeSourceURL(section.consumer.sourceRoot, source, this._sourceMapURL); 2121 | this._sources.add(source); 2122 | source = this._sources.indexOf(source); 2123 | 2124 | var name = null; 2125 | if (mapping.name) { 2126 | name = section.consumer._names.at(mapping.name); 2127 | this._names.add(name); 2128 | name = this._names.indexOf(name); 2129 | } 2130 | 2131 | // The mappings coming from the consumer for the section have 2132 | // generated positions relative to the start of the section, so we 2133 | // need to offset them to be relative to the start of the concatenated 2134 | // generated file. 2135 | var adjustedMapping = { 2136 | source: source, 2137 | generatedLine: mapping.generatedLine + 2138 | (section.generatedOffset.generatedLine - 1), 2139 | generatedColumn: mapping.generatedColumn + 2140 | (section.generatedOffset.generatedLine === mapping.generatedLine 2141 | ? section.generatedOffset.generatedColumn - 1 2142 | : 0), 2143 | originalLine: mapping.originalLine, 2144 | originalColumn: mapping.originalColumn, 2145 | name: name 2146 | }; 2147 | 2148 | this.__generatedMappings.push(adjustedMapping); 2149 | if (typeof adjustedMapping.originalLine === 'number') { 2150 | this.__originalMappings.push(adjustedMapping); 2151 | } 2152 | } 2153 | } 2154 | 2155 | quickSort(this.__generatedMappings, util.compareByGeneratedPositionsDeflated); 2156 | quickSort(this.__originalMappings, util.compareByOriginalPositions); 2157 | }; 2158 | 2159 | exports.IndexedSourceMapConsumer = IndexedSourceMapConsumer; 2160 | 2161 | 2162 | /***/ }), 2163 | 2164 | /***/ 341: 2165 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 2166 | 2167 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2168 | /* 2169 | * Copyright 2011 Mozilla Foundation and contributors 2170 | * Licensed under the New BSD license. See LICENSE or: 2171 | * http://opensource.org/licenses/BSD-3-Clause 2172 | */ 2173 | 2174 | var base64VLQ = __webpack_require__(215); 2175 | var util = __webpack_require__(983); 2176 | var ArraySet = __webpack_require__(837).ArraySet; 2177 | var MappingList = __webpack_require__(740).MappingList; 2178 | 2179 | /** 2180 | * An instance of the SourceMapGenerator represents a source map which is 2181 | * being built incrementally. You may pass an object with the following 2182 | * properties: 2183 | * 2184 | * - file: The filename of the generated source. 2185 | * - sourceRoot: A root for all relative URLs in this source map. 2186 | */ 2187 | function SourceMapGenerator(aArgs) { 2188 | if (!aArgs) { 2189 | aArgs = {}; 2190 | } 2191 | this._file = util.getArg(aArgs, 'file', null); 2192 | this._sourceRoot = util.getArg(aArgs, 'sourceRoot', null); 2193 | this._skipValidation = util.getArg(aArgs, 'skipValidation', false); 2194 | this._sources = new ArraySet(); 2195 | this._names = new ArraySet(); 2196 | this._mappings = new MappingList(); 2197 | this._sourcesContents = null; 2198 | } 2199 | 2200 | SourceMapGenerator.prototype._version = 3; 2201 | 2202 | /** 2203 | * Creates a new SourceMapGenerator based on a SourceMapConsumer 2204 | * 2205 | * @param aSourceMapConsumer The SourceMap. 2206 | */ 2207 | SourceMapGenerator.fromSourceMap = 2208 | function SourceMapGenerator_fromSourceMap(aSourceMapConsumer) { 2209 | var sourceRoot = aSourceMapConsumer.sourceRoot; 2210 | var generator = new SourceMapGenerator({ 2211 | file: aSourceMapConsumer.file, 2212 | sourceRoot: sourceRoot 2213 | }); 2214 | aSourceMapConsumer.eachMapping(function (mapping) { 2215 | var newMapping = { 2216 | generated: { 2217 | line: mapping.generatedLine, 2218 | column: mapping.generatedColumn 2219 | } 2220 | }; 2221 | 2222 | if (mapping.source != null) { 2223 | newMapping.source = mapping.source; 2224 | if (sourceRoot != null) { 2225 | newMapping.source = util.relative(sourceRoot, newMapping.source); 2226 | } 2227 | 2228 | newMapping.original = { 2229 | line: mapping.originalLine, 2230 | column: mapping.originalColumn 2231 | }; 2232 | 2233 | if (mapping.name != null) { 2234 | newMapping.name = mapping.name; 2235 | } 2236 | } 2237 | 2238 | generator.addMapping(newMapping); 2239 | }); 2240 | aSourceMapConsumer.sources.forEach(function (sourceFile) { 2241 | var sourceRelative = sourceFile; 2242 | if (sourceRoot !== null) { 2243 | sourceRelative = util.relative(sourceRoot, sourceFile); 2244 | } 2245 | 2246 | if (!generator._sources.has(sourceRelative)) { 2247 | generator._sources.add(sourceRelative); 2248 | } 2249 | 2250 | var content = aSourceMapConsumer.sourceContentFor(sourceFile); 2251 | if (content != null) { 2252 | generator.setSourceContent(sourceFile, content); 2253 | } 2254 | }); 2255 | return generator; 2256 | }; 2257 | 2258 | /** 2259 | * Add a single mapping from original source line and column to the generated 2260 | * source's line and column for this source map being created. The mapping 2261 | * object should have the following properties: 2262 | * 2263 | * - generated: An object with the generated line and column positions. 2264 | * - original: An object with the original line and column positions. 2265 | * - source: The original source file (relative to the sourceRoot). 2266 | * - name: An optional original token name for this mapping. 2267 | */ 2268 | SourceMapGenerator.prototype.addMapping = 2269 | function SourceMapGenerator_addMapping(aArgs) { 2270 | var generated = util.getArg(aArgs, 'generated'); 2271 | var original = util.getArg(aArgs, 'original', null); 2272 | var source = util.getArg(aArgs, 'source', null); 2273 | var name = util.getArg(aArgs, 'name', null); 2274 | 2275 | if (!this._skipValidation) { 2276 | this._validateMapping(generated, original, source, name); 2277 | } 2278 | 2279 | if (source != null) { 2280 | source = String(source); 2281 | if (!this._sources.has(source)) { 2282 | this._sources.add(source); 2283 | } 2284 | } 2285 | 2286 | if (name != null) { 2287 | name = String(name); 2288 | if (!this._names.has(name)) { 2289 | this._names.add(name); 2290 | } 2291 | } 2292 | 2293 | this._mappings.add({ 2294 | generatedLine: generated.line, 2295 | generatedColumn: generated.column, 2296 | originalLine: original != null && original.line, 2297 | originalColumn: original != null && original.column, 2298 | source: source, 2299 | name: name 2300 | }); 2301 | }; 2302 | 2303 | /** 2304 | * Set the source content for a source file. 2305 | */ 2306 | SourceMapGenerator.prototype.setSourceContent = 2307 | function SourceMapGenerator_setSourceContent(aSourceFile, aSourceContent) { 2308 | var source = aSourceFile; 2309 | if (this._sourceRoot != null) { 2310 | source = util.relative(this._sourceRoot, source); 2311 | } 2312 | 2313 | if (aSourceContent != null) { 2314 | // Add the source content to the _sourcesContents map. 2315 | // Create a new _sourcesContents map if the property is null. 2316 | if (!this._sourcesContents) { 2317 | this._sourcesContents = Object.create(null); 2318 | } 2319 | this._sourcesContents[util.toSetString(source)] = aSourceContent; 2320 | } else if (this._sourcesContents) { 2321 | // Remove the source file from the _sourcesContents map. 2322 | // If the _sourcesContents map is empty, set the property to null. 2323 | delete this._sourcesContents[util.toSetString(source)]; 2324 | if (Object.keys(this._sourcesContents).length === 0) { 2325 | this._sourcesContents = null; 2326 | } 2327 | } 2328 | }; 2329 | 2330 | /** 2331 | * Applies the mappings of a sub-source-map for a specific source file to the 2332 | * source map being generated. Each mapping to the supplied source file is 2333 | * rewritten using the supplied source map. Note: The resolution for the 2334 | * resulting mappings is the minimium of this map and the supplied map. 2335 | * 2336 | * @param aSourceMapConsumer The source map to be applied. 2337 | * @param aSourceFile Optional. The filename of the source file. 2338 | * If omitted, SourceMapConsumer's file property will be used. 2339 | * @param aSourceMapPath Optional. The dirname of the path to the source map 2340 | * to be applied. If relative, it is relative to the SourceMapConsumer. 2341 | * This parameter is needed when the two source maps aren't in the same 2342 | * directory, and the source map to be applied contains relative source 2343 | * paths. If so, those relative source paths need to be rewritten 2344 | * relative to the SourceMapGenerator. 2345 | */ 2346 | SourceMapGenerator.prototype.applySourceMap = 2347 | function SourceMapGenerator_applySourceMap(aSourceMapConsumer, aSourceFile, aSourceMapPath) { 2348 | var sourceFile = aSourceFile; 2349 | // If aSourceFile is omitted, we will use the file property of the SourceMap 2350 | if (aSourceFile == null) { 2351 | if (aSourceMapConsumer.file == null) { 2352 | throw new Error( 2353 | 'SourceMapGenerator.prototype.applySourceMap requires either an explicit source file, ' + 2354 | 'or the source map\'s "file" property. Both were omitted.' 2355 | ); 2356 | } 2357 | sourceFile = aSourceMapConsumer.file; 2358 | } 2359 | var sourceRoot = this._sourceRoot; 2360 | // Make "sourceFile" relative if an absolute Url is passed. 2361 | if (sourceRoot != null) { 2362 | sourceFile = util.relative(sourceRoot, sourceFile); 2363 | } 2364 | // Applying the SourceMap can add and remove items from the sources and 2365 | // the names array. 2366 | var newSources = new ArraySet(); 2367 | var newNames = new ArraySet(); 2368 | 2369 | // Find mappings for the "sourceFile" 2370 | this._mappings.unsortedForEach(function (mapping) { 2371 | if (mapping.source === sourceFile && mapping.originalLine != null) { 2372 | // Check if it can be mapped by the source map, then update the mapping. 2373 | var original = aSourceMapConsumer.originalPositionFor({ 2374 | line: mapping.originalLine, 2375 | column: mapping.originalColumn 2376 | }); 2377 | if (original.source != null) { 2378 | // Copy mapping 2379 | mapping.source = original.source; 2380 | if (aSourceMapPath != null) { 2381 | mapping.source = util.join(aSourceMapPath, mapping.source) 2382 | } 2383 | if (sourceRoot != null) { 2384 | mapping.source = util.relative(sourceRoot, mapping.source); 2385 | } 2386 | mapping.originalLine = original.line; 2387 | mapping.originalColumn = original.column; 2388 | if (original.name != null) { 2389 | mapping.name = original.name; 2390 | } 2391 | } 2392 | } 2393 | 2394 | var source = mapping.source; 2395 | if (source != null && !newSources.has(source)) { 2396 | newSources.add(source); 2397 | } 2398 | 2399 | var name = mapping.name; 2400 | if (name != null && !newNames.has(name)) { 2401 | newNames.add(name); 2402 | } 2403 | 2404 | }, this); 2405 | this._sources = newSources; 2406 | this._names = newNames; 2407 | 2408 | // Copy sourcesContents of applied map. 2409 | aSourceMapConsumer.sources.forEach(function (sourceFile) { 2410 | var content = aSourceMapConsumer.sourceContentFor(sourceFile); 2411 | if (content != null) { 2412 | if (aSourceMapPath != null) { 2413 | sourceFile = util.join(aSourceMapPath, sourceFile); 2414 | } 2415 | if (sourceRoot != null) { 2416 | sourceFile = util.relative(sourceRoot, sourceFile); 2417 | } 2418 | this.setSourceContent(sourceFile, content); 2419 | } 2420 | }, this); 2421 | }; 2422 | 2423 | /** 2424 | * A mapping can have one of the three levels of data: 2425 | * 2426 | * 1. Just the generated position. 2427 | * 2. The Generated position, original position, and original source. 2428 | * 3. Generated and original position, original source, as well as a name 2429 | * token. 2430 | * 2431 | * To maintain consistency, we validate that any new mapping being added falls 2432 | * in to one of these categories. 2433 | */ 2434 | SourceMapGenerator.prototype._validateMapping = 2435 | function SourceMapGenerator_validateMapping(aGenerated, aOriginal, aSource, 2436 | aName) { 2437 | // When aOriginal is truthy but has empty values for .line and .column, 2438 | // it is most likely a programmer error. In this case we throw a very 2439 | // specific error message to try to guide them the right way. 2440 | // For example: https://github.com/Polymer/polymer-bundler/pull/519 2441 | if (aOriginal && typeof aOriginal.line !== 'number' && typeof aOriginal.column !== 'number') { 2442 | throw new Error( 2443 | 'original.line and original.column are not numbers -- you probably meant to omit ' + 2444 | 'the original mapping entirely and only map the generated position. If so, pass ' + 2445 | 'null for the original mapping instead of an object with empty or null values.' 2446 | ); 2447 | } 2448 | 2449 | if (aGenerated && 'line' in aGenerated && 'column' in aGenerated 2450 | && aGenerated.line > 0 && aGenerated.column >= 0 2451 | && !aOriginal && !aSource && !aName) { 2452 | // Case 1. 2453 | return; 2454 | } 2455 | else if (aGenerated && 'line' in aGenerated && 'column' in aGenerated 2456 | && aOriginal && 'line' in aOriginal && 'column' in aOriginal 2457 | && aGenerated.line > 0 && aGenerated.column >= 0 2458 | && aOriginal.line > 0 && aOriginal.column >= 0 2459 | && aSource) { 2460 | // Cases 2 and 3. 2461 | return; 2462 | } 2463 | else { 2464 | throw new Error('Invalid mapping: ' + JSON.stringify({ 2465 | generated: aGenerated, 2466 | source: aSource, 2467 | original: aOriginal, 2468 | name: aName 2469 | })); 2470 | } 2471 | }; 2472 | 2473 | /** 2474 | * Serialize the accumulated mappings in to the stream of base 64 VLQs 2475 | * specified by the source map format. 2476 | */ 2477 | SourceMapGenerator.prototype._serializeMappings = 2478 | function SourceMapGenerator_serializeMappings() { 2479 | var previousGeneratedColumn = 0; 2480 | var previousGeneratedLine = 1; 2481 | var previousOriginalColumn = 0; 2482 | var previousOriginalLine = 0; 2483 | var previousName = 0; 2484 | var previousSource = 0; 2485 | var result = ''; 2486 | var next; 2487 | var mapping; 2488 | var nameIdx; 2489 | var sourceIdx; 2490 | 2491 | var mappings = this._mappings.toArray(); 2492 | for (var i = 0, len = mappings.length; i < len; i++) { 2493 | mapping = mappings[i]; 2494 | next = '' 2495 | 2496 | if (mapping.generatedLine !== previousGeneratedLine) { 2497 | previousGeneratedColumn = 0; 2498 | while (mapping.generatedLine !== previousGeneratedLine) { 2499 | next += ';'; 2500 | previousGeneratedLine++; 2501 | } 2502 | } 2503 | else { 2504 | if (i > 0) { 2505 | if (!util.compareByGeneratedPositionsInflated(mapping, mappings[i - 1])) { 2506 | continue; 2507 | } 2508 | next += ','; 2509 | } 2510 | } 2511 | 2512 | next += base64VLQ.encode(mapping.generatedColumn 2513 | - previousGeneratedColumn); 2514 | previousGeneratedColumn = mapping.generatedColumn; 2515 | 2516 | if (mapping.source != null) { 2517 | sourceIdx = this._sources.indexOf(mapping.source); 2518 | next += base64VLQ.encode(sourceIdx - previousSource); 2519 | previousSource = sourceIdx; 2520 | 2521 | // lines are stored 0-based in SourceMap spec version 3 2522 | next += base64VLQ.encode(mapping.originalLine - 1 2523 | - previousOriginalLine); 2524 | previousOriginalLine = mapping.originalLine - 1; 2525 | 2526 | next += base64VLQ.encode(mapping.originalColumn 2527 | - previousOriginalColumn); 2528 | previousOriginalColumn = mapping.originalColumn; 2529 | 2530 | if (mapping.name != null) { 2531 | nameIdx = this._names.indexOf(mapping.name); 2532 | next += base64VLQ.encode(nameIdx - previousName); 2533 | previousName = nameIdx; 2534 | } 2535 | } 2536 | 2537 | result += next; 2538 | } 2539 | 2540 | return result; 2541 | }; 2542 | 2543 | SourceMapGenerator.prototype._generateSourcesContent = 2544 | function SourceMapGenerator_generateSourcesContent(aSources, aSourceRoot) { 2545 | return aSources.map(function (source) { 2546 | if (!this._sourcesContents) { 2547 | return null; 2548 | } 2549 | if (aSourceRoot != null) { 2550 | source = util.relative(aSourceRoot, source); 2551 | } 2552 | var key = util.toSetString(source); 2553 | return Object.prototype.hasOwnProperty.call(this._sourcesContents, key) 2554 | ? this._sourcesContents[key] 2555 | : null; 2556 | }, this); 2557 | }; 2558 | 2559 | /** 2560 | * Externalize the source map. 2561 | */ 2562 | SourceMapGenerator.prototype.toJSON = 2563 | function SourceMapGenerator_toJSON() { 2564 | var map = { 2565 | version: this._version, 2566 | sources: this._sources.toArray(), 2567 | names: this._names.toArray(), 2568 | mappings: this._serializeMappings() 2569 | }; 2570 | if (this._file != null) { 2571 | map.file = this._file; 2572 | } 2573 | if (this._sourceRoot != null) { 2574 | map.sourceRoot = this._sourceRoot; 2575 | } 2576 | if (this._sourcesContents) { 2577 | map.sourcesContent = this._generateSourcesContent(map.sources, map.sourceRoot); 2578 | } 2579 | 2580 | return map; 2581 | }; 2582 | 2583 | /** 2584 | * Render the source map being generated to a string. 2585 | */ 2586 | SourceMapGenerator.prototype.toString = 2587 | function SourceMapGenerator_toString() { 2588 | return JSON.stringify(this.toJSON()); 2589 | }; 2590 | 2591 | exports.SourceMapGenerator = SourceMapGenerator; 2592 | 2593 | 2594 | /***/ }), 2595 | 2596 | /***/ 537: 2597 | /***/ (function(__unusedmodule, exports) { 2598 | 2599 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2600 | /* 2601 | * Copyright 2011 Mozilla Foundation and contributors 2602 | * Licensed under the New BSD license. See LICENSE or: 2603 | * http://opensource.org/licenses/BSD-3-Clause 2604 | */ 2605 | 2606 | var intToCharMap = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/'.split(''); 2607 | 2608 | /** 2609 | * Encode an integer in the range of 0 to 63 to a single base 64 digit. 2610 | */ 2611 | exports.encode = function (number) { 2612 | if (0 <= number && number < intToCharMap.length) { 2613 | return intToCharMap[number]; 2614 | } 2615 | throw new TypeError("Must be between 0 and 63: " + number); 2616 | }; 2617 | 2618 | /** 2619 | * Decode a single base 64 character code digit to an integer. Returns -1 on 2620 | * failure. 2621 | */ 2622 | exports.decode = function (charCode) { 2623 | var bigA = 65; // 'A' 2624 | var bigZ = 90; // 'Z' 2625 | 2626 | var littleA = 97; // 'a' 2627 | var littleZ = 122; // 'z' 2628 | 2629 | var zero = 48; // '0' 2630 | var nine = 57; // '9' 2631 | 2632 | var plus = 43; // '+' 2633 | var slash = 47; // '/' 2634 | 2635 | var littleOffset = 26; 2636 | var numberOffset = 52; 2637 | 2638 | // 0 - 25: ABCDEFGHIJKLMNOPQRSTUVWXYZ 2639 | if (bigA <= charCode && charCode <= bigZ) { 2640 | return (charCode - bigA); 2641 | } 2642 | 2643 | // 26 - 51: abcdefghijklmnopqrstuvwxyz 2644 | if (littleA <= charCode && charCode <= littleZ) { 2645 | return (charCode - littleA + littleOffset); 2646 | } 2647 | 2648 | // 52 - 61: 0123456789 2649 | if (zero <= charCode && charCode <= nine) { 2650 | return (charCode - zero + numberOffset); 2651 | } 2652 | 2653 | // 62: + 2654 | if (charCode == plus) { 2655 | return 62; 2656 | } 2657 | 2658 | // 63: / 2659 | if (charCode == slash) { 2660 | return 63; 2661 | } 2662 | 2663 | // Invalid base64 digit. 2664 | return -1; 2665 | }; 2666 | 2667 | 2668 | /***/ }), 2669 | 2670 | /***/ 596: 2671 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 2672 | 2673 | /* 2674 | * Copyright 2009-2011 Mozilla Foundation and contributors 2675 | * Licensed under the New BSD license. See LICENSE.txt or: 2676 | * http://opensource.org/licenses/BSD-3-Clause 2677 | */ 2678 | exports.SourceMapGenerator = __webpack_require__(341).SourceMapGenerator; 2679 | exports.SourceMapConsumer = __webpack_require__(327).SourceMapConsumer; 2680 | exports.SourceNode = __webpack_require__(990).SourceNode; 2681 | 2682 | 2683 | /***/ }), 2684 | 2685 | /***/ 622: 2686 | /***/ (function(module) { 2687 | 2688 | module.exports = require("path"); 2689 | 2690 | /***/ }), 2691 | 2692 | /***/ 645: 2693 | /***/ (function(__unusedmodule, __unusedexports, __webpack_require__) { 2694 | 2695 | __webpack_require__(284).install(); 2696 | 2697 | 2698 | /***/ }), 2699 | 2700 | /***/ 650: 2701 | /***/ (function(module) { 2702 | 2703 | var toString = Object.prototype.toString 2704 | 2705 | var isModern = ( 2706 | typeof Buffer.alloc === 'function' && 2707 | typeof Buffer.allocUnsafe === 'function' && 2708 | typeof Buffer.from === 'function' 2709 | ) 2710 | 2711 | function isArrayBuffer (input) { 2712 | return toString.call(input).slice(8, -1) === 'ArrayBuffer' 2713 | } 2714 | 2715 | function fromArrayBuffer (obj, byteOffset, length) { 2716 | byteOffset >>>= 0 2717 | 2718 | var maxLength = obj.byteLength - byteOffset 2719 | 2720 | if (maxLength < 0) { 2721 | throw new RangeError("'offset' is out of bounds") 2722 | } 2723 | 2724 | if (length === undefined) { 2725 | length = maxLength 2726 | } else { 2727 | length >>>= 0 2728 | 2729 | if (length > maxLength) { 2730 | throw new RangeError("'length' is out of bounds") 2731 | } 2732 | } 2733 | 2734 | return isModern 2735 | ? Buffer.from(obj.slice(byteOffset, byteOffset + length)) 2736 | : new Buffer(new Uint8Array(obj.slice(byteOffset, byteOffset + length))) 2737 | } 2738 | 2739 | function fromString (string, encoding) { 2740 | if (typeof encoding !== 'string' || encoding === '') { 2741 | encoding = 'utf8' 2742 | } 2743 | 2744 | if (!Buffer.isEncoding(encoding)) { 2745 | throw new TypeError('"encoding" must be a valid string encoding') 2746 | } 2747 | 2748 | return isModern 2749 | ? Buffer.from(string, encoding) 2750 | : new Buffer(string, encoding) 2751 | } 2752 | 2753 | function bufferFrom (value, encodingOrOffset, length) { 2754 | if (typeof value === 'number') { 2755 | throw new TypeError('"value" argument must not be a number') 2756 | } 2757 | 2758 | if (isArrayBuffer(value)) { 2759 | return fromArrayBuffer(value, encodingOrOffset, length) 2760 | } 2761 | 2762 | if (typeof value === 'string') { 2763 | return fromString(value, encodingOrOffset) 2764 | } 2765 | 2766 | return isModern 2767 | ? Buffer.from(value) 2768 | : new Buffer(value) 2769 | } 2770 | 2771 | module.exports = bufferFrom 2772 | 2773 | 2774 | /***/ }), 2775 | 2776 | /***/ 740: 2777 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 2778 | 2779 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2780 | /* 2781 | * Copyright 2014 Mozilla Foundation and contributors 2782 | * Licensed under the New BSD license. See LICENSE or: 2783 | * http://opensource.org/licenses/BSD-3-Clause 2784 | */ 2785 | 2786 | var util = __webpack_require__(983); 2787 | 2788 | /** 2789 | * Determine whether mappingB is after mappingA with respect to generated 2790 | * position. 2791 | */ 2792 | function generatedPositionAfter(mappingA, mappingB) { 2793 | // Optimized for most common case 2794 | var lineA = mappingA.generatedLine; 2795 | var lineB = mappingB.generatedLine; 2796 | var columnA = mappingA.generatedColumn; 2797 | var columnB = mappingB.generatedColumn; 2798 | return lineB > lineA || lineB == lineA && columnB >= columnA || 2799 | util.compareByGeneratedPositionsInflated(mappingA, mappingB) <= 0; 2800 | } 2801 | 2802 | /** 2803 | * A data structure to provide a sorted view of accumulated mappings in a 2804 | * performance conscious manner. It trades a neglibable overhead in general 2805 | * case for a large speedup in case of mappings being added in order. 2806 | */ 2807 | function MappingList() { 2808 | this._array = []; 2809 | this._sorted = true; 2810 | // Serves as infimum 2811 | this._last = {generatedLine: -1, generatedColumn: 0}; 2812 | } 2813 | 2814 | /** 2815 | * Iterate through internal items. This method takes the same arguments that 2816 | * `Array.prototype.forEach` takes. 2817 | * 2818 | * NOTE: The order of the mappings is NOT guaranteed. 2819 | */ 2820 | MappingList.prototype.unsortedForEach = 2821 | function MappingList_forEach(aCallback, aThisArg) { 2822 | this._array.forEach(aCallback, aThisArg); 2823 | }; 2824 | 2825 | /** 2826 | * Add the given source mapping. 2827 | * 2828 | * @param Object aMapping 2829 | */ 2830 | MappingList.prototype.add = function MappingList_add(aMapping) { 2831 | if (generatedPositionAfter(this._last, aMapping)) { 2832 | this._last = aMapping; 2833 | this._array.push(aMapping); 2834 | } else { 2835 | this._sorted = false; 2836 | this._array.push(aMapping); 2837 | } 2838 | }; 2839 | 2840 | /** 2841 | * Returns the flat, sorted array of mappings. The mappings are sorted by 2842 | * generated position. 2843 | * 2844 | * WARNING: This method returns internal data without copying, for 2845 | * performance. The return value must NOT be mutated, and should be treated as 2846 | * an immutable borrow. If you want to take ownership, you must make your own 2847 | * copy. 2848 | */ 2849 | MappingList.prototype.toArray = function MappingList_toArray() { 2850 | if (!this._sorted) { 2851 | this._array.sort(util.compareByGeneratedPositionsInflated); 2852 | this._sorted = true; 2853 | } 2854 | return this._array; 2855 | }; 2856 | 2857 | exports.MappingList = MappingList; 2858 | 2859 | 2860 | /***/ }), 2861 | 2862 | /***/ 747: 2863 | /***/ (function(module) { 2864 | 2865 | module.exports = require("fs"); 2866 | 2867 | /***/ }), 2868 | 2869 | /***/ 837: 2870 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 2871 | 2872 | /* -*- Mode: js; js-indent-level: 2; -*- */ 2873 | /* 2874 | * Copyright 2011 Mozilla Foundation and contributors 2875 | * Licensed under the New BSD license. See LICENSE or: 2876 | * http://opensource.org/licenses/BSD-3-Clause 2877 | */ 2878 | 2879 | var util = __webpack_require__(983); 2880 | var has = Object.prototype.hasOwnProperty; 2881 | var hasNativeMap = typeof Map !== "undefined"; 2882 | 2883 | /** 2884 | * A data structure which is a combination of an array and a set. Adding a new 2885 | * member is O(1), testing for membership is O(1), and finding the index of an 2886 | * element is O(1). Removing elements from the set is not supported. Only 2887 | * strings are supported for membership. 2888 | */ 2889 | function ArraySet() { 2890 | this._array = []; 2891 | this._set = hasNativeMap ? new Map() : Object.create(null); 2892 | } 2893 | 2894 | /** 2895 | * Static method for creating ArraySet instances from an existing array. 2896 | */ 2897 | ArraySet.fromArray = function ArraySet_fromArray(aArray, aAllowDuplicates) { 2898 | var set = new ArraySet(); 2899 | for (var i = 0, len = aArray.length; i < len; i++) { 2900 | set.add(aArray[i], aAllowDuplicates); 2901 | } 2902 | return set; 2903 | }; 2904 | 2905 | /** 2906 | * Return how many unique items are in this ArraySet. If duplicates have been 2907 | * added, than those do not count towards the size. 2908 | * 2909 | * @returns Number 2910 | */ 2911 | ArraySet.prototype.size = function ArraySet_size() { 2912 | return hasNativeMap ? this._set.size : Object.getOwnPropertyNames(this._set).length; 2913 | }; 2914 | 2915 | /** 2916 | * Add the given string to this set. 2917 | * 2918 | * @param String aStr 2919 | */ 2920 | ArraySet.prototype.add = function ArraySet_add(aStr, aAllowDuplicates) { 2921 | var sStr = hasNativeMap ? aStr : util.toSetString(aStr); 2922 | var isDuplicate = hasNativeMap ? this.has(aStr) : has.call(this._set, sStr); 2923 | var idx = this._array.length; 2924 | if (!isDuplicate || aAllowDuplicates) { 2925 | this._array.push(aStr); 2926 | } 2927 | if (!isDuplicate) { 2928 | if (hasNativeMap) { 2929 | this._set.set(aStr, idx); 2930 | } else { 2931 | this._set[sStr] = idx; 2932 | } 2933 | } 2934 | }; 2935 | 2936 | /** 2937 | * Is the given string a member of this set? 2938 | * 2939 | * @param String aStr 2940 | */ 2941 | ArraySet.prototype.has = function ArraySet_has(aStr) { 2942 | if (hasNativeMap) { 2943 | return this._set.has(aStr); 2944 | } else { 2945 | var sStr = util.toSetString(aStr); 2946 | return has.call(this._set, sStr); 2947 | } 2948 | }; 2949 | 2950 | /** 2951 | * What is the index of the given string in the array? 2952 | * 2953 | * @param String aStr 2954 | */ 2955 | ArraySet.prototype.indexOf = function ArraySet_indexOf(aStr) { 2956 | if (hasNativeMap) { 2957 | var idx = this._set.get(aStr); 2958 | if (idx >= 0) { 2959 | return idx; 2960 | } 2961 | } else { 2962 | var sStr = util.toSetString(aStr); 2963 | if (has.call(this._set, sStr)) { 2964 | return this._set[sStr]; 2965 | } 2966 | } 2967 | 2968 | throw new Error('"' + aStr + '" is not in the set.'); 2969 | }; 2970 | 2971 | /** 2972 | * What is the element at the given index? 2973 | * 2974 | * @param Number aIdx 2975 | */ 2976 | ArraySet.prototype.at = function ArraySet_at(aIdx) { 2977 | if (aIdx >= 0 && aIdx < this._array.length) { 2978 | return this._array[aIdx]; 2979 | } 2980 | throw new Error('No element indexed by ' + aIdx); 2981 | }; 2982 | 2983 | /** 2984 | * Returns the array representation of this set (which has the proper indices 2985 | * indicated by indexOf). Note that this is a copy of the internal array used 2986 | * for storing the members so that no one can mess with internal state. 2987 | */ 2988 | ArraySet.prototype.toArray = function ArraySet_toArray() { 2989 | return this._array.slice(); 2990 | }; 2991 | 2992 | exports.ArraySet = ArraySet; 2993 | 2994 | 2995 | /***/ }), 2996 | 2997 | /***/ 983: 2998 | /***/ (function(__unusedmodule, exports) { 2999 | 3000 | /* -*- Mode: js; js-indent-level: 2; -*- */ 3001 | /* 3002 | * Copyright 2011 Mozilla Foundation and contributors 3003 | * Licensed under the New BSD license. See LICENSE or: 3004 | * http://opensource.org/licenses/BSD-3-Clause 3005 | */ 3006 | 3007 | /** 3008 | * This is a helper function for getting values from parameter/options 3009 | * objects. 3010 | * 3011 | * @param args The object we are extracting values from 3012 | * @param name The name of the property we are getting. 3013 | * @param defaultValue An optional value to return if the property is missing 3014 | * from the object. If this is not specified and the property is missing, an 3015 | * error will be thrown. 3016 | */ 3017 | function getArg(aArgs, aName, aDefaultValue) { 3018 | if (aName in aArgs) { 3019 | return aArgs[aName]; 3020 | } else if (arguments.length === 3) { 3021 | return aDefaultValue; 3022 | } else { 3023 | throw new Error('"' + aName + '" is a required argument.'); 3024 | } 3025 | } 3026 | exports.getArg = getArg; 3027 | 3028 | var urlRegexp = /^(?:([\w+\-.]+):)?\/\/(?:(\w+:\w+)@)?([\w.-]*)(?::(\d+))?(.*)$/; 3029 | var dataUrlRegexp = /^data:.+\,.+$/; 3030 | 3031 | function urlParse(aUrl) { 3032 | var match = aUrl.match(urlRegexp); 3033 | if (!match) { 3034 | return null; 3035 | } 3036 | return { 3037 | scheme: match[1], 3038 | auth: match[2], 3039 | host: match[3], 3040 | port: match[4], 3041 | path: match[5] 3042 | }; 3043 | } 3044 | exports.urlParse = urlParse; 3045 | 3046 | function urlGenerate(aParsedUrl) { 3047 | var url = ''; 3048 | if (aParsedUrl.scheme) { 3049 | url += aParsedUrl.scheme + ':'; 3050 | } 3051 | url += '//'; 3052 | if (aParsedUrl.auth) { 3053 | url += aParsedUrl.auth + '@'; 3054 | } 3055 | if (aParsedUrl.host) { 3056 | url += aParsedUrl.host; 3057 | } 3058 | if (aParsedUrl.port) { 3059 | url += ":" + aParsedUrl.port 3060 | } 3061 | if (aParsedUrl.path) { 3062 | url += aParsedUrl.path; 3063 | } 3064 | return url; 3065 | } 3066 | exports.urlGenerate = urlGenerate; 3067 | 3068 | /** 3069 | * Normalizes a path, or the path portion of a URL: 3070 | * 3071 | * - Replaces consecutive slashes with one slash. 3072 | * - Removes unnecessary '.' parts. 3073 | * - Removes unnecessary '/..' parts. 3074 | * 3075 | * Based on code in the Node.js 'path' core module. 3076 | * 3077 | * @param aPath The path or url to normalize. 3078 | */ 3079 | function normalize(aPath) { 3080 | var path = aPath; 3081 | var url = urlParse(aPath); 3082 | if (url) { 3083 | if (!url.path) { 3084 | return aPath; 3085 | } 3086 | path = url.path; 3087 | } 3088 | var isAbsolute = exports.isAbsolute(path); 3089 | 3090 | var parts = path.split(/\/+/); 3091 | for (var part, up = 0, i = parts.length - 1; i >= 0; i--) { 3092 | part = parts[i]; 3093 | if (part === '.') { 3094 | parts.splice(i, 1); 3095 | } else if (part === '..') { 3096 | up++; 3097 | } else if (up > 0) { 3098 | if (part === '') { 3099 | // The first part is blank if the path is absolute. Trying to go 3100 | // above the root is a no-op. Therefore we can remove all '..' parts 3101 | // directly after the root. 3102 | parts.splice(i + 1, up); 3103 | up = 0; 3104 | } else { 3105 | parts.splice(i, 2); 3106 | up--; 3107 | } 3108 | } 3109 | } 3110 | path = parts.join('/'); 3111 | 3112 | if (path === '') { 3113 | path = isAbsolute ? '/' : '.'; 3114 | } 3115 | 3116 | if (url) { 3117 | url.path = path; 3118 | return urlGenerate(url); 3119 | } 3120 | return path; 3121 | } 3122 | exports.normalize = normalize; 3123 | 3124 | /** 3125 | * Joins two paths/URLs. 3126 | * 3127 | * @param aRoot The root path or URL. 3128 | * @param aPath The path or URL to be joined with the root. 3129 | * 3130 | * - If aPath is a URL or a data URI, aPath is returned, unless aPath is a 3131 | * scheme-relative URL: Then the scheme of aRoot, if any, is prepended 3132 | * first. 3133 | * - Otherwise aPath is a path. If aRoot is a URL, then its path portion 3134 | * is updated with the result and aRoot is returned. Otherwise the result 3135 | * is returned. 3136 | * - If aPath is absolute, the result is aPath. 3137 | * - Otherwise the two paths are joined with a slash. 3138 | * - Joining for example 'http://' and 'www.example.com' is also supported. 3139 | */ 3140 | function join(aRoot, aPath) { 3141 | if (aRoot === "") { 3142 | aRoot = "."; 3143 | } 3144 | if (aPath === "") { 3145 | aPath = "."; 3146 | } 3147 | var aPathUrl = urlParse(aPath); 3148 | var aRootUrl = urlParse(aRoot); 3149 | if (aRootUrl) { 3150 | aRoot = aRootUrl.path || '/'; 3151 | } 3152 | 3153 | // `join(foo, '//www.example.org')` 3154 | if (aPathUrl && !aPathUrl.scheme) { 3155 | if (aRootUrl) { 3156 | aPathUrl.scheme = aRootUrl.scheme; 3157 | } 3158 | return urlGenerate(aPathUrl); 3159 | } 3160 | 3161 | if (aPathUrl || aPath.match(dataUrlRegexp)) { 3162 | return aPath; 3163 | } 3164 | 3165 | // `join('http://', 'www.example.com')` 3166 | if (aRootUrl && !aRootUrl.host && !aRootUrl.path) { 3167 | aRootUrl.host = aPath; 3168 | return urlGenerate(aRootUrl); 3169 | } 3170 | 3171 | var joined = aPath.charAt(0) === '/' 3172 | ? aPath 3173 | : normalize(aRoot.replace(/\/+$/, '') + '/' + aPath); 3174 | 3175 | if (aRootUrl) { 3176 | aRootUrl.path = joined; 3177 | return urlGenerate(aRootUrl); 3178 | } 3179 | return joined; 3180 | } 3181 | exports.join = join; 3182 | 3183 | exports.isAbsolute = function (aPath) { 3184 | return aPath.charAt(0) === '/' || urlRegexp.test(aPath); 3185 | }; 3186 | 3187 | /** 3188 | * Make a path relative to a URL or another path. 3189 | * 3190 | * @param aRoot The root path or URL. 3191 | * @param aPath The path or URL to be made relative to aRoot. 3192 | */ 3193 | function relative(aRoot, aPath) { 3194 | if (aRoot === "") { 3195 | aRoot = "."; 3196 | } 3197 | 3198 | aRoot = aRoot.replace(/\/$/, ''); 3199 | 3200 | // It is possible for the path to be above the root. In this case, simply 3201 | // checking whether the root is a prefix of the path won't work. Instead, we 3202 | // need to remove components from the root one by one, until either we find 3203 | // a prefix that fits, or we run out of components to remove. 3204 | var level = 0; 3205 | while (aPath.indexOf(aRoot + '/') !== 0) { 3206 | var index = aRoot.lastIndexOf("/"); 3207 | if (index < 0) { 3208 | return aPath; 3209 | } 3210 | 3211 | // If the only part of the root that is left is the scheme (i.e. http://, 3212 | // file:///, etc.), one or more slashes (/), or simply nothing at all, we 3213 | // have exhausted all components, so the path is not relative to the root. 3214 | aRoot = aRoot.slice(0, index); 3215 | if (aRoot.match(/^([^\/]+:\/)?\/*$/)) { 3216 | return aPath; 3217 | } 3218 | 3219 | ++level; 3220 | } 3221 | 3222 | // Make sure we add a "../" for each component we removed from the root. 3223 | return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1); 3224 | } 3225 | exports.relative = relative; 3226 | 3227 | var supportsNullProto = (function () { 3228 | var obj = Object.create(null); 3229 | return !('__proto__' in obj); 3230 | }()); 3231 | 3232 | function identity (s) { 3233 | return s; 3234 | } 3235 | 3236 | /** 3237 | * Because behavior goes wacky when you set `__proto__` on objects, we 3238 | * have to prefix all the strings in our set with an arbitrary character. 3239 | * 3240 | * See https://github.com/mozilla/source-map/pull/31 and 3241 | * https://github.com/mozilla/source-map/issues/30 3242 | * 3243 | * @param String aStr 3244 | */ 3245 | function toSetString(aStr) { 3246 | if (isProtoString(aStr)) { 3247 | return '$' + aStr; 3248 | } 3249 | 3250 | return aStr; 3251 | } 3252 | exports.toSetString = supportsNullProto ? identity : toSetString; 3253 | 3254 | function fromSetString(aStr) { 3255 | if (isProtoString(aStr)) { 3256 | return aStr.slice(1); 3257 | } 3258 | 3259 | return aStr; 3260 | } 3261 | exports.fromSetString = supportsNullProto ? identity : fromSetString; 3262 | 3263 | function isProtoString(s) { 3264 | if (!s) { 3265 | return false; 3266 | } 3267 | 3268 | var length = s.length; 3269 | 3270 | if (length < 9 /* "__proto__".length */) { 3271 | return false; 3272 | } 3273 | 3274 | if (s.charCodeAt(length - 1) !== 95 /* '_' */ || 3275 | s.charCodeAt(length - 2) !== 95 /* '_' */ || 3276 | s.charCodeAt(length - 3) !== 111 /* 'o' */ || 3277 | s.charCodeAt(length - 4) !== 116 /* 't' */ || 3278 | s.charCodeAt(length - 5) !== 111 /* 'o' */ || 3279 | s.charCodeAt(length - 6) !== 114 /* 'r' */ || 3280 | s.charCodeAt(length - 7) !== 112 /* 'p' */ || 3281 | s.charCodeAt(length - 8) !== 95 /* '_' */ || 3282 | s.charCodeAt(length - 9) !== 95 /* '_' */) { 3283 | return false; 3284 | } 3285 | 3286 | for (var i = length - 10; i >= 0; i--) { 3287 | if (s.charCodeAt(i) !== 36 /* '$' */) { 3288 | return false; 3289 | } 3290 | } 3291 | 3292 | return true; 3293 | } 3294 | 3295 | /** 3296 | * Comparator between two mappings where the original positions are compared. 3297 | * 3298 | * Optionally pass in `true` as `onlyCompareGenerated` to consider two 3299 | * mappings with the same original source/line/column, but different generated 3300 | * line and column the same. Useful when searching for a mapping with a 3301 | * stubbed out mapping. 3302 | */ 3303 | function compareByOriginalPositions(mappingA, mappingB, onlyCompareOriginal) { 3304 | var cmp = strcmp(mappingA.source, mappingB.source); 3305 | if (cmp !== 0) { 3306 | return cmp; 3307 | } 3308 | 3309 | cmp = mappingA.originalLine - mappingB.originalLine; 3310 | if (cmp !== 0) { 3311 | return cmp; 3312 | } 3313 | 3314 | cmp = mappingA.originalColumn - mappingB.originalColumn; 3315 | if (cmp !== 0 || onlyCompareOriginal) { 3316 | return cmp; 3317 | } 3318 | 3319 | cmp = mappingA.generatedColumn - mappingB.generatedColumn; 3320 | if (cmp !== 0) { 3321 | return cmp; 3322 | } 3323 | 3324 | cmp = mappingA.generatedLine - mappingB.generatedLine; 3325 | if (cmp !== 0) { 3326 | return cmp; 3327 | } 3328 | 3329 | return strcmp(mappingA.name, mappingB.name); 3330 | } 3331 | exports.compareByOriginalPositions = compareByOriginalPositions; 3332 | 3333 | /** 3334 | * Comparator between two mappings with deflated source and name indices where 3335 | * the generated positions are compared. 3336 | * 3337 | * Optionally pass in `true` as `onlyCompareGenerated` to consider two 3338 | * mappings with the same generated line and column, but different 3339 | * source/name/original line and column the same. Useful when searching for a 3340 | * mapping with a stubbed out mapping. 3341 | */ 3342 | function compareByGeneratedPositionsDeflated(mappingA, mappingB, onlyCompareGenerated) { 3343 | var cmp = mappingA.generatedLine - mappingB.generatedLine; 3344 | if (cmp !== 0) { 3345 | return cmp; 3346 | } 3347 | 3348 | cmp = mappingA.generatedColumn - mappingB.generatedColumn; 3349 | if (cmp !== 0 || onlyCompareGenerated) { 3350 | return cmp; 3351 | } 3352 | 3353 | cmp = strcmp(mappingA.source, mappingB.source); 3354 | if (cmp !== 0) { 3355 | return cmp; 3356 | } 3357 | 3358 | cmp = mappingA.originalLine - mappingB.originalLine; 3359 | if (cmp !== 0) { 3360 | return cmp; 3361 | } 3362 | 3363 | cmp = mappingA.originalColumn - mappingB.originalColumn; 3364 | if (cmp !== 0) { 3365 | return cmp; 3366 | } 3367 | 3368 | return strcmp(mappingA.name, mappingB.name); 3369 | } 3370 | exports.compareByGeneratedPositionsDeflated = compareByGeneratedPositionsDeflated; 3371 | 3372 | function strcmp(aStr1, aStr2) { 3373 | if (aStr1 === aStr2) { 3374 | return 0; 3375 | } 3376 | 3377 | if (aStr1 === null) { 3378 | return 1; // aStr2 !== null 3379 | } 3380 | 3381 | if (aStr2 === null) { 3382 | return -1; // aStr1 !== null 3383 | } 3384 | 3385 | if (aStr1 > aStr2) { 3386 | return 1; 3387 | } 3388 | 3389 | return -1; 3390 | } 3391 | 3392 | /** 3393 | * Comparator between two mappings with inflated source and name strings where 3394 | * the generated positions are compared. 3395 | */ 3396 | function compareByGeneratedPositionsInflated(mappingA, mappingB) { 3397 | var cmp = mappingA.generatedLine - mappingB.generatedLine; 3398 | if (cmp !== 0) { 3399 | return cmp; 3400 | } 3401 | 3402 | cmp = mappingA.generatedColumn - mappingB.generatedColumn; 3403 | if (cmp !== 0) { 3404 | return cmp; 3405 | } 3406 | 3407 | cmp = strcmp(mappingA.source, mappingB.source); 3408 | if (cmp !== 0) { 3409 | return cmp; 3410 | } 3411 | 3412 | cmp = mappingA.originalLine - mappingB.originalLine; 3413 | if (cmp !== 0) { 3414 | return cmp; 3415 | } 3416 | 3417 | cmp = mappingA.originalColumn - mappingB.originalColumn; 3418 | if (cmp !== 0) { 3419 | return cmp; 3420 | } 3421 | 3422 | return strcmp(mappingA.name, mappingB.name); 3423 | } 3424 | exports.compareByGeneratedPositionsInflated = compareByGeneratedPositionsInflated; 3425 | 3426 | /** 3427 | * Strip any JSON XSSI avoidance prefix from the string (as documented 3428 | * in the source maps specification), and then parse the string as 3429 | * JSON. 3430 | */ 3431 | function parseSourceMapInput(str) { 3432 | return JSON.parse(str.replace(/^\)]}'[^\n]*\n/, '')); 3433 | } 3434 | exports.parseSourceMapInput = parseSourceMapInput; 3435 | 3436 | /** 3437 | * Compute the URL of a source given the the source root, the source's 3438 | * URL, and the source map's URL. 3439 | */ 3440 | function computeSourceURL(sourceRoot, sourceURL, sourceMapURL) { 3441 | sourceURL = sourceURL || ''; 3442 | 3443 | if (sourceRoot) { 3444 | // This follows what Chrome does. 3445 | if (sourceRoot[sourceRoot.length - 1] !== '/' && sourceURL[0] !== '/') { 3446 | sourceRoot += '/'; 3447 | } 3448 | // The spec says: 3449 | // Line 4: An optional source root, useful for relocating source 3450 | // files on a server or removing repeated values in the 3451 | // “sources” entry. This value is prepended to the individual 3452 | // entries in the “source” field. 3453 | sourceURL = sourceRoot + sourceURL; 3454 | } 3455 | 3456 | // Historically, SourceMapConsumer did not take the sourceMapURL as 3457 | // a parameter. This mode is still somewhat supported, which is why 3458 | // this code block is conditional. However, it's preferable to pass 3459 | // the source map URL to SourceMapConsumer, so that this function 3460 | // can implement the source URL resolution algorithm as outlined in 3461 | // the spec. This block is basically the equivalent of: 3462 | // new URL(sourceURL, sourceMapURL).toString() 3463 | // ... except it avoids using URL, which wasn't available in the 3464 | // older releases of node still supported by this library. 3465 | // 3466 | // The spec says: 3467 | // If the sources are not absolute URLs after prepending of the 3468 | // “sourceRoot”, the sources are resolved relative to the 3469 | // SourceMap (like resolving script src in a html document). 3470 | if (sourceMapURL) { 3471 | var parsed = urlParse(sourceMapURL); 3472 | if (!parsed) { 3473 | throw new Error("sourceMapURL could not be parsed"); 3474 | } 3475 | if (parsed.path) { 3476 | // Strip the last path component, but keep the "/". 3477 | var index = parsed.path.lastIndexOf('/'); 3478 | if (index >= 0) { 3479 | parsed.path = parsed.path.substring(0, index + 1); 3480 | } 3481 | } 3482 | sourceURL = join(urlGenerate(parsed), sourceURL); 3483 | } 3484 | 3485 | return normalize(sourceURL); 3486 | } 3487 | exports.computeSourceURL = computeSourceURL; 3488 | 3489 | 3490 | /***/ }), 3491 | 3492 | /***/ 990: 3493 | /***/ (function(__unusedmodule, exports, __webpack_require__) { 3494 | 3495 | /* -*- Mode: js; js-indent-level: 2; -*- */ 3496 | /* 3497 | * Copyright 2011 Mozilla Foundation and contributors 3498 | * Licensed under the New BSD license. See LICENSE or: 3499 | * http://opensource.org/licenses/BSD-3-Clause 3500 | */ 3501 | 3502 | var SourceMapGenerator = __webpack_require__(341).SourceMapGenerator; 3503 | var util = __webpack_require__(983); 3504 | 3505 | // Matches a Windows-style `\r\n` newline or a `\n` newline used by all other 3506 | // operating systems these days (capturing the result). 3507 | var REGEX_NEWLINE = /(\r?\n)/; 3508 | 3509 | // Newline character code for charCodeAt() comparisons 3510 | var NEWLINE_CODE = 10; 3511 | 3512 | // Private symbol for identifying `SourceNode`s when multiple versions of 3513 | // the source-map library are loaded. This MUST NOT CHANGE across 3514 | // versions! 3515 | var isSourceNode = "$$$isSourceNode$$$"; 3516 | 3517 | /** 3518 | * SourceNodes provide a way to abstract over interpolating/concatenating 3519 | * snippets of generated JavaScript source code while maintaining the line and 3520 | * column information associated with the original source code. 3521 | * 3522 | * @param aLine The original line number. 3523 | * @param aColumn The original column number. 3524 | * @param aSource The original source's filename. 3525 | * @param aChunks Optional. An array of strings which are snippets of 3526 | * generated JS, or other SourceNodes. 3527 | * @param aName The original identifier. 3528 | */ 3529 | function SourceNode(aLine, aColumn, aSource, aChunks, aName) { 3530 | this.children = []; 3531 | this.sourceContents = {}; 3532 | this.line = aLine == null ? null : aLine; 3533 | this.column = aColumn == null ? null : aColumn; 3534 | this.source = aSource == null ? null : aSource; 3535 | this.name = aName == null ? null : aName; 3536 | this[isSourceNode] = true; 3537 | if (aChunks != null) this.add(aChunks); 3538 | } 3539 | 3540 | /** 3541 | * Creates a SourceNode from generated code and a SourceMapConsumer. 3542 | * 3543 | * @param aGeneratedCode The generated code 3544 | * @param aSourceMapConsumer The SourceMap for the generated code 3545 | * @param aRelativePath Optional. The path that relative sources in the 3546 | * SourceMapConsumer should be relative to. 3547 | */ 3548 | SourceNode.fromStringWithSourceMap = 3549 | function SourceNode_fromStringWithSourceMap(aGeneratedCode, aSourceMapConsumer, aRelativePath) { 3550 | // The SourceNode we want to fill with the generated code 3551 | // and the SourceMap 3552 | var node = new SourceNode(); 3553 | 3554 | // All even indices of this array are one line of the generated code, 3555 | // while all odd indices are the newlines between two adjacent lines 3556 | // (since `REGEX_NEWLINE` captures its match). 3557 | // Processed fragments are accessed by calling `shiftNextLine`. 3558 | var remainingLines = aGeneratedCode.split(REGEX_NEWLINE); 3559 | var remainingLinesIndex = 0; 3560 | var shiftNextLine = function() { 3561 | var lineContents = getNextLine(); 3562 | // The last line of a file might not have a newline. 3563 | var newLine = getNextLine() || ""; 3564 | return lineContents + newLine; 3565 | 3566 | function getNextLine() { 3567 | return remainingLinesIndex < remainingLines.length ? 3568 | remainingLines[remainingLinesIndex++] : undefined; 3569 | } 3570 | }; 3571 | 3572 | // We need to remember the position of "remainingLines" 3573 | var lastGeneratedLine = 1, lastGeneratedColumn = 0; 3574 | 3575 | // The generate SourceNodes we need a code range. 3576 | // To extract it current and last mapping is used. 3577 | // Here we store the last mapping. 3578 | var lastMapping = null; 3579 | 3580 | aSourceMapConsumer.eachMapping(function (mapping) { 3581 | if (lastMapping !== null) { 3582 | // We add the code from "lastMapping" to "mapping": 3583 | // First check if there is a new line in between. 3584 | if (lastGeneratedLine < mapping.generatedLine) { 3585 | // Associate first line with "lastMapping" 3586 | addMappingWithCode(lastMapping, shiftNextLine()); 3587 | lastGeneratedLine++; 3588 | lastGeneratedColumn = 0; 3589 | // The remaining code is added without mapping 3590 | } else { 3591 | // There is no new line in between. 3592 | // Associate the code between "lastGeneratedColumn" and 3593 | // "mapping.generatedColumn" with "lastMapping" 3594 | var nextLine = remainingLines[remainingLinesIndex] || ''; 3595 | var code = nextLine.substr(0, mapping.generatedColumn - 3596 | lastGeneratedColumn); 3597 | remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn - 3598 | lastGeneratedColumn); 3599 | lastGeneratedColumn = mapping.generatedColumn; 3600 | addMappingWithCode(lastMapping, code); 3601 | // No more remaining code, continue 3602 | lastMapping = mapping; 3603 | return; 3604 | } 3605 | } 3606 | // We add the generated code until the first mapping 3607 | // to the SourceNode without any mapping. 3608 | // Each line is added as separate string. 3609 | while (lastGeneratedLine < mapping.generatedLine) { 3610 | node.add(shiftNextLine()); 3611 | lastGeneratedLine++; 3612 | } 3613 | if (lastGeneratedColumn < mapping.generatedColumn) { 3614 | var nextLine = remainingLines[remainingLinesIndex] || ''; 3615 | node.add(nextLine.substr(0, mapping.generatedColumn)); 3616 | remainingLines[remainingLinesIndex] = nextLine.substr(mapping.generatedColumn); 3617 | lastGeneratedColumn = mapping.generatedColumn; 3618 | } 3619 | lastMapping = mapping; 3620 | }, this); 3621 | // We have processed all mappings. 3622 | if (remainingLinesIndex < remainingLines.length) { 3623 | if (lastMapping) { 3624 | // Associate the remaining code in the current line with "lastMapping" 3625 | addMappingWithCode(lastMapping, shiftNextLine()); 3626 | } 3627 | // and add the remaining lines without any mapping 3628 | node.add(remainingLines.splice(remainingLinesIndex).join("")); 3629 | } 3630 | 3631 | // Copy sourcesContent into SourceNode 3632 | aSourceMapConsumer.sources.forEach(function (sourceFile) { 3633 | var content = aSourceMapConsumer.sourceContentFor(sourceFile); 3634 | if (content != null) { 3635 | if (aRelativePath != null) { 3636 | sourceFile = util.join(aRelativePath, sourceFile); 3637 | } 3638 | node.setSourceContent(sourceFile, content); 3639 | } 3640 | }); 3641 | 3642 | return node; 3643 | 3644 | function addMappingWithCode(mapping, code) { 3645 | if (mapping === null || mapping.source === undefined) { 3646 | node.add(code); 3647 | } else { 3648 | var source = aRelativePath 3649 | ? util.join(aRelativePath, mapping.source) 3650 | : mapping.source; 3651 | node.add(new SourceNode(mapping.originalLine, 3652 | mapping.originalColumn, 3653 | source, 3654 | code, 3655 | mapping.name)); 3656 | } 3657 | } 3658 | }; 3659 | 3660 | /** 3661 | * Add a chunk of generated JS to this source node. 3662 | * 3663 | * @param aChunk A string snippet of generated JS code, another instance of 3664 | * SourceNode, or an array where each member is one of those things. 3665 | */ 3666 | SourceNode.prototype.add = function SourceNode_add(aChunk) { 3667 | if (Array.isArray(aChunk)) { 3668 | aChunk.forEach(function (chunk) { 3669 | this.add(chunk); 3670 | }, this); 3671 | } 3672 | else if (aChunk[isSourceNode] || typeof aChunk === "string") { 3673 | if (aChunk) { 3674 | this.children.push(aChunk); 3675 | } 3676 | } 3677 | else { 3678 | throw new TypeError( 3679 | "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk 3680 | ); 3681 | } 3682 | return this; 3683 | }; 3684 | 3685 | /** 3686 | * Add a chunk of generated JS to the beginning of this source node. 3687 | * 3688 | * @param aChunk A string snippet of generated JS code, another instance of 3689 | * SourceNode, or an array where each member is one of those things. 3690 | */ 3691 | SourceNode.prototype.prepend = function SourceNode_prepend(aChunk) { 3692 | if (Array.isArray(aChunk)) { 3693 | for (var i = aChunk.length-1; i >= 0; i--) { 3694 | this.prepend(aChunk[i]); 3695 | } 3696 | } 3697 | else if (aChunk[isSourceNode] || typeof aChunk === "string") { 3698 | this.children.unshift(aChunk); 3699 | } 3700 | else { 3701 | throw new TypeError( 3702 | "Expected a SourceNode, string, or an array of SourceNodes and strings. Got " + aChunk 3703 | ); 3704 | } 3705 | return this; 3706 | }; 3707 | 3708 | /** 3709 | * Walk over the tree of JS snippets in this node and its children. The 3710 | * walking function is called once for each snippet of JS and is passed that 3711 | * snippet and the its original associated source's line/column location. 3712 | * 3713 | * @param aFn The traversal function. 3714 | */ 3715 | SourceNode.prototype.walk = function SourceNode_walk(aFn) { 3716 | var chunk; 3717 | for (var i = 0, len = this.children.length; i < len; i++) { 3718 | chunk = this.children[i]; 3719 | if (chunk[isSourceNode]) { 3720 | chunk.walk(aFn); 3721 | } 3722 | else { 3723 | if (chunk !== '') { 3724 | aFn(chunk, { source: this.source, 3725 | line: this.line, 3726 | column: this.column, 3727 | name: this.name }); 3728 | } 3729 | } 3730 | } 3731 | }; 3732 | 3733 | /** 3734 | * Like `String.prototype.join` except for SourceNodes. Inserts `aStr` between 3735 | * each of `this.children`. 3736 | * 3737 | * @param aSep The separator. 3738 | */ 3739 | SourceNode.prototype.join = function SourceNode_join(aSep) { 3740 | var newChildren; 3741 | var i; 3742 | var len = this.children.length; 3743 | if (len > 0) { 3744 | newChildren = []; 3745 | for (i = 0; i < len-1; i++) { 3746 | newChildren.push(this.children[i]); 3747 | newChildren.push(aSep); 3748 | } 3749 | newChildren.push(this.children[i]); 3750 | this.children = newChildren; 3751 | } 3752 | return this; 3753 | }; 3754 | 3755 | /** 3756 | * Call String.prototype.replace on the very right-most source snippet. Useful 3757 | * for trimming whitespace from the end of a source node, etc. 3758 | * 3759 | * @param aPattern The pattern to replace. 3760 | * @param aReplacement The thing to replace the pattern with. 3761 | */ 3762 | SourceNode.prototype.replaceRight = function SourceNode_replaceRight(aPattern, aReplacement) { 3763 | var lastChild = this.children[this.children.length - 1]; 3764 | if (lastChild[isSourceNode]) { 3765 | lastChild.replaceRight(aPattern, aReplacement); 3766 | } 3767 | else if (typeof lastChild === 'string') { 3768 | this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement); 3769 | } 3770 | else { 3771 | this.children.push(''.replace(aPattern, aReplacement)); 3772 | } 3773 | return this; 3774 | }; 3775 | 3776 | /** 3777 | * Set the source content for a source file. This will be added to the SourceMapGenerator 3778 | * in the sourcesContent field. 3779 | * 3780 | * @param aSourceFile The filename of the source file 3781 | * @param aSourceContent The content of the source file 3782 | */ 3783 | SourceNode.prototype.setSourceContent = 3784 | function SourceNode_setSourceContent(aSourceFile, aSourceContent) { 3785 | this.sourceContents[util.toSetString(aSourceFile)] = aSourceContent; 3786 | }; 3787 | 3788 | /** 3789 | * Walk over the tree of SourceNodes. The walking function is called for each 3790 | * source file content and is passed the filename and source content. 3791 | * 3792 | * @param aFn The traversal function. 3793 | */ 3794 | SourceNode.prototype.walkSourceContents = 3795 | function SourceNode_walkSourceContents(aFn) { 3796 | for (var i = 0, len = this.children.length; i < len; i++) { 3797 | if (this.children[i][isSourceNode]) { 3798 | this.children[i].walkSourceContents(aFn); 3799 | } 3800 | } 3801 | 3802 | var sources = Object.keys(this.sourceContents); 3803 | for (var i = 0, len = sources.length; i < len; i++) { 3804 | aFn(util.fromSetString(sources[i]), this.sourceContents[sources[i]]); 3805 | } 3806 | }; 3807 | 3808 | /** 3809 | * Return the string representation of this source node. Walks over the tree 3810 | * and concatenates all the various snippets together to one string. 3811 | */ 3812 | SourceNode.prototype.toString = function SourceNode_toString() { 3813 | var str = ""; 3814 | this.walk(function (chunk) { 3815 | str += chunk; 3816 | }); 3817 | return str; 3818 | }; 3819 | 3820 | /** 3821 | * Returns the string representation of this source node along with a source 3822 | * map. 3823 | */ 3824 | SourceNode.prototype.toStringWithSourceMap = function SourceNode_toStringWithSourceMap(aArgs) { 3825 | var generated = { 3826 | code: "", 3827 | line: 1, 3828 | column: 0 3829 | }; 3830 | var map = new SourceMapGenerator(aArgs); 3831 | var sourceMappingActive = false; 3832 | var lastOriginalSource = null; 3833 | var lastOriginalLine = null; 3834 | var lastOriginalColumn = null; 3835 | var lastOriginalName = null; 3836 | this.walk(function (chunk, original) { 3837 | generated.code += chunk; 3838 | if (original.source !== null 3839 | && original.line !== null 3840 | && original.column !== null) { 3841 | if(lastOriginalSource !== original.source 3842 | || lastOriginalLine !== original.line 3843 | || lastOriginalColumn !== original.column 3844 | || lastOriginalName !== original.name) { 3845 | map.addMapping({ 3846 | source: original.source, 3847 | original: { 3848 | line: original.line, 3849 | column: original.column 3850 | }, 3851 | generated: { 3852 | line: generated.line, 3853 | column: generated.column 3854 | }, 3855 | name: original.name 3856 | }); 3857 | } 3858 | lastOriginalSource = original.source; 3859 | lastOriginalLine = original.line; 3860 | lastOriginalColumn = original.column; 3861 | lastOriginalName = original.name; 3862 | sourceMappingActive = true; 3863 | } else if (sourceMappingActive) { 3864 | map.addMapping({ 3865 | generated: { 3866 | line: generated.line, 3867 | column: generated.column 3868 | } 3869 | }); 3870 | lastOriginalSource = null; 3871 | sourceMappingActive = false; 3872 | } 3873 | for (var idx = 0, length = chunk.length; idx < length; idx++) { 3874 | if (chunk.charCodeAt(idx) === NEWLINE_CODE) { 3875 | generated.line++; 3876 | generated.column = 0; 3877 | // Mappings end at eol 3878 | if (idx + 1 === length) { 3879 | lastOriginalSource = null; 3880 | sourceMappingActive = false; 3881 | } else if (sourceMappingActive) { 3882 | map.addMapping({ 3883 | source: original.source, 3884 | original: { 3885 | line: original.line, 3886 | column: original.column 3887 | }, 3888 | generated: { 3889 | line: generated.line, 3890 | column: generated.column 3891 | }, 3892 | name: original.name 3893 | }); 3894 | } 3895 | } else { 3896 | generated.column++; 3897 | } 3898 | } 3899 | }); 3900 | this.walkSourceContents(function (sourceFile, sourceContent) { 3901 | map.setSourceContent(sourceFile, sourceContent); 3902 | }); 3903 | 3904 | return { code: generated.code, map: map }; 3905 | }; 3906 | 3907 | exports.SourceNode = SourceNode; 3908 | 3909 | 3910 | /***/ }) 3911 | 3912 | /******/ }); -------------------------------------------------------------------------------- /dist/action/summary.tmpl.md: -------------------------------------------------------------------------------- 1 | ### Lightstep Logo Lightstep Services Change Report 2 | <% if (snapshotCompare) {%>Comparing topology, latency and errors of services between snapshots [`<%=snapshotBeforeId%>`](<%=helpers.snapshotLink(lightstepProj, snapshotBeforeId)%>) and [`<%=snapshotAfterId%>`](<%=helpers.snapshotLink(lightstepProj, snapshotAfterId)%>) 3 | > `<%= newServicesCount %>` new service(s) detected 4 | <% } else { %>>Topology, latency and errors summary for snapshot [`<%=snapshotBeforeId%>`](<%=helpers.snapshotLink(lightstepProj, snapshotBeforeId)%>)<% }%> 5 | <%= table %> 6 | 7 | <% if (integrations.rollbar) {%>#### Rollbar Logo Errors 8 | > <%= helpers.rollbarVersionErrors(integrations.rollbar) %><% }%> 9 | -------------------------------------------------------------------------------- /dist/main.js: -------------------------------------------------------------------------------- 1 | const { runAction, initAction } = require('./action') 2 | 3 | const init = async function(){ 4 | await initAction() 5 | await runAction() 6 | } 7 | 8 | init() -------------------------------------------------------------------------------- /examples/example-screenshot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/lightstep/lightstep-action-snapshot/166ec5f31d611858ebe9ed3437848e8fe675fb89/examples/example-screenshot.png -------------------------------------------------------------------------------- /examples/workflows/after_deploy.yml: -------------------------------------------------------------------------------- 1 | name: Lightstep Post-Deploy Check 2 | on: 3 | deployment_status: 4 | 5 | jobs: 6 | postdeploy_check_job: 7 | runs-on: ubuntu-latest 8 | name: Compare Snapshots 9 | if: github.event.deployment_status.state == 'success' 10 | steps: 11 | - name: Checkout 12 | uses: actions/checkout@v2 13 | 14 | - name: 📸 Take Lightstep Snapshot 15 | id: lightstep-take-snapshot 16 | uses: lightstep/lightstep-action-snapshot@v2 17 | with: 18 | lightstep_api_key: ${{ secrets.LIGHTSTEP_API_TOKEN }} 19 | # Recommended to use a query based on SHA (or version) so telemetry always 20 | # points to the code that's being deployed in this workflow 21 | lightstep_snapshot_query: '"github.sha" IN ("${{ github.sha }}")' 22 | 23 | - name: Wait for Snapshot to Complete 24 | run: sleep 240 25 | 26 | - name: Cache Snapshots 27 | id: cache-snapshots 28 | uses: actions/cache@v2 29 | with: 30 | path: /tmp/lightstep 31 | key: ${{ steps.lightstep-take-snapshot.outputs.lightstep_project }}-snapshots 32 | 33 | - name: Compare Snapshots 34 | id: lightstep-snapshot 35 | uses: lightstep/lightstep-action-snapshot@v2 36 | env: 37 | ROLLBAR_API_TOKEN: ${{ secrets.ROLLBAR_API_TOKEN }} 38 | PAGERDUTY_API_TOKEN: ${{ secrets.PAGERDUTY_API_TOKEN }} 39 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 40 | with: 41 | lightstep_api_key: ${{ secrets.LIGHTSTEP_API_TOKEN }} 42 | # Infer a recent snapshot to compare with using the API: 43 | # '*' uses most recent available (per project or repo tag if exists) 44 | lightstep_snapshot_compare_id: '*' 45 | # If your project has lots of services, you can filter the output 46 | # to a subset. This is useful for monorepos. 47 | lightstep_service_filter: web,iOS,android,inventory 48 | lightstep_snapshot_id: ${{ steps.lightstep-take-snapshot.outputs.lightstep_snapshot_id }} 49 | -------------------------------------------------------------------------------- /examples/workflows/pagerduty.yml: -------------------------------------------------------------------------------- 1 | on: deployment 2 | 3 | jobs: 4 | snapshot: 5 | runs-on: ubuntu-latest 6 | name: Create PagerDuty Change Event with Snapshot 7 | 8 | steps: 9 | 10 | - name: Take Lightstep Snapshot 11 | uses: lightstep/lightstep-action-snapshot@0.0.2 12 | id: lightstep-snapshot 13 | with: 14 | lightstep_api_key: ${{ secrets.LIGHTSTEP_API_KEY }} 15 | lightstep_organization: your-org 16 | lightstep_project: your-project 17 | lightstep_snapshot_query: service IN ("your-service-name") 18 | 19 | - name: Create PagerDuty Change Event 20 | run: | 21 | NOW=$(node -e 'console.log((new Date()).toISOString())') 22 | curl -s --request POST \ 23 | --url https://events.pagerduty.com/v2/change/enqueue \ 24 | --header 'content-type: application/json' \ 25 | --data '{ 26 | "routing_key": "${{ secrets.PAGERDUTY_ROUTING_KEY }}", 27 | "payload": { 28 | "summary": "Lightstep Snapshot Created", 29 | "source": "lightstep/github-action-predeploy", 30 | "timestamp": "'"$NOW"'" 31 | }, 32 | "links": [ 33 | { 34 | "href": "https://app.lightstep.com/${{ steps.lightstep-snapshot.inputs.lightstep_project }}/explorer?snapshot_id=${{ steps.lightstep-snapshot.outputs.lightstep_snapshot_id }}", 35 | "text": "Lightstep Service Snapshot" 36 | }, 37 | { 38 | "href": "https://github.com/'"$GITHUB_REPOSITORY"'/commit/'"$GITHUB_SHA"'", 39 | "text": "GitHub Commit" 40 | } 41 | ] 42 | }' -------------------------------------------------------------------------------- /examples/workflows/snapshot.yml: -------------------------------------------------------------------------------- 1 | on: workflow_dispatch 2 | 3 | jobs: 4 | take_snapshot: 5 | runs-on: ubuntu-latest 6 | name: Take Snapshot 7 | 8 | steps: 9 | 10 | - name: Take Lightstep Snapshot 11 | uses: lightstep/lightstep-action-snapshot@0.0.2 12 | id: lightstep-snapshot 13 | with: 14 | lightstep_api_key: ${{ secrets.LIGHTSTEP_API_KEY }} 15 | lightstep_organization: your-org 16 | lightstep_project: your-project 17 | lightstep_snapshot_query: service IN ("frontend") -------------------------------------------------------------------------------- /examples/workflows/snapshot_to_issue.yml: -------------------------------------------------------------------------------- 1 | name: Attach Lightstep Snapshot to Issue 2 | 3 | on: 4 | workflow_dispatch: 5 | issues: 6 | types: [labeled] 7 | 8 | jobs: 9 | add_snapshot_to_issue: 10 | runs-on: ubuntu-latest 11 | name: Take Snapshot 12 | # Run this if an issue has the label 'bug' 13 | if: contains(github.event.issue.labels.*.name, 'bug') 14 | 15 | steps: 16 | # Check out the repo to read configuration 17 | # from the `.lightstep.yml` file in the repo root directory. 18 | - name: 🛎️ Checkout 19 | uses: actions/checkout@v2 20 | 21 | - name: 📸 Take Lightstep Snapshot 22 | id: take-lightstep-snapshot 23 | uses: lightstep/lightstep-action-snapshot@v2 24 | # Pass in partner API token integrations 25 | env: 26 | ROLLBAR_API_TOKEN: ${{ secrets.ROLLBAR_API_TOKEN }} 27 | PAGERDUTY_API_TOKEN: ${{ secrets.PAGERDUTY_API_TOKEN }} 28 | with: 29 | lightstep_api_key: ${{ secrets.LIGHTSTEP_API_TOKEN }} 30 | # Infer a recent snapshot to compare with using the API: 31 | # '*' uses most recent available (per project or repo tag if exists) 32 | lightstep_snapshot_compare_id: '*' 33 | # Query for the snapshot 34 | # Recommended to use a query based on SHA (for issues, this points to the main branch) 35 | lightstep_snapshot_query: '"github.sha" IN ("${{ github.sha }}")' 36 | 37 | - name: Wait for Snapshot to Complete 38 | run: sleep 240 39 | 40 | - name: Cache Snapshots 41 | id: cache-snapshots 42 | uses: actions/cache@v2 43 | with: 44 | path: /tmp/lightstep 45 | key: ${{ steps.take-lightstep-project.outputs.lightstep_project }}-snapshots 46 | 47 | - name: Snapshot Summary 48 | id: lightstep-snapshot-summary 49 | uses: lightstep/lightstep-action-snapshot@v2 50 | env: 51 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 52 | with: 53 | lightstep_api_key: ${{ secrets.LIGHTSTEP_API_TOKEN }} 54 | lightstep_snapshot_id: ${{ steps.take-lightstep-snapshot.outputs.lightstep_snapshot_id }} 55 | # If your project has lots of services, you can filter the output 56 | # to a subset. This is useful for monorepos. 57 | lightstep_service_filter: web,iOS,android,inventory 58 | 59 | 60 | -------------------------------------------------------------------------------- /main.js: -------------------------------------------------------------------------------- 1 | const { runAction, initAction } = require('./action') 2 | 3 | const init = async function(){ 4 | await initAction() 5 | await runAction() 6 | } 7 | 8 | init() -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "lightstep-action-snapshot", 3 | "version": "0.2.0", 4 | "description": "GitHub Action that captures full distributed system behavior at a point in time. ", 5 | "main": "index.js", 6 | "scripts": { 7 | "lint": "eslint .", 8 | "build": "ncc build action.js -o dist/action --source-map && cp main.js dist/", 9 | "test": "jest", 10 | "all": "npm run lint && npm run prepare && npm run test" 11 | }, 12 | "keywords": [], 13 | "author": "Lightstep, Inc.", 14 | "license": "ISC", 15 | "dependencies": { 16 | "@actions/core": "^1.2.6", 17 | "@actions/github": "^4.0.0", 18 | "@lightstep/lightstep-api-js": "git+https://github.com/lightstep/lightstep-api-js.git", 19 | "js-yaml": "^3.14.0", 20 | "lodash.template": "^4.5.0" 21 | }, 22 | "devDependencies": { 23 | "@zeit/ncc": "^0.22.3", 24 | "eslint": "^7.12.1", 25 | "jest": "^26.6.2" 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Lightstep Services Change Report 2 | 3 | The `lightstep/lightstep-action-snapshot` action takes a [snapshot](https://lightstep.com/blog/snapshots-detailed-system-behavior-saved-shareable/) of a service in Lightstep and optionally attaches analysis of existing snapshot(s) to related pull requests or issues that helps developers understand how their production services change over time. 4 | 5 | Snapshots help you correlate code changes in GitHub with latency and errors in different environments. 6 | 7 | ![Example Output in a PR](./examples/example-screenshot.png) 8 | 9 | ## Requirements 10 | 11 | ### Lightstep 12 | 13 | * Instrumented service(s) running in a production environment ([create account here](https://app.lightstep.com/signup)) 14 | * Lightstep [API key](https://docs.lightstep.com/docs/create-and-manage-api-keys) 15 | 16 | ## Usage 17 | 18 | This action can be run on `ubuntu-latest` GitHub Actions runner. 19 | 20 | Taking a snapshot requires one step and no other dependencies: 21 | 22 | ``` 23 | steps: 24 | - name: Take Lightstep Snapshot 25 | id: lightstep-snapshot 26 | with: 27 | lightstep_api_key: ${{ secrets.LIGHTSTEP_API_KEY }} 28 | lightstep_organization: org 29 | lightstep_project: project 30 | # See https://api-docs.lightstep.com/reference#query-syntax 31 | # for supported queries. 32 | lightstep_snapshot_query: service IN ("frontend") 33 | ``` 34 | 35 | Most workflows will involve taking a snapshot *and* performing analysis on that snapshot, usually in the context of a GitHub PR or Issue. See examples below. 36 | 37 | ## Example Workfkows 38 | 39 | * [Take a snapshot in response to an API trigger](./examples/workflows/snapshot.yml) 40 | * [Take a snapshot after a deploy and get a report of what changed as a pull request comment](./examples/workflows/after_deploy.yml) 41 | * [Take a snapshot when a specific label is applied to an issue](./examples/workflows/snapshot_to_issue.yml) 42 | 43 | ## Inputs 44 | 45 | The following are **required** and can also be passed as environment variables: 46 | 47 | | Action Input | Env var | 48 | | ------------------------ | ------------------------- | 49 | | `lightstep_organization` | `LIGHTSTEP_ORGANIZATION` | 50 | | `lightstep_project` | `LIGHTSTEP_PROJECT` | 51 | | `lightstep_service` | `LIGHTSTEP_SERVICE` | 52 | | `lightstep_api_key` | `LIGHTSTEP_API_KEY` | 53 | 54 | To take a snapshot, the following input is **required**: 55 | 56 | | Action Input | Description | 57 | | -------------------------- | ---------------------------------------------------------------------------------------------------------- | 58 | | `lightstep_snapshot_query` | [Query](https://api-docs.lightstep.com/reference#query-syntax) for what telemetry to collect in a snapshot | 59 | 60 | To analyze a snapshot, the following input is **required**: 61 | 62 | | Action Input | Description | 63 | | -------------------------- | ------------------------------------------------------------------- | 64 | | `lightstep_snapshot_id` | Existing snapshot id to analyze and add as a comment on an issue/PR | 65 | 66 | 67 | The following are **optional**: 68 | 69 | | Action Input | Description | 70 | | ------------------------------- | --------------------------------------------------- | 71 | | `lightstep_snapshot_compare_id` | Snapshot to compare with `lightstep_snapshot_id` - if set to `*` will find the most recent snapshot | 72 | | `disable_comment` | Prevents a comment from being added to an issue/PR | 73 | 74 | ## Outputs 75 | 76 | | Action Input | Summary | 77 | | ------------------------ | ---------------------------------- | 78 | | `lightstep_snapshot_id` | ID of the taken snapshot | 79 | | `lightstep_snapshot_md` | Markdown analysis of the snapshot | 80 | 81 | 82 | ## Using locally 83 | 84 | This action can also be used with the [`act`](https://github.com/nektos/act) tool to run this action locally. 85 | 86 | For example, this act with the example snapshot workflow in this repository to take a snapshot from a local environment: 87 | 88 | ``` 89 | $ export LIGHTSTEP_API_KEY=your-api-key 90 | $ act deployment -s LIGHTSTEP_API_KEY=$LIGHTSTEP_API_KEY -W examples/workflows/snapshot.yml 91 | ``` 92 | 93 | ## License 94 | 95 | Apache License 2.0 96 | -------------------------------------------------------------------------------- /snapshot/api.js: -------------------------------------------------------------------------------- 1 | const lightstepSdk = require('@lightstep/lightstep-api-js') 2 | const path = require('path') 3 | const fs = require('fs') 4 | 5 | module.exports.takeSnapshot = async({ apiKey, lightstepOrg, lightstepProj, lightstepQuery }) => { 6 | const apiClient = await lightstepSdk.init(lightstepOrg, apiKey) 7 | const newSnapshot = await apiClient.sdk.apis.Snapshots.createSnapshot({ 8 | organization : lightstepOrg, 9 | project : lightstepProj, 10 | data : { 11 | data : { 12 | attributes : { 13 | query : lightstepQuery 14 | } 15 | } 16 | } 17 | }) 18 | return newSnapshot.body.data.id 19 | } 20 | 21 | const SNAPSHOT_DIR = path.join('/tmp', 'lightstep') 22 | 23 | function snapshotCachePath(id, type = '') { 24 | return path.join(SNAPSHOT_DIR, `lightstep-snapshot${type}-${id}.json`) 25 | } 26 | 27 | module.exports.getServiceDiagramCached = async (apiClient, { project, snapshotId }) => { 28 | if (!fs.existsSync(SNAPSHOT_DIR)) { 29 | fs.mkdirSync(SNAPSHOT_DIR) 30 | } 31 | 32 | if (fs.existsSync(snapshotCachePath(snapshotId, 'diagram'))) { 33 | const snapshotFile = fs.readFileSync(snapshotCachePath(snapshotId, 'diagram'), 'utf-8') 34 | // eslint-disable-next-line no-console 35 | console.log(`getting ${snapshotId} diagram snapshot from cache...`) 36 | return JSON.parse(snapshotFile) 37 | } 38 | 39 | const diagram = await apiClient.getServiceDiagram({ project, snapshotId }) 40 | return diagram 41 | } 42 | 43 | /** 44 | * Gets a locally cached *.json file that represents the snapshot API response. 45 | * 46 | * @param {} apiClient 47 | * @param {*} 48 | */ 49 | module.exports.getSnapshotCached = async(apiClient, { project, snapshotId }) => { 50 | if (!fs.existsSync(SNAPSHOT_DIR)) { 51 | fs.mkdirSync(SNAPSHOT_DIR) 52 | } 53 | 54 | if (fs.existsSync(snapshotCachePath(snapshotId))) { 55 | const snapshotFile = fs.readFileSync(snapshotCachePath(snapshotId), 'utf-8') 56 | // eslint-disable-next-line no-console 57 | console.log(`getting ${snapshotId} snapshot from cache...`) 58 | return JSON.parse(snapshotFile) 59 | } 60 | 61 | const diagram = await apiClient.getSnapshot({ project, snapshotId }) 62 | return diagram 63 | } 64 | 65 | module.exports.cacheSnapshots = function(snapshotA, diagramA, snapshotB, diagramB) { 66 | if (!process.env.DISABLE_CACHE_SNAPSHOTS) { 67 | try { 68 | fs.writeFileSync( 69 | snapshotCachePath(snapshotA.data.id), 70 | JSON.stringify(snapshotA)) 71 | fs.writeFileSync( 72 | snapshotCachePath(diagramA.data.id, 'diagram'), 73 | JSON.stringify(diagramA)) 74 | fs.writeFileSync( 75 | snapshotCachePath(snapshotB.data.id), 76 | JSON.stringify(snapshotB)) 77 | fs.writeFileSync( 78 | snapshotCachePath(diagramB.data.id, 'diagram'), 79 | JSON.stringify(diagramB)) 80 | } catch (e) { 81 | return e 82 | } 83 | } 84 | } -------------------------------------------------------------------------------- /snapshot/diff.js: -------------------------------------------------------------------------------- 1 | const lightstepSdk = require('@lightstep/lightstep-api-js') 2 | const { markdownSummary } = require('./markdown') 3 | const { getSnapshotCached, getServiceDiagramCached, cacheSnapshots } = require('./api') 4 | const core = require('@actions/core') 5 | 6 | /** 7 | * Parses service configuration to search spans in a snapshot for potential violations 8 | * of user-configured rules (i.e. no 500s) 9 | * @param {} snapshotStats 10 | * @param {*} config 11 | */ 12 | const findViolationsByService = (snapshotSummary, config) => { 13 | const snapshotStats = snapshotSummary.snapshot 14 | const diagramStats = snapshotSummary.diagram 15 | var hasViolations = false 16 | const serviceViolations = Object.keys(snapshotStats).reduce((obj, s) => { 17 | const serviceViolations = config.services()[s] && config.services()[s].violations 18 | if (!serviceViolations) { 19 | return obj 20 | } 21 | var violations = [] 22 | for (var v of config.services()[s].violations) { 23 | if (!v.type|| !v.name) { 24 | continue 25 | } 26 | var matches = [] 27 | var msg = 'unknown violation' 28 | if (v.type === 'span.attributes' && v.op === 'equals') { 29 | matches = snapshotStats[s].exemplars.filter(e => e.attributes[v.key] === `${v.value}`) 30 | } else if (v.type === 'span.attributes' && v.op === 'unset') { 31 | matches = snapshotStats[s].exemplars.filter(e => e.attributes[v.key] === undefined) 32 | } else if (v.type === 'connection') { 33 | const exists = diagramStats.edges.includes(`${s}>${v.value}`) 34 | matches = exists ? [`${s}>${v.value}`] : [] 35 | } 36 | 37 | if (matches.length === 0) { 38 | continue 39 | } 40 | 41 | hasViolations = true 42 | 43 | if (v.type == 'span.attributes') { 44 | msg = `Found ${matches.length} spans that violate the rule: ${v.name}` 45 | } 46 | 47 | if (v.type === 'connection') { 48 | msg = `${v.name}` 49 | } 50 | violations.push( { violation : v, matches, msg }) 51 | } 52 | obj[s] = { service : s, violations } 53 | return obj 54 | }, {}) 55 | return { serviceViolations, hasViolations } 56 | } 57 | 58 | /** 59 | * Tries to determine a snapshot to compare if one is 60 | * not specified. 61 | * @param {} param0 62 | */ 63 | async function selectBeforeSnapshot({ 64 | apiClient, lightstepProj, snapshotAfterId 65 | }) { 66 | const snapshotsReponse = await apiClient.getSnapshots({ project : lightstepProj }) 67 | const snapshots = snapshotsReponse.data 68 | if (snapshots.length === 0) { 69 | return null 70 | } 71 | 72 | const otherSnaps = snapshots.filter(s => s.id !== snapshotAfterId) 73 | if (otherSnaps.length === 0) { 74 | return null 75 | } 76 | 77 | // finds most recent snapshot associated with a GitHub repository 78 | if (process.env.GITHUB_REPO) { 79 | const snapshotsWithGithubMetadata = 80 | otherSnaps.filter(s => { 81 | const matches = s.attributes.query.match(/"ignore.github.repo".*?"(.*?)"/) 82 | return matches.length == 2 && matches[1] == process.env.GITHUB_REPO 83 | }) 84 | 85 | if (snapshotsWithGithubMetadata.length > 0) { 86 | const mostRecent = snapshotsWithGithubMetadata[snapshotsWithGithubMetadata.length-1] 87 | core.info(`comparing with most recent repo snapshot: ${mostRecent.id}`) 88 | return mostRecent 89 | } 90 | } 91 | const mostRecent = otherSnaps[otherSnaps.length-1] 92 | core.info(`comparing with most recent snapshot: ${mostRecent.id}`) 93 | return mostRecent 94 | } 95 | 96 | /** 97 | * Performs analysis on two snapshots. 98 | * 99 | * If the snapshot is the same, a summary is generated of a single snapshot. 100 | * 101 | */ 102 | async function snapshotDiff( 103 | { apiKey, lightstepOrg, lightstepProj, snapshotBeforeId, snapshotAfterId, serviceFilter, config }) { 104 | 105 | const apiClient = await lightstepSdk.init(lightstepOrg, apiKey) 106 | 107 | // If set to '*', try to automatically detect 108 | // a snapshot to compare the provided snapshot with 109 | if (snapshotBeforeId === '*') { 110 | const beforeSnap = await selectBeforeSnapshot({ apiClient, lightstepProj, snapshotAfterId }) 111 | 112 | if (beforeSnap === null) { 113 | core.setFailed(`could not determine a snapshot to compare with: ${snapshotAfterId}`) 114 | return 115 | } 116 | snapshotBeforeId = beforeSnap.id 117 | } 118 | 119 | const snapshotA = await getSnapshotCached(apiClient, 120 | { project : lightstepProj, snapshotId : snapshotBeforeId }) 121 | 122 | const diagramA = await getServiceDiagramCached(apiClient, 123 | { project : lightstepProj, snapshotId : snapshotBeforeId }) 124 | 125 | const snapshotB = await getSnapshotCached(apiClient, 126 | { project : lightstepProj, snapshotId : snapshotAfterId }) 127 | 128 | const diagramB = await getServiceDiagramCached(apiClient, 129 | { project : lightstepProj, snapshotId : snapshotAfterId }) 130 | 131 | // Calculate diff and get snapshot stats 132 | const beforeSummary = apiClient.utils.snapshotSummary(snapshotA, diagramA) 133 | const afterSummary = apiClient.utils.snapshotSummary(snapshotB, diagramB) 134 | const snapshotDiff = apiClient.utils.diffSummary( 135 | snapshotBeforeId, beforeSummary[snapshotBeforeId], 136 | snapshotAfterId, afterSummary[snapshotAfterId]) 137 | const data = { 138 | snapshotBeforeId, 139 | snapshotAfterId, 140 | ...beforeSummary, 141 | ...afterSummary, 142 | ...snapshotDiff 143 | } 144 | 145 | const { serviceViolations, hasViolations } = findViolationsByService(afterSummary[snapshotAfterId], config) 146 | 147 | // Snapshot API is heavily rate limited, cache locally if possible. 148 | cacheSnapshots(snapshotA, diagramA, snapshotB, diagramB) 149 | // TODO: filter out services specified by GH Action config here 150 | if (serviceFilter) { 151 | const services = serviceFilter.split(",").map(s => s.trim()) 152 | if (services.length > 0) { 153 | const filtered = Object.keys(data[snapshotAfterId].snapshot).reduce((obj, s) => { 154 | if (services.includes(s)) { 155 | obj[s] = data[data.snapshotAfterId].snapshot[s] 156 | } 157 | return obj 158 | }, {}) 159 | data[snapshotAfterId].snapshot = filtered 160 | } 161 | } 162 | 163 | const markdown = await markdownSummary({ 164 | data, 165 | lightstepProj, 166 | serviceViolations, 167 | config 168 | }) 169 | 170 | const graph = apiClient.diagramToGraphviz(diagramB).to_dot() 171 | 172 | return { graph, markdown, hasViolations } 173 | } 174 | 175 | module.exports = { snapshotDiff } -------------------------------------------------------------------------------- /snapshot/markdown.js: -------------------------------------------------------------------------------- 1 | const template = require('lodash.template') 2 | const path = require('path') 3 | 4 | const { getServiceOnCall } = require('@lightstep/lightstep-api-js').integrations.pagerduty 5 | const { getLastDeployVersions } = require('@lightstep/lightstep-api-js').integrations.rollbar 6 | 7 | const fs = require('fs') 8 | 9 | const tableTemplate = template(fs.readFileSync(path.resolve('./template/service_table.tmpl.md'), 'utf8')) 10 | const summaryTemplate = template(fs.readFileSync(path.resolve('./template/summary.tmpl.md'), 'utf8')) 11 | 12 | 13 | const viewHelpers = { 14 | icons : { 15 | // eslint-disable-next-line max-len 16 | ROLLBAR_ICON : 'https://user-images.githubusercontent.com/27153/90803304-65a97980-e2cd-11ea-8267-a711fdcc6bc9.png', 17 | // eslint-disable-next-line max-len 18 | PAGERDUTY_ICON : 'https://user-images.githubusercontent.com/27153/90803915-4fe88400-e2ce-11ea-803f-47b9c244799d.png', 19 | // eslint-disable-next-line max-len 20 | LIGHTSTEP_ICON : 'https://user-images.githubusercontent.com/27153/90803298-6510e300-e2cd-11ea-91fa-5795a4481e20.png' 21 | }, 22 | percentFormatter(service) { 23 | if (service.snapshot.errorPct === 0) { 24 | return `0%` 25 | } 26 | 27 | const errPct = (service.snapshot.errorPct && `${(service.snapshot.errorPct*100).toFixed(2)}%`) || ':question:' 28 | if (service.diff && service.diff.errorPct && 29 | (service.diff.errorPct.pct !== 0)) { 30 | const change = service.diff.errorPct.pct*100 31 | const sign = change < 0 ? '' : '+' 32 | const icon = change < 0 ? ':chart_with_downwards_trend: ' : ':chart_with_upwards_trend:' 33 | return `${icon} ${errPct} (${sign}${change.toFixed(2)}%)` 34 | } 35 | return `${errPct}` 36 | }, 37 | latencyFormatter(service) { 38 | if (service.snapshot.avgDurationMs === 0) { 39 | return `0ms` 40 | } 41 | const avgDurationMs = 42 | (service.snapshot.avgDurationMs && `${(service.snapshot.avgDurationMs).toFixed(2)}ms`) || ':question:' 43 | if (service.diff && service.diff.avgDurationMs && 44 | (service.diff.avgDurationMs.pct !== 0)) { 45 | const change = service.diff.avgDurationMs.pct*100 46 | const sign = change < 0 ? '' : '+' 47 | const icon = change < 0 ? ':chart_with_downwards_trend: ' : ':chart_with_upwards_trend:' 48 | return `${icon} ${avgDurationMs} (${sign}${change.toFixed(2)}%)` 49 | } 50 | return `${avgDurationMs}` 51 | }, 52 | rollbarVersionErrors(r) { 53 | const url = `https://rollbar.com/${r.config.account}/${r.config.project}/versions/` 54 | if (r.error) { 55 | return ':exclamation: Problem retrieving error information from Rollbar' 56 | } 57 | if (r.versions && r.versions.item_stats && r.versions.item_stats.new) { 58 | const newErrors = r.versions.item_stats.new 59 | return `[\`${newErrors.error + newErrors.critical}\` new errors](${url}) since last deploy` 60 | } 61 | return ':question_mark: Could not find Rollbar error information' 62 | }, 63 | snapshotLink(project, snapshotId) { 64 | return `https://app.lightstep.com/${project}/explorer?snapshot_id=${snapshotId}` 65 | }, 66 | snapshotOperationLink(project, snapshotId, service, operation){ 67 | const snapshotLink = viewHelpers.snapshotLink(project, snapshotId) 68 | const filterValue = `filter%5B0%5D%5Bvalue%5D=${encodeURIComponent(operation)}` 69 | const filterSelector = `${snapshotLink}&selected_node_id=${service}&filter%5B0%5D%5Btype%5D=built-in` 70 | const href = `${filterSelector}&filter%5B0%5D%5Bkey%5D=operation&${filterValue}` 71 | return `${operation}` 72 | }, 73 | snapshotFilterLink(title, project, snapshotId, service, key) { 74 | if (!key) { 75 | return title 76 | } 77 | const base = viewHelpers.snapshotLink(project, snapshotId) 78 | const params = `&selected_node_id=${service}&group_by_key=${key}&group_by_type=tag` 79 | return `${title}` 80 | }, 81 | parseOnCall(pd) { 82 | if (pd && pd.oncalls) { 83 | return `${pd.oncalls.map(o => o.user.summary).join(', ')}` 84 | } 85 | 86 | if (pd && pd.error) { 87 | return ':exclamation: API Error' 88 | } 89 | return ':question:' 90 | } 91 | } 92 | 93 | /** 94 | * Gets details for a single service, including optional integrations 95 | */ 96 | const getServiceDetail = async function(s, config) { 97 | var onCall 98 | if (config.services()[s] && config.services()[s].integrations) { 99 | const integrations = config.services()[s].integrations 100 | 101 | // get pagerduty on-call information 102 | if (integrations.pagerduty && integrations.pagerduty.service) { 103 | onCall = getServiceOnCall({ apiToken : process.env.PAGERDUTY_API_TOKEN, 104 | service : integrations.pagerduty.service }) 105 | } 106 | } 107 | if (onCall) { 108 | return onCall.then(v => { 109 | return { service : s, pagerduty : v } 110 | }).catch(e => { 111 | return { service : s, pagerduty : { error : `${e}` } } 112 | }) 113 | } 114 | 115 | return Promise.resolve().then(v => { 116 | return { service : s } 117 | }) 118 | } 119 | 120 | 121 | /** 122 | * 123 | * Presents data in a way easily consumed by the markdown template. 124 | * 125 | * @param {*} model data 126 | */ 127 | const viewModel = async function({ 128 | data, 129 | lightstepProj, 130 | serviceViolations, 131 | config 132 | }) { 133 | const model = { 134 | lightstepProj, 135 | snapshotBeforeId : data.snapshotBeforeId, 136 | snapshotAfterId : data.snapshotAfterId, 137 | integrations : { 138 | pagerduty : { enabled : false }, 139 | rollbar : { enabled : false }, 140 | gremlin : { enabled : false } 141 | }, 142 | snapshotCompare : true, 143 | serviceTable : [], 144 | newServicesCount : 0, 145 | delServicesCount : 0, 146 | helpers : viewHelpers 147 | } 148 | 149 | if (data.snapshotBeforeId === data.snapshotAfterId) { 150 | model.snapshotCompare = false 151 | } 152 | 153 | // get detail for services in both snapshots + added services 154 | const services = Object.keys(data[data.snapshotAfterId].snapshot) 155 | const serviceDetail = services.map(s => { 156 | return getServiceDetail(s, config) 157 | }) 158 | model.serviceTable = await Promise.all(serviceDetail) 159 | 160 | // annotate new services 161 | model.serviceTable.forEach(s => { 162 | const diff = data[`${data.snapshotBeforeId}_${data.snapshotAfterId}`] 163 | s.diff = diff.snapshot[s.service] 164 | s.snapshot = data[data.snapshotAfterId].snapshot[s.service] 165 | if (diff.diagram.added_services.includes(s.service)) { 166 | s.new_service = true 167 | s.newServicesCount = s.newServicesCount + 1 168 | } 169 | if (diff.diagram.deleted_services.includes(s.service)) { 170 | s.deleted_service = true 171 | s.delServicesCount = s.delServicesCount + 1 172 | } 173 | if (serviceViolations[s.service]) { 174 | s.violations = serviceViolations[s.service].violations 175 | } 176 | }) 177 | 178 | // system integrations 179 | if (config.integrations().rollbar) { 180 | model.integrations.rollbar.enabled = true 181 | model.integrations.rollbar.config = config.integrations().rollbar 182 | const env = config.integrations().rollbar.environment 183 | await getLastDeployVersions( 184 | { token : process.env.ROLLBAR_API_TOKEN, environment : env } 185 | ).then(versions => { 186 | model.integrations.rollbar.versions = versions 187 | }).catch(err => { 188 | model.integrations.rollbar.error = err 189 | }) 190 | } 191 | 192 | // service-to-partner integrations 193 | for (var s of model.serviceTable) { 194 | if (s.pagerduty) { 195 | model.integrations.pagerduty.enabled = true 196 | model.integrations.pagerduty.config = config.integrations().pagerduty 197 | } 198 | 199 | if (s.gremlin) { 200 | model.integrations.gremlin.enabled = true 201 | model.integrations.gremlin.config = config.integrations().gremlin 202 | } 203 | } 204 | return model 205 | } 206 | 207 | /** 208 | * Generates a markdown summary of the diff between two snapshots, 209 | * with optional partner integrations. 210 | * 211 | * @param {} model data 212 | */ 213 | const markdownSummary = async function({ 214 | data, 215 | lightstepProj, 216 | serviceViolations, 217 | config 218 | }) { 219 | const templateModel = await viewModel({ 220 | data, 221 | lightstepProj, 222 | serviceViolations, 223 | config 224 | }) 225 | const table = tableTemplate({... templateModel, inputTable : templateModel.serviceTable }) 226 | const markdown = summaryTemplate({ ...templateModel, table }) 227 | 228 | if (process.env.NODE_DEBUG) { 229 | // eslint-disable-next-line no-console 230 | console.log(markdown) 231 | } 232 | 233 | return markdown 234 | } 235 | 236 | module.exports = { markdownSummary } -------------------------------------------------------------------------------- /template/service_table.tmpl.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <% if (integrations.pagerduty) {%><%}%> 5 | 6 | <%for (var s of inputTable) {%> 7 | 8 | 9 | 10 | 11 | <% if (integrations.pagerduty) {%><%}%> 12 | 13 | 14 | 15 | 64 | <%}%> 65 |
ServiceAverage LatencyError %PagerDuty Logo On-Call
<% if (s.new_service) { %>:new: <%}%><%=s.service%><%= helpers.latencyFormatter(s) %><%=helpers.percentFormatter(s)%><%= helpers.parseOnCall(s.pagerduty) %>
 <% if (s.violations && s.violations.length > 0) { %> 16 |

17 | :warning: Violations 18 |
    <% for (v of s.violations) { %> 19 |
  • <%=helpers.snapshotFilterLink(v.msg, lightstepProj, snapshotAfterId, s.service, v.violation.key)%>
  • <% } %> 20 |
21 |

<% } %><% if (s.snapshot.dependencies) { %> 22 |

23 | :arrow_down_small: Downstream Dependencies 24 |
    <% for (serviceDep in s.snapshot.dependencies) { %> 25 |
  • <% if (serviceDep.new_connection) { %>:new: <%}%><%=serviceDep%>
  • <% } %> 26 |
27 |

28 | <% } %>

29 | :gear: Service Operations 30 |
    <% for (op in s.snapshot.operations) { %> 31 |
  • <%= helpers.snapshotOperationLink(lightstepProj, snapshotAfterId, s.service, op) %>
  • <% } %> 32 |
33 |

34 |

35 | 🕵️‍♀️ What caused that change? 36 |
    37 |
  • 38 |

    Snapshot<%=snapshotBeforeId%>

    39 |
      40 |
    • <%=helpers.snapshotFilterLink(':octocat: View traces by GitHub SHA', lightstepProj, snapshotBeforeId, s.service, 'github.sha')%>
    • 41 | <% if (integrations.rollbar) {%>
    • <%=helpers.snapshotFilterLink(`Rollbar Logo View traces by Rollbar error`, lightstepProj, snapshotBeforeId, s.service, 'rollbar.error_uuid')%>
    • <% } %> 42 | 44 |
    45 |
  • <% if (snapshotCompare) {%> 46 |
  • Snapshot<%=snapshotAfterId%>

    47 |
      48 |
    • <%=helpers.snapshotFilterLink(':octocat: View traces by GitHub SHA', lightstepProj, snapshotAfterId, s.service, 'github.sha')%>
    • 49 | <% if (integrations.rollbar) {%>
    • <%=helpers.snapshotFilterLink(`Rollbar Logo View traces by Rollbar error`, lightstepProj, snapshotBeforeId, s.service, 'rollbar.error_uuid')%>
    • <% } %> 50 | 52 |
    53 |
  • <% } %> 54 |
55 |
56 |

<% if (helpers.parseOnCall(s.pagerduty) === ':question:') { %> 57 |

58 | 🗒️ Recommendations 59 |
    60 |
  • :pager: No on-call information found. Add a PagerDuty service to .lightstep.yml to see on-call information for this service.
  • 61 |
62 |

<% } %> 63 |
66 | -------------------------------------------------------------------------------- /template/summary.tmpl.md: -------------------------------------------------------------------------------- 1 | ### Lightstep Logo Lightstep Services Change Report 2 | <% if (snapshotCompare) {%>Comparing topology, latency and errors of services between snapshots [`<%=snapshotBeforeId%>`](<%=helpers.snapshotLink(lightstepProj, snapshotBeforeId)%>) and [`<%=snapshotAfterId%>`](<%=helpers.snapshotLink(lightstepProj, snapshotAfterId)%>) 3 | > `<%= newServicesCount %>` new service(s) detected 4 | <% } else { %>>Topology, latency and errors summary for snapshot [`<%=snapshotBeforeId%>`](<%=helpers.snapshotLink(lightstepProj, snapshotBeforeId)%>)<% }%> 5 | <%= table %> 6 | 7 | <% if (integrations.rollbar) {%>#### Rollbar Logo Errors 8 | > <%= helpers.rollbarVersionErrors(integrations.rollbar) %><% }%> 9 | --------------------------------------------------------------------------------