rollup-plugin-filesize
Advanced tools
Comparing version
@@ -5,78 +5,137 @@ 'use strict'; | ||
function _interopNamespace(e) { | ||
if (e && e.__esModule) { return e; } else { | ||
var n = {}; | ||
if (e) { | ||
Object.keys(e).forEach(function (k) { | ||
var d = Object.getOwnPropertyDescriptor(e, k); | ||
Object.defineProperty(n, k, d.get ? d : { | ||
enumerable: true, | ||
get: function () { | ||
return e[k]; | ||
} | ||
}); | ||
}); | ||
} | ||
n['default'] = e; | ||
return n; | ||
} | ||
} | ||
var fs = require('fs'); | ||
var util = require('util'); | ||
var path = require('path'); | ||
var fileSize = _interopDefault(require('filesize')); | ||
var boxen = _interopDefault(require('boxen')); | ||
var colors = _interopDefault(require('colors')); | ||
var merge = _interopDefault(require('lodash.merge')); | ||
var gzip = _interopDefault(require('gzip-size')); | ||
var terser = _interopDefault(require('terser')); | ||
var brotli = _interopDefault(require('brotli-size')); | ||
var pacote = _interopDefault(require('pacote')); | ||
function _toConsumableArray(arr) { | ||
return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _nonIterableSpread(); | ||
} | ||
const readFile = util.promisify(fs.readFile); | ||
const thisDirectory = path.dirname(new URL((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href))).pathname); | ||
function filesize(options = {}, env) { | ||
let { | ||
render, | ||
format = {}, | ||
theme = "dark", | ||
showBeforeSizes = "none", | ||
showGzippedSize = true, | ||
showBrotliSize = false, | ||
showMinifiedSize = true | ||
} = options; | ||
function _arrayWithoutHoles(arr) { | ||
if (Array.isArray(arr)) { | ||
for (var i = 0, arr2 = new Array(arr.length); i < arr.length; i++) arr2[i] = arr[i]; | ||
const getLoggingData = async function (outputOptions, bundle) { | ||
const { | ||
code, | ||
fileName | ||
} = bundle; | ||
const info = {}; | ||
let codeBefore; | ||
return arr2; | ||
} | ||
} | ||
if (showBeforeSizes !== "none") { | ||
let file = outputOptions.file || outputOptions.dest; | ||
function _iterableToArray(iter) { | ||
if (Symbol.iterator in Object(iter) || Object.prototype.toString.call(iter) === "[object Arguments]") return Array.from(iter); | ||
} | ||
if (showBeforeSizes !== "build") { | ||
const { | ||
name | ||
} = await new Promise(function (resolve) { resolve(_interopNamespace(require(path.join(process.cwd(), "./package.json")))); }); | ||
function _nonIterableSpread() { | ||
throw new TypeError("Invalid attempt to spread non-iterable instance"); | ||
} | ||
try { | ||
const output = path.join(thisDirectory, "../.cache"); | ||
var brotli = require("brotli-size"); | ||
if (!fs.existsSync(output)) { | ||
await pacote.extract(`${name}@latest`, output); | ||
} | ||
function render(opt, outputOptions, info) { | ||
var primaryColor = opt.theme === "dark" ? "green" : "black"; | ||
var secondaryColor = opt.theme === "dark" ? "yellow" : "blue"; | ||
var title = colors[primaryColor].bold; | ||
var value = colors[secondaryColor]; | ||
var values = [].concat(_toConsumableArray(outputOptions.file ? ["".concat(title("Destination: ")).concat(value(outputOptions.file))] : info.fileName ? ["".concat(title("Bundle Name: "), " ").concat(value(info.fileName))] : []), ["".concat(title("Bundle Size: "), " ").concat(value(info.bundleSize))], _toConsumableArray(info.minSize ? ["".concat(title("Minified Size: "), " ").concat(value(info.minSize))] : []), _toConsumableArray(info.gzipSize ? ["".concat(title("Gzipped Size: "), " ").concat(value(info.gzipSize))] : []), _toConsumableArray(info.brotliSize ? ["".concat(title("Brotli size: ")).concat(value(info.brotliSize))] : [])); | ||
return boxen(values.join("\n"), { | ||
padding: 1 | ||
}); | ||
} | ||
file = path.join(output, file); | ||
} catch (err) { | ||
// Package might not exist | ||
file = null; | ||
} | ||
} | ||
function filesize() { | ||
var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}; | ||
var env = arguments.length > 1 ? arguments[1] : undefined; | ||
var defaultOptions = { | ||
format: {}, | ||
theme: "dark", | ||
render: render, | ||
showGzippedSize: true, | ||
showBrotliSize: false, | ||
showMinifiedSize: true | ||
}; | ||
var opts = merge({}, defaultOptions, options); | ||
if (file) { | ||
try { | ||
codeBefore = await readFile(file, "utf8"); | ||
} catch (err) {// File might not exist | ||
} | ||
} | ||
} | ||
if (options.render) { | ||
opts.render = options.render; | ||
} | ||
var getData = function getData(outputOptions, bundle) { | ||
var code = bundle.code, | ||
fileName = bundle.fileName; | ||
var info = {}; | ||
info.fileName = fileName; | ||
info.bundleSize = fileSize(Buffer.byteLength(code), opts.format); | ||
info.brotliSize = opts.showBrotliSize ? fileSize(brotli.sync(code), opts.format) : ""; | ||
info.bundleSize = fileSize(Buffer.byteLength(code), format); | ||
info.brotliSize = showBrotliSize ? fileSize((await brotli(code)), format) : ""; | ||
if (opts.showMinifiedSize || opts.showGzippedSize) { | ||
var minifiedCode = terser.minify(code).code; | ||
info.minSize = opts.showMinifiedSize ? fileSize(minifiedCode.length, opts.format) : ""; | ||
info.gzipSize = opts.showGzippedSize ? fileSize(gzip.sync(minifiedCode), opts.format) : ""; | ||
if (showMinifiedSize || showGzippedSize) { | ||
const minifiedCode = terser.minify(code).code; | ||
info.minSize = showMinifiedSize ? fileSize(minifiedCode.length, format) : ""; | ||
info.gzipSize = showGzippedSize ? fileSize(gzip.sync(minifiedCode), format) : ""; | ||
} | ||
return opts.render(opts, outputOptions, info); | ||
if (codeBefore) { | ||
info.bundleSizeBefore = fileSize(Buffer.byteLength(codeBefore), format); | ||
info.brotliSizeBefore = showBrotliSize ? fileSize((await brotli(codeBefore)), format) : ""; | ||
if (showMinifiedSize || showGzippedSize) { | ||
const minifiedCode = terser.minify(codeBefore).code; | ||
info.minSizeBefore = showMinifiedSize ? fileSize(minifiedCode.length, format) : ""; | ||
info.gzipSizeBefore = showGzippedSize ? fileSize(gzip.sync(minifiedCode), format) : ""; | ||
} | ||
} | ||
const opts = { | ||
format, | ||
theme, | ||
render, | ||
showBeforeSizes, | ||
showGzippedSize, | ||
showBrotliSize, | ||
showMinifiedSize | ||
}; | ||
if (render) { | ||
console.warn("`render` is now deprecated. Please use `reporter` instead."); | ||
return opts.render(opts, outputOptions, info); | ||
} | ||
const reporters = options.reporter ? Array.isArray(options.reporter) ? options.reporter : [options.reporter] : ["boxen"]; | ||
return (await Promise.all(reporters.map(async reporter => { | ||
if (typeof reporter === "string") { | ||
let p; | ||
if (reporter === "boxen") { | ||
p = new Promise(function (resolve) { resolve(_interopNamespace(require(path.dirname(new URL((typeof document === 'undefined' ? new (require('u' + 'rl').URL)('file:' + __filename).href : (document.currentScript && document.currentScript.src || new URL('index.js', document.baseURI).href))).pathname) + "/reporters/boxen.js"))); }); | ||
} else { | ||
p = new Promise(function (resolve) { resolve(_interopNamespace(require(path.resolve(process.cwd(), reporter)))); }); | ||
} | ||
reporter = (await p).default; | ||
} | ||
return reporter(opts, outputOptions, info); | ||
}))).join(""); | ||
}; | ||
if (env === "test") { | ||
return getData; | ||
return getLoggingData; | ||
} | ||
@@ -86,7 +145,8 @@ | ||
name: "filesize", | ||
generateBundle: function generateBundle(outputOptions, bundle, isWrite) { | ||
Object.keys(bundle).map(function (fileName) { | ||
return bundle[fileName]; | ||
}).filter(function (currentBundle) { | ||
if (currentBundle.hasOwnProperty("type")) { | ||
async generateBundle(outputOptions, bundle | ||
/* , isWrite */ | ||
) { | ||
const dataStrs = await Promise.all(Object.keys(bundle).map(fileName => bundle[fileName]).filter(currentBundle => { | ||
if ({}.hasOwnProperty.call(currentBundle, "type")) { | ||
return currentBundle.type !== "asset"; | ||
@@ -96,6 +156,12 @@ } | ||
return !currentBundle.isAsset; | ||
}).forEach(function (currentBundle) { | ||
console.log(getData(outputOptions, currentBundle)); | ||
}).map(currentBundle => { | ||
return getLoggingData(outputOptions, currentBundle); | ||
})); | ||
dataStrs.forEach(str => { | ||
if (str) { | ||
console.log(str); | ||
} | ||
}); | ||
} | ||
}; | ||
@@ -105,1 +171,2 @@ } | ||
module.exports = filesize; | ||
//# sourceMappingURL=index.js.map |
{ | ||
"name": "rollup-plugin-filesize", | ||
"version": "7.0.0", | ||
"version": "8.0.0-0", | ||
"description": "A rollup plugin to show filesize in the cli", | ||
"main": "dist/index.js", | ||
"jsnext:main": "src/index.js", | ||
"module": "src/index.js", | ||
"files": [ | ||
@@ -13,7 +13,11 @@ "src", | ||
"scripts": { | ||
"test": "ava test/index.test.js", | ||
"lint": "eslint .", | ||
"test": "nyc ava test/index.test.js", | ||
"pretest": "rollup -c", | ||
"prepublish": "npm run test", | ||
"prebuild": "rm -rf dist/*" | ||
"prebuild": "rimraf dist/*" | ||
}, | ||
"engines": { | ||
"node": ">=8.0.0" | ||
}, | ||
"repository": { | ||
@@ -27,2 +31,3 @@ "type": "git", | ||
"author": "Ritesh Kumar", | ||
"contributors": [], | ||
"license": "MIT", | ||
@@ -34,30 +39,35 @@ "bugs": { | ||
"dependencies": { | ||
"boxen": "^4.1.0", | ||
"boxen": "^4.2.0", | ||
"brotli-size": "4.0.0", | ||
"colors": "^1.3.3", | ||
"filesize": "^4.1.2", | ||
"colors": "^1.4.0", | ||
"filesize": "^6.1.0", | ||
"gzip-size": "^5.1.1", | ||
"lodash.merge": "^4.6.2", | ||
"terser": "^4.1.3" | ||
"pacote": "^11.1.4", | ||
"terser": "^4.6.11" | ||
}, | ||
"devDependencies": { | ||
"@babel/core": "^7.5.5", | ||
"@babel/preset-env": "^7.5.5", | ||
"@babel/register": "^7.5.5", | ||
"ava": "^2.2.0", | ||
"@babel/core": "^7.9.0", | ||
"@babel/plugin-syntax-import-meta": "^7.8.3", | ||
"@babel/preset-env": "^7.9.5", | ||
"@babel/register": "^7.9.0", | ||
"@rollup/plugin-json": "^4.0.3", | ||
"ava": "^3.7.1", | ||
"babel-eslint": "^10.1.0", | ||
"babel-register": "^6.26.0", | ||
"prettier": "^1.18.2", | ||
"rollup": "^1.19.4", | ||
"rollup-plugin-babel": "^4.3.3" | ||
"eslint": "^6.8.0", | ||
"eslint-config-prettier": "^6.10.1", | ||
"eslint-plugin-prettier": "^3.1.3", | ||
"esm": "^3.2.25", | ||
"nyc": "^15.0.1", | ||
"prettier": "^2.0.4", | ||
"rimraf": "^3.0.2", | ||
"rollup": "^2.6.1", | ||
"rollup-plugin-babel": "^4.4.0" | ||
}, | ||
"ava": { | ||
"require": [ | ||
"@babel/register" | ||
"esm", | ||
"./test/bootstrap/register.js" | ||
] | ||
}, | ||
"babel": { | ||
"presets": [ | ||
"@babel/preset-env" | ||
] | ||
} | ||
} |
@@ -30,2 +30,3 @@ # rollup-plugin-filesize | ||
#### showMinifiedSize | ||
type: `boolean` | ||
@@ -37,2 +38,3 @@ default: true | ||
#### showGzippedSize | ||
type: `boolean` | ||
@@ -44,2 +46,3 @@ default: true | ||
#### showBrotliSize | ||
type: `boolean` | ||
@@ -50,3 +53,26 @@ default: false | ||
#### showBeforeSizes | ||
**Note: this feature is experimental and may be changed in a future release.** | ||
type: `"release", ``"build"`, or `"none"` | ||
default: `"none"` | ||
Indicates how, if any, comparisons will be shown between the | ||
`output.file` file size as it was and as it is now being written. | ||
If set to `"release"`, will compare the file size at present to that of | ||
the last npm release. | ||
If set to `"build"`, the size of the file that is now being built will | ||
be compared to the immediately previous build. This means that if you run | ||
Rollup multiple times with this option, the info on the previous package | ||
size will be lost (since Rollup will have overwritten your copy), so with | ||
this option, you will need to consult your terminal history to see what the | ||
file size was prior to your changes. This option may be useful if you wish | ||
to compare size changes incrementally as you are developing rather than | ||
comparing to your last release. | ||
#### format | ||
type : `object` | ||
@@ -56,18 +82,30 @@ | ||
See the options [here](https://github.com/avoidwork/filesize.js) | ||
See the options [here](https://github.com/avoidwork/filesize.js#optional-settings) | ||
#### render | ||
type : `function` | ||
#### reporter | ||
return the command that you want to log. Eg: | ||
(Note that this replaces the deprecated optional `render` function option.) | ||
type : A reporter string (currently "boxen" only), a function, or an array thereof. | ||
Defaults to "boxen". | ||
After rendering occurs, you may wish to pass on the collected file data, | ||
e.g., to build a badge for filesizes (as does [filesize-badger](https://github.com/brettz9/filesize-badger)). | ||
You can use `reporter` to do so: | ||
```js | ||
filesize({ | ||
render : function (options, bundle, { minSize, gzipSize, brotliSize, bundleSize }){ | ||
return minSize; | ||
} | ||
}) | ||
reporter: [ | ||
function (options, bundle, { minSize, gzipSize, brotliSize, bundleSize }) { | ||
// If a promise is returned, it will be awaited before rendering. | ||
return promise; | ||
}, | ||
], | ||
}); | ||
``` | ||
#### theme | ||
type: `string` | ||
@@ -81,7 +119,4 @@ | ||
## License | ||
## License | ||
MIT | ||
196
src/index.js
@@ -0,72 +1,137 @@ | ||
import { readFile as origReadFile, existsSync } from "fs"; | ||
import { promisify } from "util"; | ||
import { dirname, resolve as pathResolve, join } from "path"; | ||
import fileSize from "filesize"; | ||
import boxen from "boxen"; | ||
import colors from "colors"; | ||
import merge from "lodash.merge"; | ||
import gzip from "gzip-size"; | ||
import terser from "terser"; | ||
import brotli from "brotli-size"; | ||
import pacote from "pacote"; | ||
const brotli = require("brotli-size"); | ||
const readFile = promisify(origReadFile); | ||
function render(opt, outputOptions, info) { | ||
const primaryColor = opt.theme === "dark" ? "green" : "black"; | ||
const secondaryColor = opt.theme === "dark" ? "yellow" : "blue"; | ||
const thisDirectory = dirname(new URL(import.meta.url).pathname); | ||
const title = colors[primaryColor].bold; | ||
const value = colors[secondaryColor]; | ||
export default function filesize(options = {}, env) { | ||
let { | ||
render, | ||
format = {}, | ||
theme = "dark", | ||
showBeforeSizes = "none", | ||
showGzippedSize = true, | ||
showBrotliSize = false, | ||
showMinifiedSize = true, | ||
} = options; | ||
const values = [ | ||
...(outputOptions.file ? | ||
[`${title("Destination: ")}${value(outputOptions.file)}`] : | ||
(info.fileName ? [`${title("Bundle Name: ")} ${value(info.fileName)}`] : [])), | ||
...[`${title("Bundle Size: ")} ${value(info.bundleSize)}`], | ||
...(info.minSize ? [`${title("Minified Size: ")} ${value(info.minSize)}`] : []), | ||
...(info.gzipSize ? [`${title("Gzipped Size: ")} ${value(info.gzipSize)}`] : []), | ||
...(info.brotliSize ? [`${title("Brotli size: ")}${value(info.brotliSize)}`] : []) | ||
]; | ||
const getLoggingData = async function (outputOptions, bundle) { | ||
const { code, fileName } = bundle; | ||
const info = {}; | ||
return boxen(values.join("\n"), { padding: 1 }); | ||
} | ||
let codeBefore; | ||
if (showBeforeSizes !== "none") { | ||
let file = outputOptions.file || outputOptions.dest; | ||
if (showBeforeSizes !== "build") { | ||
const { name } = await import(join(process.cwd(), "./package.json")); | ||
try { | ||
const output = join(thisDirectory, "../.cache"); | ||
if (!existsSync(output)) { | ||
await pacote.extract(`${name}@latest`, output); | ||
} | ||
file = join(output, file); | ||
} catch (err) { | ||
// Package might not exist | ||
file = null; | ||
} | ||
} | ||
export default function filesize(options = {}, env) { | ||
let defaultOptions = { | ||
format: {}, | ||
theme: "dark", | ||
render: render, | ||
showGzippedSize: true, | ||
showBrotliSize: false, | ||
showMinifiedSize: true | ||
}; | ||
if (file) { | ||
try { | ||
codeBefore = await readFile(file, "utf8"); | ||
} catch (err) { | ||
// File might not exist | ||
} | ||
} | ||
} | ||
let opts = merge({}, defaultOptions, options); | ||
if (options.render) { | ||
opts.render = options.render; | ||
} | ||
info.fileName = fileName; | ||
const getData = function(outputOptions, bundle) { | ||
const { code, fileName } = bundle; | ||
const info = {}; | ||
info.bundleSize = fileSize(Buffer.byteLength(code), format); | ||
info.fileName = fileName; | ||
info.bundleSize = fileSize(Buffer.byteLength(code), opts.format); | ||
info.brotliSize = opts.showBrotliSize | ||
? fileSize(brotli.sync(code), opts.format) | ||
info.brotliSize = showBrotliSize | ||
? fileSize(await brotli(code), format) | ||
: ""; | ||
if (opts.showMinifiedSize || opts.showGzippedSize) { | ||
if (showMinifiedSize || showGzippedSize) { | ||
const minifiedCode = terser.minify(code).code; | ||
info.minSize = opts.showMinifiedSize | ||
? fileSize(minifiedCode.length, opts.format) | ||
info.minSize = showMinifiedSize | ||
? fileSize(minifiedCode.length, format) | ||
: ""; | ||
info.gzipSize = opts.showGzippedSize | ||
? fileSize(gzip.sync(minifiedCode), opts.format) | ||
info.gzipSize = showGzippedSize | ||
? fileSize(gzip.sync(minifiedCode), format) | ||
: ""; | ||
} | ||
return opts.render(opts, outputOptions, info); | ||
if (codeBefore) { | ||
info.bundleSizeBefore = fileSize(Buffer.byteLength(codeBefore), format); | ||
info.brotliSizeBefore = showBrotliSize | ||
? fileSize(await brotli(codeBefore), format) | ||
: ""; | ||
if (showMinifiedSize || showGzippedSize) { | ||
const minifiedCode = terser.minify(codeBefore).code; | ||
info.minSizeBefore = showMinifiedSize | ||
? fileSize(minifiedCode.length, format) | ||
: ""; | ||
info.gzipSizeBefore = showGzippedSize | ||
? fileSize(gzip.sync(minifiedCode), format) | ||
: ""; | ||
} | ||
} | ||
const opts = { | ||
format, | ||
theme, | ||
render, | ||
showBeforeSizes, | ||
showGzippedSize, | ||
showBrotliSize, | ||
showMinifiedSize, | ||
}; | ||
if (render) { | ||
console.warn( | ||
"`render` is now deprecated. Please use `reporter` instead." | ||
); | ||
return opts.render(opts, outputOptions, info); | ||
} | ||
const reporters = options.reporter | ||
? Array.isArray(options.reporter) | ||
? options.reporter | ||
: [options.reporter] | ||
: ["boxen"]; | ||
return ( | ||
await Promise.all( | ||
reporters.map(async (reporter) => { | ||
if (typeof reporter === "string") { | ||
let p; | ||
if (reporter === "boxen") { | ||
p = import( | ||
dirname(new URL(import.meta.url).pathname) + | ||
"/reporters/boxen.js" | ||
); | ||
} else { | ||
p = import(pathResolve(process.cwd(), reporter)); | ||
} | ||
reporter = (await p).default; | ||
} | ||
return reporter(opts, outputOptions, info); | ||
}) | ||
) | ||
).join(""); | ||
}; | ||
if (env === "test") { | ||
return getData; | ||
return getLoggingData; | ||
} | ||
@@ -76,16 +141,23 @@ | ||
name: "filesize", | ||
generateBundle(outputOptions, bundle, isWrite) { | ||
Object.keys(bundle) | ||
.map(fileName => bundle[fileName]) | ||
.filter(currentBundle => { | ||
if (currentBundle.hasOwnProperty("type")) { | ||
return currentBundle.type !== "asset"; | ||
} | ||
return !currentBundle.isAsset; | ||
}) | ||
.forEach((currentBundle) => { | ||
console.log(getData(outputOptions, currentBundle)) | ||
}); | ||
} | ||
async generateBundle(outputOptions, bundle /* , isWrite */) { | ||
const dataStrs = await Promise.all( | ||
Object.keys(bundle) | ||
.map((fileName) => bundle[fileName]) | ||
.filter((currentBundle) => { | ||
if ({}.hasOwnProperty.call(currentBundle, "type")) { | ||
return currentBundle.type !== "asset"; | ||
} | ||
return !currentBundle.isAsset; | ||
}) | ||
.map((currentBundle) => { | ||
return getLoggingData(outputOptions, currentBundle); | ||
}) | ||
); | ||
dataStrs.forEach((str) => { | ||
if (str) { | ||
console.log(str); | ||
} | ||
}); | ||
}, | ||
}; | ||
} |
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
30497
251.51%8
100%358
123.75%117
42.68%17
112.5%1
Infinity%7
Infinity%1
Infinity%+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
+ Added
- Removed
- Removed
- Removed
Updated
Updated
Updated
Updated