zcatalyst-cli
Version:
Command Line Tool for CATALYST
516 lines (515 loc) • 26.8 kB
JavaScript
;
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]);
});