Skip to content

Commit

Permalink
refact: implement logger
Browse files Browse the repository at this point in the history
The `@actions/core` outputs JSON lines. We don't need this format on
development. Implement a logger that uses `@actions/core` on GitHub
workflow, and stderr on local.
  • Loading branch information
wdzeng committed Aug 16, 2024
1 parent ad7a03e commit b14b2d9
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 31 deletions.
17 changes: 8 additions & 9 deletions src/error.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import * as core from '@actions/core'
import { AxiosError } from 'axios'
import { CustomError } from 'ts-custom-error'

import { stringify } from '@/utils'
import { logger, stringify } from '@/utils'

export const ERR_XPI_VALIDATION_FAILED = 2
export const ERR_XPI_VALIDATION_TIMEOUT = 4
Expand Down Expand Up @@ -30,19 +29,19 @@ function getStringOrError(e: unknown): string | Error {

export function handleError(error: unknown): never {
if (error instanceof FirefoxAddonActionError) {
core.setFailed(error.message)
logger.setFailed(error.message)
process.exit(error.code)
}

// HTTP error. This may be a bug on server side.
if (error instanceof AxiosError) {
if (error.response) {
// Got response from FireFox API server with status code 4XX or 5XX.
core.setFailed(`Firefox API server responses with error code: ${error.response.status}`)
core.setFailed(getStringOrError(error.response.data))
logger.setFailed(`Firefox API server responses with error code: ${error.response.status}`)
logger.setFailed(getStringOrError(error.response.data))
} else {
// Incomplete HTTP request. This may be due to instable network environment.
core.setFailed(error.message)
logger.setFailed(error.message)
}
process.exit(ERR_UNKNOWN_HTTP)
}
Expand All @@ -52,11 +51,11 @@ export function handleError(error: unknown): never {
if (str_err.length > 256) {
str_err = `${str_err.slice(0, 256)} <truncated>`
}
core.debug(str_err)
logger.debug(str_err)
if (error instanceof Error) {
core.setFailed(`Unknown error occurred: ${error.message}`)
logger.setFailed(`Unknown error occurred: ${error.message}`)
} else {
core.setFailed('Unknown error occurred.')
logger.setFailed('Unknown error occurred.')
}
process.exit(ERR_UNKNOWN)
}
35 changes: 17 additions & 18 deletions src/lib.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
import { createReadStream } from 'node:fs'

import * as core from '@actions/core'
import AdmZip from 'adm-zip'
import axios from 'axios'
import jwt from 'jsonwebtoken'
Expand All @@ -13,11 +12,11 @@ import {
FirefoxAddonActionError,
convertErrorToString
} from '@/error'
import { stringify } from '@/utils'
import { logger, stringify } from '@/utils'

export function generateJwtToken(jwtIssuer: string, jwtSecret: string): string {
// https://addons-server.readthedocs.io/en/latest/topics/api/auth.html#create-a-jwt-for-each-request
core.info('Start to generate JWT token.')
logger.info('Start to generate JWT token.')
const issuedAt = Math.floor(Date.now() / 1000) // Remove milliseconds.
const payload = {
exp: issuedAt + 5 * 60, // Set expiration time to 5 minutes.
Expand All @@ -26,7 +25,7 @@ export function generateJwtToken(jwtIssuer: string, jwtSecret: string): string {
jti: Math.random().toString()
}
const jwtToken = jwt.sign(payload, jwtSecret, { algorithm: 'HS256' })
core.info('JWT token generated.')
logger.info('JWT token generated.')
return jwtToken
}

Expand All @@ -38,7 +37,7 @@ async function createVersion(
releaseNotes: Record<string, string> | undefined,
uploadUuid: string
) {
core.info('Start to create a version.')
logger.info('Start to create a version.')

// https://addons-server.readthedocs.io/en/latest/topics/api/addons.html#version-create
const url = `https://addons.mozilla.org/api/v5/addons/addon/${addonGuid}/versions/`
Expand All @@ -51,7 +50,7 @@ async function createVersion(
const headers = { Authorization: `jwt ${jwtToken}` }
await axios.post(url, body, { headers })

core.info('Version created.')
logger.info('Version created.')
}

async function createVersionSource(
Expand All @@ -61,7 +60,7 @@ async function createVersionSource(
sourceFilePath: string,
uploadUuid: string
) {
core.info('Start to create a version source.')
logger.info('Start to create a version source.')

// https://addons-server.readthedocs.io/en/latest/topics/api/addons.html#version-sources
const url = `https://addons.mozilla.org/api/v5/addons/addon/${addonGuid}/versions/`
Expand All @@ -77,7 +76,7 @@ async function createVersionSource(
}
await axios.post(url, formData, { headers })

core.info('Version source created.')
logger.info('Version source created.')
}

async function patchVersionSource(
Expand All @@ -87,7 +86,7 @@ async function patchVersionSource(
license: string | undefined,
sourceFilePath: string
) {
core.info('Start to patch a version source.')
logger.info('Start to patch a version source.')

// https://addons-server.readthedocs.io/en/latest/topics/api/addons.html#version-sources
const url = `https://addons.mozilla.org/api/v5/addons/addon/${addonGuid}/versions/${versionNumber}/`
Expand All @@ -102,7 +101,7 @@ async function patchVersionSource(
}
await axios.patch(url, formData, { headers })

core.info('Version source patched.')
logger.info('Version source patched.')
}

function getAddonVersionNumber(xpiFilePath: string): string {
Expand All @@ -111,7 +110,7 @@ function getAddonVersionNumber(xpiFilePath: string): string {
try {
manifest_content = zip.readAsText('manifest.json', 'utf8')
} catch (e: unknown) {
core.debug(convertErrorToString(e))
logger.debug(convertErrorToString(e))
throw new FirefoxAddonActionError(
'Error getting addon version because failed to read manifest.json.',
ERR_VERSION_NUMBER
Expand All @@ -127,7 +126,7 @@ function getAddonVersionNumber(xpiFilePath: string): string {
ok = false
}
if (!ok) {
core.debug(`manifest.json: ${JSON.stringify(manifest_json)}`)
logger.debug(`manifest.json: ${JSON.stringify(manifest_json)}`)
throw new FirefoxAddonActionError(
'Error getting addon version because failed to parse manifest.json. Is it a valid JSON file?',
ERR_VERSION_NUMBER
Expand All @@ -136,7 +135,7 @@ function getAddonVersionNumber(xpiFilePath: string): string {

const manifest = manifest_json as Record<string, unknown>
if (typeof manifest.version !== 'string' || !manifest.version) {
core.debug(`manifest.json: ${JSON.stringify(manifest_json)}`)
logger.debug(`manifest.json: ${JSON.stringify(manifest_json)}`)
throw new FirefoxAddonActionError(
'Error getting addon version. Does manifest.json have a valid version field?',
ERR_VERSION_NUMBER
Expand Down Expand Up @@ -179,10 +178,10 @@ async function waitUntilXpiValidated(uploadUuid: string, jwtToken: string): Prom
const headers = { Authorization: `jwt ${jwtToken}` }

while (Date.now() < endTime) {
core.info('xpi not yet validated. Wait 5 seconds.')
logger.info('xpi not yet validated. Wait 5 seconds.')
await new Promise(res => setTimeout(res, 5000))

core.info('Checking if xpi is validated.')
logger.info('Checking if xpi is validated.')
const response = await axios<UploadResponse>(url, { headers })

if (response.data.processed) {
Expand Down Expand Up @@ -210,7 +209,7 @@ export async function uploadXpi(
const url = 'https://addons.mozilla.org/api/v5/addons/upload/'

// Send upload request.
core.info('Start to upload xpi file to firefox addons server.')
logger.info('Start to upload xpi file to firefox addons server.')
const formData = new FormData()
formData.append('upload', createReadStream(xpiPath))
formData.append('channel', selfHosted ? 'unlisted' : 'listed')
Expand All @@ -219,12 +218,12 @@ export async function uploadXpi(
'Content-Type': 'multipart/form-data'
}
const response = await axios.post<UploadResponse>(url, formData, { headers })
core.info('xpi file uploaded.')
logger.info('xpi file uploaded.')

// Wait until xpi is validated.
const uploadUuid = response.data.uuid
await waitUntilXpiValidated(uploadUuid, jwtToken)

core.info('xpi processed.')
logger.info('xpi processed.')
return uploadUuid
}
35 changes: 35 additions & 0 deletions src/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import fs from 'node:fs'

import * as core from '@actions/core'

import { ERR_INVALID_INPUT, FirefoxAddonActionError } from './error'

export function stringify(e: unknown): string {
Expand Down Expand Up @@ -81,3 +83,36 @@ export function requireValidSourceFileExtensionName(path: string) {
)
}
}

export function isGitHubAction(): boolean {
// https://docs.github.com/en/actions/writing-workflows/choosing-what-your-workflow-does/store-information-in-variables#default-environment-variables
return process.env.GITHUB_ACTIONS === 'true'
}

export interface Logger {
setFailed(message: string | Error): void
error(message: string): void
warning(message: string): void
info(message: string): void
debug(message: string): void
}

class StderrLogger implements Logger {
setFailed(message: string | Error): void {
console.error(message)
}
error(message: string): void {
console.error(message)
}
warning(message: string): void {
console.error(message)
}
info(message: string): void {
console.error(message)
}
debug(message: string): void {
console.error(message)
}
}

export const logger: Logger = isGitHubAction() ? core : new StderrLogger()
5 changes: 1 addition & 4 deletions test/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import tmp from 'tmp'

import { handleError } from '@/error'
import { generateJwtToken, updateAddon, uploadXpi } from '@/lib'
import { isGitHubAction } from '@/utils'

const TEST_ADDON = `
UEsDBAoAAAAAACY8rlQAAAAAAAAAAAAAAAAQABwAY29udGVudF9zY3JpcHRzL1VUCQADuOp+Ytll
Expand Down Expand Up @@ -48,10 +49,6 @@ async function getAddonLastUpdate(addonGuid: string, jwtToken: string): Promise<
return new Date(lastUpdate).getTime()
}

function isGitHubAction(): boolean {
return Boolean(process.env.GITHUB_ACTIONS)
}

function isMainGitHubAction(): boolean {
return isGitHubAction() && process.env.GITHUB_REF === 'refs/heads/main'
}
Expand Down

0 comments on commit b14b2d9

Please sign in to comment.