UNPKG

@mondaycom/apps-cli

Version:

A cli tool to manage apps (and monday-code projects) in monday.com

201 lines (200 loc) 8.13 kB
import fs from 'node:fs'; import os from 'node:os'; import path from 'node:path'; import archiver from 'archiver'; import glob from 'glob'; import parseGitIgnore from 'parse-gitignore'; import { ZodError } from 'zod'; import { CONFIG_NAME } from './config-service.js'; import { mondaycodercSchema } from './schemas/mondaycoderc-schema.js'; import logger from '../utils/logger.js'; export const readFileData = (filePath) => { if (!checkIfFileExists(filePath)) { throw new Error(`File not found: "${filePath}"`); } const fileData = fs.readFileSync(filePath); return fileData; }; export const readZipFileAsBuffer = (filePath) => { return fs.readFileSync(filePath); }; export const compressBuildToZip = async (dirPath) => { const fileName = `${dirPath}.zip`; const output = fs.createWriteStream(fileName); const archive = archiver('zip', { zlib: { level: 9 }, }); archive.pipe(output); archive.directory(dirPath, false); await archive.finalize(); await new Promise(resolve => { output.on('close', () => resolve()); }); return fileName; }; export const verifyClientDirectory = (directoryPath) => { if (!checkIfFileExists(directoryPath)) { throw new Error(`Directory not found: ${directoryPath}`); } if (!checkIfFileExists(`${directoryPath}/index.html`)) { throw new Error(`index.html file not found in ${directoryPath}`); } }; export const checkIfFileExists = (filePath) => { return fs.existsSync(filePath); }; export const getFileExtension = (filePath) => { return path.extname(filePath).slice(1); }; export const createTarGzArchive = async (directoryPath, fileName = 'code') => { const DEBUG_TAG = 'create_archive'; try { logger.debug({ directoryPath }, `${DEBUG_TAG} - Check directory exists`); const directoryExists = fs.existsSync(directoryPath); if (!directoryExists) { throw new Error(`Directory not found: ${directoryPath}`); } const archivePath = `${directoryPath}/${fileName}.tar.gz`; const fullFileName = `**/${fileName}.tar.gz`; // a special list of files to ignore that are not in .gitignore that is may or may not be in the project const additionalFilesToIgnore = [ '.git/**', '.env', 'local-secure-storage.db.json', '.mappsrc', 'node_modules/**', 'mvnw', ]; const pathsToIgnoreFromGitIgnore = getFilesToExcludeForArchive(directoryPath); const pathsToIgnore = [...pathsToIgnoreFromGitIgnore, archivePath, fullFileName, ...additionalFilesToIgnore]; await compressDirectoryToTarGz(directoryPath, archivePath, pathsToIgnore); return archivePath; } catch (error) { logger.debug(error, DEBUG_TAG); throw new Error('Failed in creating archive'); } }; export const createGitignoreAndAppendConfigFileIfNeeded = (directoryPath, fileName = CONFIG_NAME) => { const filePath = path.join(directoryPath, '.gitignore'); if (!checkIfFileExists(filePath)) { fs.writeFileSync(filePath, '', 'utf8'); } const gitignoreContent = fs.readFileSync(filePath, 'utf8'); if (!gitignoreContent.includes(fileName)) { fs.appendFileSync(filePath, `\n${fileName}`, 'utf8'); } }; /** * Detect if the project is yarn project with a build step * if so, we will need to abort the build process as * gcloud buildpacks does not support it yet * @param directoryPath the path where the project is located * @throws Error if the project is yarn project with a build step * @returns void **/ export const validateIfCanBuild = (directoryPath) => { const filePath = path.join(directoryPath, 'yarn.lock'); if (checkIfFileExists(filePath)) { const packageJsonPath = path.join(directoryPath, 'package.json'); const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); const hasBuildCommand = packageJson?.scripts?.build; if (hasBuildCommand) { throw new Error('monday-code does not support yarn projects with a build command. If you need a build step, use npm instead'); } } const rcFilePath = path.join(directoryPath, '.mondaycoderc'); if (checkIfFileExists(rcFilePath)) { const rcFileContent = JSON.parse(fs.readFileSync(rcFilePath, 'utf8')); try { mondaycodercSchema.parse(rcFileContent); } catch (error) { if (error instanceof ZodError) { throw new TypeError(error.errors[0].message); } throw error; } } }; //* ** PRIVATE METHODS ** *// const getFilesToExcludeForArchive = (directoryPath) => { const DEBUG_TAG = 'ignore_files_for_archive'; const mappsIgnorePath = getIgnorePath(directoryPath, '.mappsignore'); if (mappsIgnorePath) { return findIgnoredFiles(directoryPath, mappsIgnorePath); } const gitIgnorePath = getIgnorePath(directoryPath, '.gitignore'); if (gitIgnorePath) { return findIgnoredFiles(directoryPath, gitIgnorePath); } logger.debug(`${DEBUG_TAG} - No ignore files found, you can use .gitignore or .mappsignore to exclude some of the folders and files in your project`); return []; }; const getIgnorePath = (directoryPath, ignoreFile) => { const DEBUG_TAG = 'ignore_files_for_archive'; logger.debug(`${DEBUG_TAG} - Searching for ${ignoreFile} file`); let ignoreSearchPattern = `${directoryPath}/**/${ignoreFile}`; if (os.platform() === 'win32') { ignoreSearchPattern = ignoreSearchPattern.replaceAll('\\', '/'); } const [ignorePath] = glob.sync(ignoreSearchPattern); return ignorePath; }; const findIgnoredFiles = (directoryPath, ignorePath) => { const DEBUG_TAG = 'ignore_files_for_archive'; logger.debug(`${DEBUG_TAG} - Found ${ignorePath}`); logger.debug(`${DEBUG_TAG} - Creating exclude files list`); const parsedIgnore = parseGitIgnore.parse(ignorePath); logger.debug(`${DEBUG_TAG} - validating and aligning exclude files list`); const filesToExclude = alignPatternsForArchive(parsedIgnore?.patterns, directoryPath); return filesToExclude; }; const alignPatternsForArchive = (patterns, directoryPath) => { const alignedPatterns = patterns?.reduce((realPatterns, pattern) => { const slashCharIfNeeded = pattern[0] === '/' ? '' : '/'; const fullPath = `${directoryPath}${slashCharIfNeeded}${pattern}`; if (!fs.existsSync(fullPath)) return realPatterns; if (fs.statSync(fullPath).isDirectory()) { const addGlobPattern = pattern.at(-1) === '/' ? '**' : '/**'; const patternWithoutBeginningSlash = pattern[0] === '/' ? pattern.slice(1, pattern.length) : pattern; realPatterns.push(`${patternWithoutBeginningSlash}${addGlobPattern}`); } else { realPatterns.push(pattern); } return realPatterns; }, []); return alignedPatterns; }; const compressDirectoryToTarGz = async (directoryPath, archivePath, pathsToIgnore) => { const DEBUG_TAG = 'archive'; logger.debug({ directoryPath, archivePath }, `${DEBUG_TAG} - Starting`); const outputStream = fs.createWriteStream(archivePath); const archive = archiver('tar', { gzip: true, gzipOptions: { level: 1, }, }); logger.debug(`${DEBUG_TAG} - Initialized`); await new Promise((resolve, reject) => { archive.pipe(outputStream); logger.debug(pathsToIgnore, `${DEBUG_TAG} - Added paths to ignore`); archive.glob('**/*', { cwd: directoryPath, ignore: pathsToIgnore, nodir: true, dot: true, }); logger.debug(`${DEBUG_TAG} - Added directory`); outputStream.on('close', resolve); archive.on('error', reject); archive.finalize().catch(reject); }); logger.debug(`${DEBUG_TAG} - created successfully`); return archivePath; };