Cleanup CI workflows, Remove Twenty CLI, Add Danger.js (#2452)

* Move dockerignore file away from root

* Delete Twenty CLI

* Create Twenty-utils

* Move release script

* Add danger.js to yarn

* Add danger

* Add Bot token

* Cancel previous steps CI

* Revert "Move dockerignore file away from root"

This reverts commit 7ed17bb2bc.
This commit is contained in:
Félix Malfait 2023-11-13 14:10:11 +01:00 committed by GitHub
parent 2befd0ff14
commit 44d046b363
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
31 changed files with 2209 additions and 6665 deletions

View File

@ -1,4 +1,4 @@
name: CD deply main
name: CD deploy main
on:
push:
branches:

View File

@ -21,3 +21,18 @@ jobs:
run: cd docs && yarn
- name: Docs / Build Documentation
run: cd docs && yarn build
vale:
name: runner / vale
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: errata-ai/vale-action@reviewdog
with:
files: ${{ steps.directories.outputs.LIST }}
fail_on_error: true
vale_flags: '--minAlertLevel=error'
reporter: github-pr-check
token: ${{ github.token }}
filter_mode: nofilter
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ github.token }}

26
.github/workflows/ci-utils.yaml vendored Normal file
View File

@ -0,0 +1,26 @@
name: CI Utils
on:
push:
branches:
- main
pull_request:
jobs:
danger-js:
runs-on: ubuntu-latest
steps:
- name: Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}
- uses: actions/checkout@v4
- name: Setup Node.js
uses: actions/setup-node@v3
with:
node-version: "18"
- name: Utils / Install Dependencies
run: cd packages/twenty-utils && yarn
- name: Utils / Run Danger.js
run: cd packages/twenty-utils && yarn danger ci
env:
GITHUB_TOKEN: ${{ secrets.TOKEN_FOR_GITHUB_BOT }}

View File

@ -1,22 +0,0 @@
name: CI-Vale
on:
push:
branches:
- main
pull_request:
jobs:
vale:
name: runner / vale
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: errata-ai/vale-action@reviewdog
with:
files: ${{ steps.directories.outputs.LIST }}
fail_on_error: true
vale_flags: '--minAlertLevel=error'
reporter: github-pr-check
token: ${{ github.token }}
filter_mode: nofilter
env:
REVIEWDOG_GITHUB_API_TOKEN: ${{ github.token }}

View File

@ -1,9 +1,5 @@
{
"name": "twenty",
"scripts": {
"release": "node infra/release/release.js"
},
"dependencies": {
"semver": "^7.5.4"
"devDependencies": {
"danger": "^11.3.0"
}
}

View File

@ -1,27 +0,0 @@
module.exports = {
parser: '@typescript-eslint/parser',
parserOptions: {
project: 'tsconfig.json',
tsconfigRootDir: __dirname,
sourceType: 'module',
},
plugins: ['@typescript-eslint/eslint-plugin'],
extends: [
'plugin:@typescript-eslint/recommended',
'plugin:prettier/recommended'
],
root: true,
env: {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js', 'codegen.js', '**/generated/*', '/lib/*'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
"@typescript-eslint/consistent-type-imports": ["error", { "prefer": "no-type-imports" }],
},
};

View File

@ -1,36 +0,0 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# Dependency directories
node_modules/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# dotenv environment variable files
.env
.env.development.local
.env.test.local
.env.production.local
.env.local
# other
lib/
.DS_Store

View File

@ -1,4 +0,0 @@
{
"singleQuote": true,
"trailingComma": "all"
}

View File

@ -1,2 +0,0 @@
# Twenty CLI
A simple CLI to get started with Twenty

View File

@ -1,8 +0,0 @@
import { execShell } from '../src/config';
export {};
test('execShell runs a shell command', async () => {
let response = await execShell('echo "hello"');
expect(response).toEqual('hello\n');
});

View File

@ -1,5 +0,0 @@
/** @type {import('ts-jest').JestConfigWithTsJest} */
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};

File diff suppressed because it is too large Load Diff

View File

@ -1,41 +0,0 @@
{
"name": "twenty-cli",
"version": "1.0.5",
"type": "module",
"description": "A simple CLI to install and interact with Twenty",
"files": [
"!lib/__tests__/**/*",
"lib/**/*",
"bin/**/*"
],
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"author": "",
"license": "ISC",
"dependencies": {
"@types/gradient-string": "^1.1.2",
"chalk": "^5.2.0",
"commander": "^10.0.1",
"dotenv": "^16.1.1",
"gradient-string": "^2.0.2",
"open": "^9.1.0",
"pg": "^8.11.0",
"prompts": "^2.4.2"
},
"bin": {
"twenty": "./lib/index.js"
},
"devDependencies": {
"@types/jest": "^29.5.1",
"@types/node": "^20.2.5",
"@types/pg": "^8.10.1",
"@types/prompts": "^2.4.4",
"@typescript-eslint/eslint-plugin": "^5.59.8",
"eslint-config-prettier": "^8.8.0",
"eslint-plugin-prettier": "^4.2.1",
"jest": "^29.5.0",
"ts-jest": "^29.1.0",
"typescript": "^5.0.4"
}
}

View File

@ -1,16 +0,0 @@
import { exec } from 'child_process';
export const execShell = (cmd: string): Promise<string> =>
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';

View File

@ -1,27 +0,0 @@
#!/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;
}
})();

View File

@ -1,35 +0,0 @@
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;
}
};

View File

@ -1,128 +0,0 @@
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}`);
});
};

View File

@ -1,35 +0,0 @@
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;
}
};

View File

@ -1,213 +0,0 @@
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://twenty:twenty@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://twenty:twenty@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}`);
});
});
});
};

View File

@ -1,16 +0,0 @@
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');
};

View File

@ -1,43 +0,0 @@
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');
};

View File

@ -1,14 +0,0 @@
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);
};

View File

@ -1,27 +0,0 @@
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;
}
};

View File

@ -1,57 +0,0 @@
import gradient from 'gradient-string';
import chalk from 'chalk';
import { PromptObject } from 'prompts';
export const 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',
},
],
};

View File

@ -1,16 +0,0 @@
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');
};

View File

@ -1,14 +0,0 @@
{
"compilerOptions": {
"target": "es5",
"module": "es2020",
"strict": true,
"outDir": "lib/",
"moduleResolution": "nodenext",
"esModuleInterop": true,
"skipLibCheck": true,
"declaration": true,
"forceConsistentCasingInFileNames": true
},
"include": ["src/**/*.ts"]
}

View File

@ -0,0 +1,9 @@
import {message, danger, warn} from "danger"
const packageChanged = danger.git.modified_files.includes('package.json');
const lockfileChanged = danger.git.modified_files.includes('yarn.lock');
if (packageChanged && !lockfileChanged) {
const message = 'Changes were made to package.json, but not to yarn.lock';
const idea = 'Perhaps you need to run `yarn install`?';
warn(`${message} - <i>${idea}</i>`);
}

View File

@ -0,0 +1,12 @@
{
"name": "twenty-utils",
"scripts": {
"release": "node release.js"
},
"dependencies": {
"semver": "^7.5.4"
},
"devDependencies": {
"danger": "^11.3.0"
}
}

File diff suppressed because it is too large Load Diff

1061
yarn.lock

File diff suppressed because it is too large Load Diff