UNPKG

zcatalyst-cli

Version:

Command Line Tool for CATALYST

387 lines (386 loc) 20.6 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; 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()); }); }; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); const prompt_1 = __importDefault(require("../../../prompt")); const logger_1 = require("../../../util_modules/logger"); const endpoints_1 = require("../../../endpoints"); const archiver_1 = __importDefault(require("../../../archiver")); const option_1 = require("../../../util_modules/option"); const path_1 = __importStar(require("path")); const runtime_store_1 = __importDefault(require("../../../runtime-store")); const fs_1 = require("../../../util_modules/fs"); const ansi_colors_1 = require("ansi-colors"); const config_1 = require("../../../util_modules/config"); const console_1 = require("console"); const error_1 = __importDefault(require("../../../error")); const constants_1 = require("../../../util_modules/constants"); const project_1 = require("../../../util_modules/project"); const toml_1 = require("../../../util_modules/toml"); const async_1 = require("../../../util_modules/fs/lib/async"); function getFrameworkOption(frameworks, source) { return __awaiter(this, void 0, void 0, function* () { let frameworkOpt = (0, option_1.getOptionValue)('framework'); if (frameworkOpt && frameworks.findIndex((framework) => framework.name === frameworkOpt) === -1) { (0, logger_1.warning)('Given framework is not supported in Slate.'); frameworkOpt = ''; } if (!frameworkOpt) { const filteredFrameworks = frameworks.filter((framework) => framework.name !== 'other'); const { selectedFramework } = yield prompt_1.default.ask(prompt_1.default.question('selectedFramework', 'Select a framework to start with: ', { type: 'list', choices: filteredFrameworks.map(({ label, name }) => prompt_1.default.choice(label, { value: name, short: label })) })); frameworkOpt = selectedFramework; } return { name: frameworkOpt, source }; }); } function validateAppName(appName, key) { if (appName.length === 0) { return `${key} name cannot be empty`; } if (appName.length > 45) { return `${key} name cannot be more than 45 characters`; } if (!appName.match(/^[a-zA-Z0-9]+(-[a-zA-Z0-9]+)*$/)) { return `${key} name must be alphanumeric and cannot contain consecutive hyphens or hyphens at the beginning or end.`; } return true; } function getAppName(existingSlates, frameworkOpt) { return __awaiter(this, void 0, void 0, function* () { let appName = (0, option_1.getOptionValue)('name'); if (!appName) { const { name } = yield prompt_1.default.ask(prompt_1.default.question('name', 'Please provide the name for your app:', { type: 'input', default: frameworkOpt, validate: (name) => { if (existingSlates.findIndex((targ) => targ.name === name) !== -1) return 'Slate already configured with this name.'; return validateAppName(name, 'App'); } })); appName = name; } else { if (existingSlates.findIndex((targ) => targ.name === appName) !== -1) { throw new error_1.default('Slate already configured with this name.'); } else { const isValidApp = validateAppName(appName, 'App'); if (isValidApp !== true) { throw new error_1.default(isValidApp + ''); } } } return appName; }); } function handleTemplate(appName, { templateName, frameworkName } = {}) { return __awaiter(this, void 0, void 0, function* () { const projectRoot = (0, project_1.getProjectRoot)(); const appPath = path_1.default.join(projectRoot, appName); const folderExists = yield fs_1.ASYNC.dirExists(appPath); const overwriteAns = folderExists ? yield prompt_1.default.ask(prompt_1.default.question('overwrite', `Directory ${(0, ansi_colors_1.underline)(appPath)} already exists. Overwrite?`, { type: 'confirm', default: false })) : { overwrite: true }; if (!overwriteAns.overwrite) { (0, logger_1.warning)('Skipping slate template setup'); return appPath; } yield fs_1.ASYNC.deleteDir(appPath).catch(console_1.error); const templatePath = 'slate-main' + (templateName ? `/templates/${templateName}` : `/examples/${frameworkName}`); const responseZip = (yield (yield (0, endpoints_1.slateAPI)()).downloadTemplate()); yield new archiver_1.default() .load(responseZip) .extract(appPath, templatePath, { ignoreLevel: 3 }) .finalize(); return appPath; }); } function addExistingSlate(existingSlates) { return __awaiter(this, void 0, void 0, function* () { yield prompt_1.default.register('file-path'); const projectRoot = (0, project_1.getProjectRoot)(); const { sourcePath } = yield prompt_1.default.ask(prompt_1.default.question('sourcePath', 'Please provide the source path of your slate service: ', { type: 'file-path', validate: (pth) => __awaiter(this, void 0, void 0, function* () { const buildPath = (0, path_1.resolve)(projectRoot, pth.value); if (existingSlates.findIndex((targ) => targ.source === buildPath) !== -1) return 'Path is already linked.'; if (yield fs_1.ASYNC.dirExists(buildPath)) { return true; } return 'Path does not exist'; }), depth: 5, empTxt: ' ', rootPath: projectRoot, ignoreFiles: true, excludeDir: true, exclude: ['**/node_modules', '**/.git', '**/.catalyst'] })); prompt_1.default.deregister('file-path'); return sourcePath; }); } function detectFramework(source, frameworkList) { return __awaiter(this, void 0, void 0, function* () { const frameworkOpt = (0, option_1.getOptionValue)('framework'); if (frameworkOpt && frameworkList.findIndex((framework) => framework.name === frameworkOpt) === -1) { return { name: frameworkOpt, source }; } const packageJsonPath = (0, path_1.join)(source, constants_1.FILENAME.package_json); const packageJsonExists = yield fs_1.ASYNC.fileExists(packageJsonPath); if (!packageJsonExists) { (0, logger_1.warning)('Unable to find the package.json file at the given source path'); return getFrameworkOption(frameworkList, source); } const data = yield fs_1.ASYNC.readJSONFile(packageJsonPath); const dependencies = Object.assign(Object.assign({}, data === null || data === void 0 ? void 0 : data.dependencies), data === null || data === void 0 ? void 0 : data.devDependencies); if (!dependencies) { (0, logger_1.warning)('The package.json file does not contain dependencies'); return getFrameworkOption(frameworkList, source); } const dependencyNames = Object.keys(dependencies); const matchesKeywordPattern = (dep, keyword) => { if (!keyword) return false; const normalizedKeyword = keyword.toLowerCase().replace(/[^a-z0-9]/g, ''); const normalizedDep = dep.toLowerCase().replace(/[^a-z0-9]/g, ''); if (normalizedDep === normalizedKeyword) { return true; } if (normalizedDep.startsWith(`${normalizedKeyword}-`) || normalizedDep.endsWith(`-${normalizedKeyword}`) || normalizedDep.startsWith(`@${normalizedKeyword}/`) || normalizedDep.endsWith(`/${normalizedKeyword}`) || normalizedDep === `@${normalizedKeyword}`) { return true; } const segments = dep.split(/[-/@]/).map((s) => s.toLowerCase()); if (segments.includes(normalizedKeyword)) { return true; } return false; }; let bestMatch = null; let maxMatches = 0; const frameworkScores = []; for (const framework of frameworkList) { if (!framework.keywords || framework.keywords.length === 0 || framework.name === 'other') { continue; } const matchingKeywords = framework.keywords.filter((keyword) => { if (!keyword || keyword.trim() === '') return false; return dependencyNames.some((dep) => matchesKeywordPattern(dep, keyword)); }); const matchCount = matchingKeywords.length; let score = matchCount; const priorityKeywords = ['next', 'nuxt', 'gatsby', 'angular', 'vue', 'solid-js']; const hasPriorityKeyword = matchingKeywords.some((keyword) => priorityKeywords.includes(keyword.toLowerCase())); if (hasPriorityKeyword) { score += 10; } if (matchCount > 1) { score += matchCount * 2; } if (matchCount > 0) { frameworkScores.push({ framework, score, matches: matchingKeywords }); } if (score > maxMatches) { maxMatches = score; bestMatch = framework; } } const perfectMatches = frameworkScores.filter(({ framework }) => framework.keywords.length > 0 && framework.keywords.every((keyword) => dependencyNames.some((dep) => matchesKeywordPattern(dep, keyword)))); if (perfectMatches.length > 0) { const filteredPerfectMatches = perfectMatches.filter((a) => !perfectMatches.some((b) => b !== a && b.framework.keywords.length > a.framework.keywords.length && a.framework.keywords.every((k) => b.framework.keywords.includes(k)))); filteredPerfectMatches.sort((a, b) => b.framework.keywords.length - a.framework.keywords.length); const best = filteredPerfectMatches[0]; return { name: best.framework.name, source }; } if (!bestMatch) { (0, logger_1.warning)('No matching framework found in package.json dependencies'); return getFrameworkOption(frameworkList, source); } return { name: bestMatch.name, source }; }); } function getConfigDetails(frameworkOpt, frameworks) { return __awaiter(this, void 0, void 0, function* () { const slateConfigs = frameworks.find((framework) => frameworkOpt.toLowerCase() === framework.name); if (frameworkOpt === 'static') { let deploymentName = 'default'; ({ deploymentName } = yield prompt_1.default.ask(prompt_1.default.question('deploymentName', 'Provide your Deployment Name:', { type: 'input', default: 'default', validate: (name) => { return validateAppName(name, 'Deployment'); } }))); return { framework: 'static', deployment_name: deploymentName }; } const slateConfigDetails = { framework: frameworkOpt, install_command: slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings.install_command.value, build_path: slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings.output_dir.value, build_command: slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings.build_command.value, deployment_name: 'default', root_path: './' }; let editDefaultConfig; if (!(0, option_1.getOptionValue)('default')) { if (slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings) { (0, console_1.log)(`Auto-detected App Configuration (${(0, ansi_colors_1.bold)(frameworkOpt)}):\n` + `${(0, ansi_colors_1.grey)((0, ansi_colors_1.bold)('Install Command: ') + (slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings.install_command.placeholder))}\n` + `${(0, ansi_colors_1.grey)((0, ansi_colors_1.bold)('Build Command: ') + (slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings.build_command.placeholder))}\n` + `${(0, ansi_colors_1.grey)((0, ansi_colors_1.bold)('Build Path: ') + (slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings.output_dir.placeholder))}\n` + `${(0, ansi_colors_1.grey)((0, ansi_colors_1.bold)('Deployment Name: ') + slateConfigDetails.deployment_name)}`); editDefaultConfig = yield prompt_1.default.ask(prompt_1.default.question('config', 'Do you want to modify these default configurations?', { type: 'confirm', default: false })); } else { (0, logger_1.warning)('Unable to detect the app configuration for the selected framework.'); } } if (!(slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings) || (editDefaultConfig === null || editDefaultConfig === void 0 ? void 0 : editDefaultConfig.config)) { const config = yield prompt_1.default.ask(prompt_1.default.question('installCommand', 'Provide your Install Command:', { type: 'input', default: slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings.install_command.value, validate: (value) => __awaiter(this, void 0, void 0, function* () { value = value.trim(); if (value === '') return 'Please provide a valid install command'; else if (value.length > 50) return 'Cannot exceed 50 characters.'; return true; }) }), prompt_1.default.question('buildCommand', 'Provide your Build Command:', { type: 'input', default: slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings.build_command.value, validate: (value) => __awaiter(this, void 0, void 0, function* () { value = value.trim(); if (value === '') return 'Please provide a valid build command'; else if (value.length > 50) return 'Cannot exceed 50 characters.'; return true; }) }), prompt_1.default.question('buildPath', 'Provide your Build Path:', { type: 'input', default: slateConfigs === null || slateConfigs === void 0 ? void 0 : slateConfigs.settings.output_dir.value, validate: (value) => __awaiter(this, void 0, void 0, function* () { value = value.trim(); if (value === '') return 'Please provide a valid build path'; else if (/[#%*$@?]/.test(value)) return 'Build path containing invalid characters.'; else if (value.length > 50) return 'Cannot exceed 50 characters.'; return true; }) }), prompt_1.default.question('deploymentName', 'Provide your Deployment Name:', { type: 'input', default: 'default', validate: (name) => { return validateAppName(name, 'Deployment'); } })); (slateConfigDetails.install_command = config.installCommand), (slateConfigDetails.build_path = config.buildPath), (slateConfigDetails.build_command = config.buildCommand), (slateConfigDetails.deployment_name = config.deploymentName); } return slateConfigDetails || {}; }); } exports.default = (add) => __awaiter(void 0, void 0, void 0, function* () { const frameworks = yield (yield (0, endpoints_1.slateAPI)()).getFrameworks(); const templateName = (0, option_1.getOptionValue)('template'); const frameworkOpt = add ? yield detectFramework(yield addExistingSlate(config_1.slateConfig.raw() || []), frameworks) : yield getFrameworkOption(frameworks); const appName = yield getAppName(config_1.slateConfig.raw() || [], frameworkOpt.name); const payload = { name: appName, source: frameworkOpt.source || (yield handleTemplate(appName, { templateName, frameworkName: frameworkOpt.name })) }; const slateConfigDetails = yield getConfigDetails(frameworkOpt.name, frameworks); if (frameworkOpt.name !== 'static') { const slateRunConfig = (0, option_1.getOptionValue)('default') ? { devCommand: 'npm start' } : yield prompt_1.default.ask(prompt_1.default.question('devCommand', 'Please provide your Development Command:', { type: 'input', default: 'npm start', validate: (value) => __awaiter(void 0, void 0, void 0, function* () { value = value.trim(); if (value === '') return 'Please provide a valid development command'; else if (value.length > 50) return 'Cannot exceed 50 characters.'; return true; }) })); yield fs_1.ASYNC.writeJSONFile((0, path_1.join)(payload.source, constants_1.FILENAME.cli_config), { slate: { dev_command: slateRunConfig.devCommand } }); } const pth = path_1.default.join(payload.source, '.catalyst', constants_1.FILENAME.slate_config); const warnContent = '# ⚠️ This file is automatically generated by the Catalyst CLI when you link or create a Slate app,and is used only for the initial deployment to the Catalyst console. \ \n# ⚠️ Please do not modify this file, as it is fully managed by the CLI.\n'; yield (0, async_1.ensureFile)(pth, true); yield fs_1.ASYNC.writeFile(pth, warnContent + '\n' + (0, toml_1.convertTOML)(slateConfigDetails)); runtime_store_1.default.set('context.payload.slate.targets', [payload]); });