@tanstack/cli
Version:
TanStack CLI
171 lines (170 loc) • 7.09 kB
JavaScript
import { intro } from '@clack/prompts';
import { finalizeAddOns, getFrameworkById, getPackageManager, loadStarter, populateAddOnOptionsDefaults, readConfigFile, } from '@tanstack/create';
import { getProjectName, promptForAddOnOptions, promptForEnvVars, selectAddOns, selectDeployment, selectExamples, selectGit, selectPackageManager, selectTemplate, selectToolchain, } from './ui-prompts.js';
import { listTemplateChoices, resolveStarterSpecifier, } from './command-line.js';
import { getCurrentDirectoryName, sanitizePackageName, validateProjectName, } from './utils.js';
export async function promptForCreateOptions(cliOptions, { forcedAddOns = [], showDeploymentOptions = false, }) {
const options = {};
options.framework = getFrameworkById(cliOptions.framework || 'react');
// Validate project name
if (cliOptions.projectName) {
// Handle "." as project name - use sanitized current directory name
if (cliOptions.projectName === '.') {
options.projectName = sanitizePackageName(getCurrentDirectoryName());
}
else {
options.projectName = cliOptions.projectName;
}
const { valid, error } = validateProjectName(options.projectName);
if (!valid) {
console.error(error);
process.exit(1);
}
}
else {
options.projectName = await getProjectName();
}
// Mode is always file-router (TanStack Start)
options.mode = 'file-router';
const template = cliOptions.template?.toLowerCase().trim();
const isLegacyTemplate = template &&
['file-router', 'typescript', 'tsx', 'javascript', 'js', 'jsx'].includes(template);
const routerOnly = !!cliOptions.routerOnly ||
(isLegacyTemplate ? template !== 'file-router' : false);
if (!cliOptions.starter) {
if (cliOptions.template && !isLegacyTemplate) {
cliOptions.starter = cliOptions.template;
}
else if (cliOptions.templateId) {
cliOptions.starter = cliOptions.templateId;
}
}
if (!routerOnly && !cliOptions.starter) {
const starterChoices = await listTemplateChoices(options.framework.id);
const selectedTemplateId = await selectTemplate(starterChoices.map((choice) => ({
id: choice.id,
name: choice.name,
description: choice.description,
})));
if (selectedTemplateId) {
cliOptions.starter = selectedTemplateId;
}
}
const starter = !routerOnly && cliOptions.starter
? await loadStarter(await resolveStarterSpecifier(cliOptions.starter, options.framework.id))
: undefined;
if (starter) {
options.framework = getFrameworkById(starter.framework) || options.framework;
options.mode = starter.mode;
}
// TypeScript is always enabled with file-router
options.typescript = true;
// Package manager selection
if (cliOptions.packageManager) {
options.packageManager = cliOptions.packageManager;
}
else {
const detectedPackageManager = await getPackageManager();
options.packageManager =
detectedPackageManager || (await selectPackageManager());
}
// Toolchain selection
const toolchain = await selectToolchain(options.framework, cliOptions.toolchain);
// Deployment selection
const deployment = showDeploymentOptions
? routerOnly
? undefined
: await selectDeployment(options.framework, cliOptions.deployment)
: undefined;
// Add-ons selection
const addOns = new Set();
// Examples/demo pages are enabled by default
const includeExamples = cliOptions.examples ?? (routerOnly ? false : await selectExamples());
options.includeExamples =
includeExamples;
if (toolchain) {
addOns.add(toolchain);
}
if (deployment) {
addOns.add(deployment);
}
if (!routerOnly) {
for (const addOn of starter?.dependsOn || []) {
addOns.add(addOn);
}
for (const addOn of forcedAddOns) {
addOns.add(addOn);
}
}
if (!routerOnly && Array.isArray(cliOptions.addOns)) {
for (const addOn of cliOptions.addOns) {
if (addOn.toLowerCase() === 'start') {
continue;
}
addOns.add(addOn);
}
}
else if (!routerOnly) {
for (const addOn of await selectAddOns(options.framework, options.mode, 'add-on', 'What add-ons would you like for your project?', forcedAddOns)) {
addOns.add(addOn);
}
if (includeExamples) {
for (const addOn of await selectAddOns(options.framework, options.mode, 'example', 'Would you like an example?', forcedAddOns, false)) {
addOns.add(addOn);
}
}
}
const chosenAddOns = Array.from(await finalizeAddOns(options.framework, options.mode, Array.from(addOns)));
options.chosenAddOns = includeExamples
? chosenAddOns
: chosenAddOns.filter((addOn) => addOn.type !== 'example');
// Tailwind is always enabled
options.tailwind = true;
// Prompt for add-on options in interactive mode
if (Array.isArray(cliOptions.addOns)) {
// Non-interactive mode: use defaults
options.addOnOptions = populateAddOnOptionsDefaults(options.chosenAddOns);
}
else {
// Interactive mode: prompt for options
const userOptions = await promptForAddOnOptions(options.chosenAddOns.map((a) => a.id), options.framework);
const defaultOptions = populateAddOnOptionsDefaults(options.chosenAddOns);
// Merge user options with defaults
options.addOnOptions = { ...defaultOptions, ...userOptions };
}
// Prompt for env vars exposed by selected add-ons in interactive mode
const envVarValues = Array.isArray(cliOptions.addOns)
? {}
: await promptForEnvVars(options.chosenAddOns);
options.envVarValues =
envVarValues;
options.git = cliOptions.git ?? (await selectGit());
if (cliOptions.install === false) {
options.install = false;
}
if (starter) {
options.starter = starter;
}
return options;
}
export async function promptForAddOns() {
const config = await readConfigFile(process.cwd());
if (!config) {
console.error('No config file found');
process.exit(1);
}
const framework = getFrameworkById(config.framework);
if (!framework) {
console.error(`Unknown framework: ${config.framework}`);
process.exit(1);
}
intro(`Adding new add-ons to '${config.projectName}'`);
const addOns = new Set();
for (const addOn of await selectAddOns(framework, config.mode, 'add-on', 'What add-ons would you like for your project?', config.chosenAddOns)) {
addOns.add(addOn);
}
for (const addOn of await selectAddOns(framework, config.mode, 'example', 'Would you like any examples?', config.chosenAddOns)) {
addOns.add(addOn);
}
return Array.from(addOns);
}