Skip to content

Commit

Permalink
Merge pull request #1128 from HubSpot/br/403-scope-errors
Browse files Browse the repository at this point in the history
Supporting a custom override for missing scope errors
  • Loading branch information
brandenrodgers authored Sep 9, 2024
2 parents 7c18d28 + f76734d commit 4357d61
Show file tree
Hide file tree
Showing 15 changed files with 101 additions and 105 deletions.
2 changes: 1 addition & 1 deletion packages/cli/commands/project/__tests__/deploy.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,7 +423,7 @@ describe('commands/project/deploy', () => {

expect(logger.error).toHaveBeenCalledTimes(1);
expect(logger.error).toHaveBeenCalledWith(
`The request in account ${accountId} failed due to a client error.`
`The request for 'project deploy' in account ${accountId} failed due to a client error.`
);
expect(processExitSpy).toHaveBeenCalledTimes(1);
expect(processExitSpy).toHaveBeenCalledWith(EXIT_CODES.ERROR);
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/commands/project/deploy.js
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,10 @@ exports.handler = async options => {
} else if (e.response && e.response.status === 400) {
logger.error(e.message);
} else {
logApiErrorInstance(e, new ApiErrorContext({ accountId, projectName }));
logApiErrorInstance(
e,
new ApiErrorContext({ accountId, request: 'project deploy' })
);
}
return process.exit(EXIT_CODES.ERROR);
}
Expand Down
5 changes: 4 additions & 1 deletion packages/cli/commands/project/download.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,10 @@ exports.handler = async options => {
);
process.exit(EXIT_CODES.SUCCESS);
} catch (e) {
logApiErrorInstance(e, new ApiErrorContext({ accountId, projectName }));
logApiErrorInstance(
e,
new ApiErrorContext({ accountId, request: 'project download' })
);
process.exit(EXIT_CODES.ERROR);
}
};
Expand Down
8 changes: 5 additions & 3 deletions packages/cli/commands/project/upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ exports.handler = async options => {
result.uploadError,
new ApiErrorContext({
accountId,
projectName: projectConfig.name,
request: 'project upload',
})
);
}
Expand All @@ -102,8 +102,10 @@ exports.handler = async options => {
process.exit(EXIT_CODES.SUCCESS);
}
} catch (e) {
const projectName = projectConfig.name;
logApiErrorInstance(e, new ApiErrorContext({ accountId, projectName }));
logApiErrorInstance(
e,
new ApiErrorContext({ accountId, request: 'project upload' })
);
process.exit(EXIT_CODES.ERROR);
}
};
Expand Down
12 changes: 3 additions & 9 deletions packages/cli/commands/project/watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -71,10 +71,7 @@ const handleUserInput = (accountId, projectName, currentBuildId) => {
) {
process.exit(EXIT_CODES.SUCCESS);
} else {
logApiErrorInstance(
err,
new ApiErrorContext({ accountId, projectName: projectName })
);
logApiErrorInstance(err, new ApiErrorContext({ accountId }));
process.exit(EXIT_CODES.ERROR);
}
}
Expand Down Expand Up @@ -146,7 +143,7 @@ exports.handler = async options => {
result.uploadError,
new ApiErrorContext({
accountId,
projectName: projectConfig.name,
request: 'project upload',
})
);
}
Expand All @@ -156,10 +153,7 @@ exports.handler = async options => {
await startWatching();
}
} catch (e) {
logApiErrorInstance(
e,
new ApiErrorContext({ accountId, projectName: projectConfig.name })
);
logApiErrorInstance(e, new ApiErrorContext({ accountId }));
}
};

Expand Down
21 changes: 2 additions & 19 deletions packages/cli/lang/en.lyaml
Original file line number Diff line number Diff line change
Expand Up @@ -1409,8 +1409,7 @@ en:
successDevSbInfo: "Initiated sync of object definitions from production to {{ accountName }}. It may take some time. {{ url }}"
failure:
invalidUser: "Couldn't sync {{ accountName }} because your account has been removed from {{ parentAccountName }} or your permission set doesn't allow you to sync the sandbox. To update your permissions, contact a super admin in {{ parentAccountName }}."
missingScopes: "Couldn’t run the sync because there are scopes missing in your production account. To update scopes, deactivate your current personal access key for {{#bold}}{{ accountName }}{{/bold}}, and generate a new one. Then run `hs auth` to update the CLI with the new key."
syncInProgress: "Couldn’t run the sync because there’s another sync in progress. Wait for the current sync to finish and then try again. To check the sync status, visit the sync activity log: {{ url }}."
syncInProgress: "Couldn't run the sync because there's another sync in progress. Wait for the current sync to finish and then try again. To check the sync status, visit the sync activity log: {{ url }}."
notSuperAdmin: "Couldn't run the sync because you are not a super admin in {{ account }}. Ask the account owner for super admin access to the sandbox."
objectNotFound: "Couldn't sync the sandbox because {{#bold}}{{ account }}{{/bold}} may have been deleted through the UI. Run {{#bold}}hs sandbox delete{{/bold}} to remove this account from the config. "
errorHandlers:
Expand All @@ -1425,21 +1424,6 @@ en:
errorOccurred: "An error occurred while {{ fileAction }} {{ filepath }}."
errorExplanation: "This is the result of a system error: {{ errorMessage }}"
apiErrors:
messageDetail: "{{ request }} in account {{ accountId }}"
unableToUpload: 'Unable to upload "{{ payload }}.'
codes:
400: "The {{ messageDetail }} was bad."
401: "The {{ messageDetail }} was unauthorized."
403MissingScope: "Couldn't run the project command because there are scopes missing in your production account. To update scopes, deactivate your current personal access key for {{ accountId }}, and generate a new one. Then run `hs auth` to update the CLI with the new key."
403Gating: "The current target account {{ accountId }} does not have access to HubSpot projects. To opt in to the CRM Development Beta and use projects, visit https://app.hubspot.com/l/product-updates/in-beta?update=13899236."
403: "The {{ messageDetail }} was forbidden."
404Request: 'The {{ action }} failed because "{{ request }}" was not found in account {{ accountId }}.'
404: "The {{ messageDetail }} was not found."
429: "The {{ messageDetail }} surpassed the rate limit. Retry in one minute."
503: "The {{ messageDetail }} could not be handled at this time. Please try again or visit https://help.hubspot.com/ to submit a ticket or contact HubSpot Support if the issue persists."
500Generic: "The {{ messageDetail }} failed due to a server error. Please try again or visit https://help.hubspot.com/ to submit a ticket or contact HubSpot Support if the issue persists."
400Generic: "The {{ messageDetail }} failed due to a client error."
generic: "The {{ messageDetail }} failed."
verifyAccessKeyAndUserAccess:
fetchScopeDataError: "Error verifying access of scopeGroup {{ scopeGroup }}: {{ error }}"
portalMissingScope: "Your account does not have access to this action. Talk to an account admin to request it."
Expand All @@ -1454,5 +1438,4 @@ en:
updateProject: "Please update your project to the latest version and try again."
docsLink: "Projects platform versioning (BETA)"
betaLink: "For more info, see {{ docsLink }}."


missingScopeError: "Couldn't execute the {{ request }} because the access key for {{ accountName }} is missing required scopes. To update scopes, run {{ authCommand }}. Then deactivate the existing key and generate a new one that includes the missing scopes."
4 changes: 1 addition & 3 deletions packages/cli/lib/errorHandlers/apiErrors.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,6 @@ class ApiErrorContext extends ErrorContext {
this.request = props.request || '';
/** @type {string} */
this.payload = props.payload || '';
/** @type {string} */
this.projectName = props.projectName || '';
}
}

Expand All @@ -54,7 +52,7 @@ function logValidationErrors(error, context) {
*/
function logApiErrorInstance(error, context) {
if (error.isAxiosError) {
if (overrideErrors(error)) return;
if (overrideErrors(error, context)) return;
const errorWithContext = getAxiosErrorWithContext(error, context);
logger.error(errorWithContext.message);
return;
Expand Down
93 changes: 57 additions & 36 deletions packages/cli/lib/errorHandlers/overrideErrors.js
Original file line number Diff line number Diff line change
@@ -1,38 +1,43 @@
const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/apiErrors');
const {
isSpecifiedError,
isMissingScopeError,
} = require('@hubspot/local-dev-lib/errors/apiErrors');
const { logger } = require('@hubspot/local-dev-lib/logger');

const { PLATFORM_VERSION_ERROR_TYPES } = require('../constants');
const { i18n } = require('../lang');
const { uiLine, uiLink } = require('../ui');
const {
uiAccountDescription,
uiLine,
uiLink,
uiCommandReference,
} = require('../ui');

const i18nKey = 'lib.errorHandlers.overrideErrors';

function createPlatformVersionError(subCategory, errData) {
const docsLink = uiLink(
i18n(`${i18nKey}.platformVersionErrors.docsLink`),
'https://developers.hubspot.com/docs/platform/platform-versioning'
);

const platformVersionKey = {
[PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_NOT_SPECIFIED]:
'unspecified platformVersion',
[PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_RETIRED]:
errData.context.RETIRED_PLATFORM_VERSION,
[PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_SPECIFIED_DOES_NOT_EXIST]:
errData.context.PLATFORM_VERSION,
};
function createPlatformVersionError(err, subCategory) {
let translationKey = 'unspecifiedPlatformVersion';
let platformVersion = 'unspecified platformVersion';
const errorContext =
err.response && err.response.data && err.response.data.context;

const errorTypeToTranslationKey = {
[PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_NOT_SPECIFIED]:
'unspecifiedPlatformVersion',
[PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_RETIRED]:
'platformVersionRetired',
[PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_SPECIFIED_DOES_NOT_EXIST]:
'nonExistentPlatformVersion',
};

const platformVersion = platformVersionKey[subCategory] || '';
const translationKey = errorTypeToTranslationKey[subCategory];
switch (subCategory) {
case [PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_RETIRED]:
translationKey = 'platformVersionRetired';
if (errorContext && errorContext[subCategory]) {
platformVersion = errorContext[subCategory];
}
break;
case [
PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_SPECIFIED_DOES_NOT_EXIST,
]:
translationKey = 'nonExistentPlatformVersion';
if (errorContext && errorContext[subCategory]) {
platformVersion = errorContext[subCategory];
}
break;
default:
break;
}

uiLine();
logger.error(i18n(`${i18nKey}.platformVersionErrors.header`));
Expand All @@ -44,21 +49,37 @@ function createPlatformVersionError(subCategory, errData) {
logger.log(i18n(`${i18nKey}.platformVersionErrors.updateProject`));
logger.log(
i18n(`${i18nKey}.platformVersionErrors.betaLink`, {
docsLink,
docsLink: uiLink(
i18n(`${i18nKey}.platformVersionErrors.docsLink`),
'https://developers.hubspot.com/docs/platform/platform-versioning'
),
})
);
uiLine();
}

function overrideErrors(err) {
function overrideErrors(err, context) {
if (isMissingScopeError(err)) {
logger.error(
i18n(`${i18nKey}.missingScopeError`, {
accountName: context.accountId
? uiAccountDescription(context.accountId)
: '',
request: context.request || 'request',
authCommand: uiCommandReference('hs auth'),
})
);
return true;
}

if (
isSpecifiedError(err, {
subCategory: PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_NOT_SPECIFIED,
})
) {
createPlatformVersionError(
PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_NOT_SPECIFIED,
err.response.data
err,
PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_NOT_SPECIFIED
);
return true;
}
Expand All @@ -69,8 +90,8 @@ function overrideErrors(err) {
})
) {
createPlatformVersionError(
PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_RETIRED,
err.response.data
err,
PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_RETIRED
);
return true;
}
Expand All @@ -82,8 +103,8 @@ function overrideErrors(err) {
})
) {
createPlatformVersionError(
PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_SPECIFIED_DOES_NOT_EXIST,
err.response.data
err,
PLATFORM_VERSION_ERROR_TYPES.PLATFORM_VERSION_SPECIFIED_DOES_NOT_EXIST
);
return true;
}
Expand Down
7 changes: 2 additions & 5 deletions packages/cli/lib/projects.js
Original file line number Diff line number Diff line change
Expand Up @@ -287,10 +287,7 @@ const ensureProjectExists = async (
);
return { projectExists: true, project };
} catch (err) {
return logApiErrorInstance(
err,
new ApiErrorContext({ accountId, projectName })
);
return logApiErrorInstance(err, new ApiErrorContext({ accountId }));
}
} else {
if (!noLogs) {
Expand All @@ -312,7 +309,7 @@ const ensureProjectExists = async (
logger.error(err.message);
process.exit(EXIT_CODES.ERROR);
}
logApiErrorInstance(err, new ApiErrorContext({ accountId, projectName }));
logApiErrorInstance(err, new ApiErrorContext({ accountId }));
process.exit(EXIT_CODES.ERROR);
}
};
Expand Down
7 changes: 2 additions & 5 deletions packages/cli/lib/projectsWatch.js
Original file line number Diff line number Diff line change
Expand Up @@ -85,10 +85,7 @@ const debounceQueueBuild = (accountId, projectName, platformVersion) => {
logger.log(i18n(`${i18nKey}.logs.watchCancelledFromUi`));
process.exit(0);
} else {
logApiErrorInstance(
err,
new ApiErrorContext({ accountId, projectName })
);
logApiErrorInstance(err, new ApiErrorContext({ accountId }));
}

return;
Expand Down Expand Up @@ -158,7 +155,7 @@ const createNewBuild = async (accountId, projectName, platformVersion) => {
);
return buildId;
} catch (err) {
logApiErrorInstance(err, new ApiErrorContext({ accountId, projectName }));
logApiErrorInstance(err, new ApiErrorContext({ accountId }));
if (
isSpecifiedError(err, { subCategory: PROJECT_ERROR_TYPES.PROJECT_LOCKED })
) {
Expand Down
28 changes: 13 additions & 15 deletions packages/cli/lib/sandboxSync.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,12 @@ const { logger } = require('@hubspot/local-dev-lib/logger');
const { i18n } = require('./lang');
const { getAvailableSyncTypes } = require('./sandboxes');
const { initiateSync } = require('@hubspot/local-dev-lib/sandboxes');
const { debugErrorAndContext } = require('./errorHandlers/standardErrors');
const {
debugErrorAndContext,
logErrorInstance,
} = require('./errorHandlers/standardErrors');
const {
isSpecifiedError,
isMissingScopeError,
} = require('@hubspot/local-dev-lib/errors/apiErrors');
logApiErrorInstance,
ApiErrorContext,
} = require('./errorHandlers/apiErrors');
const { isSpecifiedError } = require('@hubspot/local-dev-lib/errors/apiErrors');
const { getSandboxTypeAsString } = require('./sandboxes');
const { getAccountId } = require('@hubspot/local-dev-lib/config');
const {
Expand Down Expand Up @@ -97,13 +95,7 @@ const syncSandbox = async ({
});

logger.log('');
if (isMissingScopeError(err)) {
logger.error(
i18n(`${i18nKey}.failure.missingScopes`, {
accountName: uiAccountDescription(parentAccountId),
})
);
} else if (
if (
isSpecifiedError(err, {
statusCode: 403,
category: 'BANNED',
Expand Down Expand Up @@ -163,7 +155,13 @@ const syncSandbox = async ({
'https://app.hubspot.com/l/docs/guides/crm/project-cli-commands#developer-projects-cli-commands-beta'
);
} else {
logErrorInstance(err);
logApiErrorInstance(
err,
new ApiErrorContext({
accountId: parentAccountId,
request: 'sandbox sync',
})
);
}
logger.log('');
throw err;
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
"url": "https://github.com/HubSpot/hubspot-cms-tools"
},
"dependencies": {
"@hubspot/local-dev-lib": "1.12.0",
"@hubspot/local-dev-lib": "1.13.0",
"@hubspot/serverless-dev-runtime": "5.3.0",
"@hubspot/theme-preview-dev-server": "0.0.7",
"@hubspot/ui-extensions-dev-server": "0.8.33",
Expand Down
2 changes: 1 addition & 1 deletion packages/serverless-dev-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
"repository": "https://github.com/HubSpot/hubspot-cli",
"license": "Apache-2.0",
"dependencies": {
"@hubspot/local-dev-lib": "1.12.0",
"@hubspot/local-dev-lib": "1.13.0",
"body-parser": "^1.19.0",
"chalk": "^4.1.0",
"chokidar": "^3.4.3",
Expand Down
2 changes: 1 addition & 1 deletion packages/webpack-cms-plugins/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
"test": "echo \"Error: run tests from root\" && exit 1"
},
"dependencies": {
"@hubspot/local-dev-lib": "1.12.0"
"@hubspot/local-dev-lib": "1.13.0"
},
"gitHead": "0659fd19cabc3645af431b177c11d0c1b089e0f8"
}
Loading

0 comments on commit 4357d61

Please sign in to comment.