|
1 | | -import { DebugIdGenerator, SourceProcessor, SymbolUploader, ZipArchive } from '@backtrace/sourcemap-tools'; |
2 | | -import fs from 'fs'; |
| 1 | +import { Asset, matchSourceExtension, processAndUploadAssetsCommand } from '@backtrace/sourcemap-tools'; |
3 | 2 | import path from 'path'; |
4 | 3 | import webpack, { WebpackPluginInstance } from 'webpack'; |
5 | 4 | import { BacktracePluginOptions } from './models/BacktracePluginOptions'; |
6 | 5 |
|
7 | 6 | export class BacktracePlugin implements WebpackPluginInstance { |
8 | | - private readonly _sourceProcessor: SourceProcessor; |
9 | | - private readonly _sourceMapUploader?: SymbolUploader; |
10 | | - |
11 | | - constructor(public readonly options?: BacktracePluginOptions) { |
12 | | - this._sourceProcessor = new SourceProcessor(new DebugIdGenerator()); |
13 | | - this._sourceMapUploader = options?.uploadUrl |
14 | | - ? new SymbolUploader(options.uploadUrl, options.uploadOptions) |
15 | | - : undefined; |
16 | | - } |
| 7 | + constructor(public readonly options?: BacktracePluginOptions) {} |
17 | 8 |
|
18 | 9 | public apply(compiler: webpack.Compiler) { |
19 | | - const processResults = new Map<string, string | Error>(); |
20 | | - let uploadResult: string | Error | undefined; |
21 | | - |
22 | 10 | compiler.hooks.afterEmit.tapPromise(BacktracePlugin.name, async (compilation) => { |
23 | 11 | const logger = compilation.getLogger(BacktracePlugin.name); |
24 | | - if (!compilation.outputOptions.path) { |
| 12 | + const outputDir = compilation.outputOptions.path; |
| 13 | + if (!outputDir) { |
25 | 14 | logger.error( |
26 | 15 | 'Skipping everything because outputOptions.path is not set. If you see this error, please report this to Backtrace.', |
27 | 16 | ); |
28 | 17 | return; |
29 | 18 | } |
30 | 19 |
|
31 | | - const entries: [string, string, string][] = []; |
32 | | - |
33 | | - for (const asset in compilation.assets) { |
34 | | - if (!asset.match(/\.(c|m)?jsx?$/)) { |
35 | | - logger.debug(`[${asset}] skipping processing, extension does not match`); |
36 | | - continue; |
37 | | - } |
38 | | - |
39 | | - const map = asset + '.map'; |
40 | | - if (!compilation.assets[map]) { |
41 | | - logger.debug(`[${asset}] skipping processing, map file not found`); |
42 | | - continue; |
43 | | - } |
44 | | - |
45 | | - const assetPath = path.join(compilation.outputOptions.path, asset); |
46 | | - const sourceMapPath = path.join(compilation.outputOptions.path, map); |
47 | | - |
48 | | - logger.debug(`adding asset ${assetPath} with sourcemap ${sourceMapPath}`); |
49 | | - entries.push([asset, assetPath, sourceMapPath]); |
50 | | - } |
51 | | - |
52 | | - logger.log(`received ${entries.length} files for processing`); |
53 | | - |
54 | | - for (const [asset, sourcePath, sourceMapPath] of entries) { |
55 | | - let debugId: string; |
| 20 | + const processAndUpload = processAndUploadAssetsCommand(this.options ?? {}, { |
| 21 | + beforeAll: (assets) => logger.log(`processing ${assets.length} files`), |
56 | 22 |
|
57 | | - logger.time(`[${asset}] process source and sourcemap`); |
58 | | - try { |
59 | | - const result = await this._sourceProcessor.processSourceAndSourceMapFiles( |
60 | | - sourcePath, |
61 | | - sourceMapPath, |
62 | | - ); |
| 23 | + afterProcess: (asset) => logger.log(`[${asset.asset.name}] processed source and sourcemap`), |
| 24 | + afterWrite: (asset) => logger.log(`[${asset.asset.name}] wrote source and sourcemap to file`), |
| 25 | + assetFinished: (asset) => logger.info(`[${asset.asset.name}] asset processed successfully`), |
| 26 | + assetError: (asset) => logger.error(`[${asset.asset.name}] ${asset.error}`), |
63 | 27 |
|
64 | | - if (result.isErr()) { |
65 | | - logger.error(`[${asset}] process source and sourcemap failed:`, result.data); |
66 | | - processResults.set(asset, new Error(result.data)); |
67 | | - continue; |
68 | | - } |
| 28 | + beforeArchive: (paths) => logger.log(`creating archive to upload from ${paths.length} files`), |
| 29 | + beforeUpload: () => logger.log(`uploading sourcemaps...`), |
| 30 | + afterUpload: (result) => logger.info(`sourcemaps uploaded to Backtrace: ${result.rxid}`), |
| 31 | + uploadError: (error) => logger.error(`failed to upload sourcemaps: ${error}`), |
| 32 | + }); |
69 | 33 |
|
70 | | - debugId = result.data.debugId; |
71 | | - await fs.promises.writeFile(sourcePath, result.data.source, 'utf8'); |
72 | | - await fs.promises.writeFile(sourceMapPath, JSON.stringify(result.data.sourceMap), 'utf8'); |
| 34 | + const assets = Object.keys(compilation.assets) |
| 35 | + .filter(matchSourceExtension) |
| 36 | + .map<Asset>((asset) => ({ name: asset, path: path.join(outputDir, asset) })); |
73 | 37 |
|
74 | | - processResults.set(asset, debugId); |
75 | | - } catch (err) { |
76 | | - logger.error(`[${asset}] process source and sourcemap failed:`, err); |
77 | | - processResults.set(asset, err instanceof Error ? err : new Error('Unknown error.')); |
78 | | - continue; |
79 | | - } finally { |
80 | | - logger.timeEnd(`[${asset}] process source and sourcemap`); |
81 | | - } |
82 | | - } |
83 | | - |
84 | | - if (this._sourceMapUploader) { |
85 | | - logger.time(`upload sourcemaps`); |
86 | | - try { |
87 | | - const archive = new ZipArchive(); |
88 | | - const request = this._sourceMapUploader.uploadSymbol(archive); |
89 | | - |
90 | | - for (const [asset, _, sourceMapPath] of entries) { |
91 | | - const stream = fs.createReadStream(sourceMapPath); |
92 | | - archive.append(`${asset}.map`, stream); |
93 | | - } |
94 | | - |
95 | | - await archive.finalize(); |
96 | | - const result = await request; |
97 | | - if (result.isErr()) { |
98 | | - logger.error(`upload sourcemaps failed:`, result.data); |
99 | | - uploadResult = new Error(result.data); |
100 | | - } else { |
101 | | - uploadResult = result.data.rxid; |
102 | | - } |
103 | | - } catch (err) { |
104 | | - logger.error(`upload sourcemaps failed:`, err); |
105 | | - uploadResult = err instanceof Error ? err : new Error('Unknown error.'); |
106 | | - } finally { |
107 | | - logger.timeEnd(`upload sourcemaps`); |
108 | | - } |
109 | | - } |
110 | | - |
111 | | - for (const [key, result] of processResults) { |
112 | | - if (typeof result === 'string') { |
113 | | - logger.info(`[${key}] processed file successfully`); |
114 | | - logger.debug(`\tdebugId: ${result}`); |
115 | | - } else { |
116 | | - logger.error(`[${key}] failed to process file: ${result.message}`); |
117 | | - logger.debug(`Error stack trace: ${result.stack}`); |
118 | | - } |
119 | | - } |
120 | | - |
121 | | - if (uploadResult) { |
122 | | - if (typeof uploadResult === 'string') { |
123 | | - logger.info(`uploaded sourcemaps successfully`); |
124 | | - logger.debug(`\trxid: ${uploadResult}`); |
125 | | - } else { |
126 | | - logger.error(`failed to upload sourcemaps: ${uploadResult.message}`); |
127 | | - logger.debug(`Error stack trace: ${uploadResult.stack}`); |
128 | | - } |
129 | | - } |
| 38 | + await processAndUpload(assets); |
130 | 39 | }); |
131 | 40 | } |
132 | 41 | } |
0 commit comments