-
Notifications
You must be signed in to change notification settings - Fork 1.2k
Add CLI commands for Crashlytics #9688
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
igor-makarov
wants to merge
8
commits into
firebase:main
Choose a base branch
from
igor-makarov:add-crashlytics-cli
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
8 commits
Select commit
Hold shift + click to select a range
5f4d8f5
Add CLI commands for Crashlytics
igor-makarov 0d26877
CR: Add validation for --error-type option
igor-makarov 72cacce
CR: Extract --app validation to shared requireAppId helper
igor-makarov e44f161
CR: Extract event table rendering to shared renderEventsTable helper
igor-makarov 53345c8
CR: Refactor report rendering with configuration-driven approach
igor-makarov 00a4878
CR: Rename --error-type to --issue-type for consistency
igor-makarov 7fa31e9
CR: Reorder functions in crashlytics/utils.ts
igor-makarov 849114c
CR: Fix --issue-type option to handle single values
igor-makarov File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,34 @@ | ||
| import * as clc from "colorette"; | ||
|
|
||
| import { Command } from "../command"; | ||
| import { FirebaseError } from "../error"; | ||
| import { Options } from "../options"; | ||
| import { logger } from "../logger"; | ||
| import { requireAuth } from "../requireAuth"; | ||
| import { batchGetEvents } from "../crashlytics/events"; | ||
| import { requireAppId, renderEventsTable } from "../crashlytics/utils"; | ||
|
|
||
| interface CommandOptions extends Options { | ||
| app?: string; | ||
| } | ||
|
|
||
| export const command = new Command("crashlytics:events:batchget <eventNames...>") | ||
| .description("get specific Crashlytics events by resource name") | ||
| .before(requireAuth) | ||
| .option("--app <appId>", "the app id of your Firebase app") | ||
| .action(async (eventNames: string[], options: CommandOptions) => { | ||
| const appId = requireAppId(options.app); | ||
| if (!eventNames || eventNames.length === 0) { | ||
| throw new FirebaseError("provide at least one event resource name"); | ||
| } | ||
|
|
||
| const result = await batchGetEvents(appId, eventNames); | ||
|
|
||
| if (!result.events || result.events.length === 0) { | ||
| logger.info(clc.bold("No events found.")); | ||
| } else { | ||
| renderEventsTable(result.events); | ||
| } | ||
|
|
||
| return result; | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,50 @@ | ||
| import * as clc from "colorette"; | ||
|
|
||
| import { Command } from "../command"; | ||
| import { FirebaseError } from "../error"; | ||
| import { Options } from "../options"; | ||
| import { logger } from "../logger"; | ||
| import { requireAuth } from "../requireAuth"; | ||
| import { listEvents } from "../crashlytics/events"; | ||
| import { EventFilter } from "../crashlytics/filters"; | ||
| import { requireAppId, renderEventsTable } from "../crashlytics/utils"; | ||
|
|
||
| interface CommandOptions extends Options { | ||
| app?: string; | ||
| issueId?: string; | ||
| issueVariantId?: string; | ||
| pageSize?: number; | ||
| } | ||
|
|
||
| export const command = new Command("crashlytics:events:list") | ||
| .description("list recent Crashlytics events for an issue") | ||
| .before(requireAuth) | ||
| .option("--app <appId>", "the app id of your Firebase app") | ||
| .option("--issue-id <issueId>", "filter by issue id") | ||
| .option("--issue-variant-id <variantId>", "filter by issue variant id") | ||
| .option("--page-size <number>", "number of events to return", 1) | ||
| .action(async (options: CommandOptions) => { | ||
| const appId = requireAppId(options.app); | ||
| if (!options.issueId && !options.issueVariantId) { | ||
| throw new FirebaseError("set --issue-id or --issue-variant-id to filter events"); | ||
| } | ||
|
|
||
| const filter: EventFilter = {}; | ||
| if (options.issueId) { | ||
| filter.issueId = options.issueId; | ||
| } | ||
| if (options.issueVariantId) { | ||
| filter.issueVariantId = options.issueVariantId; | ||
| } | ||
|
|
||
| const pageSize = options.pageSize ?? 1; | ||
| const result = await listEvents(appId, filter, pageSize); | ||
|
|
||
| if (!result.events || result.events.length === 0) { | ||
| logger.info(clc.bold("No events found.")); | ||
| } else { | ||
| renderEventsTable(result.events); | ||
| } | ||
|
|
||
| return result; | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,42 @@ | ||
| import * as Table from "cli-table3"; | ||
|
|
||
| import { Command } from "../command"; | ||
| import { Options } from "../options"; | ||
| import { logger } from "../logger"; | ||
| import { requireAuth } from "../requireAuth"; | ||
| import { getIssue } from "../crashlytics/issues"; | ||
| import { requireAppId } from "../crashlytics/utils"; | ||
|
|
||
| interface CommandOptions extends Options { | ||
| app?: string; | ||
| } | ||
|
|
||
| export const command = new Command("crashlytics:issues:get <issueId>") | ||
| .description("get details for a Crashlytics issue") | ||
| .before(requireAuth) | ||
| .option("--app <appId>", "the app id of your Firebase app") | ||
| .action(async (issueId: string, options: CommandOptions) => { | ||
| const appId = requireAppId(options.app); | ||
|
|
||
| const issue = await getIssue(appId, issueId); | ||
|
|
||
| // Display formatted output | ||
| const table = new Table(); | ||
| table.push( | ||
| { ID: issue.id || "-" }, | ||
| { Title: issue.title || "-" }, | ||
| { Subtitle: issue.subtitle || "-" }, | ||
| { Type: issue.errorType || "-" }, | ||
| { State: issue.state || "-" }, | ||
| { "First Seen": issue.firstSeenVersion || "-" }, | ||
| { "Last Seen": issue.lastSeenVersion || "-" }, | ||
| { Variants: issue.variants?.length?.toString() || "0" }, | ||
| ); | ||
| logger.info(table.toString()); | ||
|
|
||
| if (issue.uri) { | ||
| logger.info(`\nConsole: ${issue.uri}`); | ||
| } | ||
|
|
||
| return issue; | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,37 @@ | ||
| import { Command } from "../command"; | ||
| import { FirebaseError } from "../error"; | ||
| import { Options } from "../options"; | ||
| import * as utils from "../utils"; | ||
| import { requireAuth } from "../requireAuth"; | ||
| import { updateIssue } from "../crashlytics/issues"; | ||
| import { State } from "../crashlytics/types"; | ||
| import { requireAppId } from "../crashlytics/utils"; | ||
|
|
||
| interface CommandOptions extends Options { | ||
| app?: string; | ||
| state?: string; | ||
| } | ||
|
|
||
| export const command = new Command("crashlytics:issues:update <issueId>") | ||
| .description("update the state of a Crashlytics issue") | ||
| .before(requireAuth) | ||
| .option("--app <appId>", "the app id of your Firebase app") | ||
| .option("--state <state>", "the new state for the issue (OPEN or CLOSED)") | ||
| .action(async (issueId: string, options: CommandOptions) => { | ||
| const appId = requireAppId(options.app); | ||
| if (!options.state) { | ||
| throw new FirebaseError("set --state to OPEN or CLOSED"); | ||
| } | ||
|
|
||
| const stateUpper = options.state.toUpperCase(); | ||
| if (stateUpper !== "OPEN" && stateUpper !== "CLOSED") { | ||
| throw new FirebaseError("--state must be OPEN or CLOSED"); | ||
| } | ||
|
|
||
| const state = stateUpper as State; | ||
| const issue = await updateIssue(appId, issueId, state); | ||
|
|
||
| utils.logLabeledSuccess("crashlytics", `Issue ${issueId} is now ${String(issue.state)}`); | ||
|
|
||
| return issue; | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,30 @@ | ||
| import { Command } from "../command"; | ||
| import { FirebaseError } from "../error"; | ||
| import { Options } from "../options"; | ||
| import * as utils from "../utils"; | ||
| import { requireAuth } from "../requireAuth"; | ||
| import { createNote } from "../crashlytics/notes"; | ||
| import { requireAppId } from "../crashlytics/utils"; | ||
|
|
||
| interface CommandOptions extends Options { | ||
| app?: string; | ||
| note?: string; | ||
| } | ||
|
|
||
| export const command = new Command("crashlytics:notes:create <issueId>") | ||
| .description("add a note to a Crashlytics issue") | ||
| .before(requireAuth) | ||
| .option("--app <appId>", "the app id of your Firebase app") | ||
| .option("--note <text>", "the note text to add to the issue") | ||
| .action(async (issueId: string, options: CommandOptions) => { | ||
| const appId = requireAppId(options.app); | ||
| if (!options.note) { | ||
| throw new FirebaseError("set --note <text> to specify the note content"); | ||
| } | ||
|
|
||
| const note = await createNote(appId, issueId, options.note); | ||
|
|
||
| utils.logLabeledSuccess("crashlytics", `Created note on issue ${issueId}`); | ||
|
|
||
| return note; | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,21 @@ | ||
| import { Command } from "../command"; | ||
| import { Options } from "../options"; | ||
| import * as utils from "../utils"; | ||
| import { requireAuth } from "../requireAuth"; | ||
| import { deleteNote } from "../crashlytics/notes"; | ||
| import { requireAppId } from "../crashlytics/utils"; | ||
|
|
||
| interface CommandOptions extends Options { | ||
| app?: string; | ||
| } | ||
|
|
||
| export const command = new Command("crashlytics:notes:delete <issueId> <noteId>") | ||
| .description("delete a note from a Crashlytics issue") | ||
| .before(requireAuth) | ||
| .option("--app <appId>", "the app id of your Firebase app") | ||
| .action(async (issueId: string, noteId: string, options: CommandOptions) => { | ||
| const appId = requireAppId(options.app); | ||
|
|
||
| await deleteNote(appId, issueId, noteId); | ||
| utils.logLabeledSuccess("crashlytics", `Deleted note ${noteId} from issue ${issueId}`); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,48 @@ | ||
| import * as clc from "colorette"; | ||
| import * as Table from "cli-table3"; | ||
|
|
||
| import { Command } from "../command"; | ||
| import { Options } from "../options"; | ||
| import { logger } from "../logger"; | ||
| import { requireAuth } from "../requireAuth"; | ||
| import { listNotes } from "../crashlytics/notes"; | ||
| import { requireAppId } from "../crashlytics/utils"; | ||
|
|
||
| interface CommandOptions extends Options { | ||
| app?: string; | ||
| pageSize?: number; | ||
| } | ||
|
|
||
| export const command = new Command("crashlytics:notes:list <issueId>") | ||
| .description("list notes for a Crashlytics issue") | ||
| .before(requireAuth) | ||
| .option("--app <appId>", "the app id of your Firebase app") | ||
| .option("--page-size <number>", "number of notes to return", 20) | ||
| .action(async (issueId: string, options: CommandOptions) => { | ||
| const appId = requireAppId(options.app); | ||
|
|
||
| const pageSize = options.pageSize ?? 20; | ||
| const notes = await listNotes(appId, issueId, pageSize); | ||
|
|
||
| if (notes.length === 0) { | ||
| logger.info(clc.bold("No notes found.")); | ||
| } else { | ||
| const table = new Table({ | ||
| head: ["Author", "Created", "Note"], | ||
| style: { head: ["green"] }, | ||
| colWidths: [30, 25, 50], | ||
| wordWrap: true, | ||
| }); | ||
| for (const note of notes) { | ||
| table.push([ | ||
| note.author || "-", | ||
| note.createTime ? new Date(note.createTime).toLocaleString() : "-", | ||
| note.body || "-", | ||
| ]); | ||
| } | ||
| logger.info(table.toString()); | ||
| logger.info(`\n${notes.length} note(s).`); | ||
| } | ||
|
|
||
| return notes; | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.