CLI to install project (#164)
* CLI to install project * CLI fixes * Update README.md * Cleanup gitignore
This commit is contained in:
17
cli/src/config.ts
Normal file
17
cli/src/config.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { exec } from 'child_process';
|
||||
|
||||
export function execShell(cmd: string): Promise<string> {
|
||||
return new Promise((resolve, reject) => {
|
||||
exec(cmd, (error, stdout, stderr) => {
|
||||
if (error) {
|
||||
console.warn(`Error: ${error.message}`);
|
||||
console.warn(`stderr: ${stderr}`);
|
||||
reject(error);
|
||||
}
|
||||
resolve(stdout ? stdout : stderr);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
export const REPO_URL = 'https://github.com/twentyhq/twenty.git';
|
||||
export const CLONE_DIR = 'twenty';
|
||||
27
cli/src/index.ts
Normal file
27
cli/src/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
#!/usr/bin/env node
|
||||
import { program } from 'commander';
|
||||
import { showWelcomeScreen, firstQuestion } from './install/index.js';
|
||||
import prompts from 'prompts';
|
||||
import { askContributeQuestions } from './install/contribute/index.js';
|
||||
import { askDemoQuestions } from './install/demo/index.js';
|
||||
import { askSelfhostQuestions } from './install/selfhost/index.js';
|
||||
|
||||
program;
|
||||
|
||||
showWelcomeScreen();
|
||||
|
||||
(async () => {
|
||||
const response = await prompts(firstQuestion);
|
||||
|
||||
switch (response.install_type) {
|
||||
case 'contribute':
|
||||
askContributeQuestions();
|
||||
break;
|
||||
case 'demo':
|
||||
askDemoQuestions();
|
||||
break;
|
||||
case 'selfhost':
|
||||
askSelfhostQuestions();
|
||||
break;
|
||||
}
|
||||
})();
|
||||
35
cli/src/install/contribute/index.ts
Normal file
35
cli/src/install/contribute/index.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import prompts, { PromptObject } from 'prompts';
|
||||
import { askContributeLocalQuestions } from './local/index.js';
|
||||
import { askContributeRemoteQuestions } from './remote.js';
|
||||
|
||||
export const contributeQuestions: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'contribute_type',
|
||||
message: 'Where do you want to setup your development environment?',
|
||||
choices: [
|
||||
{
|
||||
title: 'Local',
|
||||
description: 'I want to setup a development environment on my machine',
|
||||
value: 'local',
|
||||
},
|
||||
{
|
||||
title: 'Remote (via Github Codespaces)',
|
||||
description: 'A simple pre-configured remote environment',
|
||||
value: 'remote',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const askContributeQuestions: () => Promise<void> = async () => {
|
||||
const response = await prompts(contributeQuestions);
|
||||
switch (response.contribute_type) {
|
||||
case 'local':
|
||||
await askContributeLocalQuestions();
|
||||
break;
|
||||
case 'remote':
|
||||
await askContributeRemoteQuestions();
|
||||
break;
|
||||
}
|
||||
};
|
||||
128
cli/src/install/contribute/local/docker.ts
Normal file
128
cli/src/install/contribute/local/docker.ts
Normal file
@ -0,0 +1,128 @@
|
||||
import prompts, { PromptObject } from 'prompts';
|
||||
import { spawn } from 'child_process';
|
||||
import { execShell, REPO_URL } from '../../../config.js';
|
||||
import { join } from 'path';
|
||||
import * as fs from 'fs';
|
||||
|
||||
export const dockerQuestions: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'folder_name',
|
||||
initial: 'twenty',
|
||||
message: 'Name of folder where we will clone the repo?',
|
||||
},
|
||||
];
|
||||
|
||||
export const askDockerQuestions: () => Promise<void> = async () => {
|
||||
let folderResponse = await prompts(dockerQuestions);
|
||||
|
||||
let folderExists = fs.existsSync(folderResponse.folder_name);
|
||||
while (folderExists) {
|
||||
try {
|
||||
folderResponse = await prompts({
|
||||
type: 'text',
|
||||
name: 'folder_name',
|
||||
message:
|
||||
'Folder already exists. Please choose another name and press enter.',
|
||||
});
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.Signals) === 'SIGINT') {
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
folderExists = fs.existsSync(folderResponse.folder_name);
|
||||
}
|
||||
|
||||
let git_is_installed = false;
|
||||
while (!git_is_installed) {
|
||||
try {
|
||||
await execShell('git --version');
|
||||
git_is_installed = true;
|
||||
} catch (error) {
|
||||
try {
|
||||
await prompts({
|
||||
type: 'text',
|
||||
name: 'git_install',
|
||||
message:
|
||||
'Git does not appear to be installed. Please install it and press enter.',
|
||||
});
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.Signals) === 'SIGINT') {
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let docker_is_installed = false;
|
||||
while (!docker_is_installed) {
|
||||
try {
|
||||
await execShell('docker --version');
|
||||
docker_is_installed = true;
|
||||
} catch (error) {
|
||||
try {
|
||||
await prompts({
|
||||
type: 'text',
|
||||
name: 'docker_install',
|
||||
message:
|
||||
'Docker does not appear to be installed. Please install it and press enter.',
|
||||
});
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.Signals) === 'SIGINT') {
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let docker_daemon_running = false;
|
||||
while (!docker_daemon_running) {
|
||||
try {
|
||||
await execShell('docker info');
|
||||
docker_daemon_running = true;
|
||||
} catch (error) {
|
||||
try {
|
||||
await prompts({
|
||||
type: 'text',
|
||||
name: 'docker_install',
|
||||
message:
|
||||
'Docker daemon does not appear to be running. Please start it manually and press enter.',
|
||||
});
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.Signals) === 'SIGINT') {
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Cloning the Twenty repo. This can take a little while.');
|
||||
|
||||
await execShell(`git clone ${REPO_URL} ${folderResponse.folder_name}`);
|
||||
|
||||
console.log('Build the docker images. (cd infra/dev then make build)');
|
||||
|
||||
const makeBuild = spawn('make', ['build'], {
|
||||
cwd: join(folderResponse.folder_name, 'infra', 'dev'),
|
||||
});
|
||||
makeBuild.stdout.on('data', (data) => {
|
||||
console.log(`stdout: ${data}`);
|
||||
});
|
||||
makeBuild.stderr.on('data', (data) => {
|
||||
console.log(`stderr: ${data}`);
|
||||
});
|
||||
makeBuild.on('error', (error) => {
|
||||
console.log(`error: ${error.message}`);
|
||||
});
|
||||
makeBuild.on('close', (code) => {
|
||||
console.log(`child process exited with code ${code}`);
|
||||
});
|
||||
};
|
||||
35
cli/src/install/contribute/local/index.ts
Normal file
35
cli/src/install/contribute/local/index.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import prompts, { PromptObject } from 'prompts';
|
||||
import { askDockerQuestions } from './docker.js';
|
||||
import { askNoDockerQuestions } from './no-docker.js';
|
||||
|
||||
export const contributeLocalQuestions: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'local_setup_type',
|
||||
message: 'What is your prefered setup?',
|
||||
choices: [
|
||||
{
|
||||
title: 'Docker',
|
||||
description: 'A managed development environment with Postgres included',
|
||||
value: 'docker',
|
||||
},
|
||||
{
|
||||
title: 'Without docker',
|
||||
description: "You'll need to setup a Postgres instance on your own",
|
||||
value: 'no-docker',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const askContributeLocalQuestions: () => Promise<void> = async () => {
|
||||
const response = await prompts(contributeLocalQuestions);
|
||||
switch (response.local_setup_type) {
|
||||
case 'docker':
|
||||
await askDockerQuestions();
|
||||
break;
|
||||
case 'no-docker':
|
||||
await askNoDockerQuestions();
|
||||
break;
|
||||
}
|
||||
};
|
||||
213
cli/src/install/contribute/local/no-docker.ts
Normal file
213
cli/src/install/contribute/local/no-docker.ts
Normal file
@ -0,0 +1,213 @@
|
||||
import prompts, { PromptObject } from 'prompts';
|
||||
import { spawn } from 'child_process';
|
||||
import { execShell, REPO_URL } from '../../../config.js';
|
||||
import { join } from 'path';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import pkg from 'pg';
|
||||
const { Client } = pkg;
|
||||
|
||||
export const noDockerQuestion1: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'folder_name',
|
||||
initial: 'twenty',
|
||||
message: 'Name of folder where we will clone the repo?',
|
||||
},
|
||||
];
|
||||
|
||||
export const noDockerQuestion2: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'postgres_string',
|
||||
initial: 'postgres://postgres:postgrespassword@postgres:5432/default',
|
||||
message:
|
||||
'Since you are not using Docker, you need to bring your own database, please enter your postgres connection string.',
|
||||
},
|
||||
];
|
||||
|
||||
export const askNoDockerQuestions: () => Promise<void> = async () => {
|
||||
let folderResponse = await prompts(noDockerQuestion1);
|
||||
|
||||
let folderExists = fs.existsSync(folderResponse.folder_name);
|
||||
while (folderExists) {
|
||||
try {
|
||||
folderResponse = await prompts({
|
||||
type: 'text',
|
||||
name: 'folder_name',
|
||||
message:
|
||||
'Folder already exists. Please choose another name and press enter.',
|
||||
});
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.Signals) === 'SIGINT') {
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
folderExists = fs.existsSync(folderResponse.folder_name);
|
||||
}
|
||||
|
||||
let connectionStringResponse = await prompts(noDockerQuestion2);
|
||||
|
||||
let postgres_connection_valid = false;
|
||||
while (!postgres_connection_valid) {
|
||||
const client = new Client({
|
||||
connectionString: connectionStringResponse.postgres_string,
|
||||
});
|
||||
try {
|
||||
await client.connect();
|
||||
await client.end();
|
||||
postgres_connection_valid = true;
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
postgres_connection_valid = false;
|
||||
}
|
||||
if (!postgres_connection_valid) {
|
||||
try {
|
||||
connectionStringResponse = await prompts({
|
||||
type: 'text',
|
||||
name: 'postgres_string',
|
||||
initial: 'postgres://postgres:postgrespassword@postgres:5432/default',
|
||||
message:
|
||||
'Connection to Postgres failed. Please enter the string again',
|
||||
});
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.Signals) === 'SIGINT') {
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let git_is_installed = false;
|
||||
while (!git_is_installed) {
|
||||
try {
|
||||
await execShell('git --version');
|
||||
git_is_installed = true;
|
||||
} catch (error) {
|
||||
try {
|
||||
await prompts({
|
||||
type: 'text',
|
||||
name: 'git_install',
|
||||
message:
|
||||
'Git does not appear to be installed. Please install it and restart.',
|
||||
});
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.Signals) === 'SIGINT') {
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let npm_is_installed = false;
|
||||
while (!npm_is_installed) {
|
||||
try {
|
||||
await execShell('npm --version');
|
||||
npm_is_installed = true;
|
||||
} catch (error) {
|
||||
try {
|
||||
await prompts({
|
||||
type: 'text',
|
||||
name: 'git_install',
|
||||
message:
|
||||
'Npm does not appear to be installed. Please install it and press enter.',
|
||||
});
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.Signals) === 'SIGINT') {
|
||||
process.exit(0);
|
||||
} else {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
console.log('Cloning the Twenty repo. This can take a little while.');
|
||||
|
||||
await execShell(`git clone ${REPO_URL} ${folderResponse.folder_name}`);
|
||||
|
||||
await execShell(
|
||||
`cp ${folderResponse.folder_name}/front/.env.example ${folderResponse.folder_name}/front/.env`,
|
||||
);
|
||||
await execShell(
|
||||
`cp ${folderResponse.folder_name}/server/.env.example ${folderResponse.folder_name}/server/.env`,
|
||||
);
|
||||
|
||||
const envFile = path.resolve(
|
||||
join(folderResponse.folder_name, 'server', '.env'),
|
||||
);
|
||||
let envFileLines = fs.readFileSync(envFile, 'utf-8').split('\n');
|
||||
envFileLines = envFileLines.map((line) =>
|
||||
line.startsWith('PG_DATABASE_URL=')
|
||||
? `PG_DATABASE_URL=${connectionStringResponse.postgres_string}`
|
||||
: line,
|
||||
);
|
||||
// write the updated content back to the .env file
|
||||
fs.writeFileSync(envFile, envFileLines.join('\n'));
|
||||
|
||||
console.log('Building the frontend (running npm install on frontend folder)');
|
||||
const buildFront = spawn('npm', ['install'], {
|
||||
cwd: join(folderResponse.folder_name, 'front'),
|
||||
});
|
||||
buildFront.stdout.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
buildFront.stderr.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
buildFront.on('error', (error) => {
|
||||
console.log(`error: ${error.message}`);
|
||||
});
|
||||
buildFront.on('close', () => {
|
||||
console.log('Building the server (running npm install on server folder)');
|
||||
const buildServer = spawn('npm', ['install'], {
|
||||
cwd: join(folderResponse.folder_name, 'server'),
|
||||
});
|
||||
buildServer.stdout.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
buildServer.stderr.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
buildServer.on('error', (error) => {
|
||||
console.log(`error: ${error.message}`);
|
||||
});
|
||||
buildServer.on('close', () => {
|
||||
console.log(
|
||||
'Running the frontend (running npm start on frontend folder)',
|
||||
);
|
||||
const runFrontend = spawn('npm', ['run', 'start'], {
|
||||
cwd: join(folderResponse.folder_name, 'server'),
|
||||
});
|
||||
runFrontend.stdout.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
runFrontend.stderr.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
runFrontend.on('error', (error) => {
|
||||
console.log(`error: ${error.message}`);
|
||||
});
|
||||
|
||||
console.log('Running the server (running npm start on server folder)');
|
||||
const runServer = spawn('npm', ['run', 'start'], {
|
||||
cwd: join(folderResponse.folder_name, 'server'),
|
||||
});
|
||||
runServer.stdout.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
runServer.stderr.on('data', (data) => {
|
||||
console.log(`${data}`);
|
||||
});
|
||||
runServer.on('error', (error) => {
|
||||
console.log(`error: ${error.message}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
};
|
||||
16
cli/src/install/contribute/remote.ts
Normal file
16
cli/src/install/contribute/remote.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import prompts, { PromptObject } from 'prompts';
|
||||
import open from 'open';
|
||||
|
||||
export const contributeRemoteQuestions: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'local_setup_type',
|
||||
message:
|
||||
"We'll be redirecting you to a dedicated Github Codespace. Press enter to continue.",
|
||||
},
|
||||
];
|
||||
|
||||
export const askContributeRemoteQuestions: () => Promise<void> = async () => {
|
||||
await prompts(contributeRemoteQuestions);
|
||||
await open('https://codespaces.new/twentyhq/twenty');
|
||||
};
|
||||
43
cli/src/install/demo/cloud.ts
Normal file
43
cli/src/install/demo/cloud.ts
Normal file
@ -0,0 +1,43 @@
|
||||
import prompts, { PromptObject } from 'prompts';
|
||||
import open from 'open';
|
||||
|
||||
export const demoCloudQuestions: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'continue_cloud',
|
||||
message: 'We will redirect your to the cloud app. Press enter to continue.',
|
||||
},
|
||||
/*
|
||||
In the future we can let user signup from CLI directly before redirecting:
|
||||
{
|
||||
type: 'select',
|
||||
name: 'signup_type',
|
||||
message: 'How do you want to signup?',
|
||||
choices: [
|
||||
{ title: 'Google Sign-in', value: 'google' },
|
||||
{ title: 'Email with magic link', value: 'magic_link' },
|
||||
{ title: 'Email with password', value: 'password' },
|
||||
{ title: 'No-email, demo account with seeds', value: 'seeded_demo' },
|
||||
],
|
||||
},
|
||||
{
|
||||
type: (rep) => (rep == 'google' ? 'text' : null),
|
||||
name: 'google_signup',
|
||||
message:
|
||||
'A new browser window will open to sign you up with Google. Press enter to continue.',
|
||||
},
|
||||
{
|
||||
type: (rep) => {
|
||||
if (rep == 'magic_link' || rep == 'password') {
|
||||
return 'text';
|
||||
}
|
||||
},
|
||||
name: 'email_signup',
|
||||
message: 'Please enter your email',
|
||||
}, */
|
||||
];
|
||||
|
||||
export const askDemoCloudQuestions: () => Promise<void> = async () => {
|
||||
await prompts(demoCloudQuestions);
|
||||
open('https://app.twenty.com');
|
||||
};
|
||||
14
cli/src/install/demo/docker.ts
Normal file
14
cli/src/install/demo/docker.ts
Normal file
@ -0,0 +1,14 @@
|
||||
import prompts, { PromptObject } from 'prompts';
|
||||
|
||||
export const demoDockerQuestions: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'not_ready_yet',
|
||||
message: 'Not yeady yet',
|
||||
choices: [{ title: 'XXX', value: 'XXX' }],
|
||||
},
|
||||
];
|
||||
|
||||
export const askDemoDockerQuestions: () => Promise<void> = async () => {
|
||||
await prompts(demoDockerQuestions);
|
||||
};
|
||||
27
cli/src/install/demo/index.ts
Normal file
27
cli/src/install/demo/index.ts
Normal file
@ -0,0 +1,27 @@
|
||||
import prompts, { PromptObject } from 'prompts';
|
||||
import { askDemoCloudQuestions } from './cloud.js';
|
||||
import { askDemoDockerQuestions } from './docker.js';
|
||||
|
||||
export const demoQuestions: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'select',
|
||||
name: 'demo_type',
|
||||
message: 'How do you want to try the app?',
|
||||
choices: [
|
||||
{ title: 'Cloud demo', value: 'cloud' },
|
||||
{ title: 'Local docker image', value: 'docker', disabled: true },
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
export const askDemoQuestions: () => Promise<void> = async () => {
|
||||
const response = await prompts(demoQuestions);
|
||||
switch (response.demo_type) {
|
||||
case 'cloud':
|
||||
await askDemoCloudQuestions();
|
||||
break;
|
||||
case 'docker':
|
||||
await askDemoDockerQuestions();
|
||||
break;
|
||||
}
|
||||
};
|
||||
57
cli/src/install/index.ts
Normal file
57
cli/src/install/index.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import gradient from 'gradient-string';
|
||||
import chalk from 'chalk';
|
||||
import { PromptObject } from 'prompts';
|
||||
|
||||
export function showWelcomeScreen() {
|
||||
const logo = `
|
||||
|
||||
&&&&&&&&&&&&& &&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&& &&&&&&&&&&&&&&&&
|
||||
&&&& &&&&& &&&&
|
||||
&&&& &&&&&& &&&&
|
||||
&&&& &&&&&& && &&&&
|
||||
&&&&& &&&& &&&&
|
||||
&&&&&& &&&& &&&&
|
||||
&&&&&& &&&& &&&&
|
||||
&&&&& &&&& &&&&
|
||||
&&&&&&&&&&&&&&&& &&&&&&&&&&&&&&&
|
||||
&&&&&&&&&&&&&&&& &&&&&&&&&&&&
|
||||
|
||||
`;
|
||||
|
||||
const items = logo.split('\n').map((row) => gradient.mind(row));
|
||||
|
||||
/* eslint-disable no-console */
|
||||
console.log(chalk.bold(items.join('\n')));
|
||||
console.log(chalk.bold(`Welcome to Twenty!`));
|
||||
console.log(
|
||||
chalk.bold(
|
||||
gradient.mind(`We're building a modern alternative to Salesforce\n`),
|
||||
),
|
||||
);
|
||||
console.log(chalk.bold(`Let's get you started!\n\n`));
|
||||
/* eslint-enable no-console */
|
||||
}
|
||||
|
||||
export const firstQuestion: PromptObject = {
|
||||
type: 'select',
|
||||
name: 'install_type',
|
||||
message: 'What do you want to do?',
|
||||
choices: [
|
||||
{
|
||||
title: 'Contribute to the code',
|
||||
description: 'I want to setup a development environment',
|
||||
value: 'contribute',
|
||||
},
|
||||
{
|
||||
title: 'Quickly try the product',
|
||||
description: 'I want to play with a demo version',
|
||||
value: 'demo',
|
||||
},
|
||||
{
|
||||
title: 'Self-host on a server',
|
||||
description: 'I want to host the app on a distant server',
|
||||
value: 'selfhost',
|
||||
},
|
||||
],
|
||||
};
|
||||
16
cli/src/install/selfhost/index.ts
Normal file
16
cli/src/install/selfhost/index.ts
Normal file
@ -0,0 +1,16 @@
|
||||
import prompts, { PromptObject } from 'prompts';
|
||||
import open from 'open';
|
||||
|
||||
export const selfhostQuestions: PromptObject<string>[] = [
|
||||
{
|
||||
type: 'text',
|
||||
name: 'docker',
|
||||
message:
|
||||
'The options to self-host are documented in the doc. Click enter to open the relevant help page.',
|
||||
},
|
||||
];
|
||||
|
||||
export const askSelfhostQuestions: () => Promise<void> = async () => {
|
||||
await prompts(selfhostQuestions);
|
||||
await open('https://docs.twenty.com/dev-docs/getting-started/self-hosting');
|
||||
};
|
||||
Reference in New Issue
Block a user