Skip to content
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

Adding initial support for project deploys #454

Merged
merged 12 commits into from
Apr 27, 2021
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ npm-debug.log.*
*.log
.DS_Store
coverage
.env
14 changes: 14 additions & 0 deletions packages/cli-lib/api/fileMapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -255,9 +255,23 @@ async function getDirectoryContentsByPath(portalId, path) {
});
}

/**
* Deploy project
*
* @async
* @param {string} projectPath
* @returns {Promise}
*/
async function deployProject(portalId, projectPath) {
miketalley marked this conversation as resolved.
Show resolved Hide resolved
return http.post(portalId, {
uri: `${FILE_MAPPER_API_PATH}/deploy/${encodeURIComponent(projectPath)}`,
});
}

module.exports = {
deleteFile,
deleteFolder,
deployProject,
download,
downloadDefault,
fetchFileStream,
Expand Down
35 changes: 24 additions & 11 deletions packages/cli-lib/fileMapper.js
Original file line number Diff line number Diff line change
Expand Up @@ -91,11 +91,18 @@ function useApiBuffer(mode) {
}

/**
* @param {Mode} mode
* Determines API param based on mode an options
*
* @typedef {Object} APIOptions
* @property {Mode} mode
* @property {Object} options
*/
function getFileMapperApiQueryFromMode(mode) {
function getFileMapperQueryValues({ mode, options = {} }) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

return {
buffer: useApiBuffer(mode),
qs: {
buffer: useApiBuffer(mode),
environmentId: options.staging ? 2 : 1,
},
};
}

Expand Down Expand Up @@ -267,9 +274,12 @@ async function fetchAndWriteFileStream(input, srcPath, filepath) {
const { accountId } = input;

try {
const node = await fetchFileStream(accountId, srcPath, filepath, {
qs: getFileMapperApiQueryFromMode(input.mode),
});
const node = await fetchFileStream(
accountId,
srcPath,
filepath,
getFileMapperQueryValues(input)
);
await writeUtimes(input, filepath, node);
} catch (err) {
logApiErrorInstance(
Expand Down Expand Up @@ -390,17 +400,20 @@ async function downloadFile(input) {
* @returns {Promise<FileMapperNode}
*/
async function fetchFolderFromApi(input) {
const { accountId, src, mode } = input;
const { accountId, src } = input;
const { isRoot, isFolder, isHubspot } = getTypeDataFromPath(src);
if (!isFolder) {
throw new Error(`Invalid request for folder: "${src}"`);
}
try {
const srcPath = isRoot ? '@root' : src;
const qs = getFileMapperApiQueryFromMode(mode);
const node = isHubspot
? await downloadDefault(accountId, srcPath, { qs })
: await download(accountId, srcPath, { qs });
? await downloadDefault(
accountId,
srcPath,
getFileMapperQueryValues(input)
)
: await download(accountId, srcPath, getFileMapperQueryValues(input));
logger.log(
'Fetched "%s" from account %d from the Design Manager successfully',
src,
Expand Down Expand Up @@ -492,7 +505,7 @@ module.exports = {
isPathToHubspot,
downloadFileOrFolder,
recurseFolder,
getFileMapperApiQueryFromMode,
getFileMapperQueryValues,
fetchFolderFromApi,
getTypeDataFromPath,
};
2 changes: 1 addition & 1 deletion packages/cli-lib/lib/__tests__/uploadFolder.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('uploadFolder', () => {

uploadedFilesInOrder.forEach((file, index) => {
expect(upload).nthCalledWith(index + 1, accountId, file, file, {
qs: { buffer: false },
qs: { buffer: false, environmentId: 1 },
miketalley marked this conversation as resolved.
Show resolved Hide resolved
});
});
});
Expand Down
9 changes: 4 additions & 5 deletions packages/cli-lib/lib/uploadFolder.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ const path = require('path');
const { default: PQueue } = require('p-queue');

const { logger } = require('../logger');
const { getFileMapperApiQueryFromMode } = require('../fileMapper');
const { getFileMapperQueryValues } = require('../fileMapper');
const { upload } = require('../api/fileMapper');
const { createIgnoreFilter } = require('../ignoreRules');
const { walk } = require('./walk');
Expand Down Expand Up @@ -54,11 +54,8 @@ function getFilesByType(files) {
* @param {string} dest
* @param {object} options
*/
async function uploadFolder(accountId, src, dest, { mode }) {
async function uploadFolder(accountId, src, dest, options) {
const regex = new RegExp(`^${escapeRegExp(src)}`);
const apiOptions = {
qs: getFileMapperApiQueryFromMode(mode),
};
const files = await walk(src);

const allowedFiles = files
Expand All @@ -71,12 +68,14 @@ async function uploadFolder(accountId, src, dest, { mode }) {
.filter(createIgnoreFilter());

const filesByType = getFilesByType(allowedFiles);
const apiOptions = getFileMapperQueryValues(options);

const failures = [];

const uploadFile = file => {
const relativePath = file.replace(regex, '');
const destPath = convertToUnixPath(path.join(dest, relativePath));

return async () => {
logger.debug('Attempting to upload file "%s" to "%s"', file, destPath);
try {
Expand Down
8 changes: 3 additions & 5 deletions packages/cli-lib/lib/watch.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ const {
} = require('../errorHandlers');
const { uploadFolder } = require('./uploadFolder');
const { shouldIgnoreFile, ignoreFile } = require('../ignoreRules');
const { getFileMapperApiQueryFromMode } = require('../fileMapper');
const { getFileMapperQueryValues } = require('../fileMapper');
const { upload, deleteFile } = require('../api/fileMapper');
const escapeRegExp = require('./escapeRegExp');
const { convertToUnixPath, isAllowedExtension } = require('../path');
Expand All @@ -21,7 +21,7 @@ const queue = new PQueue({
concurrency: 10,
});

function uploadFile(accountId, file, dest, { mode }) {
function uploadFile(accountId, file, dest, options) {
if (!isAllowedExtension(file)) {
logger.debug(`Skipping ${file} due to unsupported extension`);
return;
Expand All @@ -32,9 +32,7 @@ function uploadFile(accountId, file, dest, { mode }) {
}

logger.debug('Attempting to upload file "%s" to "%s"', file, dest);
const apiOptions = {
qs: getFileMapperApiQueryFromMode(mode),
};
const apiOptions = getFileMapperQueryValues(options);
return queue.add(() => {
return upload(accountId, file, dest, apiOptions)
.then(() => {
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ const functionsCommand = require('../commands/functions');
const listCommand = require('../commands/list');
const openCommand = require('../commands/open');
const mvCommand = require('../commands/mv');
const projectsCommand = require('../commands/projects');

const notifier = updateNotifier({ pkg: { ...pkg, name: '@hubspot/cli' } });

Expand Down Expand Up @@ -80,6 +81,7 @@ const argv = yargs
})
.command(openCommand)
.command(mvCommand)
.command(projectsCommand)
.help()
.recommendCommands()
.demandCommand(1, '')
Expand Down
9 changes: 9 additions & 0 deletions packages/cli/commands/fetch.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,5 +82,14 @@ exports.builder = yargs => {
type: 'string',
});

yargs.options({
miketalley marked this conversation as resolved.
Show resolved Hide resolved
staging: {
describe: 'Retrieve staged changes for project',
type: 'boolean',
default: false,
hidden: true,
},
});

return yargs;
};
19 changes: 19 additions & 0 deletions packages/cli/commands/projects.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const {
addConfigOptions,
addAccountOptions,
addOverwriteOptions,
} = require('../lib/commonOpts');
const deploy = require('./projects/deploy');

exports.command = 'projects';
exports.describe = false; //'Commands for working with projects';
miketalley marked this conversation as resolved.
Show resolved Hide resolved

exports.builder = yargs => {
addOverwriteOptions(yargs, true);
addConfigOptions(yargs, true);
addAccountOptions(yargs, true);

yargs.command(deploy).demandCommand(1, '');

return yargs;
};
90 changes: 90 additions & 0 deletions packages/cli/commands/projects/deploy.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
const {
addAccountOptions,
addConfigOptions,
setLogLevel,
getAccountId,
addUseEnvironmentOptions,
} = require('../../lib/commonOpts');
const { trackCommandUsage } = require('../../lib/usageTracking');
const { logDebugInfo } = require('../../lib/debugInfo');
const {
loadConfig,
validateConfig,
checkAndWarnGitInclusion,
} = require('@hubspot/cli-lib');
const {
logApiErrorInstance,
ApiErrorContext,
} = require('@hubspot/cli-lib/errorHandlers');
const { logger } = require('@hubspot/cli-lib/logger');
const { deployProject } = require('@hubspot/cli-lib/api/fileMapper');
const { validateAccount } = require('../../lib/validation');

const loadAndValidateOptions = async options => {
setLogLevel(options);
logDebugInfo(options);
const { config: configPath } = options;
loadConfig(configPath, options);
checkAndWarnGitInclusion();

if (!(validateConfig() && (await validateAccount(options)))) {
process.exit(1);
}
};

exports.command = 'deploy <path>';
exports.describe = false;

exports.handler = async options => {
loadAndValidateOptions(options);

const { path: projectPath } = options;
const accountId = getAccountId(options);

trackCommandUsage('projects-deploy', { projectPath }, accountId);

logger.debug(`Deploying project at path: ${projectPath}`);

try {
const deployResp = await deployProject(accountId, projectPath);

if (deployResp.error) {
logger.error(`Deploy error: ${deployResp.error.message}`);
return;
}

logger.success(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will we show the success message here even if there is an error?

`Deployed project in ${projectPath} on account ${accountId}.`
);
} catch (e) {
if (e.statusCode === 400) {
logger.error(e.error.message);
} else {
logApiErrorInstance(
accountId,
e,
new ApiErrorContext({ accountId, projectPath })
);
}
}
};

exports.builder = yargs => {
yargs.positional('path', {
describe: 'Path to a project folder',
type: 'string',
});

yargs.example([
[
'$0 projects deploy myProjectFolder',
'Deploy a project within the myProjectFolder folder',
],
]);

addConfigOptions(yargs, true);
addAccountOptions(yargs, true);
addUseEnvironmentOptions(yargs, true);

return yargs;
};
13 changes: 7 additions & 6 deletions packages/cli/commands/upload.js
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@ const {
validateConfig,
checkAndWarnGitInclusion,
} = require('@hubspot/cli-lib');
const {
getFileMapperApiQueryFromMode,
} = require('@hubspot/cli-lib/fileMapper');
const { getFileMapperQueryValues } = require('@hubspot/cli-lib/fileMapper');
const { upload } = require('@hubspot/cli-lib/api/fileMapper');
const {
getCwd,
Expand Down Expand Up @@ -104,9 +102,12 @@ exports.handler = async options => {
return;
}

upload(accountId, absoluteSrcPath, normalizedDest, {
qs: getFileMapperApiQueryFromMode(mode),
})
upload(
accountId,
absoluteSrcPath,
normalizedDest,
getFileMapperQueryValues({ mode, options })
)
.then(() => {
logger.success(
'Uploaded file from "%s" to "%s" in the Design Manager of account %s',
Expand Down