-
Notifications
You must be signed in to change notification settings - Fork 64
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
Project support #537
Project support #537
Changes from 14 commits
4ae328b
29dc4ce
120f367
8174b49
6221f91
fc6528c
d84f71d
818cc2f
c3c2c34
54f86eb
adccfd1
3e73a0c
e10a664
9fb79c9
a4f1ae0
d232b57
8a0a006
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,85 @@ | ||
const http = require('../http'); | ||
const fs = require('fs'); | ||
|
||
const DEVELOPER_FILE_SYSTEM_API_PATH = 'dfs/v1/projects'; | ||
|
||
/** | ||
* Fetch projects | ||
* | ||
* @async | ||
* @returns {Promise} | ||
*/ | ||
async function fetchProjects(portalId) { | ||
return http.get(portalId, { | ||
uri: DEVELOPER_FILE_SYSTEM_API_PATH, | ||
}); | ||
} | ||
// TODO: paging? | ||
|
||
/** | ||
* Create project | ||
* | ||
* @async | ||
* @param {string} name | ||
* @returns {Promise} | ||
*/ | ||
async function createProject(portalId, name) { | ||
return http.post(portalId, { | ||
uri: DEVELOPER_FILE_SYSTEM_API_PATH, | ||
body: { | ||
name, | ||
}, | ||
}); | ||
} | ||
|
||
/** | ||
* Upload project | ||
* | ||
* @async | ||
* @param {string} projectName | ||
* @param {string} projectFile | ||
* @returns {Promise} | ||
*/ | ||
async function uploadProject(accountId, projectName, projectFile) { | ||
return http.post(accountId, { | ||
uri: `${DEVELOPER_FILE_SYSTEM_API_PATH}/upload/${projectName}`, | ||
timeout: 60000, | ||
formData: { | ||
file: fs.createReadStream(projectFile), | ||
}, | ||
}); | ||
} | ||
|
||
/** | ||
* Fetch project | ||
* | ||
* @async | ||
* @param {string} name | ||
* @returns {Promise} | ||
*/ | ||
async function fetchProject(portalId, name) { | ||
return http.get(portalId, { | ||
uri: `${DEVELOPER_FILE_SYSTEM_API_PATH}/${name}`, | ||
}); | ||
} | ||
|
||
/** | ||
* Delete project | ||
* | ||
* @async | ||
* @param {string} name | ||
* @returns {Promise} | ||
*/ | ||
async function deleteProject(portalId, name) { | ||
return http.delete(portalId, { | ||
uri: `${DEVELOPER_FILE_SYSTEM_API_PATH}/${name}`, | ||
}); | ||
} | ||
|
||
module.exports = { | ||
fetchProjects, | ||
createProject, | ||
uploadProject, | ||
fetchProject, | ||
deleteProject, | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,16 +4,21 @@ const { | |
addOverwriteOptions, | ||
} = require('../lib/commonOpts'); | ||
const deploy = require('./projects/deploy'); | ||
const init = require('./projects/init'); | ||
const upload = require('./projects/upload'); | ||
|
||
exports.command = 'projects'; | ||
exports.command = 'project'; | ||
exports.describe = false; //'Commands for working with projects'; | ||
|
||
exports.builder = yargs => { | ||
addOverwriteOptions(yargs, true); | ||
addConfigOptions(yargs, true); | ||
addAccountOptions(yargs, true); | ||
|
||
// TODO: deploy must be updated | ||
yargs.command(deploy).demandCommand(1, ''); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not currently used in the flow. Will need to be adjusted eventually, but will mostly stay the same so keeping this for now. |
||
yargs.command(init).demandCommand(0, ''); | ||
yargs.command(upload).demandCommand(0, ''); | ||
|
||
return yargs; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,118 @@ | ||
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 { createProject } = require('@hubspot/cli-lib/api/dfs'); | ||
const { validateAccount } = require('../../lib/validation'); | ||
const { getCwd } = require('../../../cli-lib/path'); | ||
const path = require('path'); | ||
const { getOrCreateProjectConfig } = require('../../lib/projects'); | ||
|
||
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 = 'init [path]'; | ||
exports.describe = false; | ||
|
||
exports.handler = async options => { | ||
loadAndValidateOptions(options); | ||
|
||
const { path: projectPath } = options; | ||
const accountId = getAccountId(options); | ||
|
||
// TODO: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we enable this now? |
||
// trackCommandUsage('projects-init', { projectPath }, accountId); | ||
|
||
const cwd = projectPath ? path.resolve(getCwd(), projectPath) : getCwd(); | ||
const projectConfig = await getOrCreateProjectConfig(cwd); | ||
|
||
logger.log(`Initializing project: ${projectConfig.name}`); | ||
|
||
try { | ||
await createProject(accountId, projectConfig.name); | ||
|
||
logger.success( | ||
`"${projectConfig.name}" creation succeeded in account ${accountId}` | ||
); | ||
} catch (e) { | ||
if (e.statusCode === 409) { | ||
logger.log( | ||
`Project ${projectConfig.name} already exists in ${accountId}.` | ||
); | ||
} else { | ||
return logApiErrorInstance( | ||
accountId, | ||
e, | ||
new ApiErrorContext({ accountId, projectPath }) | ||
); | ||
} | ||
} | ||
logger.log(''); | ||
logger.log('> Welcome to HubSpot Developer Projects!'); | ||
logger.log('-------------------------------------------------------------'); | ||
logger.log('Getting Started'); | ||
logger.log('1. hs project upload'); | ||
logger.log( | ||
' Upload your project files to HubSpot. Upload action adds your files to a build.' | ||
); | ||
logger.log(); | ||
logger.log('2. View your changes on the preview build url'); | ||
logger.log(); | ||
logger.log('Use `hs project --help` to learn more about the command.'); | ||
logger.log('-------------------------------------------------------------'); | ||
}; | ||
Comment on lines
+73
to
+86
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
|
||
exports.builder = yargs => { | ||
yargs.positional('path', { | ||
describe: 'Path to a project folder', | ||
type: 'string', | ||
}); | ||
// TODO: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I was previously accounting for these to be passed as options. Not exactly sure how that should work with the new config file. Thinking of keeping these and getting some UX feedback. |
||
yargs.options({ | ||
name: { | ||
describe: 'Project name (cannot be changed)', | ||
type: 'string', | ||
}, | ||
srcDir: { | ||
describe: 'Directory of project', | ||
type: 'string', | ||
}, | ||
}); | ||
|
||
yargs.example([ | ||
[ | ||
'$0 project init myProjectFolder', | ||
'Initialize a project within the myProjectFolder folder', | ||
], | ||
]); | ||
|
||
addConfigOptions(yargs, true); | ||
addAccountOptions(yargs, true); | ||
addUseEnvironmentOptions(yargs, true); | ||
|
||
return yargs; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
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 { uploadProject } = require('@hubspot/cli-lib/api/dfs'); | ||
const { validateAccount } = require('../../lib/validation'); | ||
const fs = require('fs'); | ||
const { getCwd } = require('../../../cli-lib/path'); | ||
const path = require('path'); | ||
const archiver = require('archiver'); | ||
const tmp = require('tmp'); | ||
const { | ||
getProjectConfig, | ||
validateProjectConfig, | ||
} = require('../../lib/projects'); | ||
const { shouldIgnoreFile } = require('@hubspot/cli-lib/ignoreRules'); | ||
|
||
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 = 'upload [path]'; | ||
exports.describe = false; | ||
|
||
const uploadProjectFiles = async (accountId, projectName, filePath) => { | ||
logger.log(`Uploading project '${projectName}'...`); | ||
try { | ||
const upload = await uploadProject(accountId, projectName, filePath); | ||
logger.log(`Project uploaded and build #${upload.buildId} created`); | ||
} catch (err) { | ||
if (err.statusCode === 404) { | ||
return logger.error( | ||
`Project '${projectName}' does not exist. Try running 'hs project init' first.` | ||
); | ||
} | ||
logApiErrorInstance(err, { | ||
context: new ApiErrorContext({ | ||
accountId, | ||
projectName, | ||
}), | ||
}); | ||
} | ||
}; | ||
|
||
exports.handler = async options => { | ||
loadAndValidateOptions(options); | ||
|
||
const { path: projectPath } = options; | ||
const accountId = getAccountId(options); | ||
|
||
// TODO: | ||
// trackCommandUsage('projects-upload', { projectPath }, accountId); | ||
|
||
const cwd = projectPath ? path.resolve(getCwd(), projectPath) : getCwd(); | ||
const projectConfig = await getProjectConfig(cwd); | ||
|
||
validateProjectConfig(projectConfig); | ||
|
||
const tempFile = tmp.fileSync({ postfix: '.zip' }); | ||
|
||
logger.log(`Compressing build files to '${tempFile.name}'`); | ||
|
||
const output = fs.createWriteStream(tempFile.name); | ||
const archive = archiver('zip'); | ||
|
||
output.on('close', async function() { | ||
logger.log(`Project files compressed: ${archive.pointer()} bytes`); | ||
|
||
await uploadProjectFiles(accountId, projectConfig.name, tempFile.name); | ||
|
||
try { | ||
tempFile.removeCallback(); | ||
logger.debug(`Cleaned up temporary file ${tempFile.name}`); | ||
} catch (e) { | ||
logger.error(e); | ||
} | ||
}); | ||
|
||
archive.on('error', function(err) { | ||
throw err; | ||
}); | ||
|
||
archive.pipe(output); | ||
|
||
archive.directory(path.resolve(cwd, projectConfig.srcDir), false, file => | ||
shouldIgnoreFile(file.name) ? false : file | ||
); | ||
|
||
archive.finalize(); | ||
}; | ||
|
||
exports.builder = yargs => { | ||
yargs.positional('path', { | ||
describe: 'Path to a project folder', | ||
type: 'string', | ||
}); | ||
|
||
yargs.example([['$0 project upload myProjectFolder', 'Upload a project']]); | ||
|
||
addConfigOptions(yargs, true); | ||
addAccountOptions(yargs, true); | ||
addUseEnvironmentOptions(yargs, true); | ||
|
||
return yargs; | ||
}; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Did we want to add paging, or is this a reminder for later?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We'll need to add paging but I'll remove the TODO for now