Skip to content

Commit e8811cd

Browse files
authored
Cli improvements (#105)
* add gitlab CI CD file * rename android uuid field to uniqueId * made all other changes * addressed some comments * renamed sub-command to subcommand * add usage block to android command * lint fix * addressed comments * moved logger debug option upwards
1 parent 1c0fcc7 commit e8811cd

8 files changed

+125
-59
lines changed

src/commands/android.ts

+85-36
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import {
2121
hasValidExtension,
2222
isValidAppId,
2323
isValidVersionCode,
24-
isValidUUID
24+
isValidUniqueId
2525
} from '../utils/inputValidations';
2626
import { UserFriendlyError } from '../utils/userFriendlyErrors';
2727
import { createLogger, LogLevel } from '../utils/logger';
@@ -31,14 +31,14 @@ import { createSpinner } from '../utils/spinner';
3131

3232
export const androidCommand = new Command('android');
3333

34-
const generateURL = (type: 'upload' | 'list', realm: string, appId: string, versionCode?: string, uuid?: string): string => {
34+
const generateURL = (type: 'upload' | 'list', realm: string, appId: string, versionCode?: string, uniqueId?: string): string => {
3535
const baseUrl = `https://api.${realm}.signalfx.com/v2/rum-mfm/proguard`;
3636

3737
if (type === 'upload') {
3838
if (!versionCode) throw new Error('Version code is required for uploading.');
3939
let uploadUrl = `${baseUrl}/${appId}/${versionCode}`;
40-
if (uuid) {
41-
uploadUrl += `/${uuid}`;
40+
if (uniqueId) {
41+
uploadUrl += `/${uniqueId}`;
4242
}
4343
return uploadUrl;
4444
}
@@ -55,14 +55,14 @@ const androidUploadDescription =
5555
`
5656
This command uploads the provided mapping.txt file.
5757
You need to provide the Application ID and version code of the app, and the path to the mapping file.
58-
Optionally, you can also include a UUID to identify the upload session.
58+
Optionally, you can also include a unique ID to identify the different pre-production app builds.
5959
`;
6060

6161
const androidUploadWithManifestDescription =
6262
`
6363
This command uploads the provided file using the packaged AndroidManifest.xml provided.
6464
You need to provide the path to the mapping file, and the path to the AndroidManifest.xml file.
65-
The application ID, version code, and optional UUID will be extracted from the manifest file.
65+
The application ID, version code, and optional unique ID will be extracted from the manifest file.
6666
This command is recommended if you want to automate the upload process without manually specifying the application details.
6767
`;
6868

@@ -75,7 +75,7 @@ interface UploadAndroidOptions {
7575
'file': string,
7676
'appId': string,
7777
'versionCode': string,
78-
'uuid': string,
78+
'uniqueId': string,
7979
'debug'?: boolean
8080
'token': string,
8181
'realm': string,
@@ -91,68 +91,92 @@ interface UploadAndroidWithManifestOptions {
9191
'dryRun'?: boolean
9292
}
9393

94+
const helpDescription = `Upload and list zipped or unzipped Proguard/R8 mapping.txt files
95+
96+
For each respective command listed below under 'Commands', please run 'o11y-dem-cli android <command> --help' for an overview of its usage and options
97+
`;
98+
99+
androidCommand
100+
.description(helpDescription)
101+
.usage('[command] [options]');
102+
94103
androidCommand
95104
.command('upload')
96105
.showHelpAfterError(true)
97-
.usage('--app-id <value> --version-code <int> --file <path> [--uuid <value>]')
106+
.usage('--app-id <value> --version-code <int> --file <path> [--uniqueId <value>]')
98107
.description(androidUploadDescription)
99-
.summary(`Uploads the Android mapping.txt file with the provided application ID, version code, and optional UUID`)
108+
.summary(`Uploads the Android mapping.txt file with the provided application ID, version code, and optional unique ID`)
100109
.requiredOption('--app-id <value>', 'Application ID')
101110
.requiredOption('--version-code <int>', 'Version code')
102111
.requiredOption('--file <path>', 'Path to the mapping file')
103112
.requiredOption('--realm <value>',
104113
'Realm for your organization (example: us0). Can also be set using the environment variable O11Y_REALM',
105114
process.env.O11Y_REALM
106115
)
107-
.requiredOption(
116+
.option(
108117
'--token <value>',
109-
'API access token. Can also be set using the environment variable O11Y_TOKEN',
110-
process.env.O11Y_TOKEN
118+
'API access token. Can also be set using the environment variable O11Y_TOKEN'
111119
)
112-
.option('--uuid <value>', 'Optional UUID for the upload')
120+
.option('--uniqueId <value>', 'Optional unique ID for the upload')
113121
.option( '--dry-run', 'Preview the file that will be uploaded')
114122
.option('--debug', 'Enable debug logs')
115123
.action(async (options: UploadAndroidOptions) => {
124+
const token = options.token || process.env.O11Y_TOKEN;
125+
if (!token) {
126+
androidCommand.error('Error: API access token is required. Please pass it into the command as the --token option, or set using the environment variable O11Y_TOKEN');
127+
} else {
128+
options.token = token;
129+
}
130+
131+
if (!options.realm || options.realm.trim() === '') {
132+
androidCommand.error('Error: Realm is required and cannot be empty. Please pass it into the command as the --realm option, or set using the environment variable O11Y_REALM');
133+
}
134+
116135
const logger = createLogger(options.debug ? LogLevel.DEBUG : LogLevel.INFO);
117136

137+
logger.debug(`Validating App ID: ${options.appId}`);
118138
if (!isValidAppId(options.appId)) {
119139
throw new UserFriendlyError(null, 'Invalid Application ID. It must be a non-empty string.');
120140
}
121141

142+
logger.debug(`Validating Version Code: ${options.versionCode}`);
122143
if (!isValidVersionCode(options.versionCode)) {
123144
throw new UserFriendlyError(null, 'Invalid Version Code. It must be an integer.');
124145
}
125146

147+
logger.debug(`Validating Mapping File Path: ${options.file}`);
126148
if (!isValidFile(options.file)) {
127149
throw new UserFriendlyError(null, `Invalid mapping file path: ${options.file}.`);
128150
}
129151

152+
logger.debug(`Validating Mapping File Extension`);
130153
if (!hasValidExtension(options.file, '.txt', '.gz')) {
131154
throw new UserFriendlyError(null, `Mapping file does not have correct extension: ${options.file}.`);
132155
}
133156

134-
if (options.uuid && !isValidUUID(options.uuid)) {
135-
throw new UserFriendlyError(null, 'Error: Invalid UUID. It must be a non-empty string.');
157+
logger.debug(`Validating optional Unique ID: ${options.uniqueId}`);
158+
if (options.uniqueId && !isValidUniqueId(options.uniqueId)) {
159+
throw new UserFriendlyError(null, 'Error: Invalid uniqueId. It must be a non-empty string.');
136160
}
137161

138162
logger.info(`Preparing to upload Android mapping file:
139163
File: ${options.file}
140164
App ID: ${options.appId}
141165
Version Code: ${options.versionCode}
142-
UUID: ${options.uuid || 'Not provided'}`);
166+
Unique ID: ${options.uniqueId || 'Not provided'}`);
143167

144168
if (options.dryRun) {
145169
logger.info('Dry Run complete - No file will be uploaded.');
146170
return;
147171
}
148172

173+
const url = generateURL('upload', options.realm, options.appId, options.versionCode, options.uniqueId);
174+
logger.debug(`URL Endpoint: ${url}`);
175+
149176
const spinner = createSpinner();
150177
spinner.start(`Uploading Android mapping file: ${options.file}`);
151178

152-
const url = generateURL('upload', options.realm, options.appId, options.versionCode, options.uuid);
153-
154179
try {
155-
logger.debug('Uploading %s', options.file);
156180
await uploadFileAndroid({
157181
url: url,
158182
file: { filePath: options.file, fieldName: 'file' },
@@ -197,53 +221,69 @@ androidCommand
197221
'Realm for your organization (example: us0). Can also be set using the environment variable O11Y_REALM',
198222
process.env.O11Y_REALM
199223
)
200-
.requiredOption(
224+
.option(
201225
'--token <value>',
202-
'API access token. Can also be set using the environment variable O11Y_TOKEN',
203-
process.env.O11Y_TOKEN
226+
'API access token. Can also be set using the environment variable O11Y_TOKEN'
204227
)
205228
.option('--dry-run', 'Preview the file that will be uploaded and the parameters extracted from the AndroidManifest.xml file')
206229
.option('--debug', 'Enable debug logs')
207230
.action(async (options: UploadAndroidWithManifestOptions) => {
231+
const token = options.token || process.env.O11Y_TOKEN;
232+
if (!token) {
233+
androidCommand.error('Error: API access token is required. Please pass it into the command as the --token option, or set using the environment variable O11Y_TOKEN');
234+
} else {
235+
options.token = token;
236+
}
237+
238+
if (!options.realm || options.realm.trim() === '') {
239+
androidCommand.error('Error: Realm is required and cannot be empty. Please pass it into the command as the --realm option, or set using the environment variable O11Y_REALM');
240+
}
241+
208242
const logger = createLogger(options.debug ? LogLevel.DEBUG : LogLevel.INFO);
209243

210244
try {
245+
logger.debug(`Validating Mapping File Path: ${options.file}`);
211246
if (!isValidFile(options.file)) {
212247
throw new UserFriendlyError(null, `Invalid mapping file path: ${options.file}.`);
213248
}
214249

215-
if (!hasValidExtension(options.file, '.txt')) {
250+
logger.debug(`Validating Mapping File Extension`);
251+
if (!hasValidExtension(options.file, '.txt', '.gz')) {
216252
throw new UserFriendlyError(null, `Mapping file does not have correct extension: ${options.file}.`);
217253
}
218254

255+
logger.debug(`Validating Manifest File Path: ${options.manifest}`);
219256
if (!isValidFile(options.manifest)) {
220-
throw new UserFriendlyError(null, `Invalid manifest file path: ${options.file}.`);
257+
throw new UserFriendlyError(null, `Invalid manifest file path: ${options.manifest}.`);
221258
}
222259

260+
logger.debug(`Validating Mapping File Extension`);
223261
if (!hasValidExtension(options.manifest, '.xml')) {
224262
throw new UserFriendlyError(null, `Manifest file does not have correct extension: ${options.manifest}.`);
225263
}
226264

227265
logger.info(`Preparing to extract parameters from ${options.manifest}`);
266+
const { package: appId, versionCode, uniqueId } = await extractManifestData(options.manifest);
228267

229-
const { package: appId, versionCode, uuid } = await extractManifestData(options.manifest);
230-
268+
logger.debug(`Validating App ID: ${appId}`);
231269
if (!isValidAppId(appId)) {
232270
throw new UserFriendlyError(null, 'Invalid Application ID extracted from the manifest.');
233271
}
234272

273+
logger.debug(`Validating Version Code: ${versionCode}`);
235274
if (!isValidVersionCode(versionCode)) {
236275
throw new UserFriendlyError(null, 'Invalid Version Code extracted from the manifest.');
237276
}
238277

239-
if (uuid && !isValidUUID(uuid)) {
240-
throw new UserFriendlyError(null, `Invalid UUID extracted from the manifest: ${uuid}.`);
278+
logger.debug(`Validating optional Unique ID: ${uniqueId}`);
279+
if (uniqueId && !isValidUniqueId(uniqueId)) {
280+
throw new UserFriendlyError(null, `Invalid uniqueId extracted from the manifest: ${uniqueId}.`);
241281
}
242282

243283
logger.info(`Preparing to upload Android mapping file:
244284
File: ${options.file}
245285
Extracted parameters from the AndroidManifest.xml:
246-
- UUID: ${uuid || 'Not provided'}
286+
- Unique ID: ${uniqueId || 'Not provided'}
247287
- App ID: ${appId}
248288
- Version Code: ${versionCode}`);
249289

@@ -252,11 +292,12 @@ androidCommand
252292
return;
253293
}
254294

295+
const url = generateURL('upload', options.realm, appId, versionCode as string, uniqueId as string);
296+
logger.debug(`URL Endpoint: ${url}`);
297+
255298
const spinner = createSpinner();
256299
spinner.start(`Uploading Android mapping file: ${options.file}`);
257-
258-
const url = generateURL('upload', options.realm, appId, versionCode as string, uuid as string);
259-
300+
260301
try {
261302
await uploadFileAndroid({
262303
url: url,
@@ -308,20 +349,28 @@ androidCommand
308349
'Realm for your organization (example: us0). Can also be set using the environment variable O11Y_REALM',
309350
process.env.O11Y_REALM
310351
)
311-
.requiredOption(
352+
.option(
312353
'--token <value>',
313-
'API access token. Can also be set using the environment variable O11Y_TOKEN',
314-
process.env.O11Y_TOKEN
354+
'API access token. Can also be set using the environment variable O11Y_TOKEN'
315355
)
316356
.showHelpAfterError(true)
317357
.description(listProguardDescription)
318358
.option('--debug',
319359
'Enable debug logs')
320360
.action(async (options) => {
361+
const token = options.token || process.env.O11Y_TOKEN;
362+
if (!token) {
363+
androidCommand.error('Error: API access token is required. Please pass it into the command as the --token option, or set using the environment variable O11Y_TOKEN');
364+
}
365+
366+
if (!options.realm || options.realm.trim() === '') {
367+
androidCommand.error('Error: Realm is required and cannot be empty. Please pass it into the command as the --realm option, or set using the environment variable O11Y_REALM');
368+
}
369+
321370
const logger = createLogger(options.debug ? LogLevel.DEBUG : LogLevel.INFO);
322371
const url = generateURL('list', options.realm, options.appId);
323-
const token = options.token;
324372
try {
373+
logger.debug(`URL Endpoint: ${url}`);
325374
const responseData = await fetchAndroidMappingMetadata({ url, token });
326375
logger.info('Uploaded mapping file metadata:', JSON.stringify(responseData, null, 2));
327376
} catch (error) {

src/commands/ios.ts

+7-1
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,11 @@ const listdSYMsDescription = `This subcommand retrieves and shows a list of the
5353
By default, it returns the last 100 dSYMs uploaded, sorted in reverse chronological order based on the upload timestamp.
5454
`;
5555

56+
const helpDescription = `Upload and list zipped iOS symbolication files (dSYMs)
57+
58+
For each respective command listed below under 'Commands', please run 'o11y-dem-cli ios <command> --help' for an overview of its usage and options
59+
`;
60+
5661
const generateUrl = ({
5762
urlPrefix,
5863
apiPath,
@@ -68,13 +73,14 @@ const generateUrl = ({
6873
};
6974

7075
iOSCommand
71-
.description('Upload and list iOS symbolication files (dSYMs)')
76+
.description(helpDescription)
7277
.addHelpText('after', `
7378
Examples:
7479
$ o11y-dem-cli ios upload --path /path/to/dSYMs --realm us0
7580
$ o11y-dem-cli ios list --realm us0
7681
`);
7782

83+
7884
iOSCommand
7985
.command('upload')
8086
.helpOption(false)

src/commands/sourcemaps.ts

+8
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,14 @@ to your environment, any reported stack traces will be automatically symbolicate
5757
uploaded source maps.
5858
`;
5959

60+
const helpDescription = `Prepares JavaScript files to support error symbolication and uploads JavaScript source maps
61+
62+
For each respective command listed below under 'Commands', please run 'o11y-dem-cli sourcemaps <command> --help' for an overview of its usage and options
63+
`;
64+
65+
sourcemapsCommand
66+
.description(helpDescription);
67+
6068
sourcemapsCommand
6169
.command('inject')
6270
.showHelpAfterError(true)

src/index.ts

+5-2
Original file line numberDiff line numberDiff line change
@@ -28,13 +28,16 @@ const program = new Command();
2828
const helpDescription =
2929
`A CLI tool for uploading and displaying of Android, iOS, and Browser symbolication files to and from Splunk O11y Cloud.
3030
31-
For each respective command listed below, please run 'o11y-dem-cli <command>' for an overview of available subcommands and options
31+
For each respective command listed below under 'Commands', please run 'o11y-dem-cli <command>' for an overview of available subcommands and options.
32+
33+
For subcommands like "upload" and "list" that make an API call, please ensure that the realm and token are either passed into the command as options, or set using the environment variables O11Y_REALM and O11Y_TOKEN.
3234
`;
3335

3436
program
3537
.version('1.0.0')
3638
.description(helpDescription)
37-
.usage('[ios|android|sourcemaps] [sub-command] [options]');
39+
.name('o11y-dem-cli')
40+
.usage('[command] [subcommand] [options]');
3841

3942
program.addCommand(iOSCommand);
4043
program.addCommand(androidCommand);

src/utils/androidManifestUtils.ts

+6-6
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ import { throwAsUserFriendlyErrnoException, UserFriendlyError } from './userFrie
2121
interface ManifestData {
2222
package: unknown;
2323
versionCode: unknown;
24-
uuid?: unknown;
24+
uniqueId?: unknown;
2525
}
2626

2727
export const extractManifestData = async (manifestPath: string): Promise<ManifestData> => {
@@ -31,12 +31,12 @@ export const extractManifestData = async (manifestPath: string): Promise<Manifes
3131

3232
const packageId = extractPackageId(result);
3333
const versionCode = extractVersionCode(result);
34-
const uuid = extractUuid(result);
34+
const uniqueId = extractUniqueId(result);
3535

3636
return {
3737
package: packageId,
3838
versionCode,
39-
uuid,
39+
uniqueId,
4040
};
4141
} catch (error: unknown) {
4242
const fileMessages = {
@@ -68,13 +68,13 @@ const extractVersionCode = (manifest: any): unknown => {
6868
};
6969

7070
/* eslint-disable */
71-
const extractUuid = (manifest: any): unknown => {
71+
const extractUniqueId = (manifest: any): unknown => {
7272
const metaData = manifest.manifest.application[0]['meta-data'];
7373
if (!metaData) return undefined;
7474

75-
const uuidMeta = metaData.find((meta: { $: { [key: string]: string } }) =>
75+
const uniqueIdMeta = metaData.find((meta: { $: { [key: string]: string } }) =>
7676
meta.$['android:name'] === 'SPLUNK_O11Y_CUSTOM_UUID'
7777
);
7878

79-
return uuidMeta ? uuidMeta.$['android:value'] : undefined;
79+
return uniqueIdMeta ? uniqueIdMeta.$['android:value'] : undefined;
8080
};

0 commit comments

Comments
 (0)