UNPKG

zcatalyst-cli

Version:

Command Line Tool for CATALYST

516 lines (515 loc) 26.8 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 () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __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"); const DEFAULT_DEV_COMMANDS = { cra: 'PORT=$ZC_SLATE_PORT npm start', 'create-react-app': 'PORT=$ZC_SLATE_PORT npm start', react: 'PORT=$ZC_SLATE_PORT npm start', 'react-vite': 'npm run dev -- --port $ZC_SLATE_PORT', 'react-vitejs': 'npm run dev -- --port $ZC_SLATE_PORT', nextjs: 'npm run dev -- --port $ZC_SLATE_PORT', next: 'npm run dev -- --port $ZC_SLATE_PORT', angular: 'npm start -- --port $ZC_SLATE_PORT', 'vue-vite': 'npm run dev -- --port $ZC_SLATE_PORT', vue: 'npm run dev -- --port $ZC_SLATE_PORT', solidjs: 'npm run dev -- --port $ZC_SLATE_PORT', 'solid-js': 'npm run dev -- --port $ZC_SLATE_PORT', solid: 'npm run dev -- --port $ZC_SLATE_PORT', astro: 'npm run dev -- --port $ZC_SLATE_PORT', preact: 'npm run dev -- --port $ZC_SLATE_PORT', svelte: 'npm run dev -- --port $ZC_SLATE_PORT', sveltekit: 'npm run dev -- --port $ZC_SLATE_PORT', nuxt: 'npm run dev -- --port $ZC_SLATE_PORT', gatsby: 'npm run develop -- --port $ZC_SLATE_PORT', remix: 'npm run dev -- --port $ZC_SLATE_PORT' }; const GENERIC_DEV_COMMAND = 'npm run dev -- --port $ZC_SLATE_PORT'; function hasPortReference(cmd) { if (!cmd) return false; return /\$\{?ZC_SLATE_PORT\}?|%ZC_SLATE_PORT%/.test(cmd); } function resolveDevCommand(frameworkName, frameworks) { var _a, _b; const fw = frameworks.find((f) => f.name === frameworkName); const apiCmd = (_b = (_a = fw === null || fw === void 0 ? void 0 : fw.settings) === null || _a === void 0 ? void 0 : _a.dev_command) === null || _b === void 0 ? void 0 : _b.value; if (apiCmd && hasPortReference(apiCmd)) { return apiCmd; } const mapped = DEFAULT_DEV_COMMANDS[frameworkName.toLowerCase()]; if (mapped) return mapped; return GENERIC_DEV_COMMAND; } function promptFramework(frameworks, defaultName) { return __awaiter(this, void 0, void 0, function* () { let ordered = frameworks; if (defaultName) { const idx = frameworks.findIndex((f) => f.name === defaultName); if (idx > 0) { ordered = [frameworks[idx], ...frameworks.slice(0, idx), ...frameworks.slice(idx + 1)]; } } const choices = ordered.map(({ label, name }) => prompt_1.default.choice(label, { value: name, short: label })); const defaultIdx = defaultName ? ordered.findIndex((f) => f.name === defaultName) : -1; const { selectedFramework } = yield prompt_1.default.ask(prompt_1.default.question('selectedFramework', 'Select a framework to start with: ', Object.assign({ type: 'list', choices }, (defaultIdx >= 0 ? { default: defaultIdx } : {})))); return selectedFramework; }); } 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 "${frameworkOpt}" is not supported in Slate.`); frameworkOpt = ''; } if (!frameworkOpt) { frameworkOpt = yield promptFramework(frameworks.filter((f) => f.name !== 'other')); } 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_1) { return __awaiter(this, arguments, void 0, function* (appName, { templateName, frameworkName } = {}) { 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* () { const projectRoot = (0, project_1.getProjectRoot)(); const sourceOpt = (0, option_1.getOptionValue)('source'); if (sourceOpt) { const buildPath = (0, path_1.resolve)(projectRoot, sourceOpt); if (existingSlates.findIndex((targ) => targ.source === buildPath) !== -1) { throw new error_1.default(`Path ${(0, ansi_colors_1.bold)(buildPath)} is already linked to a Slate app.`, { exit: 1 }); } if (!(yield fs_1.ASYNC.dirExists(buildPath))) { throw new error_1.default(`Source path ${(0, ansi_colors_1.bold)(buildPath)} does not exist.`, { exit: 1 }); } return buildPath; } yield prompt_1.default.register('file-path'); 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) { if (frameworkList.findIndex((framework) => framework.name === frameworkOpt) !== -1) { return { name: frameworkOpt, source }; } (0, logger_1.warning)(`Given framework "${frameworkOpt}" is not supported in Slate. Auto-detecting...`); } const fallback = (reason) => __awaiter(this, void 0, void 0, function* () { (0, logger_1.warning)(reason); const name = yield promptFramework(frameworkList.filter((f) => f.name !== 'other')); return { name, source }; }); const packageJsonPath = (0, path_1.join)(source, constants_1.FILENAME.package_json); if (!(yield fs_1.ASYNC.fileExists(packageJsonPath))) { return fallback('Unable to find the package.json file at the given source path'); } let data; try { data = yield fs_1.ASYNC.readJSONFile(packageJsonPath); } catch (err) { return fallback(`Could not parse package.json (${err instanceof Error ? err.message : String(err)})`); } 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 (Object.keys(dependencies).length === 0) { return fallback('The package.json file does not contain dependencies'); } const dependencyNames = Object.keys(dependencies); const scripts = ((data === null || data === void 0 ? void 0 : data.scripts) || {}); const scriptCommands = [scripts.dev, scripts.start, scripts.build, scripts.serve] .filter((s) => typeof s === 'string') .join(' '); const scriptTokens = new Set(scriptCommands .toLowerCase() .replace(/[a-z_][a-z0-9_]*=\S+/gi, ' ') .split(/[\s&|;]+/) .filter(Boolean)); const normalize = (s) => s.toLowerCase().replace(/[^a-z0-9]/g, ''); const matchesKeywordPattern = (dep, keyword) => { if (!keyword) return false; const k = normalize(keyword); if (!k) return false; const lowerDep = dep.toLowerCase(); const normDep = normalize(dep); if (normDep === k) return true; if (lowerDep.startsWith(`@${keyword.toLowerCase()}/`)) return true; if (lowerDep === `@${keyword.toLowerCase()}`) return true; if (normDep === `${k}js` || normDep === `${k}dom` || normDep === `${k}native`) return true; return false; }; const scriptKeywordHit = (keyword) => { if (!keyword) return false; const k = keyword.toLowerCase(); return scriptTokens.has(k) || scriptTokens.has(normalize(keyword)); }; const frameworkScores = []; let bestMatch = null; let maxScore = 0; for (const framework of frameworkList) { if (!framework.keywords || framework.keywords.length === 0 || framework.name === 'other') { continue; } const matchingKeywords = framework.keywords.filter((keyword) => !!keyword && keyword.trim() !== '' && dependencyNames.some((dep) => matchesKeywordPattern(dep, keyword))); const scriptHits = framework.keywords.filter((keyword) => scriptKeywordHit(keyword)); const matchCount = matchingKeywords.length; let score = matchCount + scriptHits.length * 50; if (matchCount > 1) { score += matchCount * 2; } if (score > 0) { frameworkScores.push({ framework, score, matches: matchingKeywords, scriptHits }); } if (score > maxScore) { maxScore = score; bestMatch = framework; } } const perfectMatches = frameworkScores.filter(({ framework }) => framework.keywords.length > 0 && framework.keywords.every((keyword) => dependencyNames.some((dep) => matchesKeywordPattern(dep, keyword)))); let pick = null; let highConfidence = false; 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) => { if (a.scriptHits.length !== b.scriptHits.length) { return b.scriptHits.length - a.scriptHits.length; } if (a.framework.keywords.length !== b.framework.keywords.length) { return b.framework.keywords.length - a.framework.keywords.length; } return b.score - a.score; }); pick = filteredPerfectMatches[0]; highConfidence = pick.scriptHits.length > 0 || (pick.framework.keywords.length > 1 && filteredPerfectMatches.length === 1); } else if (bestMatch) { const best = frameworkScores.find((s) => s.framework === bestMatch); pick = best ? { framework: best.framework, scriptHits: best.scriptHits } : null; highConfidence = !!best && best.scriptHits.length > 0; } if (!pick) { return fallback('No matching framework found in package.json dependencies'); } const pickedLabel = pick.framework.label || pick.framework.name; if (highConfidence) { (0, console_1.log)((0, ansi_colors_1.grey)(`Auto-detected framework: ${(0, ansi_colors_1.bold)(pickedLabel)}`)); return { name: pick.framework.name, source }; } (0, logger_1.warning)(`Framework detection is uncertain. Best guess: ${(0, ansi_colors_1.bold)(pickedLabel)}.`); if ((0, option_1.getOptionValue)('default')) { return { name: pick.framework.name, source }; } const name = yield promptFramework(frameworkList.filter((f) => f.name !== 'other'), pick.framework.name); return { 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); if (add && templateName) { (0, logger_1.warning)('--template is ignored when linking an existing Slate app.'); } 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 defaultDevCommand = resolveDevCommand(frameworkOpt.name, frameworks); const slateRunConfig = (0, option_1.getOptionValue)('default') ? { devCommand: defaultDevCommand } : yield prompt_1.default.ask(prompt_1.default.question('devCommand', 'Please provide your Development Command:', { type: 'input', default: defaultDevCommand, validate: (value) => __awaiter(void 0, void 0, void 0, function* () { value = value.trim(); if (value === '') return 'Please provide a valid development command'; if (value.length > 50) return 'Cannot exceed 50 characters.'; if (!hasPortReference(value)) { return (0, ansi_colors_1.grey)('Command must reference ' + (0, ansi_colors_1.bold)('$ZC_SLATE_PORT') + ' so Catalyst can bind the dev server to a free port (e.g. ' + (0, ansi_colors_1.bold)(defaultDevCommand) + ').'); } 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]); });