diff --git a/README.md b/README.md
index 95596ca..17207c6 100644
--- a/README.md
+++ b/README.md
@@ -46,11 +46,28 @@ USAGE
# Commands
+* [`cdt bundlephobia [FILE]`](#cdt-bundlephobia-file)
* [`cdt crypto [STRING]`](#cdt-crypto-string)
* [`cdt hash [STRING]`](#cdt-hash-string)
* [`cdt help [COMMAND]`](#cdt-help-command)
* [`cdt minify [FILE]`](#cdt-minify-file)
+## `cdt bundlephobia [FILE]`
+
+describe the command here
+
+```
+USAGE
+ $ cdt bundlephobia [FILE]
+
+OPTIONS
+ -f, --force
+ -h, --help show CLI help
+ -n, --name=name name to print
+```
+
+_See code: [src/commands/bundlephobia.ts](https://github.com/codingtools/cdt/blob/v0.1.2/src/commands/bundlephobia.ts)_
+
## `cdt crypto [STRING]`
Encryption and Decryption functionality for File/String
diff --git a/error.json b/error.json
new file mode 100644
index 0000000..8081885
--- /dev/null
+++ b/error.json
@@ -0,0 +1,17 @@
+{
+ data: {
+ assets: [ [Object] ],
+ dependencyCount: 3,
+ dependencySizes: [ [Object] ],
+ description: 'React is a JavaScript library for building user interfaces.',
+ gzip: 2630,
+ hasJSModule: false,
+ hasJSNext: false,
+ hasSideEffects: true,
+ name: 'react',
+ repository: 'https://github.com/facebook/react.git',
+ scoped: false,
+ size: 6499,
+ version: '16.11.0'
+ }
+}
diff --git a/package-lock.json b/package-lock.json
index 64ee1fb..5f44a59 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -415,6 +415,15 @@
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==",
"dev": true
},
+ "axios": {
+ "version": "0.19.0",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz",
+ "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==",
+ "requires": {
+ "follow-redirects": "1.5.10",
+ "is-buffer": "^2.0.2"
+ }
+ },
"balanced-match": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
@@ -1100,6 +1109,29 @@
"is-buffer": "~2.0.3"
}
},
+ "follow-redirects": {
+ "version": "1.5.10",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz",
+ "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==",
+ "requires": {
+ "debug": "=3.1.0"
+ },
+ "dependencies": {
+ "debug": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz",
+ "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==",
+ "requires": {
+ "ms": "2.0.0"
+ }
+ },
+ "ms": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
+ "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g="
+ }
+ }
+ },
"foreground-child": {
"version": "1.5.6",
"resolved": "https://registry.npmjs.org/foreground-child/-/foreground-child-1.5.6.tgz",
@@ -1387,8 +1419,7 @@
"is-buffer": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.4.tgz",
- "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A==",
- "dev": true
+ "integrity": "sha512-Kq1rokWXOPXWuaMAqZiJW4XxsmD9zGx9q4aePabbn3qCRGedtH7Cm+zV8WETitMfu1wdh+Rvd6w5egwSngUX2A=="
},
"is-callable": {
"version": "1.1.4",
@@ -2047,7 +2078,7 @@
"dependencies": {
"find-up": {
"version": "3.0.0",
- "resolved": "https://registry.npmjs.org/find-up/-/find-up-3.0.0.tgz",
+ "resolved": false,
"integrity": "sha512-1yD6RmLI1XBfxugvORwlck6f75tYL+iR0jqwsOrOxMZyGYqUuDhJ0l4AXdO1iX/FTs9cBAMEk1gWSEx1kSbylg==",
"requires": {
"locate-path": "^3.0.0"
@@ -2055,7 +2086,7 @@
},
"locate-path": {
"version": "3.0.0",
- "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-3.0.0.tgz",
+ "resolved": false,
"integrity": "sha512-7AO748wWnIhNqAuaty2ZWHkQHRSNfPVIsPIfwEOWO22AmaoVrWavlOcMR5nzTLNYvp36X220/maaRsrec1G65A==",
"requires": {
"p-locate": "^3.0.0",
@@ -2073,7 +2104,7 @@
},
"p-locate": {
"version": "3.0.0",
- "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-3.0.0.tgz",
+ "resolved": false,
"integrity": "sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==",
"requires": {
"p-limit": "^2.0.0"
@@ -2081,7 +2112,7 @@
},
"path-exists": {
"version": "3.0.0",
- "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-3.0.0.tgz",
+ "resolved": false,
"integrity": "sha1-zg6+ql94yxiSXqfYENe1mwEP1RU="
}
}
diff --git a/package.json b/package.json
index 81cb362..e4e2cf6 100644
--- a/package.json
+++ b/package.json
@@ -15,6 +15,8 @@
"@oclif/plugin-help": "^2.2.1",
"@types/crypto-js": "^3.1.43",
"@types/signale": "^1.2.1",
+ "axios": "^0.19.0",
+ "chalk": "^2.4.2",
"crypto-js": "^3.1.9-1",
"jshashes": "^1.0.7",
"minify": "^4.1.3",
diff --git a/src/commands/bundlephobia.ts b/src/commands/bundlephobia.ts
new file mode 100644
index 0000000..5c51752
--- /dev/null
+++ b/src/commands/bundlephobia.ts
@@ -0,0 +1,123 @@
+import {Command, flags} from '@oclif/command'
+import axios from 'axios'
+import chalk from 'chalk'
+
+import Logger from '../utilities/logger'
+
+// TODO:
+// ADD package.json support
+// ADD VALID tests ( for now they just ignoring )
+export default class Bundlephobia extends Command {
+ static description = 'Find cost of adding a npm/yarn package'
+
+ static flags = {
+ help: flags.help({char: 'h'}),
+ packages: flags.string({
+ char: 'p',
+ description: 'packages for which cost is required, pass more than one separated by space',
+ multiple: true // can get multiple package names
+ }),
+ }
+
+ static args = [{name: 'package'}] // only one can be passed club which one passed through flag and arg
+
+ private static getPackages(flags: any, args: any) {
+ let packages = []
+
+ if (args.package)
+ packages.push(args.package)
+ if (flags.packages)
+ packages = packages.concat(flags.packages) // not inplace operation
+ return packages
+ }
+
+ private static getErrorMessage(pkg: string, message: string) {
+ // replacing will be useful when we do not have specific version
+ // output will be like below
+/*
+ ⚠ @codingtools/cdt@1.2.3 This package has not been published with this particular version.
+ Valid versions - `latest`, `0.1.1` and `0.1.2`
+*/
+ if (message.includes('This package has not been published with this particular version.'))
+ message = message.replace(/`|<\/code>`/g, '')
+
+ return `${chalk.red(pkg)} ${message}`
+ }
+
+ private static getSize(byteSize: number) {
+ if (byteSize >= 1024 * 1024)
+ return `${chalk.red((byteSize / (1024 * 1024)).toFixed(1) + 'MB')}`
+ else if (byteSize >= 1024)
+ return `${chalk.blue((byteSize / (1024)).toFixed(1) + 'KB')}`
+ else //if (byteSize < 1024)
+ return `${chalk.green(byteSize.toFixed(1) + 'B')}`
+ }
+
+ // values needed package
+ async run() {
+ const {args, flags} = this.parse(Bundlephobia)
+
+ args.packages = Bundlephobia.getPackages(flags, args) // get a list
+
+ this.checkParameters(flags, args)
+ this.bundlePhobia(flags, args)
+ }
+
+ // tslint:disable-next-line:no-unused
+ private checkParameters(flags: unknown, args: any) {
+ if (args.packages.length === 0)
+ Logger.error(this, 'At least one package must be passed')
+ }
+
+ // tslint:disable-next-line:no-unused
+ private bundlePhobia(flags: any, args: any) {
+ Logger.progressStart(this, 'finding size...')
+
+ let size = 0
+ let gzip = 0
+ let dependencyCount = 0
+ let packagesResolved = 0
+
+ let packagesInfo: any[] = args.packages.map(
+ (pkg: string) => {
+ return {
+ url: `https://bundlephobia.com/api/size?package=${pkg}`,
+ pkg
+ }
+ }
+ )
+
+ // tslint:disable-next-line:no-unsafe-any no-unused
+ let x = axios.all(packagesInfo.map((packageInfo: any) => { // have to use x for removing TSLintError: promises must be handled appropriately
+ return axios.get(packageInfo.url).then(successResponse => {
+ packagesResolved ++
+ size += successResponse.data.size
+ gzip += successResponse.data.gzip
+ dependencyCount += successResponse.data.dependencyCount
+ Logger.progressStop(this, this.getSuccessMessage(successResponse.data))
+ }).catch(errorResponse => {
+ Logger.progressStopError(this, Bundlephobia.getErrorMessage(packageInfo.pkg, errorResponse.response.data.error.message))
+ })
+ // tslint:disable-next-line:no-unused
+ }))
+ .then(() => {}).catch(() => {})
+ . finally(() => {
+ Logger.success(this, this.getFinalMessage({
+ count: packagesResolved,
+ dependencyCount,
+ size,
+ gzip
+ }))
+ })
+
+ }
+
+ private getFinalMessage(data: any) {
+ return `${chalk.magenta('Total')} [${chalk.cyan(data.count + ' packages resolved')}] has ${data.dependencyCount} dependencies with size of ${Bundlephobia.getSize(data.size)}(${Bundlephobia.getSize(data.gzip)} gzipped)`
+ }
+
+ private getSuccessMessage(data: any) {
+ return `${chalk.magenta(data.name)}@${chalk.cyan(data.version)} has ${data.dependencyCount} dependencies with size of ${Bundlephobia.getSize(data.size)}(${Bundlephobia.getSize(data.gzip)} gzipped)`
+ }
+
+}
diff --git a/src/commands/minify.ts b/src/commands/minify.ts
index 3103f1f..1f765d6 100644
--- a/src/commands/minify.ts
+++ b/src/commands/minify.ts
@@ -10,7 +10,7 @@ export default class Minify extends Command {
static flags = {
help: flags.help({char: 'h'}),
- type: flags.string({char: 't' , description: 'type of file to be minified, it will try to find type with extension'}),
+ type: flags.string({char: 't' , description: 'type of file to be minified, it will try to find type with extension supported: JS, HTML/HTM, CSS'}),
file: flags.string({char: 'f' , description: 'file to be minified'}),
}
diff --git a/src/utilities/logger.ts b/src/utilities/logger.ts
index 154fc47..a72d6d4 100644
--- a/src/utilities/logger.ts
+++ b/src/utilities/logger.ts
@@ -19,6 +19,12 @@ export default class Logger {
signale.error(`${message}`)
thisRef.exit(0) //added to exit command
}
+
+ // tslint:disable-next-line:no-unused
+ public static warn(thisRef: any, message: any) {
+ signale.warn(`${message}`)
+ }
+
// tslint:disable-next-line:no-unused
public static progressStart(thisRef: any, message: string) {
// signale.watch(`${message}`)
@@ -31,6 +37,11 @@ export default class Logger {
Logger.spinner.succeed(message)
}
+ // tslint:disable-next-line:no-unused
+ public static progressStopError(thisRef: any, message: string) {
+ Logger.spinner.warn(message)
+ }
+
// public static logSuccess(thisRef: any, message: string) {
// thisRef.log(` › Success: ${message}`)
// }
@@ -40,5 +51,4 @@ export default class Logger {
// public static logError(thisRef: any, message: string) {
// thisRef.error(`${message}`)
// }
-
}
diff --git a/test/commands/bundlephobia.test.ts b/test/commands/bundlephobia.test.ts
new file mode 100644
index 0000000..7de47c4
--- /dev/null
+++ b/test/commands/bundlephobia.test.ts
@@ -0,0 +1,31 @@
+import {expect, test} from '@oclif/test'
+
+// TODO: add test for invalid package
+// test for valid with matching
+describe('bundlephobia', () => {
+ test
+ .stdout()
+ .command(['bundlephobia'])
+ .exit(0)
+ .it('if no package passed', ctx => {
+ expect(ctx.stdout).to.contain('At least one package must be passed')
+ })
+
+ test
+ .stdout()
+ .command(['bundlephobia', 'react@16.10.2'])
+ .it('if package passed with argument', ctx => {
+ setTimeout(() => // TODO: can we remove it and check if we can resolve promise here
+ expect(ctx.stdout).to.contain(' [react@16.10.2] minified:6.5 kB gzip:2.6 kB')
+ , 5000) // proving 5 seconds just to be safe
+ })
+
+ test
+ .stdout()
+ .command(['bundlephobia', '-p', 'react@16.10.2', 'react@15.10.2'])
+ .it('if package passed with flag', ctx => {
+ setTimeout(() => // TODO: can we remove it and check if we can resolve promise here
+ expect(ctx.stdout).to.contain(' [react@16.10.2] minified:6.5 kB gzip:2.6 kB')
+ , 5000)
+ })
+})
diff --git a/test/commands/crypto.test.ts b/test/commands/crypto.test.ts
index b1e8e3a..98da100 100644
--- a/test/commands/crypto.test.ts
+++ b/test/commands/crypto.test.ts
@@ -19,49 +19,49 @@ describe('crypto', () => {
test
.stdout()
- .command(['crypto', '-d', 'aes', '-s','U2FsdGVkX1/OLQ6Lp+V3O1d5SaxEf9pAf8CV7ErBC9o=', '-k', 'Secret Passphrase'])
+ .command(['crypto', '-d', 'aes', '-s', 'U2FsdGVkX1/OLQ6Lp+V3O1d5SaxEf9pAf8CV7ErBC9o=', '-k', 'Secret Passphrase'])
.it('AES Decryption string passed as flag', ctx => {
expect(ctx.stdout).to.contain('Message')
})
test
.stdout()
- .command(['crypto', '-d', 'des', '-s','U2FsdGVkX180d+J1kUcxGL9bbBAErXAw', '-k', 'Secret Passphrase'])
+ .command(['crypto', '-d', 'des', '-s', 'U2FsdGVkX180d+J1kUcxGL9bbBAErXAw', '-k', 'Secret Passphrase'])
.it('DES Decryption', ctx => {
expect(ctx.stdout).to.contain('Message')
})
test
.stdout()
- .command(['crypto', '-d', 'des', '-s','U2FsdGVkX186YglxZ7yF7aqTjFQA3Yzs', '-k', 'Secret Passphrase','-m','ECB'])
+ .command(['crypto', '-d', 'des', '-s', 'U2FsdGVkX186YglxZ7yF7aqTjFQA3Yzs', '-k', 'Secret Passphrase', '-m', 'ECB'])
.it('DES Decryption with Mode ECB', ctx => {
expect(ctx.stdout).to.contain('Message')
})
test
.stdout()
- .command(['crypto', '-d', '3des', '-s','U2FsdGVkX1+2jkjCxuWwL8uMgdu6SXJc', '-k', 'Secret Passphrase'])
+ .command(['crypto', '-d', '3des', '-s', 'U2FsdGVkX1+2jkjCxuWwL8uMgdu6SXJc', '-k', 'Secret Passphrase'])
.it('3DES Decryption', ctx => {
expect(ctx.stdout).to.contain('Message')
})
test
.stdout()
- .command(['crypto', '-d', 'RABBIT', '-s','U2FsdGVkX185oOsUqvpF+7x0zPUxNJw=', '-k', 'Secret Passphrase'])
+ .command(['crypto', '-d', 'RABBIT', '-s', 'U2FsdGVkX185oOsUqvpF+7x0zPUxNJw=', '-k', 'Secret Passphrase'])
.it('RABBIT Decryption', ctx => {
expect(ctx.stdout).to.contain('Message')
})
test
.stdout()
- .command(['crypto', '-d', 'RC4', '-s','U2FsdGVkX1+/oErpaqQQk1Fj2eXwL1o=', '-k', 'Secret Passphrase'])
+ .command(['crypto', '-d', 'RC4', '-s', 'U2FsdGVkX1+/oErpaqQQk1Fj2eXwL1o=', '-k', 'Secret Passphrase'])
.it('RC4 Decryption', ctx => {
expect(ctx.stdout).to.contain('Message')
})
test
.stdout()
- .command(['crypto', '-d', 'RC4DROP', '-s','U2FsdGVkX18+D1WNQ64XzaCwkUM6moE=', '-k', 'Secret Passphrase'])
+ .command(['crypto', '-d', 'RC4DROP', '-s', 'U2FsdGVkX18+D1WNQ64XzaCwkUM6moE=', '-k', 'Secret Passphrase'])
.it('RC4Drop Decryption', ctx => {
expect(ctx.stdout).to.contain('Message')
})
@@ -92,7 +92,7 @@ describe('crypto', () => {
test
.stdout()
- .command(['crypto', '-e', 'aes','-d', 'aes', '-s', 'Message', '-k', 'Secret Passphrase'])
+ .command(['crypto', '-e', 'aes', '-d', 'aes', '-s', 'Message', '-k', 'Secret Passphrase'])
.exit(0)
.it('Both encryption and decryption methods passed', ctx => {
expect(ctx.stdout).to.contain('Both encryption and decryption methods passed')
@@ -106,5 +106,4 @@ describe('crypto', () => {
expect(ctx.stdout).to.contain('Neither encryption or decryption methods passed')
})
-
})