npm package: @grafana/faro-cli
Description: CLI for uploading sourcemaps to the Faro source map API
Author: Grafana Labs
Latest version: 0.9.0

# Faro CLI

A command-line interface for uploading source maps to the Faro source map API using cURL.

## Installation

```bash
npm install --save-dev @grafana/faro-cli
```

or

```bash
yarn add --dev @grafana/faro-cli
```

## Requirements

- cURL must be installed on your system and available in your PATH.

## Usage

### Uploading Source Maps

The CLI uses cURL under the hood to upload source maps to the Faro API:

```bash
npx faro-cli upload \
  --endpoint "your-faro-sourcemap-api-url" \
  --app-id "your-app-id" \
  --api-key "your-api-key" \
  --stack-id "your-stack-id" \
  --bundle-id "your-bundle-id" \
  --output-path "./dist" \
  --verbose
```

The CLI will automatically find and upload all `.map` files in the specified output directory and its subdirectories. It recursively searches through all folders to find any source map files, so you don't need to specify patterns or worry about nested directory structures.

#### File Size Limits

The Faro API has a 30MB limit for individual file uploads by default. This limit applies to the uncompressed size of the files, regardless of whether compression is used during transmission. The CLI automatically handles this by:

1. Checking file sizes before uploading
2. Warning about files that exceed the limit
3. Skipping files that are too large
4. Processing files in a streaming fashion, accumulating files until reaching the size limit before uploading each batch

This streaming approach is the same method used by the bundler plugins, ensuring consistent behavior across all upload methods. The CLI intelligently processes files one by one, uploading batches as they reach the size limit, which optimizes the upload process while staying within the API's size limits.

While the `--gzip-payload` option can significantly reduce the network transfer size, the original uncompressed file size must still be under the configured size limit to be accepted by the API.

You can customize the maximum upload size using the `--max-upload-size` option, which allows you to specify a different size limit in bytes. However, you must file a support ticket with Grafana Cloud to increase the limit on the backend.

#### Gzipping Options

The CLI provides two different gzipping options to optimize uploads:

1. **Gzip Contents (`-g, --gzip-contents`)**: Compresses multiple source map files into a tarball before uploading. Files are processed in a streaming fashion, accumulating until reaching the 30MB limit before creating and uploading each tarball. This is useful when uploading multiple files at once.

2. **Gzip Payload (`-z, --gzip-payload`)**: Compresses the HTTP payload itself using gzip content encoding. This can significantly reduce upload size and is especially useful for large source map files.

Example with gzip payload:

```bash
npx faro-cli upload \
  --endpoint "your-faro-sourcemap-api-url" \
  --app-id "your-app-id" \
  --api-key "your-api-key" \
  --stack-id "your-stack-id" \
  --bundle-id "your-bundle-id" \
  --output-path "./dist" \
  --patterns "*.map" \
  --gzip-payload \
  --verbose
```

You can use both options together for maximum compression:

```bash
npx faro-cli upload \
  --endpoint "your-faro-sourcemap-api-url" \
  --app-id "your-app-id" \
  --api-key "your-api-key" \
  --stack-id "your-stack-id" \
  --bundle-id "your-bundle-id" \
  --output-path "./dist" \
  --patterns "*.map" \
  --gzip-contents \
  --gzip-payload \
  --verbose
```

#### Using a Proxy

If you need to route requests through a proxy server, you can use the `--proxy` option:

```bash
npx faro-cli upload \
  --endpoint "your-faro-sourcemap-api-url" \
  --app-id "your-app-id" \
  --api-key "your-api-key" \
  --stack-id "your-stack-id" \
  --bundle-id "your-bundle-id" \
  --output-path "./dist" \
  --proxy "your-proxy:port" \
  --proxy-user "user:pass" \
  --verbose
```

The proxy URL will be passed to cURL using the `--proxy` parameter. If your proxy requires authentication, you can use the `--proxy-user` option (or `-U`) to provide credentials in the format `username:password`.

### Injecting Bundle ID into JavaScript Files

For applications that don't use Webpack or Rollup, or in cases where you need to add the bundle ID to already built JavaScript files, you can use the `inject-bundle-id` command:

```bash
npx faro-cli inject-bundle-id \
  --bundle-id "your-bundle-id" \
  --app-name "your-app-name" \
  --files "dist/**/*.js" \
  --verbose
```

This command will:
1. Locate all JavaScript files matching the specified glob patterns
2. Check if each file already has a bundle ID snippet
3. Prepend the bundle ID snippet to files that don't have it
4. Export the bundle ID to an environment variable for potential later use with other commands

#### Options

- `--bundle-id, -b`: The bundle ID to inject (leave blank to generate a random ID)
- `--app-name, -n`: Application name used in the bundle ID snippet
- `--files, -f`: File patterns to match (multiple patterns can be specified)
- `--verbose, -v`: Enable verbose logging
- `--dry-run, -d`: Only print which files would be modified without making changes

#### Examples

Generate a random bundle ID and inject it into all JS files:

```bash
npx faro-cli inject-bundle-id \
  --app-name "my-app" \
  --files "dist/**/*.js" \
  --verbose
```

Do a dry run first to see which files would be modified:

```bash
npx faro-cli inject-bundle-id \
  --bundle-id "your-bundle-id" \
  --app-name "my-app" \
  --files "dist/**/*.js" \
  --dry-run \
  --verbose
```

### Using with Bundler Plugins

When using with the Faro bundler plugins, you can set the `skipUpload` option to `true` in the plugin configuration to skip uploading source maps during the build process and instead use the CLI to upload them later.

#### Rollup Example

```js
// rollup.config.js
import faroUploader from '@grafana/faro-rollup-plugin';

export default {
  // ... other rollup config
  plugins: [
    // ... other plugins
    faroUploader({
      // this URL is different from the Faro Collector URL - find this value in the Frontend Observability plugin under "Settings" -> "Source Maps" -> "Configure source map uploads"
      endpoint: 'https://faro-api-prod-us-east-0.grafana.net/faro/api/v1',
      appName: 'my-app',
      appId: 'your-app-id',
      apiKey: 'your-api-key',
      stackId: 'your-stack-id',
      skipUpload: true, // Skip uploading during build
      verbose: true,
    }),
  ],
};
```

Then, after the build, you can upload the source maps using the CLI:

```bash
npx faro-cli upload \
  --endpoint "your-faro-sourcemap-api-url" \
  --app-id "your-app-id" \
  --api-key "your-api-key" \
  --stack-id "your-stack-id" \
  --bundle-id env \
  --app-name "my-app" \
  --output-path "./dist" \
  --verbose
```

Note the use of `--bundle-id env` and `--app-name "my-app"` to read the bundle ID from the environment variable set by the bundler plugin.

#### Webpack Example

```js
// webpack.config.js
const FaroSourceMapUploaderPlugin = require('@grafana/faro-webpack-plugin');

module.exports = {
  // ... other webpack config
  plugins: [
    // ... other plugins
    new FaroSourceMapUploaderPlugin({
      endpoint: 'https://faro-api-prod-us-east-0.grafana.net/faro/api/v1',
      appName: 'my-app',
      appId: 'your-app-id',
      apiKey: 'your-api-key',
      stackId: 'your-stack-id',
      skipUpload: true, // Skip uploading during build
      verbose: true,
    }),
  ],
};
```

### Generating a curl Command

If you prefer to use curl directly, you can generate a curl command:

```bash
npx faro-cli curl \
  --endpoint "your-faro-sourcemap-api-url" \
  --app-id "your-app-id" \
  --api-key "your-api-key" \
  --stack-id "your-stack-id" \
  --bundle-id "your-bundle-id" \
  --file "./dist/main.js.map"
```

You can also generate a curl command that uses gzip compression:

```bash
npx faro-cli curl \
  --endpoint "your-faro-sourcemap-api-url" \
  --app-id "your-app-id" \
  --api-key "your-api-key" \
  --stack-id "your-stack-id" \
  --bundle-id "your-bundle-id" \
  --file "./dist/main.js.map" \
  --gzip-payload
```

You can also generate a curl command that uses a proxy:

```bash
npx faro-cli curl \
  --endpoint "your-faro-sourcemap-api-url" \
  --app-id "your-app-id" \
  --api-key "your-api-key" \
  --stack-id "your-stack-id" \
  --bundle-id "your-bundle-id" \
  --file "./dist/main.js.map" \
  --proxy "http://proxy.example.com:8080" \
  --proxy-user "username:password"
```

This will output a curl command that you can copy and run manually.

## Options

### Upload Command

- `-e, --endpoint <url>`: Faro API endpoint URL (required) - find this value in the Frontend Observability plugin under **Settings** -> **Source Maps** -> **Configure source map uploads**
- `-a, --app-id <id>`: Faro application ID (required)
- `-k, --api-key <key>`: Faro API key (required)
- `-s, --stack-id <id>`: Faro stack ID (required) - find this value in the Frontend Observability plugin under **Settings** -> **Source Maps** -> **Configure source map uploads**
- `-b, --bundle-id <id>`: Bundle ID (required, can be set to "env" to read from environment variable)
- `-o, --output-path <path>`: Path to the directory containing source maps (required)
- `-n, --app-name <name>`: Application name (used to find bundleId in environment variables)
- `-k, --keep-sourcemaps`: Keep source maps after uploading (default: false)
- `-g, --gzip-contents`: Compress source maps as a tarball before uploading; files are processed in a streaming fashion, accumulating until the size limit (default: false)
- `-z, --gzip-payload`: Gzip the HTTP payload for smaller uploads (default: false)
- `-v, --verbose`: Enable verbose logging (default: false)
- `-r, --recursive`: Recursively search subdirectories for source maps (default: false)
- `-i, --max-upload-size <size>`: Maximum upload size in bytes, default is 30MB. The Faro API has a 30MB limit for individual file uploads by default. In special circumstances, this limit may be changed by contacting Grafana Cloud support.
- `-x, --proxy <url>`: Proxy URL to use for cURL requests (optional)
- `-U, --proxy-user <user:password>`: Username and password for proxy authentication (optional)

### Curl Command

- `-e, --endpoint <url>`: Faro API endpoint URL (required) - find this value in the Frontend Observability plugin under **Settings** -> **Source Maps** -> **Configure source map uploads**
- `-a, --app-id <id>`: Faro application ID (required)
- `-k, --api-key <key>`: Faro API key (required)
- `-s, --stack-id <id>`: Faro stack ID (required) - find this value in the Frontend Observability plugin under **Settings** -> **Source Maps** -> **Configure source map uploads**
- `-b, --bundle-id <id>`: Bundle ID (required, can be set to "env" to read from environment variable)
- `-f, --file <path>`: Path to the source map file (required)
- `-n, --app-name <name>`: Application name (used to find bundleId in environment variables)
- `-t, --content-type <type>`: Content type for the upload (default: "application/json")
- `-z, --gzip-payload`: Generate a command that gzips the payload (default: false)
- `-x, --proxy <url>`: Proxy URL to use for cURL requests (optional)
- `-U, --proxy-user <user:password>`: Username and password for proxy authentication (optional)

## License

Apache-2.0