From a786c9f6ed55291ec12b8e03fa0e860dd0f42d51 Mon Sep 17 00:00:00 2001 From: Bobby Galli Date: Mon, 30 Oct 2023 20:32:46 -0400 Subject: [PATCH] feat: release package as a library (#80) * feat: release package as a library * fix: export ApiClient * chore: update README --- README.md | 39 +++-------------- bin/index.ts | 85 +++---------------------------------- index.ts | 5 ++- spec/compression.spec.ts | 2 +- spec/pdb.spec.ts | 2 +- spec/sym.spec.ts | 2 +- spec/tmp.spec.ts | 2 +- spec/worker.spec.ts | 4 +- {bin => src}/compression.js | 0 {bin => src}/info.ts | 0 {bin => src}/pdb.ts | 0 {bin => src}/sym.ts | 0 {bin => src}/tmp.ts | 0 src/upload.ts | 76 +++++++++++++++++++++++++++++++++ {bin => src}/worker.ts | 0 tsconfig.json | 2 +- 16 files changed, 101 insertions(+), 118 deletions(-) rename {bin => src}/compression.js (100%) rename {bin => src}/info.ts (100%) rename {bin => src}/pdb.ts (100%) rename {bin => src}/sym.ts (100%) rename {bin => src}/tmp.ts (100%) create mode 100644 src/upload.ts rename {bin => src}/worker.ts (100%) diff --git a/README.md b/README.md index 612d527..d96f646 100644 --- a/README.md +++ b/README.md @@ -111,7 +111,7 @@ Links 2. Import `BugSplatApiClient` and `VersionsApiClient` from @bugsplat/symbol-upload. Alternatively, you can import `OAuthClientCredentialsClient` if you'd prefer to authenticate with an [OAuth2 Client Credentials](https://docs.bugsplat.com/introduction/development/web-services/oauth2#client-credentials) Client ID and Client Secret. ```ts -import { BugSplatApiClient, OAuthClientCredentialsClient, VersionsApiClient } from '@bugsplat/symbol-upload'; +import { BugSplatApiClient, OAuthClientCredentialsClient, uploadSymbolFiles } from '@bugsplat/symbol-upload'; ``` 3. Create a new instance of `BugSplatApiClient` using the `createAuthenticatedClientForNode` async factory function or `OAuthClientCredentialsClient` using the `createAuthenticatedClient` async factory function. @@ -124,41 +124,16 @@ const bugsplat = await BugSplatApiClient.createAuthenticatedClientForNode(email, const bugsplat = await OAuthClientCredentialsClient.createAuthenticatedClient(clientId, clientSecret); ``` -4. Create an `UploadableFile` object for each symbol file path. +4. Upload your symbol files to bugsplat by calling the `uploadSymbolFiles` function. ```ts -const files = paths.map(path => { - const stat = fs.statSync(path); - const size = stat.size; - const name = basename(path); - const file = fs.createReadStream(path); - return { - name, - size, - file - }; -}); +const directory = '/path/to/symbols/dir'; +const files = '**/*.+(exe|dll|pdb)'; +await uploadSymbolFiles(bugsplat, database, application, version, directory, files); ``` -5. Create an instance of `VersionsApiClient` passing it an instance of `BugSplatApiClient`. +If you've done everything correctly, your symbols should be shown by clicking the application link on the [Versions](https://app.bugsplat.com/v2/versions) page. -```ts -const versionsApiClient = new VersionsApiClient(bugsplat); -``` - -6. Await the call to `postSymbols` passing it the name of your BugSplat `database`, `application`, `version` and an array of `files`. These values need to match the values you used to initialize BugSplat on whichever [platform](https://docs.bugsplat.com/introduction/getting-started/integrations) you've integrated with. - -```ts -await versionsApiClient.postSymbols( - database, - application, - version, - files -); -``` - -If you've done everything correctly your symbols should now be shown on the [Versions](https://app.bugsplat.com/v2/versions) page. - -![Versions](https://bugsplat-public.s3.amazonaws.com/npm/symbol-upload/versions.png) +image Thanks for using BugSplat! diff --git a/bin/index.ts b/bin/index.ts index 4bf9dc5..797c47c 100644 --- a/bin/index.ts +++ b/bin/index.ts @@ -1,20 +1,10 @@ #! /usr/bin/env node -import { ApiClient, BugSplatApiClient, OAuthClientCredentialsClient, SymbolsApiClient, VersionsApiClient } from '@bugsplat/js-api-client'; +import { ApiClient, BugSplatApiClient, OAuthClientCredentialsClient, VersionsApiClient } from '@bugsplat/js-api-client'; import commandLineArgs, { CommandLineOptions } from 'command-line-args'; import commandLineUsage from 'command-line-usage'; -import { glob } from 'glob'; -import { existsSync } from 'node:fs'; -import { mkdir, readFile, stat } from 'node:fs/promises'; -import { basename, dirname, extname, join, relative } from 'node:path'; -import { pool } from 'workerpool'; +import { readFile, stat } from 'node:fs/promises'; +import { uploadSymbolFiles } from '../src/upload'; import { CommandLineDefinition, argDefinitions, usageDefinitions } from './command-line-definitions'; -import { SymbolFileInfo } from './info'; -import { tryGetPdbGuid, tryGetPeGuid } from './pdb'; -import { getSymFileInfo } from './sym'; -import { safeRemoveTmp, tmpDir } from './tmp'; -import { createWorkersFromSymbolFiles } from './worker'; - -const workerPool = pool(join(__dirname, 'compression.js')); (async () => { let { @@ -75,11 +65,10 @@ const workerPool = pool(join(__dirname, 'compression.js')); console.log('Authentication success!'); - const versionsApiClient = new VersionsApiClient(bugsplat); - const symbolsApiClient = new SymbolsApiClient(bugsplat); - if (remove) { try { + const versionsApiClient = new VersionsApiClient(bugsplat); + console.log(`About to delete symbols for ${database}-${application}-${version}...`); await versionsApiClient.deleteSymbols( @@ -98,75 +87,15 @@ const workerPool = pool(join(__dirname, 'compression.js')); } directory = normalizeDirectory(directory); - const globPattern = `${directory}/${files}`; - - let returnCode = 0; - try { - const symbolFilePaths = await glob(globPattern); - - if (!symbolFilePaths.length) { - throw new Error(`Could not find any files to upload using glob ${globPattern}!`); - } - - console.log(`Found files:\n ${symbolFilePaths.join('\n')}`); - console.log(`About to upload symbols for ${database}-${application}-${version}...`); - - if (!existsSync(tmpDir)) { - await mkdir(tmpDir); - } - const symbolFiles = await Promise.all(symbolFilePaths.map(async (symbolFilePath) => await createSymbolFileInfo(directory, symbolFilePath))); - const workers = createWorkersFromSymbolFiles(workerPool, symbolFiles, [symbolsApiClient, versionsApiClient]); - const uploads = workers.map((worker) => worker.upload(database, application, version)); - await Promise.all(uploads); - - console.log('Symbols uploaded successfully!'); - } catch (error) { - console.error(error); - returnCode = 1; - } finally { - await safeRemoveTmp(); - } + await uploadSymbolFiles(bugsplat, database, application, version, directory, files); - process.exit(returnCode); + process.exit(0); })().catch((error) => { console.error(error.message); process.exit(1); }); -async function createSymbolFileInfo(searchDirectory: string, symbolFilePath: string): Promise { - const path = symbolFilePath; - const relativePath = relative(searchDirectory, dirname(path)); - const extLowerCase = extname(path).toLowerCase(); - const isSymFile = extLowerCase.includes('.sym'); - const isPdbFile = extLowerCase.includes('.pdb'); - const isPeFile = extLowerCase.includes('.exe') || extLowerCase.includes('.dll'); - - let dbgId = ''; - let moduleName = ''; - - if (isPdbFile) { - dbgId = await tryGetPdbGuid(path); - } - - if (isPeFile) { - dbgId = await tryGetPeGuid(path); - } - - if (isSymFile) { - ({ dbgId, moduleName } = await getSymFileInfo(path)); - } - - moduleName = moduleName || basename(path); - - return { - path, - dbgId, - moduleName, - relativePath - } as SymbolFileInfo; -} - async function createBugSplatClient({ user, password, diff --git a/index.ts b/index.ts index 88ac6c3..ca715a4 100644 --- a/index.ts +++ b/index.ts @@ -1,8 +1,11 @@ export { + ApiClient, BugSplatApiClient, VersionsApiClient, SymbolsApiClient, OAuthClientCredentialsClient, UploadableFile, GZippedSymbolFile -} from '@bugsplat/js-api-client'; \ No newline at end of file +} from '@bugsplat/js-api-client'; + +export { uploadSymbolFiles } from './src/upload'; \ No newline at end of file diff --git a/spec/compression.spec.ts b/spec/compression.spec.ts index 907221a..98120df 100644 --- a/spec/compression.spec.ts +++ b/spec/compression.spec.ts @@ -6,7 +6,7 @@ import { join } from 'node:path'; import workerpool from 'workerpool'; import extract from 'extract-zip'; import { cwd } from 'node:process'; -const pool = workerpool.pool(join(__dirname, '../bin/compression.js')); +const pool = workerpool.pool(join(__dirname, '../src/compression.js')); describe('gzip', () => { describe('createGzipFile', () => { diff --git a/spec/pdb.spec.ts b/spec/pdb.spec.ts index 424e12b..2f7f395 100644 --- a/spec/pdb.spec.ts +++ b/spec/pdb.spec.ts @@ -1,4 +1,4 @@ -import { tryGetPdbGuid, tryGetPeGuid } from '../bin/pdb'; +import { tryGetPdbGuid, tryGetPeGuid } from '../src/pdb'; describe('pdb', () => { describe('tryGetPdbGuid', () => { diff --git a/spec/sym.spec.ts b/spec/sym.spec.ts index 4dac062..1126ced 100644 --- a/spec/sym.spec.ts +++ b/spec/sym.spec.ts @@ -1,4 +1,4 @@ -import { getSymFileInfo } from '../bin/sym'; +import { getSymFileInfo } from '../src/sym'; describe('getSymFileInfo', () => { it('should get debug id for file with a 33 character debug id', async () => { diff --git a/spec/tmp.spec.ts b/spec/tmp.spec.ts index 02a0857..7dc324d 100644 --- a/spec/tmp.spec.ts +++ b/spec/tmp.spec.ts @@ -1,4 +1,4 @@ -import { safeRemoveTmp } from '../bin/tmp'; +import { safeRemoveTmp } from '../src/tmp'; describe('tmp', () => { it('should retry removing tmp directory', async () => { diff --git a/spec/worker.spec.ts b/spec/worker.spec.ts index e1b0d3a..f14d907 100644 --- a/spec/worker.spec.ts +++ b/spec/worker.spec.ts @@ -1,7 +1,7 @@ import { SymbolsApiClient, VersionsApiClient } from '@bugsplat/js-api-client'; import retryPromise from 'promise-retry'; -import { SymbolFileInfo } from '../bin/info'; -import { UploadWorker, createWorkersFromSymbolFiles } from '../bin/worker'; +import { SymbolFileInfo } from '../src/info'; +import { UploadWorker, createWorkersFromSymbolFiles } from '../src/worker'; import { cpus } from 'node:os'; import { WorkerPool } from 'workerpool'; import { basename } from 'node:path'; diff --git a/bin/compression.js b/src/compression.js similarity index 100% rename from bin/compression.js rename to src/compression.js diff --git a/bin/info.ts b/src/info.ts similarity index 100% rename from bin/info.ts rename to src/info.ts diff --git a/bin/pdb.ts b/src/pdb.ts similarity index 100% rename from bin/pdb.ts rename to src/pdb.ts diff --git a/bin/sym.ts b/src/sym.ts similarity index 100% rename from bin/sym.ts rename to src/sym.ts diff --git a/bin/tmp.ts b/src/tmp.ts similarity index 100% rename from bin/tmp.ts rename to src/tmp.ts diff --git a/src/upload.ts b/src/upload.ts new file mode 100644 index 0000000..a6a599a --- /dev/null +++ b/src/upload.ts @@ -0,0 +1,76 @@ +import { ApiClient, SymbolsApiClient, VersionsApiClient } from "@bugsplat/js-api-client"; +import { existsSync } from "node:fs"; +import { mkdir } from "node:fs/promises"; +import { basename, dirname, extname, join, relative } from "node:path"; +import { pool } from "workerpool"; +import { SymbolFileInfo } from './info'; +import { tryGetPdbGuid, tryGetPeGuid } from './pdb'; +import { getSymFileInfo } from './sym'; +import { safeRemoveTmp, tmpDir } from './tmp'; +import { createWorkersFromSymbolFiles } from './worker'; +import { glob } from "glob"; + +const workerPool = pool(join(__dirname, 'compression.js')); + +export async function uploadSymbolFiles(bugsplat: ApiClient, database: string, application: string, version: string, directory: string, filesGlob: string) { + try { + const globPattern = `${directory}/${filesGlob}`; + + const symbolFilePaths = await glob(globPattern); + + if (!symbolFilePaths.length) { + throw new Error(`Could not find any files to upload using glob ${globPattern}!`); + } + + console.log(`Found files:\n ${symbolFilePaths.join('\n')}`); + console.log(`About to upload symbols for ${database}-${application}-${version}...`); + + if (!existsSync(tmpDir)) { + await mkdir(tmpDir); + } + + const symbolsApiClient = new SymbolsApiClient(bugsplat); + const versionsApiClient = new VersionsApiClient(bugsplat); + const symbolFiles = await Promise.all(symbolFilePaths.map(async (symbolFilePath) => await createSymbolFileInfo(directory, symbolFilePath))); + const workers = createWorkersFromSymbolFiles(workerPool, symbolFiles, [symbolsApiClient, versionsApiClient]); + const uploads = workers.map((worker) => worker.upload(database, application, version)); + await Promise.all(uploads); + + console.log('Symbols uploaded successfully!'); + } finally { + await safeRemoveTmp(); + } +} + +async function createSymbolFileInfo(searchDirectory: string, symbolFilePath: string): Promise { + const path = symbolFilePath; + const relativePath = relative(searchDirectory, dirname(path)); + const extLowerCase = extname(path).toLowerCase(); + const isSymFile = extLowerCase.includes('.sym'); + const isPdbFile = extLowerCase.includes('.pdb'); + const isPeFile = extLowerCase.includes('.exe') || extLowerCase.includes('.dll'); + + let dbgId = ''; + let moduleName = ''; + + if (isPdbFile) { + dbgId = await tryGetPdbGuid(path); + } + + if (isPeFile) { + dbgId = await tryGetPeGuid(path); + } + + if (isSymFile) { + ({ dbgId, moduleName } = await getSymFileInfo(path)); + } + + moduleName = moduleName || basename(path); + + return { + path, + dbgId, + moduleName, + relativePath + } as SymbolFileInfo; +} \ No newline at end of file diff --git a/bin/worker.ts b/src/worker.ts similarity index 100% rename from bin/worker.ts rename to src/worker.ts diff --git a/tsconfig.json b/tsconfig.json index 60af9dc..105d7d8 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -15,6 +15,6 @@ "files": [ "./index.ts", "./bin/index.ts", - "./bin/compression.js" + "src/compression.js" ] } \ No newline at end of file