resultify-hubspot-cms-windows

作成日 差分は期限切れになりません
60 削除
424
38 追加
412
/** @module hubspot/upload */
/** @module hubspot/upload */
/// <reference path="../types/types.js" />
/// <reference path="../types/types.js" />
import fsPromises from 'fs/promises'
import fsPromises from 'fs/promises'
import uploadFolder from '@hubspot/local-dev-lib/cms/uploadFolder'
import uploadFolder from '@hubspot/local-dev-lib/cms/uploadFolder'
import { deleteFile, getDirectoryContentsByPath } from '@hubspot/local-dev-lib/api/fileMapper'
import { deleteFile, getDirectoryContentsByPath } from '@hubspot/local-dev-lib/api/fileMapper'
import { setLogLevel, LOG_LEVEL } from '@hubspot/local-dev-lib/logger'
import { setLogLevel, LOG_LEVEL } from '@hubspot/local-dev-lib/logger'
import { isAllowedExtension } from '@hubspot/local-dev-lib/path'
import { isAllowedExtension } from '@hubspot/local-dev-lib/path'
import { createIgnoreFilter } from '@hubspot/local-dev-lib/ignoreRules'
import { createIgnoreFilter } from '@hubspot/local-dev-lib/ignoreRules'
import { walk } from '@hubspot/local-dev-lib/fs'
import { walk } from '@hubspot/local-dev-lib/fs'
import * as ui from '../utils/ui.js'
import * as ui from '../utils/ui.js'
import { getThemeOptions } from '../utils/options.js'
import { getThemeOptions } from '../utils/options.js'
import { throwErrorIfMissingScope } from './auth/scopes.js'
import { throwErrorIfMissingScope } from './auth/scopes.js'
import ora from 'ora'
import ora from 'ora'
import chalk from 'chalk'
import chalk from 'chalk'
import checkbox from '@inquirer/checkbox'
import checkbox from '@inquirer/checkbox'
import confirm from '@inquirer/confirm'
import confirm from '@inquirer/confirm'
import { getFileList, isFileDir } from '../utils/fs.js'
import { getFileList, isFileDir } from '../utils/fs.js'
import path from 'path'


/**
/**
* @type {"draft" | "publish"}
* @type {"draft" | "publish"}
*/
*/
let cmsMode = 'publish'
let cmsMode = 'publish'
if (process.env.HUB_MODE === 'draft') {
if (process.env.HUB_MODE === 'draft') {
cmsMode = 'draft'
cmsMode = 'draft'
}
}


/**
/**
* #### Walks the src folder for files, filters them based on ignore filter.
* #### Walks the src folder for files, filters them based on ignore filter.
* @async
* @async
* @private
* @private
* @param {string} src - src folder
* @param {string} src - src folder
* @returns {Promise<Array<string>>} src file list
* @returns {Promise<Array<string>>} src file list
*/
*/
const getUploadableFileList = async (src) => {
const getUploadableFileList = async (src) => {
/**
/**
* @type {Array<string>}
* @type {Array<string>}
*/
*/
let filePaths = []
let filePaths = []
try {
try {
filePaths = await walk(src)
filePaths = await walk(src)
} catch (error) {
} catch (error) {
console.error(error)
console.error(error)
}
}
const allowedFiles = filePaths.filter((/** @type {any} */ file) => {
const allowedFiles = filePaths.filter((/** @type {any} */ file) => {
if (!isAllowedExtension(file)) {
if (!isAllowedExtension(file)) {
return false
return false
}
}
return true
return true
// @ts-ignore
// @ts-ignore
}).filter(createIgnoreFilter())
}).filter(createIgnoreFilter())
return allowedFiles
return allowedFiles
}
}


/**
/**
* #### Reads the .cihsignore file and returns the list of files to ignore.
* #### Reads the .cihsignore file and returns the list of files to ignore.
* @private
* @private
* @async
* @async
* @returns {Promise<string[]>} List of files to ignore.
* @returns {Promise<string[]>} List of files to ignore.
*/
*/
async function readCihsIgnore() {
async function readCihsIgnore () {
return new Promise((resolve) => {
try {
const cihsignorePath = path.join(process.cwd(), '.cihsignore');
if (await isFileDir(`${process.cwd()}/.cihsignore`)) {
const ignoreContent = await fsPromises.readFile(`${process.cwd()}/.cihsignore`, 'utf8')
fsPromises.access(cihsignorePath)
return ignoreContent.split('\n').map(line => line.trim()).filter(line => line && !line.startsWith('#'))
.then(() => fsPromises.readFile(cihsignorePath, 'utf8'))
} else {
.then(ignoreContent => {
return []
const ignoredPaths = ignoreContent
}
.split(/\r?\n/)
} catch (error) {
.map(line => line.trim())
console.error('Error reading .cihsignore file:', error)
.filter(line => line && !line.startsWith('#'));
return []
resolve(ignoredPaths);
}
})
.catch(error => {
console.error('Error reading .cihsignore file:', error);
resolve([]);
});
});
}
}


/**
/**
* #### Walks the src folder for files, filters them based on ignore filter and CI .cihsignore file.
* #### Walks the src folder for files, filters them based on ignore filter and CI .cihsignore file.
* @async
* @async
* @private
* @private
* @param {string} src - src folder
* @param {string} src - src folder
* @returns {Promise<Array<string>>} src file list
* @returns {Promise<Array<string>>} src file list
*/
*/
const getCiUploadableFileList = async (src) => {
const getCiUploadableFileList = async (src) => {
/**
/**
* @type {Array<string>}
* @type {Array<string>}
*/
*/
let filePaths = []
let filePaths = []
try {
try {
filePaths = await walk(src)
filePaths = await walk(src)
} catch (error) {
} catch (error) {
console.error(error)
console.error(error)
}
}
let allowedFiles = filePaths.filter((/** @type {any} */ file) => {
let allowedFiles = filePaths.filter((/** @type {any} */ file) => {
if (!isAllowedExtension(file)) {
if (!isAllowedExtension(file)) {
return false
return false
}
}
return true
return true
// @ts-ignore
// @ts-ignore
}).filter(createIgnoreFilter())
}).filter(createIgnoreFilter())


async function filterFiles (/** @type {any} */ files) {
async function filterFiles (/** @type {any} */ files) {
const ignoredPaths = await readCihsIgnore()
const ignoredPaths = await readCihsIgnore()
return files.filter((/** @type {any} */ file) => !ignoredPaths.some(ignoredPath => file.includes(ignoredPath)))
return files.filter((/** @type {any} */ file) => !ignoredPaths.some(ignoredPath => file.includes(ignoredPath)))
}
}
allowedFiles = await filterFiles(allowedFiles)
allowedFiles = await filterFiles(allowedFiles)
return allowedFiles
return allowedFiles
}
}


/**
/**
* #### upload all HubSpot theme files
* #### upload all HubSpot theme files
* @async
* @async
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @param {string} themeName - theme name
* @param {string} themeName - theme name
* @returns undefined
* @returns undefined
*/
*/
async function uploadTheme (config, themeName) {
async function uploadTheme (config, themeName) {
try {
try {
const timeStart = ui.startTask('uploadTheme')
const timeStart = ui.startTask('uploadTheme')
const cmslibOptions = getThemeOptions()
const cmslibOptions = getThemeOptions()
const src = path.join(process.cwd(), cmslibOptions.themeFolder)
const src = `${process.cwd()}/${cmslibOptions.themeFolder}`
const dest = themeName
const dest = themeName
const portalId = config.portals[0].portalId
const portalId = config.portals[0].portalId


throwErrorIfMissingScope(config, 'design_manager')
throwErrorIfMissingScope(config, 'design_manager')
setLogLevel(LOG_LEVEL.LOG)
setLogLevel(LOG_LEVEL.LOG)
const uploadableFileList = await getUploadableFileList(src)
const uploadableFileList = await getUploadableFileList(src)
await uploadFolder.uploadFolder(
await uploadFolder.uploadFolder(
portalId,
portalId,
src,
src,
dest,
dest,
{ overwrite: false },
{ overwrite: false },
{ saveOutput: true, convertFields: false },
{ saveOutput: true, convertFields: false },
uploadableFileList,
uploadableFileList,
cmsMode
cmsMode
)
)
ui.endTask({ taskName: 'uploadTheme', timeStart })
ui.endTask({ taskName: 'uploadTheme', timeStart })
} catch (error) {
} catch (error) {
console.error(chalk.red('Error:'))
console.error(chalk.red('Error:'))
console.error(error.message)
console.error(error.message)
process.exitCode = 1
process.exitCode = 1
if (process.env.DEBUG_MODE === 'debug') {
if (process.env.DEBUG_MODE === 'debug') {
console.error(error)
console.error(error)
process.exitCode = 1
process.exitCode = 1
}
}
}
}
}
}


/**
/**
* #### delete all templates in theme and reupload them
* #### delete all templates in theme and reupload them
* @async
* @async
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @param {string} themeName - theme name
* @param {string} themeName - theme name
* @returns undefined
* @returns undefined
*/
*/
async function cleanUploadThemeTemplates (config, themeName) {
async function cleanUploadThemeTemplates (config, themeName) {
const timeStart = ui.startTask('cleanUploadThemeTemplates')
const timeStart = ui.startTask('cleanUploadThemeTemplates')
const spinner = ora('Reupload all templates').start()
const spinner = ora('Reupload all templates').start()
try {
try {
const cmslibOptions = getThemeOptions()
const cmslibOptions = getThemeOptions()
const src = path.join(process.cwd(), cmslibOptions.themeFolder, 'templates')
const src = `${process.cwd()}/${cmslibOptions.themeFolder}/templates`
const dest = path.join(themeName, 'templates')
const dest = `${themeName}/templates`
const portalId = config.portals[0].portalId
const portalId = config.portals[0].portalId


throwErrorIfMissingScope(config, 'design_manager')
throwErrorIfMissingScope(config, 'design_manager')
setLogLevel(LOG_LEVEL.NONE)
setLogLevel(LOG_LEVEL.NONE)
const uploadableFileList = await getUploadableFileList(src)
const uploadableFileList = await getUploadableFileList(src)
const filesToDelete = await getDirectoryContentsByPath(portalId, dest)
const filesToDelete = await getDirectoryContentsByPath(portalId, dest)
if (filesToDelete !== undefined && filesToDelete.data.children.length > 0) {
if (filesToDelete !== undefined && filesToDelete.data.children.length > 0) {
for await (const file of filesToDelete.data.children) {
for await (const file of filesToDelete.data.children) {
// @ts-ignore
// @ts-ignore
if (file.includes('.html')) {
if (file.includes('.html')) {
await deleteFile(portalId, path.join(dest, file))
await deleteFile(portalId, `${dest}/${file}`)
}
}
}
}
}
}
await uploadFolder.uploadFolder(
await uploadFolder.uploadFolder(
portalId,
portalId,
src,
src,
dest,
dest,
{ overwrite: false },
{ overwrite: false },
{ saveOutput: true, convertFields: false },
{ saveOutput: true, convertFields: false },
uploadableFileList,
uploadableFileList,
cmsMode
cmsMode
)
)
spinner.succeed()
spinner.succeed()
ui.endTask({ taskName: 'cleanUploadThemeTemplates', timeStart })
ui.endTask({ taskName: 'cleanUploadThemeTemplates', timeStart })
} catch (error) {
} catch (error) {
spinner.fail()
spinner.fail()
console.error(chalk.red('Error:'))
console.error(chalk.red('Error:'))
console.error(error.message)
console.error(error.message)
process.exitCode = 1
process.exitCode = 1
if (process.env.DEBUG_MODE === 'debug') {
if (process.env.DEBUG_MODE === 'debug') {
console.error(error)
console.error(error)
process.exitCode = 1
process.exitCode = 1
}
}
}
}
}
}


/**
/**
* #### clean theme and reupload it
* #### clean theme and reupload it
* @async
* @async
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @param {string} themeName - theme name
* @param {string} themeName - theme name
* @returns undefined
* @returns undefined
*/
*/
async function cleanUploadTheme (config, themeName) {
async function cleanUploadTheme (config, themeName) {
const timeStart = ui.startTask('cleanUploadTheme')
const timeStart = ui.startTask('cleanUploadTheme')
const dest = themeName
const dest = themeName
const spinner = ora(`Clean ${dest} folder before upload`)
const spinner = ora(`Clean ${dest} folder before upload`)
try {
try {
const cmslibOptions = getThemeOptions()
const cmslibOptions = getThemeOptions()
const src = path.join(process.cwd(), cmslibOptions.themeFolder)
const src = `${process.cwd()}/${cmslibOptions.themeFolder}`
const portalId = config.portals[0].portalId
const portalId = config.portals[0].portalId


throwErrorIfMissingScope(config, 'design_manager')
throwErrorIfMissingScope(config, 'design_manager')
setLogLevel(LOG_LEVEL.LOG)
setLogLevel(LOG_LEVEL.LOG)
const uploadableFileList = await getUploadableFileList(src)
const uploadableFileList = await getUploadableFileList(src)
spinner.start()
spinner.start()
const filesToDelete = await getDirectoryContentsByPath(portalId, '/')
const filesToDelete = await getDirectoryContentsByPath(portalId, '/')
if (filesToDelete !== undefined && filesToDelete.data.children.length > 0) {
if (filesToDelete !== undefined && filesToDelete.data.children.length > 0) {
for await (const file of filesToDelete.data.children) {
for await (const file of filesToDelete.data.children) {
// @ts-ignore
// @ts-ignore
if (file === dest) {
if (file === dest) {
await deleteFile(portalId, file)
await deleteFile(portalId, file)
}
}
}
}
}
}
spinner.succeed()
spinner.succeed()
await uploadFolder.uploadFolder(
await uploadFolder.uploadFolder(
portalId,
portalId,
src,
src,
dest,
dest,
{ overwrite: false },
{ overwrite: false },
{ saveOutput: true, convertFields: false },
{ saveOutput: true, convertFields: false },
uploadableFileList,
uploadableFileList,
cmsMode
cmsMode
)
)
ui.endTask({ taskName: 'cleanUploadTheme', timeStart })
ui.endTask({ taskName: 'cleanUploadTheme', timeStart })
} catch (error) {
} catch (error) {
spinner.fail()
spinner.fail()
console.error(chalk.red('Error:'))
console.error(chalk.red('Error:'))
console.error(error.message)
console.error(error.message)
process.exitCode = 1
process.exitCode = 1
if (process.env.DEBUG_MODE === 'debug') {
if (process.env.DEBUG_MODE === 'debug') {
console.error(error)
console.error(error)
process.exitCode = 1
process.exitCode = 1
}
}
}
}
}
}


/**
/**
* #### CI clean theme upload
* #### CI clean theme upload
* @async
* @async
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @param {string} themeName - theme name
* @param {string} themeName - theme name
* @returns undefined
* @returns undefined
*/
*/
async function ciUploadTheme (config, themeName) {
async function ciUploadTheme (config, themeName) {
const timeStart = ui.startTask('ciUpload')
const timeStart = ui.startTask('ciUpload')
const dest = themeName
const dest = themeName
const spinner = ora(`Clean ${dest} folder before upload`)
const spinner = ora(`Clean ${dest} folder before upload`)
try {
try {
const cmslibOptions = getThemeOptions()
const cmslibOptions = getThemeOptions()
const src = path.join(process.cwd(), cmslibOptions.themeFolder)
const src = `${process.cwd()}/${cmslibOptions.themeFolder}`
const portalId = config.portals[0].portalId
const portalId = config.portals[0].portalId


throwErrorIfMissingScope(config, 'design_manager')
throwErrorIfMissingScope(config, 'design_manager')
setLogLevel(LOG_LEVEL.LOG)
setLogLevel(LOG_LEVEL.LOG)
const uploadableFileList = await getCiUploadableFileList(src)
const uploadableFileList = await getCiUploadableFileList(src)
spinner.start()
spinner.start()
const filesToDelete = await getDirectoryContentsByPath(portalId, '/')
const filesToDelete = await getDirectoryContentsByPath(portalId, '/')
if (filesToDelete !== undefined && filesToDelete.data.children.length > 0) {
if (filesToDelete !== undefined && filesToDelete.data.children.length > 0) {
for await (const file of filesToDelete.data.children) {
for await (const file of filesToDelete.data.children) {
// @ts-ignore
// @ts-ignore
if (file === dest) {
if (file === dest) {
await deleteFile(portalId, file)
await deleteFile(portalId, file)
}
}
}
}
}
}
spinner.succeed()
spinner.succeed()
await uploadFolder.uploadFolder(
await uploadFolder.uploadFolder(
portalId,
portalId,
src,
src,
dest,
dest,
{ overwrite: false },
{ overwrite: false },
{ saveOutput: true, convertFields: false },
{ saveOutput: true, convertFields: false },
uploadableFileList,
uploadableFileList,
cmsMode
cmsMode
)
)
ui.endTask({ taskName: 'ciUpload', timeStart })
ui.endTask({ taskName: 'ciUpload', timeStart })
} catch (error) {
} catch (error) {
spinner.fail()
spinner.fail()
console.error(chalk.red('Error:'))
console.error(chalk.red('Error:'))
console.error(error.message)
console.error(error.message)
process.exitCode = 1
process.exitCode = 1
if (process.env.DEBUG_MODE === 'debug') {
if (process.env.DEBUG_MODE === 'debug') {
console.error(error)
console.error(error)
process.exitCode = 1
process.exitCode = 1
}
}
}
}
}
}


/**
/**
* #### prepare file choices for prompt
* #### prepare file choices for prompt
* @private
* @private
* @param {Array<FILE_LIST>} files - hubdb files
* @param {Array<FILE_LIST>} files - hubdb files
* @returns {Array<{name:string, value:{path:string, name:string, label:string}}>} files choices
* @returns {Array<{name:string, value:{path:string, name:string, label:string}}>} files choices
*/
*/
function prepareFileChoices (files) {
function prepareFileChoices (files) {
const fileChoices = []
const fileChoices = []
for (const file of files) {
for (const file of files) {
fileChoices.push({ name: file.name, value: { path: file.path, name: file.name, label: file.name.slice(0, -7) } })
fileChoices.push({ name: file.name, value: { path: file.path, name: file.name, label: file.name.slice(0, -7) } })
}
}
return fileChoices
return fileChoices
}
}


/**
/**
* #### select standalome modules to upload
* #### select standalome modules to upload
* @async
* @async
* @private
* @private
* @param {Array<FILE_LIST>} dirs - files
* @param {Array<FILE_LIST>} dirs - files
* @returns {Promise<Array<{path:string, name:string, label:string}>>} selected files
* @returns {Promise<Array<{path:string, name:string, label:string}>>} selected files
*/
*/
async function selectModules (dirs) {
async function selectModules (dirs) {
const selectFiles = {
const selectFiles = {
message: 'Pick modules:',
message: 'Pick modules:',
choices: prepareFileChoices(dirs),
choices: prepareFileChoices(dirs),
pageSize: 5,
pageSize: 5,
loop: false
loop: false
}
}
const confirmPortal = (/** @type {any} */ selectedFiles) => {
const confirmPortal = (/** @type {any} */ selectedFiles) => {
const filesNameList = []
const filesNameList = []
for (const file of selectedFiles) {
for (const file of selectedFiles) {
filesNameList.push(file.name)
filesNameList.push(file.name)
}
}
if (filesNameList.length === 1) {
if (filesNameList.length === 1) {
return {
return {
message: `Continue with ${chalk.cyan.bold(filesNameList.join(','))} module?`
message: `Continue with ${chalk.cyan.bold(filesNameList.join(','))} module?`
}
}
} else {
} else {
return {
return {
message: `Continue with ${chalk.cyan.bold(filesNameList.join(','))} modules?`
message: `Continue with ${chalk.cyan.bold(filesNameList.join(','))} modules?`
}
}
}
}
}
}
// select files
// select files
let selectedTables = await checkbox(selectFiles)
let selectedTables = await checkbox(selectFiles)
if (!selectedTables.length) {
if (!selectedTables.length) {
console.log(chalk.yellow('No modules selected'))
console.log(chalk.yellow('No modules selected'))
process.exit(0)
process.exit(0)
}
}
// confirm selection
// confirm selection
let confirmed = await confirm(confirmPortal(selectedTables))
let confirmed = await confirm(confirmPortal(selectedTables))
if (!confirmed) {
if (!confirmed) {
// try again one more time
// try again one more time
selectedTables = await checkbox(selectFiles)
selectedTables = await checkbox(selectFiles)
if (!selectedTables.length) {
if (!selectedTables.length) {
console.log(chalk.yellow('No modules selected'))
console.log(chalk.yellow('No modules selected'))
process.exit(0)
process.exit(0)
}
}
confirmed = await confirm(confirmPortal(selectedTables))
confirmed = await confirm(confirmPortal(selectedTables))
if (!confirmed) {
if (!confirmed) {
process.exit(0)
process.exit(0)
}
}
}
}
return selectedTables
return selectedTables
}
}


/**
/**
* #### upload module to HubSpot
* #### upload module to HubSpot
* @async
* @async
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @param {HUBSPOT_AUTH_CONFIG} config - hubspot authentication config
* @returns undefined
* @returns undefined
*/
*/
async function uploadSelectedModules (config) {
async function uploadSelectedModules (config) {
try {
try {
const timeStart = ui.startTask('uploadModules')
const timeStart = ui.startTask('uploadModules')
const modulesToUpload = await getFileList(path.normalize('standalone-modules/*.module'), {
const modulesToUpload = await getFileList('standalone-modules/*.module', { objectMode: true, onlyDirectories: true, deep: 1 })
objectMode: true,
onlyDirectories: true,
deep: 1
})
const selectedModules = await selectModules(modulesToUpload)
const selectedModules = await selectModules(modulesToUpload)
const portalId = config.portals[0].portalId
const portalId = config.portals[0].portalId
throwErrorIfMissingScope(config, 'design_manager')
throwErrorIfMissingScope(config, 'design_manager')
setLogLevel(LOG_LEVEL.LOG)
setLogLevel(LOG_LEVEL.LOG)


for (const module of selectedModules) {
for (const module of selectedModules) {
const src = module.path
const src = module.path
const dest = module.name
const dest = module.name


if (await isFileDir(src) === false) {
if (await isFileDir(src) === false) {
throw new Error(`${chalk.red('Error')}: Module ${chalk.green(src)} does not exist`)
throw new Error(`${chalk.red('Error')}: Module ${chalk.green(src)} does not exist`)
}
}
const uploadableFileList = await getUploadableFileList(src)
const uploadableFileList = await getUploadableFileList(src)
await uploadFolder.uploadFolder(
await uploadFolder.uploadFolder(
portalId,
portalId,
src,
src,
dest,
dest,
{ overwrite: false },
{ overwrite: false },
{ saveOutput: true, convertFields: false },
{ saveOutput: true, convertFields: false },
uploadableFileList,
uploadableFileList,
cmsMode
cmsMode
)
)
}
}
ui.endTask({ taskName: 'uploadModules', timeStart })
ui.endTask({ taskName: 'uploadModules', timeStart })
} catch (error) {
} catch (error) {
console.error(chalk.red('Error:'))
console.error(chalk.red('Error:'))
console.error(error.message)
console.error(error.message)
process.exitCode = 1
process.exitCode = 1
if (process.env.DEBUG_MODE === 'debug') {
if (process.env.DEBUG_MODE === 'debug') {
console.error(error)
console.error(error)
process.exitCode = 1
process.exitCode = 1
}
}
}
}
}
}


export { uploadTheme, cleanUploadThemeTemplates, cleanUploadTheme, ciUploadTheme, uploadSelectedModules }
export { uploadTheme, cleanUploadThemeTemplates, cleanUploadTheme, ciUploadTheme, uploadSelectedModules }