Skip to content
This repository was archived by the owner on Feb 1, 2021. It is now read-only.

Add CLI to upload stats & adapt new API #2

Merged
merged 2 commits into from
Sep 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion .eslintignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules/
lib/
lib/
dist.js
__fixtures__
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
node_modules/
lib/
lib/
dist.js
3 changes: 2 additions & 1 deletion .prettierignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
node_modules/
package.json
CHANGELOG.md
CHANGELOG.md
dist.js
3 changes: 3 additions & 0 deletions jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
testEnvironment: 'node',
}
16 changes: 11 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@
],
"scripts": {
"build": "lerna run build",
"ci": "yarn build && yarn lint",
"ci": "yarn build && yarn lint && yarn test --ci",
"dev": "lerna run build --parallel -- --watch",
"format": "prettier --write \"**/*.{js,json,md}\"",
"lint": "eslint .",
"release": "lerna publish --conventional-commits && conventional-github-releaser --preset angular"
"release": "lerna publish --conventional-commits && conventional-github-releaser --preset angular",
"test": "jest"
},
"devDependencies": {
"@babel/cli": "^7.4.4",
Expand All @@ -17,15 +19,19 @@
"@babel/preset-env": "^7.4.5",
"babel-eslint": "^10.0.1",
"conventional-github-releaser": "^3.1.3",
"eslint": "^6.0.1",
"eslint": "^6.4.0",
"eslint-config-airbnb": "^18.0.1",
"eslint-config-prettier": "^6.2.0",
"eslint-config-prettier": "^6.3.0",
"eslint-config-smooth": "^2.1.1",
"eslint-plugin-import": "^2.18.2",
"eslint-plugin-jsx-a11y": "^6.2.3",
"eslint-plugin-react": "^7.14.3",
"eslint-plugin-react-hooks": "^2.0.1",
"jest": "^24.9.0",
"lerna": "^3.14.1",
"prettier": "^1.18.2"
"memory-fs": "^0.4.1",
"nock": "^11.3.4",
"prettier": "^1.18.2",
"webpack": "^4.40.2"
}
}
3 changes: 3 additions & 0 deletions packages/cli/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
/*
!/lib/**/*.js
*.test.js
23 changes: 23 additions & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# @bundle-analyzer/cli

Official CLI compatible with [Bundle Analyzer service](https://www.bundle-analyzer.com).

## Install

```
npm install --save-dev @bundle-analyzer/cli
```

## Usage

```js
webpack --json | bundle-analyzer --token <your-token>
```

## Complete documentation

👉 [See full documentation](https://docs.bundle-analyzer.com/)

## License

MIT
1 change: 1 addition & 0 deletions packages/cli/__fixtures__/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('hello world!')
3 changes: 3 additions & 0 deletions packages/cli/bin/bundle-analyzer
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
#!/usr/bin/env node

require('../lib/index')
31 changes: 31 additions & 0 deletions packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
{
"name": "@bundle-analyzer/cli",
"description": "Bundle Analyzer CLI.",
"version": "0.2.1",
"main": "lib/index.js",
"repository": "https://github.com/smooth-code/bundle-analyzer-javascript/tree/master/packages/cli",
"author": "Greg Bergé <[email protected]>",
"publishConfig": {
"access": "public"
},
"keywords": [
"bundle-analyzer",
"bundlesize"
],
"engines": {
"node": ">=8"
},
"license": "MIT",
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "yarn run build"
},
"dependencies": {
"@bundle-analyzer/core": "^0.2.1",
"commander": "^3.0.1"
},
"devDependencies": {
"webpack-cli": "^3.3.8"
}
}
45 changes: 45 additions & 0 deletions packages/cli/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/* eslint-disable no-console */
import program from 'commander'
import { uploadStats } from '@bundle-analyzer/core'
import pkg from '../package.json'

program
.version(pkg.version)
.usage('[options] <stats>')
.option('--token <repository-token>', 'specify the repository token')

program.on('--help', () => {
console.log(`
Example:
webpack --json | bundle-analyzer --token "your-repository-token"
cat webpack-stats.json | bundle-analyzer --token "your-repository-token"
`)
})

program.parse(process.argv)

async function readStdin() {
return new Promise((resolve, reject) => {
let stdin = ''

process.stdin.setEncoding('utf8')
process.stdin.on('readable', () => {
const chunk = process.stdin.read()
if (chunk !== null) stdin += chunk
})
process.stdin.on('error', reject)
process.stdin.on('end', () => resolve(stdin))
})
}

async function run() {
const rawStats = await readStdin()
const stats = JSON.parse(rawStats)
await uploadStats({ webpackStats: stats, token: program.token })
}

run().catch(error => {
setTimeout(() => {
throw error
})
})
43 changes: 43 additions & 0 deletions packages/core/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
# @bundle-analyzer/core

Official Node.js package compatible with [Bundle Analyzer service](https://www.bundle-analyzer.com).

## Install

```
npm install --save-dev @bundle-analyzer/core
```

## Usage

```js
import { uploadStats } from '@bundle-analyzer/core'
import webpackStats from './webpack-stats.json'

uploadStats({ webpackStats, token: '<repository-token>' })
.then(() => {
console.log('uploaded)
})
```

## Options

### webpackStats

Stats generated from webpack.

### token

You can specify the token using options or environment variable `BUNDLE_ANALYZER_TOKEN`.

### fileSystem

Custom filesystem.

## Complete documentation

👉 [See full documentation](https://docs.bundle-analyzer.com/)

## License

MIT
1 change: 1 addition & 0 deletions packages/core/__fixtures__/main.js
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
console.log('hello world!')
30 changes: 30 additions & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "@bundle-analyzer/core",
"description": "Bundle Analyzer Node.js uploader.",
"version": "0.2.1",
"main": "lib/index.js",
"repository": "https://github.com/smooth-code/bundle-analyzer-javascript/tree/master/packages/core",
"author": "Greg Bergé <[email protected]>",
"publishConfig": {
"access": "public"
},
"keywords": [
"bundle-analyzer",
"bundlesize"
],
"engines": {
"node": ">=8"
},
"license": "MIT",
"scripts": {
"prebuild": "rm -rf lib/",
"build": "babel --config-file ../../babel.config.js -d lib --ignore \"**/*.test.js\" src",
"prepublishOnly": "yarn run build"
},
"dependencies": {
"axios": "^0.19.0",
"brotli-size": "^4.0.0",
"gzip-size": "^5.1.1",
"omit-deep": "^0.3.0"
}
}
49 changes: 49 additions & 0 deletions packages/core/src/config.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import { getToken, getApiUrl } from './config'

describe('config', () => {
describe('#getToken', () => {
it('throws an error if token is not specified', () => {
expect(() => getToken()).toThrow(
'Token not found, please specify a token using BUNDLE_ANALYZER_TOKEN env variable',
)
})

describe('with BUNDLE_ANALYZER_TOKEN env variable', () => {
beforeEach(() => {
process.env.BUNDLE_ANALYZER_TOKEN = 'env-token'
})

afterEach(() => {
delete process.env.BUNDLE_ANALYZER_TOKEN
})

it('returns provided token', () => {
expect(getToken('foo')).toBe('foo')
})

it('uses it as default', () => {
expect(getToken()).toBe('env-token')
})
})
})

describe('#getApiUrl', () => {
describe('with BUNDLE_ANALYZER_API_URL env variable', () => {
beforeEach(() => {
process.env.BUNDLE_ANALYZER_API_URL = 'env-api'
})

afterEach(() => {
delete process.env.BUNDLE_ANALYZER_API_URL
})

it('uses it', () => {
expect(getApiUrl()).toBe('env-api')
})
})

it('returns default API url', () => {
expect(getApiUrl()).toBe('https://api.bundle-analyzer.com')
})
})
})
86 changes: 86 additions & 0 deletions packages/core/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import zlib from 'zlib'
import { promisify } from 'util'
import path from 'path'
import fs from 'fs'
import axios from 'axios'
import gzipSize from 'gzip-size'
import brotliSize from 'brotli-size'
import omitDeep from 'omit-deep'
import { detectProvider } from './provider'
import { getToken, getApiUrl } from './config'

const gzip = promisify(zlib.gzip)

async function sizeAssets(webpackStats, { fileSystem = fs } = {}) {
const readFile = promisify(fileSystem.readFile.bind(fileSystem))
return Promise.all(
webpackStats.assets.map(async asset => {
const fullPath = path.join(webpackStats.outputPath, asset.name)
const buffer = await readFile(fullPath)
return {
...asset,
gzipSize: await gzipSize(buffer),
brotliSize: await brotliSize(buffer),
}
}),
)
}

function getErrorMessage(error) {
if (
error.response &&
error.response.data &&
error.response.data.error &&
error.response.data.error.message
) {
return error.response.data.error.message
}
return error.message
}

export async function uploadStats({
webpackStats,
token: optionToken,
fileSystem,
}) {
try {
const token = getToken(optionToken)
const apiUrl = getApiUrl()
const metadata = detectProvider()
const stats = omitDeep(webpackStats, 'source')
const assets = await sizeAssets(stats, { fileSystem })

const { data: bundle } = await axios.post(`${apiUrl}/bundles`, {
token,
bundler: 'webpack',
stats: {
assets,
chunksNumber: stats.chunks.length,
modulesNumber: stats.modules.length,
assetsNumber: stats.assets.length,
},
})

const data = await gzip(Buffer.from(JSON.stringify(stats)))

await axios.request({
method: 'put',
url: bundle.webpackStatsPutUrl,
data,
headers: {
'content-encoding': 'gzip',
},
maxContentLength: 30 * 1024 * 1024,
})

await axios.post(`${apiUrl}/builds`, {
token,
bundleId: bundle.id,
branch: metadata.branch,
commit: metadata.commit,
providerMetadata: metadata,
})
} catch (error) {
throw new Error(getErrorMessage(error))
}
}
Loading