@mondaycom/apps-cli
Version:
A cli tool to manage apps (and monday-code projects) in monday.com
141 lines (140 loc) • 5.48 kB
JavaScript
import { spawn } from 'node:child_process';
import * as fs from 'node:fs';
import * as os from 'node:os';
import * as path from 'node:path';
import { ensureDir } from 'fs-extra';
import { MONDAY_GITHUB_REPO, MONDAY_GITHUB_REPO_BRANCH, MONDAY_GITHUB_REPO_URL } from '../consts/scaffold.js';
import { cloneFolderFromGitRepo } from './git-service.js';
import logger from '../utils/logger.js';
const DEBUG_TAG = 'scaffold_service';
const isWindows = () => process.platform === 'win32';
const npmCmd = isWindows() ? 'npm.cmd' : 'npm';
export const downloadTemplateTask = async (ctx, task) => {
const output = (data) => {
task.output = data;
};
task.title = 'Downloading template from GitHub';
const gitRepoUrl = `https://github.com/${MONDAY_GITHUB_REPO}`;
const folderPath = `apps/${ctx.project.name}`;
await cloneFolderFromGitRepo(gitRepoUrl, folderPath, MONDAY_GITHUB_REPO_BRANCH, ctx.projectPath, output);
task.title = 'Template downloaded successfully';
};
export const editEnvFileTask = async (ctx, task) => {
task.title = 'Configuring environment variables';
const filePath = path.join(ctx.projectPath, '.env');
if (!fs.existsSync(filePath)) {
task.skip('.env file not found, skipping configuration');
return;
}
if (!ctx.signingSecret) {
task.skip('No signing secret provided, skipping .env configuration');
return;
}
try {
let envLines = fs.readFileSync(filePath, 'utf8').replaceAll('\r\n', '\n').split('\n');
// Update MONDAY_SIGNING_SECRET if provided
envLines = envLines.map(line => line.startsWith('MONDAY_SIGNING_SECRET=') ? `MONDAY_SIGNING_SECRET=${ctx.signingSecret}` : line);
fs.writeFileSync(filePath, envLines.join(os.EOL), 'utf8');
task.title = 'Environment variables configured';
}
catch (error) {
logger.debug(error, DEBUG_TAG);
task.skip('Failed to configure environment variables');
}
};
export const openSetupFileTask = async (ctx, task) => {
if (!ctx.project.openSetupMd) {
task.skip('No setup documentation for this template');
return;
}
task.title = 'Opening setup documentation';
const setupUrl = `${MONDAY_GITHUB_REPO_URL}/blob/${MONDAY_GITHUB_REPO_BRANCH}/apps/${ctx.project.name}/SETUP.md`;
try {
// Map platform identifiers to their corresponding open commands
const platformCommands = {
darwin: 'open',
win32: 'start',
linux: 'xdg-open',
};
const command = platformCommands[process.platform] ?? platformCommands.linux;
spawn(command, [setupUrl], { detached: true, stdio: 'ignore' }).unref();
task.title = `Setup documentation opened in browser`;
}
catch (error) {
logger.debug(error, DEBUG_TAG);
task.skip(`Setup URL: ${setupUrl}`);
}
};
export const installDependenciesTask = async (ctx, task) => {
task.title = 'Installing npm packages';
return new Promise((resolve, reject) => {
const installProcess = spawn(npmCmd, ['install'], {
cwd: ctx.projectPath,
shell: true,
stdio: ['ignore', 'pipe', 'pipe'],
});
let errorOutput = '';
installProcess.stderr?.on('data', (data) => {
errorOutput += data.toString();
});
installProcess.on('exit', code => {
if (code === 0) {
task.title = 'Dependencies installed successfully';
resolve();
}
else {
logger.debug(`npm install failed with code ${code}: ${errorOutput}`, DEBUG_TAG);
reject(new Error(`Failed to install dependencies (exit code ${code})`));
}
});
installProcess.on('error', error => {
logger.debug(error, DEBUG_TAG);
reject(new Error(`Failed to run npm install: ${error.message}`));
});
});
};
export const runProjectTask = async (ctx, task) => {
task.title = 'Starting the project';
return new Promise((resolve, reject) => {
const startProcess = spawn(npmCmd, ['run', ctx.startCommand], {
cwd: ctx.projectPath,
shell: true,
stdio: 'inherit',
});
// Handle process cleanup on exit
const cleanup = () => {
if (!startProcess.killed) {
startProcess.kill('SIGTERM');
}
};
process.on('SIGINT', cleanup);
process.on('SIGTERM', cleanup);
process.on('exit', cleanup);
startProcess.on('exit', code => {
if (code === 0) {
task.title = 'Project started successfully';
resolve();
}
else if (code !== null) {
reject(new Error(`Project exited with code ${code}`));
}
});
startProcess.on('error', error => {
logger.debug(error, DEBUG_TAG);
reject(new Error(`Failed to start project: ${error.message}`));
});
// Resolve after a short delay to let the process start
setTimeout(() => {
task.title = `Project is running (npm run ${ctx.startCommand})`;
resolve();
}, 2000);
});
};
export const validateDestination = async (destination) => {
try {
await ensureDir(destination);
}
catch {
throw new Error(`Invalid destination directory: ${destination}`);
}
};