Add base application project yarn release file (#16238)

As title
This commit is contained in:
martmull
2025-12-02 13:10:50 +01:00
committed by GitHub
parent 5d4170d4c3
commit 6ea817dd6c
12 changed files with 352 additions and 97 deletions
+1 -1
View File
@@ -19,7 +19,7 @@ Create Twenty App is the official scaffolding CLI for building apps on top of [T
- Strong TypeScript support and typed client generation
## Prerequisites
- Node.js 18+ (recommended) and Yarn 4
- Node.js 24+ (recommended) and Yarn 4
- A Twenty workspace and an API key (create one at https://app.twenty.com/settings/api-webhooks)
## Quick start
+14 -1
View File
@@ -4,7 +4,7 @@
"projectType": "library",
"tags": ["scope:create-app"],
"targets": {
"build": {
"before-build": {
"executor": "nx:run-commands",
"cache": true,
"options": {
@@ -13,6 +13,19 @@
},
"dependsOn": ["^build", "typecheck"]
},
"build": {
"executor": "nx:run-commands",
"cache": true,
"options": {
"cwd": "packages/create-twenty-app",
"commands": [
"mkdir -p dist/constants/base-application",
"cp -R src/constants/base-application dist/constants",
"cp -R ../../.yarn/releases dist/constants/base-application/.yarn"
]
},
"dependsOn": ["before-build"]
},
"dev": {
"executor": "nx:run-commands",
"dependsOn": ["build"],
@@ -0,0 +1 @@
24.5.0
@@ -0,0 +1,3 @@
yarnPath: .yarn/releases/yarn-4.9.2.cjs
nodeLinker: node-modules
@@ -0,0 +1,35 @@
This is a [Twenty](https://twenty.com) application project bootstrapped with [`create-twenty-app`](https://www.npmjs.com/package/create-twenty-app).
## Getting Started
First, authenticate to your workspace:
```bash
yarn auth
```
Then, run the development server:
```bash
yarn dev
```
Open your Twenty instance and go to `/settings/applications` section to see the result.
You can start adding twenty entities by running
```bash
yarn create-entity
```
The application auto-updates as you edit the file.
## Learn More
To learn more about Twenty applications, take a look at the following resources:
- [twenty-sdk](https://www.npmjs.com/package/twenty-sdk) - learn about `twenty-sdk` tool.
- [Twenty doc](https://docs.twenty.com/) - Twenty's documentation.
- Join our [Discord](https://discord.gg/cx5n4Jzs57)
You can check out [the Twenty GitHub repository](https://github.com/twentyhq/twenty) - your feedback and contributions are welcome!
@@ -0,0 +1,137 @@
import js from '@eslint/js';
import typescriptEslint from '@typescript-eslint/eslint-plugin';
import typescriptParser from '@typescript-eslint/parser';
import importPlugin from 'eslint-plugin-import';
import preferArrowPlugin from 'eslint-plugin-prefer-arrow';
import prettierPlugin from 'eslint-plugin-prettier';
import unusedImportsPlugin from 'eslint-plugin-unused-imports';
export default [
// Base JS rules
js.configs.recommended,
// Global ignores
{
ignores: ['**/node_modules/**', '**/dist/**', '**/coverage/**'],
},
// Base config for TS/JS files
{
files: ['**/*.{js,jsx,ts,tsx}'],
languageOptions: {
ecmaVersion: 'latest',
sourceType: 'module',
},
plugins: {
prettier: prettierPlugin,
import: importPlugin,
'prefer-arrow': preferArrowPlugin,
'unused-imports': unusedImportsPlugin,
},
rules: {
// General rules (aligned with main project)
'func-style': ['error', 'declaration', { allowArrowFunctions: true }],
'no-console': [
'warn',
{ allow: ['group', 'groupCollapsed', 'groupEnd'] },
],
'no-control-regex': 0,
'no-debugger': 'error',
'no-duplicate-imports': 'error',
'no-undef': 'off',
'no-unused-vars': 'off',
// Import rules
'import/no-relative-packages': 'error',
'import/no-useless-path-segments': 'error',
'import/no-duplicates': ['error', { considerQueryString: true }],
// Prefer arrow functions
'prefer-arrow/prefer-arrow-functions': [
'error',
{
disallowPrototype: true,
singleReturnOnly: false,
classPropertiesAllowed: false,
},
],
// Unused imports
'unused-imports/no-unused-imports': 'warn',
'unused-imports/no-unused-vars': [
'warn',
{
vars: 'all',
varsIgnorePattern: '^_',
args: 'after-used',
argsIgnorePattern: '^_',
},
],
// Prettier (formatting as lint errors if you want)
'prettier/prettier': 'error',
},
},
// TypeScript-specific configuration
{
files: ['**/*.{ts,tsx}'],
languageOptions: {
parser: typescriptParser,
parserOptions: {
ecmaFeatures: {
jsx: true,
},
},
},
plugins: {
'@typescript-eslint': typescriptEslint,
},
rules: {
// Turn off base rule and use TS-aware versions
'no-redeclare': 'off',
'@typescript-eslint/no-redeclare': 'error',
'@typescript-eslint/ban-ts-comment': 'error',
'@typescript-eslint/consistent-type-imports': [
'error',
{
prefer: 'type-imports',
fixStyle: 'inline-type-imports',
},
],
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/no-empty-interface': [
'error',
{
allowSingleExtends: true,
},
],
'@typescript-eslint/no-explicit-any': 'off',
'@typescript-eslint/no-empty-function': 'off',
'@typescript-eslint/no-unused-vars': 'off',
},
},
// Test files (Jest)
{
files: ['**/*.spec.@(ts|tsx|js|jsx)', '**/*.test.@(ts|tsx|js|jsx)'],
languageOptions: {
globals: {
jest: true,
describe: true,
it: true,
expect: true,
beforeEach: true,
afterEach: true,
beforeAll: true,
afterAll: true,
},
},
rules: {
'@typescript-eslint/no-non-null-assertion': 'off',
},
},
];
@@ -0,0 +1,27 @@
{
"compileOnSave": false,
"compilerOptions": {
"sourceMap": true,
"declaration": true,
"outDir": "./dist",
"rootDir": ".",
"moduleResolution": "node",
"allowSyntheticDefaultImports": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"importHelpers": true,
"allowUnreachableCode": false,
"strictNullChecks": true,
"alwaysStrict": true,
"noImplicitAny": true,
"strictBindCallApply": false,
"target": "es2018",
"module": "esnext",
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true,
},
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}
@@ -2,16 +2,14 @@ import chalk from 'chalk';
import * as fs from 'fs-extra';
import inquirer from 'inquirer';
import * as path from 'path';
import { exec } from 'child_process';
import { promisify } from 'util';
import { copyBaseApplicationProject } from './utils/app-template';
import kebabCase from 'lodash.kebabcase';
import { convertToLabel } from './utils/convert-to-label';
import { tryGitInit } from './utils/try-git-init';
import { install } from './utils/install';
const CURRENT_EXECUTION_DIRECTORY = process.env.INIT_CWD || process.cwd();
const execPromise = promisify(exec);
export class CreateAppCommand {
async execute(directory?: string): Promise<void> {
try {
@@ -31,18 +29,11 @@ export class CreateAppCommand {
appDirectory,
});
try {
const result = await execPromise('yarn --version', {
cwd: appDirectory,
});
console.log('Installing dependencies using yarn', result.stdout);
await execPromise('yarn', { cwd: appDirectory });
} catch (error: any) {
console.error(chalk.red('yarn install failed:'), error.stdout);
process.exit(1);
}
await install(appDirectory);
await this.logSuccess(appDirectory);
await tryGitInit(appDirectory);
this.logSuccess(appDirectory);
} catch (error) {
console.error(
chalk.red('Initialization failed:'),
@@ -128,10 +119,10 @@ export class CreateAppCommand {
}
private logSuccess(appDirectory: string): void {
console.log(chalk.green('✅ Application created successfully!'));
console.log(chalk.green('✅ Application created!'));
console.log('');
console.log(chalk.blue('Next steps:'));
console.log(` cd ${appDirectory.split('/').reverse()[0] ?? ''}`);
console.log(' twenty app dev');
console.log(`cd ${appDirectory.split('/').reverse()[0] ?? ''}`);
console.log('twenty app dev');
}
}
@@ -13,27 +13,19 @@ export const copyBaseApplicationProject = async ({
appDescription: string;
appDirectory: string;
}) => {
await fs.copy(join(__dirname, '../constants/base-application'), appDirectory);
await createPackageJson({ appName, appDirectory });
await createGitignore(appDirectory);
await createYarnLock(appDirectory);
await createYarnRc(appDirectory);
await createNvmRc(appDirectory);
await createTsConfig(appDirectory);
await createApplicationConfig({
displayName: appDisplayName,
description: appDescription,
appDirectory,
});
await createReadmeContent({
displayName: appDisplayName,
appDescription,
appDirectory,
});
};
const createYarnLock = async (appDirectory: string) => {
@@ -43,55 +35,45 @@ const createYarnLock = async (appDirectory: string) => {
await fs.writeFile(join(appDirectory, 'yarn.lock'), yarnLockContent);
};
const createGitignore = async (appDirectory: string) => {
const gitignoreContent = `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
const createYarnRc = async (appDirectory: string) => {
const yarnRcContent = `nodeLinker: node-modules
# dependencies
/node_modules
/.pnp
.pnp.*
.yarn
# codegen
generated
# testing
/coverage
# dev
/dist/
# production
/build
# misc
.DS_Store
*.pem
# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*
.pnpm-debug.log*
# env files (can opt-in for committing if needed)
.env*
# typescript
*.tsbuildinfo
`;
await fs.writeFile(join(appDirectory, '.yarnrc.yml'), yarnRcContent);
};
const createNvmRc = async (appDirectory: string) => {
const nvmRcContent = `24.5.0
`;
await fs.writeFile(join(appDirectory, '.nvmrc'), nvmRcContent);
};
const createTsConfig = async (appDirectory: string) => {
const tsConfigJson = {
compileOnSave: false,
compilerOptions: {
sourceMap: true,
declaration: true,
outDir: './dist',
rootDir: '.',
moduleResolution: 'node',
allowSyntheticDefaultImports: true,
emitDecoratorMetadata: true,
experimentalDecorators: true,
importHelpers: true,
allowUnreachableCode: false,
strictNullChecks: true,
alwaysStrict: true,
noImplicitAny: true,
strictBindCallApply: false,
target: 'es2018',
module: 'esnext',
lib: ['es2020', 'dom'],
skipLibCheck: true,
skipDefaultLibCheck: true,
resolveJsonModule: true,
},
exclude: ['node_modules', 'dist', '**/*.test.ts', '**/*.spec.ts'],
};
await fs.writeFile(
join(appDirectory, 'tsconfig.json'),
JSON.stringify(tsConfigJson, null, 2),
'utf8',
);
await fs.writeFile(join(appDirectory, '.gitignore'), gitignoreContent);
};
const createApplicationConfig = async ({
@@ -126,7 +108,7 @@ const createPackageJson = async ({
}) => {
const packageJson = {
name: appName,
version: '0.0.1',
version: '0.1.0',
license: 'MIT',
engines: {
node: '^24.5.0',
@@ -157,20 +139,3 @@ const createPackageJson = async ({
'utf8',
);
};
const createReadmeContent = async ({
displayName,
appDescription,
appDirectory,
}: {
displayName: string;
appDescription: string;
appDirectory: string;
}) => {
const readmeContent = `# ${displayName}
${appDescription}
`;
await fs.writeFile(join(appDirectory, 'README.md'), readmeContent);
};
@@ -0,0 +1,13 @@
import chalk from 'chalk';
import { promisify } from 'util';
import { exec } from 'child_process';
const execPromise = promisify(exec);
export const install = async (root: string) => {
try {
await execPromise('yarn', { cwd: root });
} catch (error: any) {
console.error(chalk.red('yarn install failed:'), error.stdout);
}
};
@@ -0,0 +1,70 @@
import * as fs from 'fs-extra';
import { join } from 'path';
import { promisify } from 'util';
import { exec } from 'child_process';
const execPromise = promisify(exec);
const isInGitRepository = async (root: string): Promise<boolean> => {
try {
await execPromise('git rev-parse --is-inside-work-tree', { cwd: root });
return true;
} catch {
// Empty catch block
}
return false;
};
const isInMercurialRepository = async (root: string): Promise<boolean> => {
try {
await execPromise('hg --cwd . root', { cwd: root });
return true;
} catch {
// Empty catch block
}
return false;
};
const isDefaultBranchSet = async (root: string): Promise<boolean> => {
try {
await execPromise('git config init.defaultBranch', { cwd: root });
return true;
} catch {
// Empty catch block
}
return false;
};
export const tryGitInit = async (root: string): Promise<boolean> => {
try {
await execPromise('git --version', { cwd: root });
if (
(await isInGitRepository(root)) ||
(await isInMercurialRepository(root))
) {
return false;
}
await execPromise('git init', { cwd: root });
try {
if (!(await isDefaultBranchSet(root))) {
await execPromise('git checkout -b main', { cwd: root });
}
await execPromise('git add -A', { cwd: root });
await execPromise(
'git commit -m "Initial commit from Create Twenty App"',
{
cwd: root,
},
);
return true;
} catch {
fs.rm(join(root, '.git'), { recursive: true, force: true });
return false;
}
} catch {
return false;
}
};
+1 -1
View File
@@ -19,7 +19,7 @@ A CLI and SDK to develop, build, and publish applications that extend [Twenty CR
- Works great with the scaffolder: [create-twenty-app](https://www.npmjs.com/package/create-twenty-app)
## Prerequisites
- Node.js 18+ (recommended) and Yarn 4
- Node.js 24+ (recommended) and Yarn 4
- A Twenty workspace and an API key. Generate one at https://app.twenty.com/settings/api-webhooks
## Installation