Skip to content

Commit

Permalink
fix: improve error messages when there's a generic google error (#95)
Browse files Browse the repository at this point in the history
  • Loading branch information
GabeDuarteM authored Jul 8, 2022
1 parent c8189a3 commit 5111d98
Show file tree
Hide file tree
Showing 6 changed files with 174 additions and 40 deletions.
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ This package export the following plugins:

Verify the presence of the authentication parameters, which are set via environment variables (see [Chrome webstore authentication][chrome-authentication]).

#### `verifyConditions` parameters

- `extensionId`: **REQUIRED** parameter. The `extension id` from the webstore. For example: If the url of your extension is [https://chrome.google.com/webstore/detail/webplayer-hotkeys-shortcu/ikmkicnmahfdilneilgibeppbnolgkaf](https://chrome.google.com/webstore/detail/webplayer-hotkeys-shortcu/ikmkicnmahfdilneilgibeppbnolgkaf), then the last portion, `ikmkicnmahfdilneilgibeppbnolgkaf`, will be the `extension id`. You can also take this ID on the [developers dashboard](https://chrome.google.com/webstore/developer/dashboard), under the name `Item ID` located inside the `More info` dialog. This is used so that we can confirm that the credentials are working for the extension you are trying to publish.

### `prepare`

Writes the correct version to the `manifest.json` and creates a `zip` file with everything inside the `dist` folder.
Expand All @@ -58,14 +62,13 @@ This plugin requires some parameters to be set, so be sure to check below and fi

Uploads the generated zip file to the webstore and publishes a new release.

Unfortunately, due to Google's restrictions, this plugin can only publish extensions that already exists on the store, so you will have to at least make a draft release for yourself, so the plugin can create a proper release for the first time. You can create a draft release with just a minimum `manifest.json` with version `0.0.1` compressed in a zip file.
If you decide to make the draft, make sure to fill all the required fields on the drafts page, otherwise the publish will fail with a `400` status code (Bad request).

#### `publish` parameters

- `extensionId`: **REQUIRED** parameter. The `extension id` from the webstore. For example: If the url of your extension is [https://chrome.google.com/webstore/detail/webplayer-hotkeys-shortcu/ikmkicnmahfdilneilgibeppbnolgkaf](https://chrome.google.com/webstore/detail/webplayer-hotkeys-shortcu/ikmkicnmahfdilneilgibeppbnolgkaf), then the last portion, `ikmkicnmahfdilneilgibeppbnolgkaf`, will be the `extension id`. You can also take this ID on the [developers dashboard](https://chrome.google.com/webstore/developer/dashboard), under the name `Item ID` located inside the `More info` dialog.

Unfortunately, due to Google's restrictions, this plugin can only publish extensions that already exists on the store, so you will have to at least make a draft release for yourself, so the plugin can create a proper release for the first time. You can create a draft release with just a minimum `manifest.json` with version `0.0.1` compressed in a zip file.

If you decide to make the draft, make sure to fill all the required fields on the drafts page, otherwise the publishing will fail with a `400` status code (Bad request).

- `asset`: **REQUIRED** parameter. The zip file that will be published to the chrome webstore.

- `target`: Valid options are:
Expand All @@ -85,7 +88,13 @@ A basic configuration file example is available below:

```json
{
"verifyConditions": ["semantic-release-chrome", "@semantic-release/github"],
"verifyConditions": [
{
"path": "semantic-release-chrome",
"extensionId": "mppjhhbajcciljocgbadbhbgphjfdmhj"
},
"@semantic-release/github"
],
"prepare": [
{
"path": "semantic-release-chrome",
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
},
"dependencies": {
"@semantic-release/error": "3.0.0",
"aggregate-error": "4.0.1",
"archiver": "5.3.1",
"chrome-webstore-upload": "1.0.0",
"fs-extra": "10.1.0"
Expand Down
14 changes: 14 additions & 0 deletions src/getEsModule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
const modulesCache: { [keyof: string]: any } = {}

async function getEsModule(moduleName: string) {
if (modulesCache[moduleName]) {
return modulesCache[moduleName]
}

const module = (await import(moduleName)).default
modulesCache[moduleName] = module

return module
}

export default getEsModule
88 changes: 65 additions & 23 deletions src/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,22 +3,10 @@ import { createReadStream } from 'fs-extra'
import { Context } from 'semantic-release'

import type PluginConfig from './@types/pluginConfig'
import getEsModule from './getEsModule'

const errorWhitelist = ['PUBLISHED_WITH_FRICTION_WARNING']

const modulesCache: { [keyof: string]: any } = {}

const getEsModule = async (module: string) => {
if (modulesCache[module]) {
return modulesCache[module]
}

const esModule = await import(module)
modulesCache[module] = esModule.default || esModule

return modulesCache[module]
}

const publish = async (
{ extensionId, target, asset }: PluginConfig,
{ logger }: Context,
Expand All @@ -43,49 +31,103 @@ const publish = async (
)
}

const webStore = await (
await getEsModule('chrome-webstore-upload')
)({
const chromeWebstoreUpload = (await getEsModule(
'chrome-webstore-upload',
)) as typeof import('chrome-webstore-upload')['default']

const webStore = (await chromeWebstoreUpload({
clientId,
clientSecret,
extensionId,
refreshToken,
})
})) as typeof import('chrome-webstore-upload')['default']

logger.log('Creating zip file...')

const zipFile = createReadStream(asset)
const uploadRes = await webStore.uploadExisting(zipFile)
const errorMessage = `
[ERROR] Semantic Release Chrome
Unfortunately we can't tell for sure what's the reason for that, but usually this happens when there's something wrong or missing with the configs.
Make sure to check:
* The extensionId is correctly set on the plugin config
* The environment variables GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, and GOOGLE_REFRESH_TOKEN are correctly set (Double check the authentication guide: https://github.com/GabrielDuarteM/semantic-release-chrome/blob/master/Authentication.md)
* Go to https://chrome.google.com/webstore/devconsole, click on the extension you are publishing, and verify that there's no errors there (for example, on the "Why can't I submit" button modal, or on any of the other tabs there)
`
let uploadRes

logger.log('Uploading zip file to Google Web Store...')
try {
uploadRes = await webStore.uploadExisting(zipFile)
} catch (err) {
throw new SemanticReleaseError(
`Error uploading extension to Google Web Store. ${errorMessage} Error details:\n\n${err}`,
err as string,
)
}

const AggregateError = (await getEsModule(
'aggregate-error',
)) as typeof import('aggregate-error')['default']

if (uploadRes.uploadState === 'FAILURE') {
if (uploadRes?.uploadState === 'FAILURE') {
const errors: SemanticReleaseError[] = []

uploadRes.itemError.forEach((err: any) => {
const semanticError = new SemanticReleaseError(
err.error_detail,
err.error_code,
)

errors.push(semanticError)
})
throw new AggregateError(errors).errors

throw new AggregateError(errors)
}

logger.log(`Successfully uploaded extension to Google Web Store`)

if (target !== 'draft') {
const publishRes = await webStore.publish(target || 'default')
logger.log('Publishing extension to Google Web Store...')

if (!publishRes.status.includes('OK')) {
let publishRes

try {
publishRes = await webStore.publish(target || 'default')
} catch (err) {
throw new SemanticReleaseError(
err as string,
`Error publishing extension to Google Web Store. ${errorMessage} Error details:\n\n`,
)
}

if (!publishRes?.status.includes('OK')) {
const errors: SemanticReleaseError[] = []

for (let i = 0; i < publishRes.status.length; i += 1) {
const code = publishRes.status[i]
const message = publishRes.statusDetail[i]

if (errorWhitelist.includes(code)) {
logger.log(`${code}: ${message}`)
} else {
const err = new SemanticReleaseError(message, code)

errors.push(err)
}
}

if (errors.length > 0) {
throw new AggregateError(errors).errors
throw new AggregateError(errors)
}
}

logger.log(`Successfully published extension to Google Web Store`)
} else {
logger.log(`Target option is set to "draft", skipping publish step`)
}

return {
Expand Down
67 changes: 55 additions & 12 deletions src/verifyConditions.ts
Original file line number Diff line number Diff line change
@@ -1,35 +1,78 @@
import SemanticReleaseError from '@semantic-release/error'
import { Context } from 'semantic-release'
import PluginConfig from './@types/pluginConfig'
import getEsModule from './getEsModule'

const configMessage = 'Check the README.md for config info.'

const createErrorPATH = (param: string, code: string) =>
const createErrorEnvFile = (param: string, code: string) =>
new SemanticReleaseError(
`No ${param} specified inside PATH. ${configMessage}`,
`Environment variable not found: ${param}. ${configMessage}`,
code,
)

const verifyConditions = () => {
const {
GOOGLE_CLIENT_ID,
GOOGLE_CLIENT_SECRET,
GOOGLE_REFRESH_TOKEN,
} = process.env
const verifyConditions = async (
{ extensionId }: PluginConfig,
{ logger }: Context,
) => {
const { GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET, GOOGLE_REFRESH_TOKEN } =
process.env
const errors: Error[] = []

if (!GOOGLE_CLIENT_ID) {
errors.push(createErrorPATH('GOOGLE_CLIENT_ID', 'EGOOGLECLIENTID'))
errors.push(createErrorEnvFile('GOOGLE_CLIENT_ID', 'EGOOGLECLIENTID'))
}

if (!GOOGLE_CLIENT_SECRET) {
errors.push(createErrorPATH('GOOGLE_CLIENT_SECRET', 'EGOOGLECLIENTSECRET'))
errors.push(
createErrorEnvFile('GOOGLE_CLIENT_SECRET', 'EGOOGLECLIENTSECRET'),
)
}

if (!GOOGLE_REFRESH_TOKEN) {
errors.push(createErrorPATH('GOOGLE_REFRESH_TOKEN', 'EGOOGLEREFRESHTOKEN'))
errors.push(
createErrorEnvFile('GOOGLE_REFRESH_TOKEN', 'EGOOGLEREFRESHTOKEN'),
)
}

if (!extensionId) {
errors.push(
new SemanticReleaseError(
"Option 'extensionId' was not included in the verifyConditions config. Check the README.md for config info.",
'ENOEXTENSIONID',
),
)
}

if (errors.length > 0) {
throw new AggregateError(errors).errors
const AggregateError = (await getEsModule(
'aggregate-error',
)) as typeof import('aggregate-error')['default']

throw new AggregateError(errors)
}

const chromeWebstoreUpload = (await getEsModule(
'chrome-webstore-upload',
)) as typeof import('chrome-webstore-upload')['default']

const webStore = await chromeWebstoreUpload({
extensionId,
clientId: GOOGLE_CLIENT_ID,
clientSecret: GOOGLE_CLIENT_SECRET,
refreshToken: GOOGLE_REFRESH_TOKEN,
})

let mainErrorMsg: string | undefined

try {
logger.log('Verifying chrome webstore credentials...')
await webStore.get()
logger.log('Chrome webstore credentials seem to be valid.')
} catch (e) {
mainErrorMsg =
'\n[semantic-release-chrome] Could not connect to Chrome Web Store with the provided credentials. Please check if they are correct.'
throw new Error(mainErrorMsg)
}
}

Expand Down
25 changes: 25 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -1624,6 +1624,14 @@ agentkeepalive@^4.2.1:
depd "^1.1.2"
humanize-ms "^1.2.1"

[email protected]:
version "4.0.1"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-4.0.1.tgz#25091fe1573b9e0be892aeda15c7c66a545f758e"
integrity sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==
dependencies:
clean-stack "^4.0.0"
indent-string "^5.0.0"

aggregate-error@^3.0.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/aggregate-error/-/aggregate-error-3.1.0.tgz#92670ff50f5359bdb7a3e0d40d0ec30c5737687a"
Expand Down Expand Up @@ -2035,6 +2043,13 @@ clean-stack@^2.0.0:
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==

clean-stack@^4.0.0:
version "4.2.0"
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-4.2.0.tgz#c464e4cde4ac789f4e0735c5d75beb49d7b30b31"
integrity sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==
dependencies:
escape-string-regexp "5.0.0"

cli-columns@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/cli-columns/-/cli-columns-4.0.0.tgz#9fe4d65975238d55218c41bd2ed296a7fa555646"
Expand Down Expand Up @@ -2456,6 +2471,11 @@ escalade@^3.1.1:
resolved "https://registry.yarnpkg.com/escalade/-/escalade-3.1.1.tgz#d8cfdc7000965c5a0174b4a82eaa5c0552742e40"
integrity sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==

[email protected]:
version "5.0.0"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz#4683126b500b61762f2dbebace1806e8be31b1c8"
integrity sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==

escape-string-regexp@^1.0.5:
version "1.0.5"
resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz#1b61c0562190a8dff6ae3bb2cf0200ca130b86d4"
Expand Down Expand Up @@ -3067,6 +3087,11 @@ indent-string@^4.0.0:
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-4.0.0.tgz#624f8f4497d619b2d9768531d58f4122854d7251"
integrity sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==

indent-string@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/indent-string/-/indent-string-5.0.0.tgz#4fd2980fccaf8622d14c64d694f4cf33c81951a5"
integrity sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==

infer-owner@^1.0.4:
version "1.0.4"
resolved "https://registry.yarnpkg.com/infer-owner/-/infer-owner-1.0.4.tgz#c4cefcaa8e51051c2a40ba2ce8a3d27295af9467"
Expand Down

0 comments on commit 5111d98

Please sign in to comment.