├── .eslintignore ├── .eslintrc ├── .github ├── pull_request_template.md └── workflows │ └── node.js.yml ├── .gitignore ├── .nycrc.yml ├── LICENSE ├── README.md ├── bin └── rollbar ├── package.json ├── src ├── common │ ├── output.js │ └── rollbar-api.js ├── deploy │ ├── command.js │ └── deployer.js ├── index.js └── sourcemaps │ ├── command.js │ ├── requester.js │ ├── scanner.js │ ├── signed-url-uploader.js │ └── uploader.js └── test ├── common ├── output.test.js └── rollbar-api.test.js ├── deploy.test.js ├── deploy ├── command.test.js └── deployer.test.js ├── fixtures └── builds │ ├── angular9 │ └── dist │ │ └── app │ │ ├── favicon.ico │ │ ├── index.html │ │ ├── main-es2015.js │ │ ├── main-es2015.js.map │ │ ├── main-es5.js │ │ ├── main-es5.js.map │ │ ├── polyfills-es2015.js │ │ ├── polyfills-es2015.js.map │ │ ├── polyfills-es5.js │ │ ├── polyfills-es5.js.map │ │ ├── runtime-es2015.js │ │ ├── runtime-es2015.js.map │ │ ├── runtime-es5.js │ │ ├── runtime-es5.js.map │ │ ├── styles-es2015.js │ │ ├── styles-es2015.js.map │ │ ├── styles-es5.js │ │ ├── styles-es5.js.map │ │ ├── vendor-es2015.js │ │ ├── vendor-es2015.js.map │ │ ├── vendor-es5.js │ │ └── vendor-es5.js.map │ ├── invalid │ ├── main1.js │ ├── main1.js.map │ ├── main2.js │ └── main2.js.map │ └── react16 │ └── build │ ├── asset-manifest.json │ ├── favicon.ico │ ├── index.html │ ├── manifest.json │ ├── precache-manifest.028769a3713cb7467890c784fe175e7a.js │ ├── service-worker.js │ └── static │ ├── css │ ├── main.2cce8147.chunk.css │ └── main.2cce8147.chunk.css.map │ └── js │ ├── 2.373ccd69.chunk.js │ ├── 2.373ccd69.chunk.js.map │ ├── main.d504f0ad.chunk.js │ ├── main.d504f0ad.chunk.js.map │ ├── runtime~main.a8a9905a.js │ └── runtime~main.a8a9905a.js.map ├── sourcemaps.test.js └── sourcemaps ├── command.test.js ├── requester.test.js ├── scanner.test.js ├── signed-url-uploader.test.js └── uploader.test.js /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | test/fixtures 3 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "eslint:recommended", 3 | "env": { 4 | "node": true, 5 | "es6": true 6 | }, 7 | "parserOptions": { 8 | "ecmaVersion": 8 9 | }, 10 | "rules": { 11 | "camelcase": ["error", {"properties": "never"}], 12 | "quotes": ["error", "single", "avoid-escape"], 13 | "no-underscore-dangle": "off", 14 | "no-useless-escape": "off", 15 | "complexity": ["error", { "max": 35 }], 16 | "no-use-before-define": ["off", { "functions": false }], 17 | "no-unused-vars": ["error", { "argsIgnorePattern": "^_" }], 18 | "no-prototype-builtins": "off", 19 | "no-var": "error", 20 | "prefer-const": ["error", { "destructuring": "all" }] 21 | }, 22 | "globals": { 23 | "output": true 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /.github/pull_request_template.md: -------------------------------------------------------------------------------- 1 | ## Description of the change 2 | 3 | > Please include a summary of the change and which issues are fixed. 4 | > Please also include relevant motivation and context. 5 | 6 | ## Type of change 7 | 8 | - [ ] Bug fix (non-breaking change that fixes an issue) 9 | - [ ] New feature (non-breaking change that adds functionality) 10 | - [ ] Breaking change (fix or feature that would cause existing functionality to not work as expected) 11 | - [ ] Maintenance 12 | - [ ] New release 13 | 14 | ## Related issues 15 | 16 | > Shortcut stories and GitHub issues (delete irrelevant) 17 | 18 | - Fix [SC-] 19 | - Fix #1 20 | 21 | ## Checklists 22 | 23 | ### Development 24 | 25 | - [ ] Lint rules pass locally 26 | - [ ] The code changed/added as part of this pull request has been covered with tests 27 | - [ ] All tests related to the changed code pass in development 28 | 29 | ### Code review 30 | 31 | - [ ] This pull request has a descriptive title and information useful to a reviewer. There may be a screenshot or screencast attached 32 | - [ ] "Ready for review" label attached to the PR and reviewers assigned 33 | - [ ] Issue from task tracker has a link to this pull request 34 | - [ ] Changes have been reviewed by at least one other engineer 35 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ master ] 9 | pull_request: 10 | branches: [ master ] 11 | 12 | jobs: 13 | build: 14 | 15 | runs-on: ubuntu-latest 16 | 17 | strategy: 18 | matrix: 19 | node-version: [10.x, 12.x, 14.x] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | - name: Use Node.js ${{ matrix.node-version }} 24 | uses: actions/setup-node@v1 25 | with: 26 | node-version: ${{ matrix.node-version }} 27 | - run: npm install 28 | - run: npm test 29 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | package-lock.json 3 | coverage 4 | .nyc_output 5 | .DS_Store 6 | *.swp 7 | -------------------------------------------------------------------------------- /.nycrc.yml: -------------------------------------------------------------------------------- 1 | --- 2 | check-coverage: true 3 | lines: 90 4 | branches: 80 5 | functions: 80 6 | statements: 90 7 | watermarks: 8 | lines: [90, 95] 9 | functions: [70, 90] 10 | branches: [70, 90] 11 | statements: [90, 95] 12 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Rollbar 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # rollbar-cli 2 | 3 | The Rollbar CLI provides easy command line access to Rollbar's API features, 4 | starting with source map uploads and notifying deploys. 5 | 6 | ![build](https://github.com/rollbar/rollbar-cli/workflows/Node.js%20CI/badge.svg) 7 | 8 | ## Install 9 | 10 | ``` 11 | npm install -g rollbar-cli 12 | ``` 13 | 14 | ## Usage and Reference 15 | Currently upload-sourcemaps and notify-deploy commands are supported. 16 | 17 | ### upload-sourcemaps 18 | Upload source maps recursively from a directory. 19 | 20 | ``` 21 | rollbar-cli upload-sourcemaps [options] 22 | 23 | upload sourcemaps 24 | 25 | Options: 26 | --version Show version number [boolean] 27 | -v, --verbose Verbose status output [boolean] 28 | -q, --quiet Silent status output [boolean] 29 | --help Show help [boolean] 30 | --access-token Access token for the Rollbar API [string] [required] 31 | --url-prefix Base part of the stack trace URLs [string] [required] 32 | --code-version Code version string must match value in the Rollbar item 33 | [string] [required] 34 | --next Next version. Zip all the source map files and upload as one 35 | file [boolean] 36 | -D, --dry-run Scan and validate source maps without uploading [boolean] 37 | ``` 38 | 39 | Some of these options are required and must be specified for a successful upload. 40 | 41 | `path`: Absolute or relative path to build directory. This directory should contain .js 42 | files with `sourceMappingURL` directives included. The current version of the CLI 43 | supports detecting files with `sourceMappingURL` directives and uploading related 44 | map files within a directory. 45 | 46 | `--access-token`: The Rollbar API `post_server_item` token. 47 | 48 | `--url-prefix`: The base portion of the URL to be concatenated with the js filenames 49 | discovered while scanning `path`. The Rollbar backend uses this to match stack frame locations 50 | and it must exactly match the URLs in the error stack frames. See `minified_url` at 51 | [Source Maps](https://docs.rollbar.com/docs/source-maps) for more information. 52 | 53 | `--code-version`: The code version string must match the string passed in the Rollbar 54 | error payload, which is usually set in the config options for Rollbar.js. 55 | See [Source Maps](https://docs.rollbar.com/docs/source-maps) for more information. 56 | 57 | `--next`: This is an optional parameter triggering next version. When specified, all source map files 58 | are compressed and uploaded as one zip file directly. 59 | 60 | Example: 61 | ``` 62 | rollbar-cli upload-sourcemaps ./dist --access-token 638d... --url-prefix 'http://example.com/' --code-version 123.456 63 | ``` 64 | or 65 | ``` 66 | rollbar-cli upload-sourcemaps ./dist --access-token 638d... --url-prefix 'http://example.com/' --code-version 123.456 --next 67 | ``` 68 | 69 | ### notify-deploy 70 | Notify deploy to Rollbar. 71 | 72 | ``` 73 | rollbar-cli notify-deploy [options] 74 | 75 | Notify deploy to Rollbar 76 | 77 | Options: 78 | --version Show version number [boolean] 79 | -v, --verbose Verbose status output [boolean] 80 | -q, --quiet Silent status output [boolean] 81 | --help Show help [boolean] 82 | --access-token Use a post server item access token for the Rollbar API 83 | [string] [required] 84 | --code-version Code version or Git SHA of revision being deployed 85 | [string] [required] 86 | --deploy-id ID of the deploy to update [string] 87 | --environment Environment to which the revision was deployed such as 88 | production [string] [required] 89 | --status Status of the deploy - started, succeeded (default), 90 | failed, or timed_out [string] 91 | --rollbar-username Rollbar username of person who deployed [string] 92 | --local-username Local username of person who deployed [string] 93 | --comment Additional text to include with the deploy [string] 94 | ``` 95 | 96 | Example: 97 | ``` 98 | rollbar-cli notify-deploy --access-token 1234 --code-version 1.0.1 --environment production --rollbar-username foobar --status succeeded --local-username foo_bar --comment 'Deploy Test' 99 | ``` 100 | 101 | Output on success: 102 | ``` 103 | { deploy_id: 12345678 } 104 | Deploy successful 105 | ``` 106 | 107 | ## Release History & Changelog 108 | 109 | See our [Releases](https://github.com/rollbar/rollbar-cli/releases) page for a list of all releases, including changes. 110 | 111 | ## Help / Support 112 | 113 | If you run into any issues, please email us at [support@rollbar.com](mailto:support@rollbar.com). 114 | 115 | For bug reports, please [open an issue on GitHub](https://github.com/rollbar/rollbar-cli/issues/new). 116 | 117 | ## Developing 118 | 119 | To set up a development environment, you'll need Node.js and npm. 120 | 121 | 1. Install dependencies 122 | `npm install` 123 | 124 | 2. Link the rollbar-cli command to the local repo 125 | `npm link` 126 | 127 | 3. Run the tests 128 | `npm test` 129 | 130 | ## Contributing 131 | 132 | 1. [Fork it](https://github.com/rollbar/rollbar-cli). 133 | 2. Create your feature branch (`git checkout -b my-new-feature`). 134 | 3. Commit your changes (`git commit -am 'Added some feature'`). 135 | 4. Push to the branch (`git push origin my-new-feature`). 136 | 5. Create a new Pull Request. 137 | 138 | ## License 139 | 140 | rollbar-cli is free software released under the MIT License. See LICENSE for details. 141 | -------------------------------------------------------------------------------- /bin/rollbar: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | 'use strict'; 4 | 5 | (require('../src/index'))(); 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "rollbar-cli", 3 | "version": "0.2.1", 4 | "license": "MIT", 5 | "bin": { 6 | "rollbar-cli": "bin/rollbar" 7 | }, 8 | "scripts": { 9 | "lint": "eslint . --ext .js", 10 | "test": "nyc --reporter=html --reporter=text mocha './test/{,!(fixtures)/**/}*test.js'" 11 | }, 12 | "dependencies": { 13 | "adm-zip": "^0.5.2", 14 | "axios": "^0.24.0", 15 | "chalk": "^4.1.0", 16 | "form-data": "^3.0.0", 17 | "glob": "^7.1.6", 18 | "source-map": "^0.7.3", 19 | "yargs": "^15.4.1" 20 | }, 21 | "devDependencies": { 22 | "chai": "^4.2.0", 23 | "eslint": "^6.8.0", 24 | "mocha": "^7.2.0", 25 | "nyc": "^15.1.0", 26 | "sinon": "^9.0.3" 27 | }, 28 | "description": "![build](https://github.com/rollbar/rollbar-cli/workflows/Node.js%20CI/badge.svg)", 29 | "main": "index.js", 30 | "directories": { 31 | "test": "test" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/rollbar/rollbar-cli.git" 36 | }, 37 | "keywords": [], 38 | "author": "", 39 | "bugs": { 40 | "url": "https://github.com/rollbar/rollbar-cli/issues" 41 | }, 42 | "homepage": "https://github.com/rollbar/rollbar-cli#readme" 43 | } 44 | -------------------------------------------------------------------------------- /src/common/output.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const util = require('util'); 4 | const chalk = require('chalk'); 5 | 6 | class Output { 7 | constructor(options = {}) { 8 | this.all = !!options.verbose; 9 | this.enable = !options.quiet; 10 | this.labelSize = options.labelSize || 6; 11 | } 12 | 13 | status() { 14 | this.write(chalk.white(this.format(...arguments))); 15 | } 16 | 17 | verbose() { 18 | if (this.all) { 19 | this.write(chalk.grey(this.format(...arguments))); 20 | } 21 | } 22 | 23 | warn() { 24 | this.write(chalk.yellow(this.format(...arguments))); 25 | } 26 | 27 | error() { 28 | this.write(chalk.red(this.format(...arguments))); 29 | } 30 | 31 | fail() { 32 | this.write(chalk.red(this.format(...arguments))); 33 | } 34 | 35 | success() { 36 | this.write(chalk.green(this.format(...arguments))); 37 | } 38 | 39 | format() { 40 | const args = Array.from(arguments); 41 | let paddedLabel = args[0].padEnd(this.labelSize, ' '); 42 | 43 | if (args[0].trim().length) { 44 | paddedLabel = `[${paddedLabel}] `; 45 | } else { 46 | paddedLabel = ` ${paddedLabel} `; 47 | } 48 | 49 | return paddedLabel + util.format(...args.slice(1)) + '\n'; 50 | } 51 | 52 | write(str) { 53 | if (this.enable) { 54 | process.stdout.write(str); 55 | } 56 | } 57 | } 58 | 59 | module.exports = Output; 60 | -------------------------------------------------------------------------------- /src/common/rollbar-api.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const axios = require('axios'); 4 | const FormData = require('form-data'); 5 | 6 | class RollbarAPI { 7 | constructor(accessToken) { 8 | this.accessToken = accessToken; 9 | 10 | this.axios = axios.create({ 11 | baseURL: 'https://api.rollbar.com/api/1/', 12 | headers: { 'X-Rollbar-Access-Token': accessToken }, 13 | // Always resolve, regardless of status code. 14 | // When we let axios reject, we end up with less specific error messages. 15 | validateStatus: function (_status) { return true; }, 16 | // Let axios send anything, and let the API decide what the max length should be. 17 | maxContentLength: Infinity, 18 | maxBodyLength: Infinity, 19 | }); 20 | } 21 | 22 | async deploy(request, deployId) { 23 | let resp; 24 | if(deployId) { 25 | output.verbose('', 'Update to an existing deploy with deploy_id: ' + deployId); 26 | resp = await this.axios.patch('/deploy/' + deployId, request); 27 | } else { 28 | output.verbose('','deploy_id not present so likely a new deploy'); 29 | resp = await this.axios.post('/deploy', request); 30 | } 31 | 32 | // Output deploy-id 33 | if (resp.status === 200) { 34 | output.success('', resp.data.data); 35 | } 36 | return this.processResponse(resp); 37 | } 38 | 39 | async sigendURLsourcemaps(request) { 40 | 41 | const resp = await this.axios.post( 42 | '/signed_url/sourcemap_bundle', { version: request.version , prefix_url: request.baseUrl} 43 | ); 44 | return this.processSignedURLResponse(resp); 45 | } 46 | 47 | async sourcemaps(request) { 48 | output.verbose('', 'minified_url: ' + request.minified_url); 49 | 50 | const form = this.convertRequestToForm(request); 51 | const resp = await this.axios.post( 52 | '/sourcemap', 53 | form.getBuffer(), // use buffer to prevent unwanted string escaping. 54 | { headers: { 55 | // axios needs some help with headers for form data. 56 | 'Content-Type': `multipart/form-data; boundary=${form.getBoundary()}`, 57 | 'Content-Length': form.getLengthSync() 58 | }} 59 | ); 60 | 61 | return this.processResponse(resp); 62 | } 63 | 64 | convertRequestToForm(request) { 65 | const form = new FormData(); 66 | 67 | form.append('version', request.version); 68 | form.append('minified_url', request.minified_url); 69 | form.append('source_map', Buffer.from(request.source_map), { filename: 'source_map' }); 70 | 71 | if (request.sources) { 72 | for (const filename of Object.keys(request.sources)) { 73 | if (filename && request.sources[filename]) { 74 | form.append(filename, Buffer.from(request.sources[filename]), { filename: filename }); 75 | } 76 | } 77 | } 78 | return form; 79 | } 80 | 81 | processSignedURLResponse(resp) { 82 | output.verbose('', 'response:', resp.data, resp.status, resp.statusText); 83 | return resp.data; 84 | } 85 | 86 | processResponse(resp) { 87 | output.verbose('', 'response:', resp.data, resp.status, resp.statusText); 88 | if (resp.status === 200) { 89 | return null; 90 | } 91 | return resp.data; 92 | } 93 | } 94 | 95 | module.exports = RollbarAPI; 96 | -------------------------------------------------------------------------------- /src/deploy/command.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Deployer = require('./deployer.js'); 4 | const Output = require('../common/output.js'); 5 | 6 | exports.command = 'notify-deploy [options]' 7 | 8 | exports.describe = 'Notify deploy to Rollbar' 9 | 10 | exports.builder = function (yargs) { 11 | return yargs 12 | .option('access-token', { 13 | describe: 'Use a post server item access token for the Rollbar API', 14 | requiresArg: true, 15 | type: 'string', 16 | demandOption: true 17 | }) 18 | .option('code-version', { 19 | describe: 'Code version or Git SHA of revision being deployed', 20 | requiresArg: true, 21 | type: 'string', 22 | demandOption: true 23 | }) 24 | .option('deploy-id', { 25 | describe: 'ID of the deploy to update', 26 | requiresArg: false, 27 | type: 'string', 28 | demandOption: false 29 | }) 30 | .option('environment', { 31 | describe: 'Environment to which the revision was deployed such as production', 32 | requiresArg: true, 33 | type: 'string', 34 | demandOption: true 35 | }) 36 | .option('status', { 37 | describe: 'Status of the deploy - started, succeeded (default), failed, or timed_out', 38 | requiresArg: false, 39 | type: 'string', 40 | demandOption: false 41 | }) 42 | .option('rollbar-username', { 43 | describe: 'Rollbar username of person who deployed', 44 | requiresArg: false, 45 | type: 'string', 46 | demandOption: false 47 | }) 48 | .option('local-username', { 49 | describe: 'Local username of person who deployed', 50 | requiresArg: false, 51 | type: 'string', 52 | demandOption: false 53 | }) 54 | .option('comment', { 55 | describe: 'Additional text to include with the deploy', 56 | requiresArg: false, 57 | type: 'string', 58 | demandOption: false 59 | }) 60 | } 61 | 62 | exports.handler = async function (argv) { 63 | global.output = new Output({ 64 | verbose: argv['verbose'], 65 | quiet: argv['quiet'] 66 | }); 67 | 68 | const deployer = new Deployer({ 69 | accessToken: argv['access-token'], 70 | codeVersion: argv['code-version'], 71 | deployId: argv['deploy-id'], 72 | environment: argv['environment'], 73 | status: argv['status'], 74 | rollbarUsername: argv['rollbar-username'], 75 | localUsername: argv['local-username'], 76 | comment: argv['comment'] 77 | }) 78 | 79 | await deployer.deploy(); 80 | } 81 | -------------------------------------------------------------------------------- /src/deploy/deployer.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RollbarAPI = require('../common/rollbar-api'); 4 | 5 | class Deployer { 6 | constructor(options) { 7 | this.rollbarAPI = new RollbarAPI(options.accessToken); 8 | this.version = options.codeVersion; 9 | this.deploy_id = options.deployId; 10 | this.environment = options.environment; 11 | this.status = options.status; 12 | this.rollbar_username = options.rollbarUsername; 13 | this.local_username = options.localUsername; 14 | this.comment = options.comment; 15 | } 16 | 17 | async deploy() { 18 | let error; 19 | try { 20 | error = await this.rollbarAPI.deploy(this.buildRequest(), this.deploy_id); 21 | } catch (e) { 22 | error = e.message; 23 | } 24 | 25 | if (error) 26 | output.error('Error', error); 27 | else 28 | output.success('','Deploy successful'); 29 | } 30 | 31 | buildRequest() { 32 | return { 33 | revision: this.version, 34 | environment: this.environment, 35 | status: this.status, 36 | rollbar_username: this.rollbar_username, 37 | local_username: this.local_username, 38 | comment: this.comment 39 | } 40 | } 41 | } 42 | 43 | module.exports = Deployer; 44 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const yargs = require('yargs'); 4 | 5 | function cli() { 6 | yargs 7 | .option('v', { 8 | alias: 'verbose', 9 | describe: 'Verbose status output', 10 | requiresArg: false, 11 | type: 'boolean', 12 | demandOption: false 13 | }) 14 | .option('q', { 15 | alias: 'quiet', 16 | describe: 'Silent status output', 17 | requiresArg: false, 18 | type: 'boolean', 19 | demandOption: false 20 | }) 21 | .command(require('./sourcemaps/command')) 22 | .command(require('./deploy/command')) 23 | .help() 24 | .argv 25 | 26 | return 0; 27 | } 28 | 29 | module.exports = cli; 30 | -------------------------------------------------------------------------------- /src/sourcemaps/command.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Scanner = require('./scanner'); 4 | const Uploader = require('./uploader'); 5 | const SignedUrlUploader = require('./signed-url-uploader'); 6 | const Requester = require('./requester'); 7 | const Output = require('../common/output.js'); 8 | 9 | exports.command = 'upload-sourcemaps [options]' 10 | 11 | exports.describe = 'upload sourcemaps' 12 | 13 | exports.builder = function (yargs) { 14 | return yargs 15 | .option('access-token', { 16 | describe: 'Access token for the Rollbar API', 17 | requiresArg: true, 18 | type: 'string', 19 | demandOption: true 20 | }) 21 | .option('url-prefix', { 22 | describe: 'Base part of the stack trace URLs', 23 | requiresArg: true, 24 | type: 'string', 25 | demandOption: true 26 | }) 27 | .option('code-version', { 28 | describe: 'Code version string must match value in the Rollbar item', 29 | requiresArg: true, 30 | type: 'string', 31 | demandOption: true 32 | }) 33 | .option('next', { 34 | describe: 'Next version. Zip all the source map files and upload as one file', 35 | requiresArg: false, 36 | type: 'boolean', 37 | demandOption: false 38 | }) 39 | .option('D', { 40 | alias: 'dry-run', 41 | describe: 'Scan and validate source maps without uploading', 42 | requiresArg: false, 43 | type: 'boolean', 44 | demandOption: false 45 | }) 46 | } 47 | 48 | exports.handler = async function (argv) { 49 | global.output = new Output({ 50 | verbose: argv['verbose'], 51 | quiet: argv['quiet'] 52 | }); 53 | 54 | const scanner = new Scanner({ 55 | targetPath: argv['path'], 56 | sources: argv['sources'] 57 | }); 58 | 59 | await scanner.scan(); 60 | 61 | if (argv['next']) { 62 | const requester = new Requester({ 63 | accessToken: argv['access-token'], 64 | baseUrl: argv['url-prefix'], 65 | codeVersion: argv['code-version'], 66 | dryRun: argv['dry-run'] 67 | }); 68 | 69 | await requester.requestSignedUrl(); 70 | const signedUrlUploader = new SignedUrlUploader(requester); 71 | if (requester.data && requester.data['err'] === 0) { 72 | requester.setProjectID(); 73 | requester.createManifestData(); 74 | await signedUrlUploader.upload(argv['dry-run'], scanner.files, requester.data['result']['signed_url']); 75 | } 76 | } else { 77 | const uploader = new Uploader({ 78 | accessToken: argv['access-token'], 79 | baseUrl: argv['url-prefix'], 80 | codeVersion: argv['code-version'] 81 | }); 82 | 83 | uploader.mapFiles(scanner.files); 84 | 85 | await uploader.upload(argv['dry-run']); 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/sourcemaps/requester.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RollbarAPI = require('../common/rollbar-api'); 4 | 5 | class Requester { 6 | constructor(options) { 7 | this.data = null; 8 | this.rollbarAPI = new RollbarAPI(options.accessToken); 9 | this.baseUrl = options.baseUrl; 10 | this.version = options.codeVersion; 11 | this.projectID = 0; 12 | this.dryRun = options.dryRun; 13 | this.manifestData = ''; 14 | } 15 | 16 | async requestSignedUrl() { 17 | if (this.dryRun) { 18 | // TODO: Maybe more can be done here, but the important part is just to 19 | // return without sending. The bulk of validation is done earlier 20 | // in the scanning phase. 21 | return this; 22 | } 23 | 24 | try { 25 | const data = await this.rollbarAPI.sigendURLsourcemaps(this.buildRequest()); 26 | 27 | this.data = data; 28 | if (data && data['err'] === 0) { 29 | output.success('', 'Requested for signed URL successfully'); 30 | } else { 31 | output.error('Error', data.message); 32 | } 33 | 34 | } catch (e) { 35 | output.error('Error', e.message); 36 | } 37 | } 38 | 39 | buildRequest() { 40 | return { 41 | version: this.version, 42 | baseUrl: this.baseUrl, 43 | }; 44 | } 45 | 46 | setProjectID() { 47 | this.projectID = this.data['result']['project_id']; 48 | } 49 | 50 | createManifestData() { 51 | const data = { 52 | projectID: this.projectID, 53 | version: this.version, 54 | baseUrl: this.baseUrl, 55 | }; 56 | 57 | const strData = JSON.stringify(data, null, 2); 58 | this.manifestData = strData; 59 | } 60 | } 61 | 62 | module.exports = Requester; 63 | -------------------------------------------------------------------------------- /src/sourcemaps/scanner.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const fs = require('fs'); 4 | const glob = require('glob'); 5 | const path = require('path'); 6 | const BasicSourceMapConsumer = require('source-map/lib/source-map-consumer').BasicSourceMapConsumer; 7 | 8 | class Scanner { 9 | constructor(options) { 10 | this.files = []; 11 | this.mappings = {}; 12 | this.targetPath = options.targetPath; 13 | this.projectPath = './'; 14 | this.sources = options.sources; 15 | } 16 | 17 | async scan() { 18 | await this.scanFiles(); 19 | } 20 | 21 | async scanFiles() { 22 | if (this.targetPath) { 23 | this.files = this.targetFiles(); 24 | } 25 | 26 | for (const file of this.files) { 27 | output.status('Found', file.filePathName); 28 | 29 | this.extractMapPath(file); 30 | await this.loadMapData(file); 31 | 32 | for(const error of file.errors) { 33 | output.warn('Error', error.error); 34 | } 35 | } 36 | output.verbose('files:', this.files); 37 | } 38 | 39 | extractMapPath(file) { 40 | const mapPath = this.parseMapPath(file.filePathName); 41 | 42 | if(mapPath) { 43 | output.status('', mapPath); 44 | file.mapPathName = mapPath; 45 | file.sourceMappingURL = true; 46 | file.mappedFile = path.join(this.targetPath, mapPath); 47 | 48 | } else { 49 | output.warn('', 'map not found'); 50 | } 51 | } 52 | 53 | async loadMapData(file) { 54 | if (file.mapPathName) { 55 | try { 56 | const filePath = path.dirname(file.filePathName); 57 | const mapPath = path.resolve(filePath, file.mapPathName); 58 | const data = fs.readFileSync(mapPath).toString(); 59 | 60 | if (data) { 61 | file.mapData = data; 62 | file.map = JSON.parse(file.mapData); 63 | file.sources = (this.sources && file.map.sources) ? await this.originalSources(file) : {}; 64 | const errors = await this.validate(file); 65 | if (errors && errors.length) { 66 | file.errors.push(...errors); 67 | } else { 68 | file.validated = true; 69 | } 70 | } else { 71 | output.warn('', 'map data not valid'); 72 | } 73 | 74 | if (file.validated) { 75 | output.success('', 'valid'); 76 | } else { 77 | output.warn('', 'map not valid'); 78 | } 79 | 80 | } catch (e) { 81 | file.errors.push({ 82 | error: 'Error parsing map file: ' + e.message, 83 | file: file 84 | }); 85 | } 86 | } 87 | } 88 | 89 | async originalSources(file) { 90 | const map = file.map; 91 | const filenames = map.sources; 92 | const sources = {}; 93 | 94 | output.status('', `${filenames.length} original sources`); 95 | 96 | const consumer = await new BasicSourceMapConsumer(map); 97 | 98 | for (const filename of filenames) { 99 | if (filename) { 100 | if (filename.includes('.') && !filename.startsWith('..')) { 101 | const filepath = path.join(this.projectPath, filename); 102 | output.verbose(filepath); 103 | try { 104 | output.verbose('', filename); 105 | sources[filename] = consumer.sourceContentFor(filepath, true); 106 | } catch (e) { 107 | output.warn('', e.message); 108 | file.errors.push({ 109 | error: e.message, 110 | file: file 111 | }); 112 | } 113 | } else { 114 | // Avoid known issues with names Rollbar API doesn't accept. 115 | output.verbose('', 'Ignored: ' + filename); 116 | } 117 | } 118 | } 119 | 120 | return sources; 121 | } 122 | 123 | async validate(file) { 124 | const map = file.map; 125 | const errors = []; 126 | 127 | file.metadata.version = map.version; 128 | file.metadata.file = map.file; 129 | if (map.sections) { 130 | file.metadata.sections = map.sections.length; 131 | } 132 | 133 | if (map.sources) { 134 | file.metadata.sources = map.sources; 135 | } 136 | 137 | // For now, validation is lightweight and just answers the question: 138 | // Is this a valid source map at all? 139 | // BasicSourceMapConsumer will complain if not, so load it up and see if it throws. 140 | try { 141 | await new BasicSourceMapConsumer(map); 142 | } catch (e) { 143 | errors.push({ 144 | error: 'Error parsing map file: ' + e.message, 145 | file: file 146 | }); 147 | } 148 | 149 | return errors; 150 | } 151 | 152 | mappedFiles() { 153 | return this.files; 154 | } 155 | 156 | targetFiles() { 157 | const globPath = path.join(this.targetPath, '**/*.js'); 158 | const outFiles = []; 159 | 160 | const files = glob.sync(globPath); 161 | for (const filename of files) { 162 | outFiles.push(this.initFile(filename)); 163 | } 164 | 165 | return outFiles; 166 | } 167 | 168 | initFile(filePathName) { 169 | return { 170 | filePathName: filePathName, 171 | fileName: path.relative(this.targetPath, filePathName), 172 | sourceMappingURL: false, 173 | mapPathName: null, 174 | mapData: null, 175 | validated: false, 176 | metadata: {}, 177 | errors: [] 178 | } 179 | } 180 | 181 | parseMapPath(path) { 182 | const regex = /^\s*\/\/#\s*sourceMappingURL\s*=\s*(.+)\s*$/; 183 | const data = fs.readFileSync(path).toString(); 184 | const lines = data.split('\n').reverse(); 185 | 186 | for (const line of lines) { 187 | const matched = line.match(regex); 188 | if (matched) { 189 | return matched[1]; 190 | } 191 | } 192 | } 193 | } 194 | 195 | module.exports = Scanner; 196 | -------------------------------------------------------------------------------- /src/sourcemaps/signed-url-uploader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const AdmZip = require('adm-zip'); 4 | const axios = require('axios'); 5 | const zipFile = new AdmZip(); 6 | 7 | class SignedUrlUploader { 8 | constructor(requester) { 9 | this.zippedMapFile = ''; 10 | this.files = []; 11 | this.requester = requester; 12 | this.zipBuffer = Buffer; 13 | 14 | } 15 | 16 | mapFiles(files) { 17 | this.files = files; 18 | 19 | return this; 20 | } 21 | 22 | zipFiles() { 23 | 24 | try { 25 | zipFile.addFile('manifest.json', this.requester.manifestData); 26 | } catch (e) { 27 | output.status('Error', e.message); 28 | } 29 | for (const file of this.files) { 30 | try { 31 | if (file.validated) { 32 | zipFile.addLocalFile(file.mappedFile); 33 | } 34 | } catch(e) { 35 | output.status('Error', e.message); 36 | } 37 | } 38 | try { 39 | this.zipBuffer = zipFile.toBuffer(); 40 | if (this.zipBuffer.length != 0) { 41 | output.success('', 'Zipped all the source map files successfully'); 42 | } else { 43 | output.error('', 'Zip was unsuccessful'); 44 | } 45 | } catch(e) { 46 | output.status('Error', e.message); 47 | } 48 | } 49 | 50 | async upload(dryRun, files, signedUrl) { 51 | if (dryRun) { 52 | // TODO: Maybe more can be done here, but the important part is just to 53 | // return without sending. The bulk of validation is done earlier 54 | // in the scanning phase. 55 | return this.files; 56 | } 57 | 58 | try { 59 | this.mapFiles(files); 60 | this.zipFiles(); 61 | 62 | const resp = await axios.put(signedUrl, this.zipBuffer, { 63 | headers: { 64 | 'Content-Type': 'application/octet-stream', 65 | }, 66 | }); 67 | if (resp.status === 200) { 68 | output.status('Success', 'Uploaded zip file successfully'); 69 | } else { 70 | output.status('Error', 'Could not upload the zip file'); 71 | } 72 | 73 | } catch (e) { 74 | output.status('Error', e.message); 75 | } 76 | } 77 | } 78 | 79 | module.exports = SignedUrlUploader; 80 | -------------------------------------------------------------------------------- /src/sourcemaps/uploader.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const RollbarAPI = require('../common/rollbar-api'); 4 | const URL = require('url').URL; 5 | 6 | class Uploader { 7 | constructor(options) { 8 | this.files = []; 9 | this.rollbarAPI = new RollbarAPI(options.accessToken); 10 | this.baseUrl = options.baseUrl; 11 | this.version = options.codeVersion; 12 | } 13 | 14 | mapFiles(files) { 15 | this.files = files; 16 | 17 | return this; 18 | } 19 | 20 | async upload(dryRun) { 21 | if (dryRun) { 22 | // TODO: Maybe more can be done here, but the important part is just to 23 | // return without sending. The bulk of validation is done earlier 24 | // in the scanning phase. 25 | return this.files; 26 | } 27 | 28 | for (const file of this.files) { 29 | output.status('Upload', file.mapPathName); 30 | 31 | try { 32 | if (this.skip(file)) { 33 | continue; 34 | } 35 | 36 | const error = await this.rollbarAPI.sourcemaps(this.buildRequest(file)); 37 | 38 | if (error) { 39 | file.errors.push({ 40 | error: error.message, 41 | file: file 42 | }); 43 | } 44 | } catch (e) { 45 | file.errors.push({ 46 | error: e.message, 47 | file: file 48 | }); 49 | } 50 | 51 | if (!file.errors.length) { 52 | output.success('', 'Upload successful'); 53 | } 54 | 55 | for (const error of file.errors) { 56 | output.error('Error', error.error); 57 | } 58 | } 59 | 60 | return this.files; 61 | } 62 | 63 | buildRequest(file) { 64 | return { 65 | version: this.version, 66 | minified_url: this.minifiedUrl(file.fileName), 67 | source_map: file.mapData, 68 | sources: file.sources 69 | } 70 | } 71 | 72 | minifiedUrl(fileName) { 73 | return (new URL(fileName, this.baseUrl)).toString(); 74 | } 75 | 76 | skip(file) { 77 | if (file.fileName && file.mapData && !file.errors.length){ 78 | return false; 79 | } 80 | 81 | output.warn('', 'skip: ' + file.filePathName); 82 | 83 | return true; 84 | } 85 | } 86 | 87 | module.exports = Uploader; 88 | -------------------------------------------------------------------------------- /test/common/output.test.js: -------------------------------------------------------------------------------- 1 | /* globals describe */ 2 | /* globals it */ 3 | 4 | const expect = require('chai').expect; 5 | 6 | const Output = require('../../src/common/output'); 7 | 8 | describe('Output()', function() { 9 | it('should initialize successfully', function() { 10 | const options = { 11 | verbose: true, 12 | quiet: true, 13 | labelSize: 8 14 | }; 15 | const output = new Output(options); 16 | 17 | expect(output.all).to.equal(!!options.verbose); 18 | expect(output.enable).to.equal(!options.quiet); 19 | expect(output.labelSize).to.equal(options.labelSize); 20 | }); 21 | }); 22 | -------------------------------------------------------------------------------- /test/common/rollbar-api.test.js: -------------------------------------------------------------------------------- 1 | /* globals describe */ 2 | /* globals it */ 3 | /* globals beforeEach */ 4 | /* globals afterEach */ 5 | 6 | const expect = require('chai').expect; 7 | const sinon = require('sinon'); 8 | 9 | const RollbarAPI = require('../../src/common/rollbar-api'); 10 | const Output = require('../../src/common/output'); 11 | 12 | describe('RollbarAPI()', function() { 13 | it('should initialize successfully', function() { 14 | const accessToken = 'abcd'; 15 | const rollbarAPI = new RollbarAPI(accessToken); 16 | 17 | expect(rollbarAPI.accessToken).to.equal(accessToken); 18 | expect(rollbarAPI.axios).to.have.property('post'); 19 | }); 20 | }); 21 | 22 | describe('.sourcemaps()', function() { 23 | beforeEach(function(done) { 24 | const accessToken = 'abcd'; 25 | this.currentTest.rollbarAPI = new RollbarAPI(accessToken); 26 | global.output = new Output({verbose: true}); 27 | 28 | this.currentTest.stub = sinon.stub(this.currentTest.rollbarAPI.axios, 'post'); 29 | 30 | done(); 31 | }); 32 | 33 | afterEach(function() { 34 | global.output = null; 35 | this.currentTest.stub.restore(); 36 | }); 37 | 38 | it('should send well formed request for signed URL', async function() { 39 | const rollbarAPI = this.test.rollbarAPI; 40 | const stub = this.test.stub; 41 | 42 | stub.resolves({ 43 | status: 200, 44 | statusText: 'Success', 45 | data: { err: 0, result: { uuid: 'd4c7acef55bf4c9ea95e4fe9428a8287'}} 46 | }); 47 | 48 | const request = { 49 | version: '123', 50 | prefix_url: 'https://example.com/', 51 | source_map: '{ \ 52 | "version" : 3, \ 53 | "file": "out.js", \ 54 | "sourceRoot": "", \ 55 | "sources": ["foo.js", "bar.js"], \ 56 | "sourcesContent": [null, null], \ 57 | "names": ["src", "maps", "are", "fun"], \ 58 | "mappings": "A,AAAB;;ABCDE;" \ 59 | }', 60 | sources: [] 61 | }; 62 | 63 | const response = await rollbarAPI.sigendURLsourcemaps(request); 64 | 65 | expect(response).to.be.not.null; 66 | expect(stub.calledOnce).to.be.true; 67 | 68 | const body = stub.getCall(0).args; 69 | expect(body[0]).to.equal('/signed_url/sourcemap_bundle'); 70 | expect(body[1]).to.be.a('Object'); 71 | }); 72 | 73 | it('should send well formed request', async function() { 74 | const rollbarAPI = this.test.rollbarAPI; 75 | const stub = this.test.stub; 76 | 77 | stub.resolves({ 78 | status: 200, 79 | statusText: 'Success', 80 | data: { err: 0, result: { uuid: 'd4c7acef55bf4c9ea95e4fe9428a8287'}} 81 | }); 82 | 83 | const request = { 84 | version: '123', 85 | minified_url: 'https://example.com/foo.js', 86 | source_map: '{ \ 87 | "version" : 3, \ 88 | "file": "out.js", \ 89 | "sourceRoot": "", \ 90 | "sources": ["foo.js", "bar.js"], \ 91 | "sourcesContent": [null, null], \ 92 | "names": ["src", "maps", "are", "fun"], \ 93 | "mappings": "A,AAAB;;ABCDE;" \ 94 | }', 95 | sources: [] 96 | }; 97 | 98 | const response = await rollbarAPI.sourcemaps(request); 99 | 100 | expect(response).to.be.null; 101 | expect(stub.calledOnce).to.be.true; 102 | 103 | const body = stub.getCall(0).args; 104 | expect(body[0]).to.equal('/sourcemap'); 105 | expect(body[1]).to.be.a('Uint8Array'); // This is how Chai sees the Buffer type 106 | expect(body[2].headers['Content-Type']).to.have.string('multipart/form-data; boundary=--------------------------'); 107 | expect(body[2].headers['Content-Length']).to.equal(726); 108 | }); 109 | 110 | it('should handle error responses', async function() { 111 | const rollbarAPI = this.test.rollbarAPI; 112 | const stub = this.test.stub; 113 | 114 | stub.resolves({ 115 | status: 401, 116 | statusText: 'Unauthorized', 117 | data: { err: 1, message: 'invalid access token'} 118 | }); 119 | 120 | const request = { 121 | version: '123', 122 | minified_url: 'https://example.com/foo.js', 123 | source_map: '{}', 124 | sources: [] 125 | }; 126 | 127 | const response = await rollbarAPI.sourcemaps(request); 128 | 129 | expect(response.err).to.equal(1); 130 | expect(response.message).to.equal('invalid access token'); 131 | expect(stub.calledOnce).to.be.true; 132 | }); 133 | }); 134 | 135 | describe('.deploy() without deployId', function() { 136 | beforeEach(function(done) { 137 | const accessToken = 'abcd'; 138 | this.currentTest.rollbarAPI = new RollbarAPI(accessToken); 139 | global.output = new Output({verbose: true}); 140 | 141 | this.currentTest.stub = sinon.stub(this.currentTest.rollbarAPI.axios, 'post'); 142 | done(); 143 | }); 144 | 145 | afterEach(function() { 146 | global.output = null; 147 | this.currentTest.stub.restore(); 148 | }); 149 | 150 | it('should handle basic deploy request', async function() { 151 | const rollbarAPI = this.test.rollbarAPI; 152 | const stub = this.test.stub; 153 | 154 | stub.resolves({ 155 | status: 200, 156 | statusText: 'Success', 157 | data: { deploy_id: 12345678 } 158 | }); 159 | 160 | const request = { 161 | revision: '123', 162 | environment: 'production', 163 | status: '', 164 | rollbar_username: '', 165 | local_username: '', 166 | comment: '' 167 | }; 168 | 169 | const deployId = ''; 170 | 171 | const response = await rollbarAPI.deploy(request, deployId); 172 | 173 | expect(response).to.be.null; 174 | expect(stub.calledOnce).to.be.true; 175 | 176 | const body = stub.getCall(0).args; 177 | expect(body[0]).to.equal('/deploy'); 178 | expect(body[1]).to.be.a('object'); 179 | }); 180 | 181 | it('should handle pre-deploy', async function() { 182 | const rollbarAPI = this.test.rollbarAPI; 183 | const stub = this.test.stub; 184 | 185 | stub.resolves({ 186 | status: 200, 187 | statusText: 'Success', 188 | data: { deploy_id: 12345678 } 189 | }); 190 | 191 | const request = { 192 | revision: '123', 193 | environment: 'production', 194 | status: 'started', 195 | rollbar_username: 'foobar', 196 | local_username: 'foo_bar', 197 | comment: 'Deploy Test' 198 | }; 199 | 200 | const deployId = ''; 201 | 202 | const response = await rollbarAPI.deploy(request, deployId); 203 | 204 | expect(response).to.be.null; 205 | expect(stub.calledOnce).to.be.true; 206 | 207 | const body = stub.getCall(0).args; 208 | expect(body[0]).to.equal('/deploy'); 209 | expect(body[1]).to.be.a('object'); 210 | }); 211 | 212 | it('should handle deploy error response', async function() { 213 | const rollbarAPI = this.test.rollbarAPI; 214 | const stub = this.test.stub; 215 | 216 | stub.resolves({ 217 | status: 422, 218 | statusText: 'Unprocessable Entity', 219 | data: { err: 1, message: 'Missing \'environment\' key. Missing \'revision\' or \'head_long\' key. \'environment\' key must be a string.' } 220 | }); 221 | 222 | const request = { 223 | revision: '', 224 | environment: '', 225 | status: '', 226 | rollbar_username: '', 227 | local_username: '', 228 | comment: '' 229 | }; 230 | 231 | const deployId = ''; 232 | 233 | const response = await rollbarAPI.deploy(request, deployId); 234 | 235 | expect(response.err).to.equal(1); 236 | expect(response.message).to.equal('Missing \'environment\' key. Missing \'revision\' or \'head_long\' key. \'environment\' key must be a string.'); 237 | expect(stub.calledOnce).to.be.true; 238 | }); 239 | }); 240 | 241 | describe('.deploy() with deployId', function() { 242 | beforeEach(function(done) { 243 | const accessToken = 'abcd'; 244 | this.currentTest.rollbarAPI = new RollbarAPI(accessToken); 245 | global.output = new Output({verbose: true}); 246 | 247 | this.currentTest.stub = sinon.stub(this.currentTest.rollbarAPI.axios, 'patch'); 248 | done(); 249 | }); 250 | 251 | afterEach(function() { 252 | global.output = null; 253 | this.currentTest.stub.restore(); 254 | }); 255 | 256 | it('should handle post-deploy', async function() { 257 | const rollbarAPI = this.test.rollbarAPI; 258 | const stub = this.test.stub; 259 | 260 | stub.resolves({ 261 | status: 200, 262 | statusText: 'Success', 263 | data: { deploy_id: 12345678 } 264 | }); 265 | 266 | const request = { 267 | revision: '123', 268 | environment: 'production', 269 | status: 'succeeded', 270 | rollbar_username: 'foobar', 271 | local_username: 'foo_bar', 272 | comment: 'comment' 273 | }; 274 | 275 | const deployId = '12345678'; 276 | 277 | const response = await rollbarAPI.deploy(request, deployId); 278 | 279 | expect(response).to.be.null; 280 | expect(stub.calledOnce).to.be.true; 281 | 282 | const body = stub.getCall(0).args; 283 | expect(body[0]).to.equal('/deploy/12345678'); 284 | expect(body[1]).to.be.a('object'); // This is how Chai sees the Buffer type 285 | }); 286 | }); 287 | 288 | -------------------------------------------------------------------------------- /test/deploy.test.js: -------------------------------------------------------------------------------- 1 | /* globals describe */ 2 | /* globals it */ 3 | const expect = require('chai').expect; 4 | const exec = require('child_process').exec; 5 | const execSync = require('child_process').execSync; 6 | 7 | describe('rollbar-cli notify-deploy', function() { 8 | it('returns help output', done => { 9 | this.timeout(5000); 10 | 11 | exec('./bin/rollbar notify-deploy --help', 'utf8', (_err, stdout, _stderr) => { 12 | expect(stdout).to.have.string('notify-deploy [options]'); 13 | done(); 14 | }) 15 | }); 16 | 17 | it('should handle error response', done => { 18 | this.timeout(5000); 19 | 20 | const stdout = execSync('./bin/rollbar notify-deploy --access-token 1234 --code-version 1.0.1 --environment production --rollbar-username foobar --status succeeded --deploy-id 12345678 --local-username foo_bar --comment \'Deploy Test\''); 21 | 22 | const lines = stdout.toString().split('\n'); 23 | 24 | expect(lines.length).to.equal(2); 25 | expect(stdout.toString()).to.have.string('invalid access token'); 26 | 27 | done(); 28 | }); 29 | }); 30 | -------------------------------------------------------------------------------- /test/deploy/command.test.js: -------------------------------------------------------------------------------- 1 | /* globals describe */ 2 | /* globals it */ 3 | /* globals beforeEach */ 4 | /* globals afterEach */ 5 | 6 | const expect = require('chai').expect; 7 | 8 | const Command = require('../../src/deploy/command'); 9 | const Output = require('../../src/common/output'); 10 | const yargs = require('yargs'); 11 | const sinon = require('sinon'); 12 | 13 | describe('deploy Command()', function() { 14 | beforeEach(function() { 15 | global.output = new Output({verbose: false}); 16 | this.currentTest.stubWarn = sinon.spy(global.output, 'warn'); 17 | this.currentTest.stubSuccess = sinon.spy(global.output, 'success'); 18 | }); 19 | 20 | afterEach(function() { 21 | global.output = null; 22 | this.currentTest.stubWarn.restore(); 23 | this.currentTest.stubSuccess.restore(); 24 | }); 25 | 26 | it('returns help output', async () => { 27 | const parser = yargs.command(Command).help(); 28 | 29 | const result = await new Promise((resolve) => { 30 | parser.parse('--help', (_err, _argv, output) => { 31 | resolve(output); 32 | }) 33 | }); 34 | 35 | expect(result).to.have.string('notify-deploy [options]'); 36 | }); 37 | }); 38 | -------------------------------------------------------------------------------- /test/deploy/deployer.test.js: -------------------------------------------------------------------------------- 1 | /* globals describe */ 2 | /* globals it */ 3 | /* globals beforeEach */ 4 | 5 | const expect = require('chai').expect; 6 | const sinon = require('sinon'); 7 | 8 | const Deployer = require('../../src/deploy/deployer'); 9 | const Output = require('../../src/common/output'); 10 | 11 | describe('Deployer()', function() { 12 | it('should initialize successfully', function() { 13 | const options = { 14 | accessToken: 'abcd', 15 | codeVersion: '123', 16 | deployId: '12345678', 17 | environment: 'production', 18 | status: 'succeeded', 19 | rollbarUsername: 'foobar', 20 | localUsername: 'foo_bar', 21 | comment: 'Deploy Test' 22 | }; 23 | const deployer = new Deployer(options); 24 | 25 | expect(deployer.version).to.equal(options.codeVersion); 26 | expect(deployer.deploy_id).to.equal(options.deployId); 27 | expect(deployer.environment).to.equal(options.environment); 28 | expect(deployer.status).to.equal(options.status); 29 | expect(deployer.rollbar_username).to.equal(options.rollbarUsername); 30 | expect(deployer.local_username).to.equal(options.localUsername); 31 | expect(deployer.comment).to.equal(options.comment); 32 | expect(deployer).to.have.property('rollbarAPI'); 33 | expect(deployer.rollbarAPI.accessToken).to.equal(options.accessToken); 34 | }); 35 | }); 36 | 37 | describe('.deploy()', function() { 38 | beforeEach(function() { 39 | global.output = new Output({quiet: true}); 40 | }); 41 | 42 | it('should deploy successfully', async function() { 43 | const options = { 44 | accessToken: 'abcd', 45 | codeVersion: '123', 46 | environment: 'production' 47 | }; 48 | const deployer = new Deployer(options); 49 | const stub = sinon.stub(deployer.rollbarAPI.axios, 'post'); 50 | stub.resolves({ 51 | status: 200, 52 | statusText: 'Success', 53 | data: { deploy_id: 12345678 } 54 | }); 55 | 56 | await deployer.deploy(); 57 | 58 | expect(stub.callCount).to.equal(1); 59 | 60 | stub.restore(); 61 | }); 62 | 63 | it('should handle and report API errors', async function() { 64 | const options = { 65 | accessToken: '', 66 | codeVersion: '123', 67 | environment: 'production' 68 | }; 69 | 70 | const deployer = new Deployer(options); 71 | const stub = sinon.stub(deployer.rollbarAPI.axios, 'post'); 72 | stub.resolves({ 73 | status: 401, 74 | statusText: 'Unauthorized', 75 | data: { err: 1, message: 'invalid access token'} 76 | }); 77 | 78 | await deployer.deploy(); 79 | 80 | expect(stub.callCount).to.equal(1); 81 | 82 | stub.restore(); 83 | }); 84 | }); 85 | -------------------------------------------------------------------------------- /test/fixtures/builds/angular9/dist/app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/rollbar/rollbar-cli/05b203dad2602c2195c4cf9b4f9b3f4cf49b2353/test/fixtures/builds/angular9/dist/app/favicon.ico -------------------------------------------------------------------------------- /test/fixtures/builds/angular9/dist/app/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Angular9 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /test/fixtures/builds/angular9/dist/app/main-es2015.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["./$_lazy_route_resource lazy namespace object","./src/app/app.component.ts","./src/app/app.component.html","./src/app/app.module.ts","./src/environments/environment.ts","./src/main.ts"],"names":[],"mappings":"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","file":"main-es2015.js","sourcesContent":["function webpackEmptyAsyncContext(req) {\n\t// Here Promise.resolve().then() is used instead of new Promise() to prevent\n\t// uncaught exception popping up in devtools\n\treturn Promise.resolve().then(function() {\n\t\tvar e = new Error(\"Cannot find module '\" + req + \"'\");\n\t\te.code = 'MODULE_NOT_FOUND';\n\t\tthrow e;\n\t});\n}\nwebpackEmptyAsyncContext.keys = function() { return []; };\nwebpackEmptyAsyncContext.resolve = webpackEmptyAsyncContext;\nmodule.exports = webpackEmptyAsyncContext;\nwebpackEmptyAsyncContext.id = \"./$$_lazy_route_resource lazy recursive\";","import { Component } from '@angular/core';\n\n@Component({\n selector: 'app-root',\n templateUrl: './app.component.html',\n styleUrls: ['./app.component.css']\n})\nexport class AppComponent {\n title = 'angular9';\n}\n","\n\n\n\n\n\n\n\n\n\n\n\n
\n \n Welcome\n
\n \n \n \n \n \n \n
\n\n
\n\n \n
\n\n \n \n \n \n \n \n \n \n \n\n {{ title }} app is running!\n\n \n \n \n\n
\n\n \n

Resources

\n

Here are some links to help you get started:

\n\n \n\n \n

Next Steps

\n

What do you want to do next with your app?

\n\n \n\n
\n
\n \n\n New Component\n
\n\n
\n \n\n Angular Material\n
\n\n
\n \n\n Add PWA Support\n
\n\n
\n \n\n Add Dependency\n
\n\n
\n \n\n Run and Watch Tests\n
\n\n
\n \n\n Build for Production\n
\n
\n\n \n
\n
ng generate component xyz
\n
ng add @angular/material
\n
ng add @angular/pwa
\n
ng add _____
\n
ng test
\n
ng build --prod
\n
\n\n \n \n\n \n \n\n \n \n \n\n
\n\n\n\n\n\n\n\n\n\n\n","import { BrowserModule } from '@angular/platform-browser';\nimport { NgModule } from '@angular/core';\n\nimport { AppComponent } from './app.component';\n\n@NgModule({\n declarations: [\n AppComponent\n ],\n imports: [\n BrowserModule\n ],\n providers: [],\n bootstrap: [AppComponent]\n})\nexport class AppModule { }\n","// This file can be replaced during build by using the `fileReplacements` array.\n// `ng build --prod` replaces `environment.ts` with `environment.prod.ts`.\n// The list of file replacements can be found in `angular.json`.\n\nexport const environment = {\n production: false\n};\n\n/*\n * For easier debugging in development mode, you can import the following file\n * to ignore zone related error stack frames such as `zone.run`, `zoneDelegate.invokeTask`.\n *\n * This import should be commented out in production mode because it will have a negative impact\n * on performance if an error is thrown.\n */\n// import 'zone.js/dist/zone-error'; // Included with Angular CLI.\n","import { enableProdMode } from '@angular/core';\nimport { platformBrowserDynamic } from '@angular/platform-browser-dynamic';\n\nimport { AppModule } from './app/app.module';\nimport { environment } from './environments/environment';\n\nif (environment.production) {\n enableProdMode();\n}\n\nplatformBrowserDynamic().bootstrapModule(AppModule)\n .catch(err => console.error(err));\n"],"sourceRoot":"webpack:///"} -------------------------------------------------------------------------------- /test/fixtures/builds/angular9/dist/app/runtime-es2015.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // install a JSONP callback for chunk loading 3 | /******/ function webpackJsonpCallback(data) { 4 | /******/ var chunkIds = data[0]; 5 | /******/ var moreModules = data[1]; 6 | /******/ var executeModules = data[2]; 7 | /******/ 8 | /******/ // add "moreModules" to the modules object, 9 | /******/ // then flag all "chunkIds" as loaded and fire callback 10 | /******/ var moduleId, chunkId, i = 0, resolves = []; 11 | /******/ for(;i < chunkIds.length; i++) { 12 | /******/ chunkId = chunkIds[i]; 13 | /******/ if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) { 14 | /******/ resolves.push(installedChunks[chunkId][0]); 15 | /******/ } 16 | /******/ installedChunks[chunkId] = 0; 17 | /******/ } 18 | /******/ for(moduleId in moreModules) { 19 | /******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { 20 | /******/ modules[moduleId] = moreModules[moduleId]; 21 | /******/ } 22 | /******/ } 23 | /******/ if(parentJsonpFunction) parentJsonpFunction(data); 24 | /******/ 25 | /******/ while(resolves.length) { 26 | /******/ resolves.shift()(); 27 | /******/ } 28 | /******/ 29 | /******/ // add entry modules from loaded chunk to deferred list 30 | /******/ deferredModules.push.apply(deferredModules, executeModules || []); 31 | /******/ 32 | /******/ // run deferred modules when all chunks ready 33 | /******/ return checkDeferredModules(); 34 | /******/ }; 35 | /******/ function checkDeferredModules() { 36 | /******/ var result; 37 | /******/ for(var i = 0; i < deferredModules.length; i++) { 38 | /******/ var deferredModule = deferredModules[i]; 39 | /******/ var fulfilled = true; 40 | /******/ for(var j = 1; j < deferredModule.length; j++) { 41 | /******/ var depId = deferredModule[j]; 42 | /******/ if(installedChunks[depId] !== 0) fulfilled = false; 43 | /******/ } 44 | /******/ if(fulfilled) { 45 | /******/ deferredModules.splice(i--, 1); 46 | /******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); 47 | /******/ } 48 | /******/ } 49 | /******/ 50 | /******/ return result; 51 | /******/ } 52 | /******/ 53 | /******/ // The module cache 54 | /******/ var installedModules = {}; 55 | /******/ 56 | /******/ // object to store loaded and loading chunks 57 | /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 58 | /******/ // Promise = chunk loading, 0 = chunk loaded 59 | /******/ var installedChunks = { 60 | /******/ "runtime": 0 61 | /******/ }; 62 | /******/ 63 | /******/ var deferredModules = []; 64 | /******/ 65 | /******/ // The require function 66 | /******/ function __webpack_require__(moduleId) { 67 | /******/ 68 | /******/ // Check if module is in cache 69 | /******/ if(installedModules[moduleId]) { 70 | /******/ return installedModules[moduleId].exports; 71 | /******/ } 72 | /******/ // Create a new module (and put it into the cache) 73 | /******/ var module = installedModules[moduleId] = { 74 | /******/ i: moduleId, 75 | /******/ l: false, 76 | /******/ exports: {} 77 | /******/ }; 78 | /******/ 79 | /******/ // Execute the module function 80 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 81 | /******/ 82 | /******/ // Flag the module as loaded 83 | /******/ module.l = true; 84 | /******/ 85 | /******/ // Return the exports of the module 86 | /******/ return module.exports; 87 | /******/ } 88 | /******/ 89 | /******/ 90 | /******/ // expose the modules object (__webpack_modules__) 91 | /******/ __webpack_require__.m = modules; 92 | /******/ 93 | /******/ // expose the module cache 94 | /******/ __webpack_require__.c = installedModules; 95 | /******/ 96 | /******/ // define getter function for harmony exports 97 | /******/ __webpack_require__.d = function(exports, name, getter) { 98 | /******/ if(!__webpack_require__.o(exports, name)) { 99 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 100 | /******/ } 101 | /******/ }; 102 | /******/ 103 | /******/ // define __esModule on exports 104 | /******/ __webpack_require__.r = function(exports) { 105 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 106 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 107 | /******/ } 108 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 109 | /******/ }; 110 | /******/ 111 | /******/ // create a fake namespace object 112 | /******/ // mode & 1: value is a module id, require it 113 | /******/ // mode & 2: merge all properties of value into the ns 114 | /******/ // mode & 4: return value when already ns object 115 | /******/ // mode & 8|1: behave like require 116 | /******/ __webpack_require__.t = function(value, mode) { 117 | /******/ if(mode & 1) value = __webpack_require__(value); 118 | /******/ if(mode & 8) return value; 119 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 120 | /******/ var ns = Object.create(null); 121 | /******/ __webpack_require__.r(ns); 122 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 123 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 124 | /******/ return ns; 125 | /******/ }; 126 | /******/ 127 | /******/ // getDefaultExport function for compatibility with non-harmony modules 128 | /******/ __webpack_require__.n = function(module) { 129 | /******/ var getter = module && module.__esModule ? 130 | /******/ function getDefault() { return module['default']; } : 131 | /******/ function getModuleExports() { return module; }; 132 | /******/ __webpack_require__.d(getter, 'a', getter); 133 | /******/ return getter; 134 | /******/ }; 135 | /******/ 136 | /******/ // Object.prototype.hasOwnProperty.call 137 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 138 | /******/ 139 | /******/ // __webpack_public_path__ 140 | /******/ __webpack_require__.p = ""; 141 | /******/ 142 | /******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; 143 | /******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); 144 | /******/ jsonpArray.push = webpackJsonpCallback; 145 | /******/ jsonpArray = jsonpArray.slice(); 146 | /******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); 147 | /******/ var parentJsonpFunction = oldJsonpFunction; 148 | /******/ 149 | /******/ 150 | /******/ // run deferred modules from other chunks 151 | /******/ checkDeferredModules(); 152 | /******/ }) 153 | /************************************************************************/ 154 | /******/ ([]); -------------------------------------------------------------------------------- /test/fixtures/builds/angular9/dist/app/runtime-es2015.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack/bootstrap"],"names":[],"mappings":";QAAA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;QACA,QAAQ,oBAAoB;QAC5B;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA,iBAAiB,4BAA4B;QAC7C;QACA;QACA,kBAAkB,2BAA2B;QAC7C;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;;QAEA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;;;QAGA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA,0CAA0C,gCAAgC;QAC1E;QACA;;QAEA;QACA;QACA;QACA,wDAAwD,kBAAkB;QAC1E;QACA,iDAAiD,cAAc;QAC/D;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,yCAAyC,iCAAiC;QAC1E,gHAAgH,mBAAmB,EAAE;QACrI;QACA;;QAEA;QACA;QACA;QACA,2BAA2B,0BAA0B,EAAE;QACvD,iCAAiC,eAAe;QAChD;QACA;QACA;;QAEA;QACA,sDAAsD,+DAA+D;;QAErH;QACA;;QAEA;QACA;QACA;QACA;QACA,gBAAgB,uBAAuB;QACvC;;;QAGA;QACA","file":"runtime-es2015.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t\"runtime\": 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \tvar jsonpArray = window[\"webpackJsonp\"] = window[\"webpackJsonp\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":"webpack:///"} -------------------------------------------------------------------------------- /test/fixtures/builds/angular9/dist/app/runtime-es5.js: -------------------------------------------------------------------------------- 1 | /******/ (function(modules) { // webpackBootstrap 2 | /******/ // install a JSONP callback for chunk loading 3 | /******/ function webpackJsonpCallback(data) { 4 | /******/ var chunkIds = data[0]; 5 | /******/ var moreModules = data[1]; 6 | /******/ var executeModules = data[2]; 7 | /******/ 8 | /******/ // add "moreModules" to the modules object, 9 | /******/ // then flag all "chunkIds" as loaded and fire callback 10 | /******/ var moduleId, chunkId, i = 0, resolves = []; 11 | /******/ for(;i < chunkIds.length; i++) { 12 | /******/ chunkId = chunkIds[i]; 13 | /******/ if(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) { 14 | /******/ resolves.push(installedChunks[chunkId][0]); 15 | /******/ } 16 | /******/ installedChunks[chunkId] = 0; 17 | /******/ } 18 | /******/ for(moduleId in moreModules) { 19 | /******/ if(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) { 20 | /******/ modules[moduleId] = moreModules[moduleId]; 21 | /******/ } 22 | /******/ } 23 | /******/ if(parentJsonpFunction) parentJsonpFunction(data); 24 | /******/ 25 | /******/ while(resolves.length) { 26 | /******/ resolves.shift()(); 27 | /******/ } 28 | /******/ 29 | /******/ // add entry modules from loaded chunk to deferred list 30 | /******/ deferredModules.push.apply(deferredModules, executeModules || []); 31 | /******/ 32 | /******/ // run deferred modules when all chunks ready 33 | /******/ return checkDeferredModules(); 34 | /******/ }; 35 | /******/ function checkDeferredModules() { 36 | /******/ var result; 37 | /******/ for(var i = 0; i < deferredModules.length; i++) { 38 | /******/ var deferredModule = deferredModules[i]; 39 | /******/ var fulfilled = true; 40 | /******/ for(var j = 1; j < deferredModule.length; j++) { 41 | /******/ var depId = deferredModule[j]; 42 | /******/ if(installedChunks[depId] !== 0) fulfilled = false; 43 | /******/ } 44 | /******/ if(fulfilled) { 45 | /******/ deferredModules.splice(i--, 1); 46 | /******/ result = __webpack_require__(__webpack_require__.s = deferredModule[0]); 47 | /******/ } 48 | /******/ } 49 | /******/ 50 | /******/ return result; 51 | /******/ } 52 | /******/ 53 | /******/ // The module cache 54 | /******/ var installedModules = {}; 55 | /******/ 56 | /******/ // object to store loaded and loading chunks 57 | /******/ // undefined = chunk not loaded, null = chunk preloaded/prefetched 58 | /******/ // Promise = chunk loading, 0 = chunk loaded 59 | /******/ var installedChunks = { 60 | /******/ "runtime": 0 61 | /******/ }; 62 | /******/ 63 | /******/ var deferredModules = []; 64 | /******/ 65 | /******/ // The require function 66 | /******/ function __webpack_require__(moduleId) { 67 | /******/ 68 | /******/ // Check if module is in cache 69 | /******/ if(installedModules[moduleId]) { 70 | /******/ return installedModules[moduleId].exports; 71 | /******/ } 72 | /******/ // Create a new module (and put it into the cache) 73 | /******/ var module = installedModules[moduleId] = { 74 | /******/ i: moduleId, 75 | /******/ l: false, 76 | /******/ exports: {} 77 | /******/ }; 78 | /******/ 79 | /******/ // Execute the module function 80 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__); 81 | /******/ 82 | /******/ // Flag the module as loaded 83 | /******/ module.l = true; 84 | /******/ 85 | /******/ // Return the exports of the module 86 | /******/ return module.exports; 87 | /******/ } 88 | /******/ 89 | /******/ 90 | /******/ // expose the modules object (__webpack_modules__) 91 | /******/ __webpack_require__.m = modules; 92 | /******/ 93 | /******/ // expose the module cache 94 | /******/ __webpack_require__.c = installedModules; 95 | /******/ 96 | /******/ // define getter function for harmony exports 97 | /******/ __webpack_require__.d = function(exports, name, getter) { 98 | /******/ if(!__webpack_require__.o(exports, name)) { 99 | /******/ Object.defineProperty(exports, name, { enumerable: true, get: getter }); 100 | /******/ } 101 | /******/ }; 102 | /******/ 103 | /******/ // define __esModule on exports 104 | /******/ __webpack_require__.r = function(exports) { 105 | /******/ if(typeof Symbol !== 'undefined' && Symbol.toStringTag) { 106 | /******/ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' }); 107 | /******/ } 108 | /******/ Object.defineProperty(exports, '__esModule', { value: true }); 109 | /******/ }; 110 | /******/ 111 | /******/ // create a fake namespace object 112 | /******/ // mode & 1: value is a module id, require it 113 | /******/ // mode & 2: merge all properties of value into the ns 114 | /******/ // mode & 4: return value when already ns object 115 | /******/ // mode & 8|1: behave like require 116 | /******/ __webpack_require__.t = function(value, mode) { 117 | /******/ if(mode & 1) value = __webpack_require__(value); 118 | /******/ if(mode & 8) return value; 119 | /******/ if((mode & 4) && typeof value === 'object' && value && value.__esModule) return value; 120 | /******/ var ns = Object.create(null); 121 | /******/ __webpack_require__.r(ns); 122 | /******/ Object.defineProperty(ns, 'default', { enumerable: true, value: value }); 123 | /******/ if(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key)); 124 | /******/ return ns; 125 | /******/ }; 126 | /******/ 127 | /******/ // getDefaultExport function for compatibility with non-harmony modules 128 | /******/ __webpack_require__.n = function(module) { 129 | /******/ var getter = module && module.__esModule ? 130 | /******/ function getDefault() { return module['default']; } : 131 | /******/ function getModuleExports() { return module; }; 132 | /******/ __webpack_require__.d(getter, 'a', getter); 133 | /******/ return getter; 134 | /******/ }; 135 | /******/ 136 | /******/ // Object.prototype.hasOwnProperty.call 137 | /******/ __webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); }; 138 | /******/ 139 | /******/ // __webpack_public_path__ 140 | /******/ __webpack_require__.p = ""; 141 | /******/ 142 | /******/ var jsonpArray = window["webpackJsonp"] = window["webpackJsonp"] || []; 143 | /******/ var oldJsonpFunction = jsonpArray.push.bind(jsonpArray); 144 | /******/ jsonpArray.push = webpackJsonpCallback; 145 | /******/ jsonpArray = jsonpArray.slice(); 146 | /******/ for(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]); 147 | /******/ var parentJsonpFunction = oldJsonpFunction; 148 | /******/ 149 | /******/ 150 | /******/ // run deferred modules from other chunks 151 | /******/ checkDeferredModules(); 152 | /******/ }) 153 | /************************************************************************/ 154 | /******/ ([]); 155 | //# sourceMappingURL=runtime-es5.js.map -------------------------------------------------------------------------------- /test/fixtures/builds/angular9/dist/app/runtime-es5.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["webpack/bootstrap"],"names":[],"mappings":";QAAA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;QACA,QAAQ,oBAAoB;QAC5B;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA,iBAAiB,4BAA4B;QAC7C;QACA;QACA,kBAAkB,2BAA2B;QAC7C;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;;QAEA;;QAEA;QACA;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;;QAEA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;;;QAGA;QACA;;QAEA;QACA;;QAEA;QACA;QACA;QACA,0CAA0C,gCAAgC;QAC1E;QACA;;QAEA;QACA;QACA;QACA,wDAAwD,kBAAkB;QAC1E;QACA,iDAAiD,cAAc;QAC/D;;QAEA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA;QACA,yCAAyC,iCAAiC;QAC1E,gHAAgH,mBAAmB,EAAE;QACrI;QACA;;QAEA;QACA;QACA;QACA,2BAA2B,0BAA0B,EAAE;QACvD,iCAAiC,eAAe;QAChD;QACA;QACA;;QAEA;QACA,sDAAsD,+DAA+D;;QAErH;QACA;;QAEA;QACA;QACA;QACA;QACA,gBAAgB,uBAAuB;QACvC;;;QAGA;QACA","file":"runtime-es5.js","sourcesContent":[" \t// install a JSONP callback for chunk loading\n \tfunction webpackJsonpCallback(data) {\n \t\tvar chunkIds = data[0];\n \t\tvar moreModules = data[1];\n \t\tvar executeModules = data[2];\n\n \t\t// add \"moreModules\" to the modules object,\n \t\t// then flag all \"chunkIds\" as loaded and fire callback\n \t\tvar moduleId, chunkId, i = 0, resolves = [];\n \t\tfor(;i < chunkIds.length; i++) {\n \t\t\tchunkId = chunkIds[i];\n \t\t\tif(Object.prototype.hasOwnProperty.call(installedChunks, chunkId) && installedChunks[chunkId]) {\n \t\t\t\tresolves.push(installedChunks[chunkId][0]);\n \t\t\t}\n \t\t\tinstalledChunks[chunkId] = 0;\n \t\t}\n \t\tfor(moduleId in moreModules) {\n \t\t\tif(Object.prototype.hasOwnProperty.call(moreModules, moduleId)) {\n \t\t\t\tmodules[moduleId] = moreModules[moduleId];\n \t\t\t}\n \t\t}\n \t\tif(parentJsonpFunction) parentJsonpFunction(data);\n\n \t\twhile(resolves.length) {\n \t\t\tresolves.shift()();\n \t\t}\n\n \t\t// add entry modules from loaded chunk to deferred list\n \t\tdeferredModules.push.apply(deferredModules, executeModules || []);\n\n \t\t// run deferred modules when all chunks ready\n \t\treturn checkDeferredModules();\n \t};\n \tfunction checkDeferredModules() {\n \t\tvar result;\n \t\tfor(var i = 0; i < deferredModules.length; i++) {\n \t\t\tvar deferredModule = deferredModules[i];\n \t\t\tvar fulfilled = true;\n \t\t\tfor(var j = 1; j < deferredModule.length; j++) {\n \t\t\t\tvar depId = deferredModule[j];\n \t\t\t\tif(installedChunks[depId] !== 0) fulfilled = false;\n \t\t\t}\n \t\t\tif(fulfilled) {\n \t\t\t\tdeferredModules.splice(i--, 1);\n \t\t\t\tresult = __webpack_require__(__webpack_require__.s = deferredModule[0]);\n \t\t\t}\n \t\t}\n\n \t\treturn result;\n \t}\n\n \t// The module cache\n \tvar installedModules = {};\n\n \t// object to store loaded and loading chunks\n \t// undefined = chunk not loaded, null = chunk preloaded/prefetched\n \t// Promise = chunk loading, 0 = chunk loaded\n \tvar installedChunks = {\n \t\t\"runtime\": 0\n \t};\n\n \tvar deferredModules = [];\n\n \t// The require function\n \tfunction __webpack_require__(moduleId) {\n\n \t\t// Check if module is in cache\n \t\tif(installedModules[moduleId]) {\n \t\t\treturn installedModules[moduleId].exports;\n \t\t}\n \t\t// Create a new module (and put it into the cache)\n \t\tvar module = installedModules[moduleId] = {\n \t\t\ti: moduleId,\n \t\t\tl: false,\n \t\t\texports: {}\n \t\t};\n\n \t\t// Execute the module function\n \t\tmodules[moduleId].call(module.exports, module, module.exports, __webpack_require__);\n\n \t\t// Flag the module as loaded\n \t\tmodule.l = true;\n\n \t\t// Return the exports of the module\n \t\treturn module.exports;\n \t}\n\n\n \t// expose the modules object (__webpack_modules__)\n \t__webpack_require__.m = modules;\n\n \t// expose the module cache\n \t__webpack_require__.c = installedModules;\n\n \t// define getter function for harmony exports\n \t__webpack_require__.d = function(exports, name, getter) {\n \t\tif(!__webpack_require__.o(exports, name)) {\n \t\t\tObject.defineProperty(exports, name, { enumerable: true, get: getter });\n \t\t}\n \t};\n\n \t// define __esModule on exports\n \t__webpack_require__.r = function(exports) {\n \t\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n \t\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n \t\t}\n \t\tObject.defineProperty(exports, '__esModule', { value: true });\n \t};\n\n \t// create a fake namespace object\n \t// mode & 1: value is a module id, require it\n \t// mode & 2: merge all properties of value into the ns\n \t// mode & 4: return value when already ns object\n \t// mode & 8|1: behave like require\n \t__webpack_require__.t = function(value, mode) {\n \t\tif(mode & 1) value = __webpack_require__(value);\n \t\tif(mode & 8) return value;\n \t\tif((mode & 4) && typeof value === 'object' && value && value.__esModule) return value;\n \t\tvar ns = Object.create(null);\n \t\t__webpack_require__.r(ns);\n \t\tObject.defineProperty(ns, 'default', { enumerable: true, value: value });\n \t\tif(mode & 2 && typeof value != 'string') for(var key in value) __webpack_require__.d(ns, key, function(key) { return value[key]; }.bind(null, key));\n \t\treturn ns;\n \t};\n\n \t// getDefaultExport function for compatibility with non-harmony modules\n \t__webpack_require__.n = function(module) {\n \t\tvar getter = module && module.__esModule ?\n \t\t\tfunction getDefault() { return module['default']; } :\n \t\t\tfunction getModuleExports() { return module; };\n \t\t__webpack_require__.d(getter, 'a', getter);\n \t\treturn getter;\n \t};\n\n \t// Object.prototype.hasOwnProperty.call\n \t__webpack_require__.o = function(object, property) { return Object.prototype.hasOwnProperty.call(object, property); };\n\n \t// __webpack_public_path__\n \t__webpack_require__.p = \"\";\n\n \tvar jsonpArray = window[\"webpackJsonp\"] = window[\"webpackJsonp\"] || [];\n \tvar oldJsonpFunction = jsonpArray.push.bind(jsonpArray);\n \tjsonpArray.push = webpackJsonpCallback;\n \tjsonpArray = jsonpArray.slice();\n \tfor(var i = 0; i < jsonpArray.length; i++) webpackJsonpCallback(jsonpArray[i]);\n \tvar parentJsonpFunction = oldJsonpFunction;\n\n\n \t// run deferred modules from other chunks\n \tcheckDeferredModules();\n"],"sourceRoot":"webpack:///"} -------------------------------------------------------------------------------- /test/fixtures/builds/angular9/dist/app/styles-es2015.js: -------------------------------------------------------------------------------- 1 | (window["webpackJsonp"] = window["webpackJsonp"] || []).push([["styles"],{ 2 | 3 | /***/ "./node_modules/@angular-devkit/build-angular/src/angular-cli-files/plugins/raw-css-loader.js!./node_modules/postcss-loader/src/index.js?!./src/styles.css": 4 | /*!*****************************************************************************************************************************************************************!*\ 5 | !*** ./node_modules/@angular-devkit/build-angular/src/angular-cli-files/plugins/raw-css-loader.js!./node_modules/postcss-loader/src??embedded!./src/styles.css ***! 6 | \*****************************************************************************************************************************************************************/ 7 | /*! no static exports found */ 8 | /***/ (function(module, exports) { 9 | 10 | module.exports = [[module.i, "/* You can add global styles to this file, and also import other style files */\n\n/*# sourceMappingURL=data:application/json;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbInNyYy9zdHlsZXMuY3NzIl0sIm5hbWVzIjpbXSwibWFwcGluZ3MiOiJBQUFBLDhFQUE4RSIsImZpbGUiOiJzcmMvc3R5bGVzLmNzcyIsInNvdXJjZXNDb250ZW50IjpbIi8qIFlvdSBjYW4gYWRkIGdsb2JhbCBzdHlsZXMgdG8gdGhpcyBmaWxlLCBhbmQgYWxzbyBpbXBvcnQgb3RoZXIgc3R5bGUgZmlsZXMgKi9cbiJdfQ== */", '', '']] 11 | 12 | /***/ }), 13 | 14 | /***/ "./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js": 15 | /*!****************************************************************************!*\ 16 | !*** ./node_modules/style-loader/dist/runtime/injectStylesIntoStyleTag.js ***! 17 | \****************************************************************************/ 18 | /*! no static exports found */ 19 | /***/ (function(module, exports, __webpack_require__) { 20 | 21 | "use strict"; 22 | 23 | 24 | var stylesInDom = {}; 25 | 26 | var isOldIE = function isOldIE() { 27 | var memo; 28 | return function memorize() { 29 | if (typeof memo === 'undefined') { 30 | // Test for IE <= 9 as proposed by Browserhacks 31 | // @see http://browserhacks.com/#hack-e71d8692f65334173fee715c222cb805 32 | // Tests for existence of standard globals is to allow style-loader 33 | // to operate correctly into non-standard environments 34 | // @see https://github.com/webpack-contrib/style-loader/issues/177 35 | memo = Boolean(window && document && document.all && !window.atob); 36 | } 37 | 38 | return memo; 39 | }; 40 | }(); 41 | 42 | var getTarget = function getTarget() { 43 | var memo = {}; 44 | return function memorize(target) { 45 | if (typeof memo[target] === 'undefined') { 46 | var styleTarget = document.querySelector(target); // Special case to return head of iframe instead of iframe itself 47 | 48 | if (window.HTMLIFrameElement && styleTarget instanceof window.HTMLIFrameElement) { 49 | try { 50 | // This will throw an exception if access to iframe is blocked 51 | // due to cross-origin restrictions 52 | styleTarget = styleTarget.contentDocument.head; 53 | } catch (e) { 54 | // istanbul ignore next 55 | styleTarget = null; 56 | } 57 | } 58 | 59 | memo[target] = styleTarget; 60 | } 61 | 62 | return memo[target]; 63 | }; 64 | }(); 65 | 66 | function listToStyles(list, options) { 67 | var styles = []; 68 | var newStyles = {}; 69 | 70 | for (var i = 0; i < list.length; i++) { 71 | var item = list[i]; 72 | var id = options.base ? item[0] + options.base : item[0]; 73 | var css = item[1]; 74 | var media = item[2]; 75 | var sourceMap = item[3]; 76 | var part = { 77 | css: css, 78 | media: media, 79 | sourceMap: sourceMap 80 | }; 81 | 82 | if (!newStyles[id]) { 83 | styles.push(newStyles[id] = { 84 | id: id, 85 | parts: [part] 86 | }); 87 | } else { 88 | newStyles[id].parts.push(part); 89 | } 90 | } 91 | 92 | return styles; 93 | } 94 | 95 | function addStylesToDom(styles, options) { 96 | for (var i = 0; i < styles.length; i++) { 97 | var item = styles[i]; 98 | var domStyle = stylesInDom[item.id]; 99 | var j = 0; 100 | 101 | if (domStyle) { 102 | domStyle.refs++; 103 | 104 | for (; j < domStyle.parts.length; j++) { 105 | domStyle.parts[j](item.parts[j]); 106 | } 107 | 108 | for (; j < item.parts.length; j++) { 109 | domStyle.parts.push(addStyle(item.parts[j], options)); 110 | } 111 | } else { 112 | var parts = []; 113 | 114 | for (; j < item.parts.length; j++) { 115 | parts.push(addStyle(item.parts[j], options)); 116 | } 117 | 118 | stylesInDom[item.id] = { 119 | id: item.id, 120 | refs: 1, 121 | parts: parts 122 | }; 123 | } 124 | } 125 | } 126 | 127 | function insertStyleElement(options) { 128 | var style = document.createElement('style'); 129 | 130 | if (typeof options.attributes.nonce === 'undefined') { 131 | var nonce = true ? __webpack_require__.nc : undefined; 132 | 133 | if (nonce) { 134 | options.attributes.nonce = nonce; 135 | } 136 | } 137 | 138 | Object.keys(options.attributes).forEach(function (key) { 139 | style.setAttribute(key, options.attributes[key]); 140 | }); 141 | 142 | if (typeof options.insert === 'function') { 143 | options.insert(style); 144 | } else { 145 | var target = getTarget(options.insert || 'head'); 146 | 147 | if (!target) { 148 | throw new Error("Couldn't find a style target. This probably means that the value for the 'insert' parameter is invalid."); 149 | } 150 | 151 | target.appendChild(style); 152 | } 153 | 154 | return style; 155 | } 156 | 157 | function removeStyleElement(style) { 158 | // istanbul ignore if 159 | if (style.parentNode === null) { 160 | return false; 161 | } 162 | 163 | style.parentNode.removeChild(style); 164 | } 165 | /* istanbul ignore next */ 166 | 167 | 168 | var replaceText = function replaceText() { 169 | var textStore = []; 170 | return function replace(index, replacement) { 171 | textStore[index] = replacement; 172 | return textStore.filter(Boolean).join('\n'); 173 | }; 174 | }(); 175 | 176 | function applyToSingletonTag(style, index, remove, obj) { 177 | var css = remove ? '' : obj.css; // For old IE 178 | 179 | /* istanbul ignore if */ 180 | 181 | if (style.styleSheet) { 182 | style.styleSheet.cssText = replaceText(index, css); 183 | } else { 184 | var cssNode = document.createTextNode(css); 185 | var childNodes = style.childNodes; 186 | 187 | if (childNodes[index]) { 188 | style.removeChild(childNodes[index]); 189 | } 190 | 191 | if (childNodes.length) { 192 | style.insertBefore(cssNode, childNodes[index]); 193 | } else { 194 | style.appendChild(cssNode); 195 | } 196 | } 197 | } 198 | 199 | function applyToTag(style, options, obj) { 200 | var css = obj.css; 201 | var media = obj.media; 202 | var sourceMap = obj.sourceMap; 203 | 204 | if (media) { 205 | style.setAttribute('media', media); 206 | } 207 | 208 | if (sourceMap && btoa) { 209 | css += "\n/*# sourceMappingURL=data:application/json;base64,".concat(btoa(unescape(encodeURIComponent(JSON.stringify(sourceMap)))), " */"); 210 | } // For old IE 211 | 212 | /* istanbul ignore if */ 213 | 214 | 215 | if (style.styleSheet) { 216 | style.styleSheet.cssText = css; 217 | } else { 218 | while (style.firstChild) { 219 | style.removeChild(style.firstChild); 220 | } 221 | 222 | style.appendChild(document.createTextNode(css)); 223 | } 224 | } 225 | 226 | var singleton = null; 227 | var singletonCounter = 0; 228 | 229 | function addStyle(obj, options) { 230 | var style; 231 | var update; 232 | var remove; 233 | 234 | if (options.singleton) { 235 | var styleIndex = singletonCounter++; 236 | style = singleton || (singleton = insertStyleElement(options)); 237 | update = applyToSingletonTag.bind(null, style, styleIndex, false); 238 | remove = applyToSingletonTag.bind(null, style, styleIndex, true); 239 | } else { 240 | style = insertStyleElement(options); 241 | update = applyToTag.bind(null, style, options); 242 | 243 | remove = function remove() { 244 | removeStyleElement(style); 245 | }; 246 | } 247 | 248 | update(obj); 249 | return function updateStyle(newObj) { 250 | if (newObj) { 251 | if (newObj.css === obj.css && newObj.media === obj.media && newObj.sourceMap === obj.sourceMap) { 252 | return; 253 | } 254 | 255 | update(obj = newObj); 256 | } else { 257 | remove(); 258 | } 259 | }; 260 | } 261 | 262 | module.exports = function (list, options) { 263 | options = options || {}; 264 | options.attributes = typeof options.attributes === 'object' ? options.attributes : {}; // Force single-tag solution on IE6-9, which has a hard limit on the # of