spatie / flare-vite-plugin-sourcemap-uploader

A Vite plugin for uploading sourcemaps to Flare
https://flareapp.io
MIT License
2 stars 2 forks source link

Need a way to upload sourcemaps manually using command line #5

Closed wasimTQ closed 11 months ago

wasimTQ commented 1 year ago

I was able to upload sourcemaps using the flareSourcemapUploader vite plugin in both local and ci (I'm using gitlab ci).

But what I need is to be able to upload the sourcemaps using custom code by using a command like yarn upload.

What I'm doing right now:

  1. Build the app. It's available in dist in base directory.
  2. The sourcemap custom code forked from this repo is in src/sourcemap.
  3. Currently how it's being running is cd src/sourcemap && yarn && yarn build && yarn upload // (calls node dist/index.js)

Here's the glimpse of the sourcemap code for context:

// src/sourcemap/src/index.ts

export type PluginConfig = {
    key: string;
    base?: string;
    apiEndpoint?: string;
    runInDevelopment?: boolean;
    version?: string;
    removeSourcemaps?: boolean;
};

export type Sourcemap = {
    original_file: string;
    content: string;
    sourcemap_url: string;
};

export default async function flareSourcemapUploader({
    key,
    base,
    apiEndpoint = 'https://flareapp.io/api/sourcemaps',
    runInDevelopment = false,
    version = uuid(),
    removeSourcemaps = false,
}: PluginConfig) {
    const dir = resolve(__dirname, '../../../')
    base = dir
    if (!key) {
        flareLog('No Flare API key was provided, not uploading sourcemaps to Flare.');
    }

    const flare = new FlareApi(apiEndpoint, key, version);

    const enableUploadingSourcemaps =
        key && (process.env.NODE_ENV !== 'development' || runInDevelopment) && process.env.SKIP_SOURCEMAPS !== 'true';

    if (!enableUploadingSourcemaps) {
        return;
    }

    const outputDir = resolve(dir, 'dist');

    const files = await glob('./**/*.map', { cwd: outputDir });
    console.log(files)
    const sourcemaps = files
        .map((file): Sourcemap | null => {
            const sourcePath = file.replace(/\.map$/, '');
            const sourceFilename = resolve(outputDir, sourcePath);

            if (!existsSync(sourceFilename)) {
                flareLog(`no corresponding source found for "${file}"`, true);
                return null;
            }

            const sourcemapLocation = resolve(outputDir, file);

            try {
                return {
                    content: readFileSync(sourcemapLocation, 'utf8'),
                    sourcemap_url: sourcemapLocation,
                    original_file: `${base}${sourcePath}`,
                };
            } catch (error) {
                flareLog('Error reading sourcemap file ' + sourcemapLocation + ': ' + error, true);
                return null;
            }
        })
        .filter((sourcemap) => sourcemap !== null) as Sourcemap[];

    if (!sourcemaps.length) {
        return;
    }

    flareLog(`Uploading ${sourcemaps.length} sourcemap files to Flare.`);

    const pendingUploads = sourcemaps.map((sourcemap) => () => flare.uploadSourcemap(sourcemap));

    try {
        while (pendingUploads.length) {
            await Promise.all(pendingUploads.splice(0, 10).map((f) => f()));
        }

        flareLog('Successfully uploaded sourcemaps to Flare.');
    } catch (error) {
        flareLog(`Something went wrong while uploading the sourcemaps to Flare: ${error}`, true);
    }

    if (removeSourcemaps) {
        sourcemaps.forEach(({ sourcemap_url }) => {
            try {
                unlinkSync(sourcemap_url);
            } catch (error) {
                console.error('Error removing sourcemap file', sourcemap_url, ': ', error);
            }
        });

        flareLog('Successfully removed sourcemaps.');
    }
}

// Calling point
flareSourcemapUploader({
    key: process.env.FLARE_PROJECT_KEY,
})
// src/sourcemap/src/flareApi.ts

import { deflateRawSync } from 'zlib';
import axios from 'axios';
import { Sourcemap } from './index';

export default class FlareApi {
    endpoint: string;
    key: string;
    version: string;

    constructor(endpoint: string, key: string, version: string) {
        this.endpoint = endpoint;
        this.key = key;
        this.version = version;
    }

    uploadSourcemap(sourcemap: Sourcemap) {
        return new Promise((resolve, reject) => {
            const base64GzipSourcemap = deflateRawSync(sourcemap.content).toString('base64');

            axios
                .post(this.endpoint, {
                    key: this.key,
                    version_id: this.version,
                    relative_filename: sourcemap.original_file,
                    sourcemap: base64GzipSourcemap,
                })
                .then(resolve)
                .catch((error) => {
                    return reject(`${error.response.status}: ${JSON.stringify(error.response.data)}`);
                });
        });
    }
}

The output is

@flareapp/vite-plugin-sourcemap-uploader: Uploading 3 sourcemap files to Flare.
@flareapp/vite-plugin-sourcemap-uploader: Successfully uploaded sourcemaps to Flare.

File structure:

dist
src/frontend
src/sourcemap // It's a standalone folder cloned from this repo
vite.config.ts // Config for the frontend

What I need in the gitlab ci is to be able to build the app once, cache the dist folder alone and use the custom code for sourcemap to manually upload the files in multiple environments without having to build it again and again.

But it doesn't seem like it uploaded it successfully as I can't inspect the code that throws error in flare dashboard.

Let me know if you'll need more details.

Sam-Apostel commented 11 months ago

Per the console output you provided, it looks like your sourcemaps are successfully uploaded to Flare.

The sourcemap uploader generates a unique version id (defaults to uuid()). This version needs to match the sourcemap_version_id that the flare js client uses to send an error to Flare.

In the code snippets you provided, I don't see the FLARE_SOURCEMAP_VERSION being set. Our sourcemap uploader does that here.

You could achieve this in a number of ways:

If each build has some unique id you could also use that to upload the sourcemaps to each project during the build step.