esa-cli
Version:
A CLI for operating Alibaba Cloud ESA Functions and Pages.
304 lines (303 loc) • 11.3 kB
JavaScript
var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) {
function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); }
return new (P || (P = Promise))(function (resolve, reject) {
function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } }
function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } }
function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); }
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
import { exec } from 'child_process';
import { isIP } from 'net';
import chokidar from 'chokidar';
import SelectItems from '../../components/selectInput.js';
import t from '../../i18n/index.js';
import logger from '../../libs/logger.js';
import checkAndInputPort from '../../utils/checkDevPort.js';
import { checkOS, Platforms } from '../../utils/checkOS.js';
import debounce from '../../utils/debounce.js';
import { getRoot } from '../../utils/fileUtils/base.js';
import { getProjectConfig, generateConfigFile, getDevConf } from '../../utils/fileUtils/index.js';
import { preCheckDeno } from '../../utils/installDeno.js';
import { preCheckEw2 } from '../../utils/installEw2.js';
import doProcess from './doProcess.js';
import ew2Pack from './ew2/devPack.js';
import Ew2Server from './ew2/server.js';
import mockPack from './mockWorker/devPack.js';
import MockServer from './mockWorker/server.js';
let yargsIns;
const OS = checkOS();
const EW2OS = [Platforms.AppleArm, Platforms.AppleIntel, Platforms.LinuxX86];
const useEw2 = EW2OS.includes(OS);
const dev = {
command: 'dev [entry]',
describe: `💻 ${t('dev_describe').d('Start a local server for developing your project')}`,
builder: (yargs) => {
yargsIns = yargs
.positional('entry', {
describe: t('dev_entry_describe').d('Entry file of Functions& Pages'),
type: 'string',
demandOption: false
})
.option('p', {
alias: 'port',
describe: t('dev_port_describe').d('Port to listen on'),
type: 'number'
})
.option('m', {
alias: 'minify',
describe: t('dev_option_minify').d('Minify code during development'),
type: 'boolean',
default: false
})
.option('refresh-command', {
describe: t('dev_refresh_command_describe').d('Provide a command to be executed before the auto-refresh on save'),
type: 'string'
})
.option('local-upstream', {
describe: t('dev_option_local_upstream').d('Host to act as origin in development'),
type: 'string'
})
.option('debug', {
describe: t('dev_option_debugger').d('Output debug logs'),
type: 'boolean',
default: false
});
if (!useEw2) {
yargsIns.option('inspect-port', {
describe: t('dev_inspect_port_describe').d('Chrome inspect devTool port'),
type: 'number'
});
}
return yargsIns;
},
handler: (argv) => __awaiter(void 0, void 0, void 0, function* () {
let { port, inspectPort } = argv;
let userFileRepacking = false; // Indicates that user code repacking does not change the .dev file
const { entry, minify, refreshCommand, localUpstream, help, debug } = argv;
if (yargsIns && help) {
yargsIns.showHelp('log');
process.exit(0);
}
if (debug) {
logger.setLogLevel('debug');
}
// Get options and set global variables
const projectConfig = getProjectConfig();
if (!projectConfig) {
if (!entry) {
logger.notInProject();
process.exit(1);
}
try {
// If no config is found and an entry is provided, create a new one.
yield selectToCreateConf(entry);
}
catch (_) {
logger.notInProject();
process.exit(1);
}
}
if (entry) {
// @ts-ignore
global.entry = entry;
}
if (port) {
if (Array.isArray(port)) {
port = Number(port[0]);
}
else {
port = Number(port);
if (isNaN(port)) {
logger.warn(t('dev_import_port_invalid').d('Invalid port entered, default port will be used.'));
}
}
// @ts-ignore
global.port = port;
logger.debug(`Entered port: ${port}`);
}
if (inspectPort) {
if (Array.isArray(inspectPort)) {
inspectPort = Number(inspectPort[0]);
}
else {
inspectPort = Number(inspectPort);
if (isNaN(inspectPort)) {
logger.warn(t('dev_import_port_invalid').d('Invalid port entered, default port will be used.'));
}
}
// @ts-ignore
global.inspectPort = inspectPort;
}
if (minify) {
// @ts-ignore
global.minify = minify;
}
if (localUpstream) {
const url = localUpstream;
// @ts-ignore
global.localUpstream = localUpstream;
try {
const hostname = new URL(url).hostname;
if (isIP(hostname)) {
logger.error(t('dev_ip_not_allowed', { url }).d(`Direct access to IP addresses is not allowed when setting local-upstream: ${url}`));
process.exit(1);
}
}
catch (err) {
logger.error(t('dev_url_invalid', { url }).d(`Invalid URL: ${url}. Please enter a valid URL.`));
process.exit(1);
}
}
const checkFunc = useEw2 ? preCheckEw2 : preCheckDeno;
const checkResult = yield checkFunc();
if (!checkResult) {
process.exit(1);
}
if (useEw2) {
const speEw2Port = getDevConf('port', 'dev', 18080);
const result = yield checkAndInputPort(speEw2Port);
// @ts-ignore
global.port = result.port;
}
else {
const speDenoPort = getDevConf('port', 'dev', 18080);
const speInspectPort = getDevConf('inspectPort', 'dev', 9229);
const result = yield checkAndInputPort(speDenoPort, speInspectPort);
// @ts-ignore
global.port = result.port;
// @ts-ignore
global.inspectPort = result.inspectPort;
}
const devPack = useEw2 ? ew2Pack : mockPack;
try {
yield devPack();
}
catch (err) {
process.exit(1);
}
let worker;
if (useEw2) {
worker = new Ew2Server({ onClose: onWorkerClosed });
}
else {
worker = new MockServer({ command: checkResult });
}
try {
yield worker.start();
}
catch (err) {
logger.error(`Worker start failed: ${err}`);
process.exit(1);
}
const ignored = (path) => {
return /(^|[\/\\])\.(?!dev($|[\/\\]))/.test(path);
};
const watcher = chokidar.watch([`${getRoot()}/src`, `${getRoot()}/.dev`], {
ignored,
persistent: true
});
watcher.on('change', debounce((path) => __awaiter(void 0, void 0, void 0, function* () {
if (path.includes('.dev')) {
if (userFileRepacking) {
userFileRepacking = false;
return;
}
worker.restart(devPack);
return;
}
logger.info('Dev repack');
logger.log(`${t('dev_repacking').d('Detected local file changes, re-packaging')}...`);
if (refreshCommand) {
try {
yield execRefreshCommand(refreshCommand);
}
catch (err) { }
}
userFileRepacking = true;
yield worker.restart(devPack);
}), 500));
var { devElement, exit } = doProcess(worker);
const { waitUntilExit } = devElement;
yield waitUntilExit();
function onWorkerClosed() {
exit === null || exit === void 0 ? void 0 : exit();
}
watcher.close();
})
};
function execCommand(cmd) {
return new Promise((resolve, reject) => {
exec(cmd, (error, stdout, stderr) => {
logger.log(stdout);
if (error) {
reject({ error, stderr });
}
else {
resolve({ stdout, stderr });
}
});
});
}
function isSafeCommand(cmd) {
const dangerousPatterns = [
/[`;]/,
/\|\|/,
/&&/,
/>|<|>>|<<|&/,
/\$(?=.*\()|=/
];
return !dangerousPatterns.some((pattern) => pattern.test(cmd));
}
function execRefreshCommand(cmd) {
return __awaiter(this, void 0, void 0, function* () {
if (isSafeCommand(cmd)) {
logger.ora.start(`${t('dev_refresh_exec_start').d('Trying to execute command:')} ${cmd}`);
try {
const { stderr } = yield execCommand(cmd);
logger.ora.succeed(`${t('dev_refresh_exec_success').d('Command executed successfully.')}`);
if (stderr) {
logger.ora.fail();
logger.error(`Errors: ${stderr}`);
}
}
catch (err) {
logger.ora.fail();
logger.error(`${t('dev_refresh_exec_fail').d('Command execution failed:')} ${err.error.message}`);
if (err.stderr) {
logger.error(`Errors: ${err.stderr}`);
}
logger.warn('Jumped it.');
}
}
else {
logger.error(t('dev_refresh_exec_unsafe').d('Command execution failed: Invalid command'));
}
});
}
function selectToCreateConf(entry) {
return new Promise((resolve, reject) => {
logger.info('Configuration file not found');
logger.log(t('dev_create_config').d('Configuration file not found. Would you like to create one?'));
SelectItems({
items: [
{ label: 'Yes', value: 'yes' },
{ label: 'No', value: 'no' }
],
handleSelect: (item) => __awaiter(this, void 0, void 0, function* () {
if (item.value === 'yes') {
yield generateConfigFile(undefined, {
dev: {
entry
}
});
resolve(true);
}
else {
reject(false);
}
})
});
});
}
export default dev;