1574 extensibility twenty cli use workspace migration v2 to synchronize serverless function triggers (#14830)
twenty-cli serverless triggers follow up. Fixes: - eventName don't support wildcard - universalIdentifier not used to create or update trigger : update does not work properly (does deletion then creation) - add a base project in twenty-cli that is copied when creating a new app
This commit is contained in:
@@ -47,6 +47,10 @@ twenty auth logout
|
||||
# Initialize a new application
|
||||
twenty app init [name]
|
||||
|
||||
# Add a new core entity to your application
|
||||
twenty app add [options]
|
||||
-p, --path <path> Application directory path (default: current directory)
|
||||
|
||||
# Start development mode with file watching
|
||||
twenty app dev [options]
|
||||
-p, --path <path> Application directory path (default: current directory)
|
||||
|
||||
@@ -32,7 +32,7 @@ const jestConfig = {
|
||||
global: {
|
||||
statements: 2,
|
||||
lines: 1,
|
||||
functions: 5,
|
||||
functions: 1,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -7,7 +7,7 @@
|
||||
"twenty": "dist/cli.js"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "tsc",
|
||||
"build": "echo 'use npx nx build'",
|
||||
"dev": "tsx src/cli.ts",
|
||||
"start": "node dist/cli.js"
|
||||
},
|
||||
@@ -30,14 +30,13 @@
|
||||
"fs-extra": "^11.2.0",
|
||||
"inquirer": "^10.0.0",
|
||||
"jsonc-parser": "^3.2.0",
|
||||
"ora": "^8.0.0",
|
||||
"yaml": "^2.4.0",
|
||||
"zod": "^4.1.11"
|
||||
"lodash.kebabcase": "^4.1.1"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/fs-extra": "^11.0.0",
|
||||
"@types/inquirer": "^9.0.0",
|
||||
"@types/jest": "^29.5.0",
|
||||
"@types/lodash.kebabcase": "^4.1.7",
|
||||
"@types/node": "^20.0.0",
|
||||
"jest": "^29.5.0",
|
||||
"tsx": "^4.7.0",
|
||||
|
||||
@@ -4,17 +4,27 @@
|
||||
"projectType": "application",
|
||||
"tags": ["scope:cli"],
|
||||
"targets": {
|
||||
"build": {
|
||||
"after-build": {
|
||||
"executor": "nx:run-commands",
|
||||
"cache": true,
|
||||
"options": {
|
||||
"cwd": "packages/twenty-cli",
|
||||
"commands": ["rimraf dist", "tsc"]
|
||||
},
|
||||
"dependsOn": ["^build"]
|
||||
"dependsOn": ["^after-build"]
|
||||
},
|
||||
"build": {
|
||||
"executor": "nx:run-commands",
|
||||
"cache": true,
|
||||
"options": {
|
||||
"cwd": "packages/twenty-cli",
|
||||
"commands": ["cp -R src/constants/base-application-project dist/constants"]
|
||||
},
|
||||
"dependsOn": ["after-build"]
|
||||
},
|
||||
"dev": {
|
||||
"executor": "nx:run-commands",
|
||||
"dependsOn": ["build"],
|
||||
"options": {
|
||||
"cwd": "packages/twenty-cli",
|
||||
"command": "tsx src/cli.ts"
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
"title": "Twenty App Manifest",
|
||||
"description": "Schema for Twenty application manifest files",
|
||||
"type": "object",
|
||||
"required": ["universalIdentifier", "label", "version", "license", "engines", "packageManager"],
|
||||
"required": ["universalIdentifier", "name", "version", "license", "engines", "packageManager"],
|
||||
"properties": {
|
||||
"$schema": {
|
||||
"type": "string",
|
||||
@@ -15,7 +15,7 @@
|
||||
"description": "Unique identifier (UUID format recommended)",
|
||||
"pattern": "^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$"
|
||||
},
|
||||
"label": {
|
||||
"name": {
|
||||
"type": "string",
|
||||
"description": "Human-readable display name for the application",
|
||||
"minLength": 1,
|
||||
@@ -90,7 +90,7 @@
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
"yarn": ">=4.9.2"
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,7 +2,7 @@ import chalk from 'chalk';
|
||||
import * as fs from 'fs-extra';
|
||||
import inquirer from 'inquirer';
|
||||
import path from 'path';
|
||||
import { v4 } from 'uuid';
|
||||
import { randomUUID } from 'crypto';
|
||||
import { resolveAppPath } from '../utils/app-path-resolver';
|
||||
import { parseJsoncFile, writeJsoncFile } from '../utils/jsonc-parser';
|
||||
import { getSchemaUrls } from '../utils/schema-validator';
|
||||
@@ -141,7 +141,7 @@ export class AppAddCommand {
|
||||
) {
|
||||
const schemas = getSchemaUrls();
|
||||
|
||||
const uuid = v4();
|
||||
const uuid = randomUUID();
|
||||
|
||||
const entityToCreateData: Record<string, string> = {
|
||||
$schema: schemas[entity],
|
||||
@@ -255,7 +255,7 @@ export class AppAddCommand {
|
||||
}
|
||||
|
||||
private async createDatabaseEventTrigger() {
|
||||
const uuid = v4();
|
||||
const uuid = randomUUID();
|
||||
|
||||
const { eventName } = await inquirer.prompt([
|
||||
{
|
||||
@@ -266,8 +266,8 @@ export class AppAddCommand {
|
||||
if (input.length === 0) {
|
||||
return 'Event name is required';
|
||||
}
|
||||
if (!/^[a-zA-Z]+\.(created|updated|deleted)$/.test(input)) {
|
||||
return 'Event name must be in format: objectName.(created|updated|deleted)';
|
||||
if (!/^(?:[a-zA-Z]+|\*)\.(created|updated|deleted|\*)$/.test(input)) {
|
||||
return 'Event name must be in format: (objectName|*).(created|updated|deleted|*)';
|
||||
}
|
||||
return true;
|
||||
},
|
||||
@@ -282,7 +282,7 @@ export class AppAddCommand {
|
||||
}
|
||||
|
||||
private async createCronTrigger() {
|
||||
const uuid = v4();
|
||||
const uuid = randomUUID();
|
||||
|
||||
const { schedule } = await inquirer.prompt([
|
||||
{
|
||||
|
||||
@@ -2,27 +2,28 @@ import chalk from 'chalk';
|
||||
import * as fs from 'fs-extra';
|
||||
import inquirer from 'inquirer';
|
||||
import * as path from 'path';
|
||||
import {
|
||||
createBasePackageJson,
|
||||
createGitignoreContent,
|
||||
createReadmeContent,
|
||||
} from '../utils/app-template';
|
||||
import { writeJsoncFile } from '../utils/jsonc-parser';
|
||||
import { copyBaseApplicationProject } from '../utils/app-template';
|
||||
import kebabCase from 'lodash.kebabcase';
|
||||
|
||||
export class AppInitCommand {
|
||||
async execute(options: { path?: string; name?: string }): Promise<void> {
|
||||
async execute(options: { path?: string }): Promise<void> {
|
||||
try {
|
||||
const { name, description } = await this.getAppInfos(options.name);
|
||||
const { appName, appDirectory, appDescription } =
|
||||
await this.getAppInfos(options);
|
||||
|
||||
const appDir = this.determineAppDirectory(name, options.path);
|
||||
await this.validateDirectory(appDirectory);
|
||||
|
||||
await this.validateDirectory(appDir);
|
||||
this.logCreationInfo({ appDirectory, appName });
|
||||
|
||||
this.logCreationInfo(appDir, name);
|
||||
await fs.ensureDir(appDirectory);
|
||||
|
||||
await this.createAppStructure(appDir, name, description);
|
||||
await copyBaseApplicationProject({
|
||||
appName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
});
|
||||
|
||||
this.logSuccess(appDir);
|
||||
this.logSuccess(appDirectory);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red('Initialization failed:'),
|
||||
@@ -32,22 +33,18 @@ export class AppInitCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private async getAppInfos(
|
||||
providedName?: string,
|
||||
): Promise<{ name: string; description: string }> {
|
||||
if (providedName) {
|
||||
return { name: providedName, description: '' };
|
||||
}
|
||||
|
||||
return inquirer.prompt([
|
||||
private async getAppInfos(options: { path?: string }): Promise<{
|
||||
appName: string;
|
||||
appDirectory: string;
|
||||
appDescription: string;
|
||||
}> {
|
||||
const { name, description } = await inquirer.prompt([
|
||||
{
|
||||
type: 'input',
|
||||
name: 'name',
|
||||
message: 'Application name:',
|
||||
message: 'Application name (eg: My awesome application):',
|
||||
validate: (input) => {
|
||||
if (input.length === 0) return 'Application name is required';
|
||||
if (!/^[a-z0-9-]+$/.test(input))
|
||||
return 'Name must contain only lowercase letters, numbers, and hyphens';
|
||||
return true;
|
||||
},
|
||||
},
|
||||
@@ -58,66 +55,49 @@ export class AppInitCommand {
|
||||
default: '',
|
||||
},
|
||||
]);
|
||||
|
||||
const appName = name.trim();
|
||||
|
||||
const appDescription = description.trim();
|
||||
|
||||
const appDirectory = options.path
|
||||
? path.resolve(options.path, kebabCase(appName))
|
||||
: path.join(process.cwd(), kebabCase(appName)!);
|
||||
|
||||
return { appName, appDirectory, appDescription };
|
||||
}
|
||||
|
||||
private determineAppDirectory(
|
||||
appName: string,
|
||||
providedPath?: string,
|
||||
): string {
|
||||
if (providedPath) {
|
||||
return path.resolve(providedPath, appName);
|
||||
}
|
||||
|
||||
return path.join(process.cwd(), appName!);
|
||||
}
|
||||
|
||||
private async validateDirectory(appDir: string): Promise<void> {
|
||||
if (!(await fs.pathExists(appDir))) {
|
||||
private async validateDirectory(appDirectory: string): Promise<void> {
|
||||
if (!(await fs.pathExists(appDirectory))) {
|
||||
return;
|
||||
}
|
||||
|
||||
const files = await fs.readdir(appDir);
|
||||
const files = await fs.readdir(appDirectory);
|
||||
if (files.length > 0) {
|
||||
throw new Error(`Directory ${appDir} already exists and is not empty`);
|
||||
throw new Error(
|
||||
`Directory ${appDirectory} already exists and is not empty`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
private logCreationInfo(appDir: string, appName: string): void {
|
||||
private logCreationInfo({
|
||||
appDirectory,
|
||||
appName,
|
||||
}: {
|
||||
appDirectory: string;
|
||||
appName: string;
|
||||
}): void {
|
||||
console.log(chalk.blue('🎯 Creating Twenty Application'));
|
||||
console.log(chalk.gray(`📁 Directory: ${appDir}`));
|
||||
console.log(chalk.gray(`📁 Directory: ${appDirectory}`));
|
||||
console.log(chalk.gray(`📝 Name: ${appName}`));
|
||||
console.log('');
|
||||
}
|
||||
|
||||
private async createAppStructure(
|
||||
appDir: string,
|
||||
appName: string,
|
||||
description: string,
|
||||
): Promise<void> {
|
||||
await fs.ensureDir(appDir);
|
||||
|
||||
// Create main basePackageJson with agent references
|
||||
const basePackageJson = createBasePackageJson(appName, description);
|
||||
const basePackageJsonPath = path.join(appDir, 'package.json');
|
||||
await writeJsoncFile(basePackageJsonPath, basePackageJson);
|
||||
|
||||
// Create README
|
||||
const readmeContent = createReadmeContent(appName, appDir);
|
||||
await fs.writeFile(path.join(appDir, 'README.md'), readmeContent);
|
||||
|
||||
// Create empty yarn.lock
|
||||
await fs.writeFile(path.join(appDir, 'yarn.lock'), '');
|
||||
|
||||
// Create .gitignore
|
||||
const gitignoreContent = createGitignoreContent();
|
||||
await fs.writeFile(path.join(appDir, '.gitignore'), gitignoreContent);
|
||||
}
|
||||
|
||||
private logSuccess(appDir: string): void {
|
||||
private logSuccess(appDirectory: string): void {
|
||||
console.log(chalk.green('✅ Application created successfully!'));
|
||||
console.log('');
|
||||
console.log(chalk.blue('Next steps:'));
|
||||
console.log(` cd ${appDir}`);
|
||||
console.log(` cd ${appDirectory}`);
|
||||
console.log(' twenty app dev');
|
||||
}
|
||||
}
|
||||
|
||||
@@ -41,7 +41,6 @@ export class AppCommand {
|
||||
.command('init')
|
||||
.description('Initialize a new Twenty application')
|
||||
.option('-p, --path <path>', 'Directory to create the application in')
|
||||
.option('-n, --name <name>', 'Application name')
|
||||
.action(async (options) => {
|
||||
await this.initCommand.execute(options);
|
||||
});
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
import { join } from 'path';
|
||||
|
||||
export const BASE_APPLICATION_PROJECT_PATH = join(
|
||||
__dirname,
|
||||
'../constants/base-application-project',
|
||||
);
|
||||
Binary file not shown.
Vendored
Executable
+942
File diff suppressed because one or more lines are too long
@@ -0,0 +1,3 @@
|
||||
yarnPath: .yarn/releases/yarn-4.9.2.cjs
|
||||
|
||||
nodeLinker: node-modules
|
||||
@@ -0,0 +1,15 @@
|
||||
# {title}
|
||||
|
||||
{description}
|
||||
|
||||
## Development
|
||||
|
||||
```bash
|
||||
twenty app dev --path {appDir}
|
||||
```
|
||||
|
||||
### Deployment
|
||||
|
||||
```bash
|
||||
twenty app sync --path {appDir}
|
||||
```
|
||||
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"version": "0.0.1",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^24.5.0",
|
||||
"npm": "please-use-yarn",
|
||||
"yarn": ">=4.0.2"
|
||||
},
|
||||
"packageManager": "[email protected]"
|
||||
}
|
||||
@@ -1,134 +0,0 @@
|
||||
import {
|
||||
createAgentManifest,
|
||||
createBasePackageJson,
|
||||
createReadmeContent,
|
||||
} from '../app-template';
|
||||
import {
|
||||
AGENT_SCHEMA_URL,
|
||||
APP_MANIFEST_SCHEMA_URL,
|
||||
} from '../../constants/schemas';
|
||||
|
||||
// Mock crypto.randomUUID to make tests deterministic
|
||||
jest.mock('crypto', () => ({
|
||||
randomUUID: jest.fn(() => 'mocked-uuid-12345'),
|
||||
}));
|
||||
|
||||
describe('app-template', () => {
|
||||
describe('createBasePackageJson', () => {
|
||||
it('should create a valid app package.json with correct structure', () => {
|
||||
const appName = 'my-test-app';
|
||||
const description = 'A Twenty application for my-test-app';
|
||||
const basePackageJson = createBasePackageJson(appName, description);
|
||||
|
||||
expect(basePackageJson).toEqual({
|
||||
$schema: APP_MANIFEST_SCHEMA_URL,
|
||||
universalIdentifier: 'mocked-uuid-12345',
|
||||
label: 'My Test App',
|
||||
description: 'A Twenty application for my-test-app',
|
||||
version: '0.0.1',
|
||||
engines: {
|
||||
node: '^24.5.0',
|
||||
npm: 'please-use-yarn',
|
||||
yarn: '>=4.0.2',
|
||||
},
|
||||
packageManager: '[email protected]',
|
||||
license: 'MIT',
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle single word app names', () => {
|
||||
const appName = 'calculator';
|
||||
const basePackageJson = createBasePackageJson(appName, '');
|
||||
|
||||
expect(basePackageJson.label).toBe('Calculator');
|
||||
expect(basePackageJson.universalIdentifier).toBe('mocked-uuid-12345');
|
||||
});
|
||||
|
||||
it('should handle kebab-case app names correctly', () => {
|
||||
const appName = 'user-management-system';
|
||||
const basePackageJson = createBasePackageJson(appName, '');
|
||||
|
||||
expect(basePackageJson.label).toBe('User Management System');
|
||||
expect(basePackageJson.universalIdentifier).toBe('mocked-uuid-12345');
|
||||
});
|
||||
|
||||
it('should generate unique universalIdentifiers', () => {
|
||||
const basePackageJson = createBasePackageJson('test-app', '');
|
||||
|
||||
expect(basePackageJson.universalIdentifier).toBeDefined();
|
||||
expect(typeof basePackageJson.universalIdentifier).toBe('string');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createAgentManifest', () => {
|
||||
it('should create a valid agent manifest with correct structure', () => {
|
||||
const appName = 'my-test-app';
|
||||
const agent = createAgentManifest(appName);
|
||||
|
||||
expect(agent).toEqual({
|
||||
$schema: AGENT_SCHEMA_URL,
|
||||
standardId: 'mocked-uuid-12345',
|
||||
name: 'myTestAppAgent',
|
||||
label: 'My Test App Agent',
|
||||
description: 'AI agent for my-test-app',
|
||||
prompt:
|
||||
'You are an AI agent for my-test-app. Help users with their tasks and provide assistance with Twenty CRM features.',
|
||||
modelId: 'auto',
|
||||
responseFormat: {
|
||||
type: 'text',
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
it('should handle single word app names', () => {
|
||||
const appName = 'calculator';
|
||||
const agent = createAgentManifest(appName);
|
||||
|
||||
expect(agent.name).toBe('calculatorAgent');
|
||||
expect(agent.label).toBe('Calculator Agent');
|
||||
});
|
||||
|
||||
it('should handle kebab-case app names correctly', () => {
|
||||
const appName = 'user-management-system';
|
||||
const agent = createAgentManifest(appName);
|
||||
|
||||
expect(agent.name).toBe('userManagementSystemAgent');
|
||||
expect(agent.label).toBe('User Management System Agent');
|
||||
});
|
||||
});
|
||||
|
||||
describe('createReadmeContent', () => {
|
||||
it('should generate correct README content', () => {
|
||||
const appName = 'my-awesome-app';
|
||||
const appDir = '/path/to/my-awesome-app';
|
||||
const readmeContent = createReadmeContent(appName, appDir);
|
||||
|
||||
expect(readmeContent).toContain('# my-awesome-app');
|
||||
expect(readmeContent).toContain('A Twenty application.');
|
||||
expect(readmeContent).toContain(
|
||||
'twenty app dev --path /path/to/my-awesome-app',
|
||||
);
|
||||
expect(readmeContent).toContain('cd /path/to/my-awesome-app');
|
||||
expect(readmeContent).toContain(
|
||||
'twenty app deploy --path /path/to/my-awesome-app',
|
||||
);
|
||||
});
|
||||
|
||||
it('should include development and deployment sections', () => {
|
||||
const readmeContent = createReadmeContent('test-app', '/test/path');
|
||||
|
||||
expect(readmeContent).toContain('## Development');
|
||||
expect(readmeContent).toContain('## Deployment');
|
||||
expect(readmeContent).toContain('To start development mode:');
|
||||
expect(readmeContent).toContain('To deploy the application:');
|
||||
});
|
||||
|
||||
it('should handle different app directories', () => {
|
||||
const appName = 'sample-app';
|
||||
const appDir = '/custom/directory/sample-app';
|
||||
const readmeContent = createReadmeContent(appName, appDir);
|
||||
|
||||
expect(readmeContent).toContain('/custom/directory/sample-app');
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,87 +1,82 @@
|
||||
import { randomUUID } from 'crypto';
|
||||
import { AgentManifest, PackageJson } from '../types/config.types';
|
||||
import { getSchemaUrls } from './schema-validator';
|
||||
import * as fs from 'fs-extra';
|
||||
import { BASE_APPLICATION_PROJECT_PATH } from '../constants/base-application-project-path';
|
||||
import { writeJsoncFile } from '../utils/jsonc-parser';
|
||||
import { join } from 'path';
|
||||
import path from 'path';
|
||||
|
||||
export const copyBaseApplicationProject = async ({
|
||||
appName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
}: {
|
||||
appName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
await fs.copy(BASE_APPLICATION_PROJECT_PATH, appDirectory);
|
||||
|
||||
await createBasePackageJson({
|
||||
appName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
});
|
||||
|
||||
await createReadmeContent({
|
||||
appName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
});
|
||||
};
|
||||
|
||||
const createBasePackageJson = async ({
|
||||
appName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
}: {
|
||||
appName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
const base = JSON.parse(await readBaseApplicationProjectFile('package.json'));
|
||||
|
||||
export const createBasePackageJson = (
|
||||
appName: string,
|
||||
description: string,
|
||||
): PackageJson => {
|
||||
const schemas = getSchemaUrls();
|
||||
|
||||
return {
|
||||
$schema: schemas.appManifest,
|
||||
universalIdentifier: randomUUID(),
|
||||
label: appName
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' '),
|
||||
engines: {
|
||||
node: '^24.5.0',
|
||||
npm: 'please-use-yarn',
|
||||
yarn: '>=4.0.2',
|
||||
},
|
||||
packageManager: '[email protected]',
|
||||
description,
|
||||
license: 'MIT',
|
||||
version: '0.0.1',
|
||||
};
|
||||
base['$schema'] = schemas.appManifest;
|
||||
base['universalIdentifier'] = randomUUID();
|
||||
base['name'] = appName
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ');
|
||||
base['description'] = appDescription;
|
||||
|
||||
await writeJsoncFile(join(appDirectory, 'package.json'), base);
|
||||
};
|
||||
|
||||
export const createAgentManifest = (appName: string): AgentManifest => {
|
||||
const schemas = getSchemaUrls();
|
||||
const createReadmeContent = async ({
|
||||
appName,
|
||||
appDescription,
|
||||
appDirectory,
|
||||
}: {
|
||||
appName: string;
|
||||
appDescription: string;
|
||||
appDirectory: string;
|
||||
}) => {
|
||||
let readmeContent = await readBaseApplicationProjectFile('README.md');
|
||||
|
||||
return {
|
||||
$schema: schemas.agent,
|
||||
standardId: randomUUID(),
|
||||
name: `${appName.replace(/-([a-z])/g, (_, letter) => letter.toUpperCase())}Agent`,
|
||||
label: `${appName
|
||||
.split('-')
|
||||
.map((word) => word.charAt(0).toUpperCase() + word.slice(1))
|
||||
.join(' ')} Agent`,
|
||||
description: `AI agent for ${appName}`,
|
||||
prompt: `You are an AI agent for ${appName}. Help users with their tasks and provide assistance with Twenty CRM features.`,
|
||||
modelId: 'auto',
|
||||
responseFormat: {
|
||||
type: 'text',
|
||||
},
|
||||
};
|
||||
readmeContent = readmeContent.replace(/\{title}/g, appName);
|
||||
|
||||
readmeContent = readmeContent.replace(/\{description}/g, appDescription);
|
||||
|
||||
readmeContent = readmeContent.replace(/\{appDir}/g, appDirectory);
|
||||
|
||||
await fs.writeFile(path.join(appDirectory, 'README.md'), readmeContent);
|
||||
};
|
||||
|
||||
export const createGitignoreContent = () => {
|
||||
return `node_modules
|
||||
.yarn/install-state.gz
|
||||
`;
|
||||
};
|
||||
|
||||
export const createReadmeContent = (
|
||||
appName: string,
|
||||
appDir: string,
|
||||
): string => {
|
||||
return `# ${appName}
|
||||
|
||||
A Twenty application.
|
||||
|
||||
## Development
|
||||
|
||||
To start development mode:
|
||||
|
||||
\`\`\`bash
|
||||
twenty app dev --path ${appDir}
|
||||
\`\`\`
|
||||
|
||||
Or from the app directory:
|
||||
|
||||
\`\`\`bash
|
||||
cd ${appDir}
|
||||
twenty app dev
|
||||
\`\`\`
|
||||
|
||||
## Deployment
|
||||
|
||||
To deploy the application:
|
||||
|
||||
\`\`\`bash
|
||||
twenty app deploy --path ${appDir}
|
||||
\`\`\`
|
||||
`;
|
||||
const readBaseApplicationProjectFile = async (fileName: string) => {
|
||||
return await fs.readFile(
|
||||
join(BASE_APPLICATION_PROJECT_PATH, fileName),
|
||||
'utf-8',
|
||||
);
|
||||
};
|
||||
|
||||
@@ -524,6 +524,7 @@ export class ApplicationSyncService {
|
||||
settings: {
|
||||
eventName: triggerToCreate.eventName,
|
||||
},
|
||||
universalIdentifier: triggerToCreate.universalIdentifier,
|
||||
serverlessFunctionId,
|
||||
};
|
||||
|
||||
@@ -634,6 +635,7 @@ export class ApplicationSyncService {
|
||||
settings: {
|
||||
pattern: triggerToCreate.schedule,
|
||||
},
|
||||
universalIdentifier: triggerToCreate.universalIdentifier,
|
||||
serverlessFunctionId,
|
||||
};
|
||||
|
||||
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
@@ -16,4 +16,7 @@ export class CreateCronTriggerInput {
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
serverlessFunctionId: string;
|
||||
|
||||
@HideField()
|
||||
universalIdentifier?: string;
|
||||
}
|
||||
|
||||
+3
-3
@@ -1,4 +1,4 @@
|
||||
import { v4 as uuidv4 } from 'uuid';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { type CreateCronTriggerInput } from 'src/engine/metadata-modules/cron-trigger/dtos/create-cron-trigger.input';
|
||||
import { type FlatCronTrigger } from 'src/engine/metadata-modules/cron-trigger/types/flat-cron-trigger.type';
|
||||
@@ -13,8 +13,8 @@ export const fromCreateCronTriggerInputToFlatCronTrigger = ({
|
||||
const now = new Date();
|
||||
|
||||
return {
|
||||
id: uuidv4(),
|
||||
universalIdentifier: uuidv4(),
|
||||
id: v4(),
|
||||
universalIdentifier: createCronTriggerInput.universalIdentifier ?? v4(),
|
||||
settings: createCronTriggerInput.settings,
|
||||
serverlessFunctionId: createCronTriggerInput.serverlessFunctionId,
|
||||
workspaceId,
|
||||
|
||||
+4
-1
@@ -1,4 +1,4 @@
|
||||
import { Field, InputType } from '@nestjs/graphql';
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import { IsNotEmpty, IsObject, IsUUID } from 'class-validator';
|
||||
import GraphQLJSON from 'graphql-type-json';
|
||||
@@ -16,4 +16,7 @@ export class CreateDatabaseEventTriggerInput {
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
serverlessFunctionId: string;
|
||||
|
||||
@HideField()
|
||||
universalIdentifier?: string;
|
||||
}
|
||||
|
||||
+2
-1
@@ -14,7 +14,8 @@ export const fromCreateDatabaseEventTriggerInputToFlatDatabaseEventTrigger = ({
|
||||
|
||||
return {
|
||||
id: uuidV4(),
|
||||
universalIdentifier: uuidV4(),
|
||||
universalIdentifier:
|
||||
createDatabaseEventTriggerInput.universalIdentifier ?? uuidV4(),
|
||||
settings: createDatabaseEventTriggerInput.settings,
|
||||
serverlessFunctionId: createDatabaseEventTriggerInput.serverlessFunctionId,
|
||||
workspaceId,
|
||||
|
||||
@@ -27840,15 +27840,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-cursor@npm:^5.0.0":
|
||||
version: 5.0.0
|
||||
resolution: "cli-cursor@npm:5.0.0"
|
||||
dependencies:
|
||||
restore-cursor: "npm:^5.0.0"
|
||||
checksum: 10c0/7ec62f69b79f6734ab209a3e4dbdc8af7422d44d360a7cb1efa8a0887bbe466a6e625650c466fe4359aee44dbe2dc0b6994b583d40a05d0808a5cb193641d220
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-highlight@npm:^2.1.11":
|
||||
version: 2.1.11
|
||||
resolution: "cli-highlight@npm:2.1.11"
|
||||
@@ -27881,7 +27872,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2":
|
||||
"cli-spinners@npm:^2.5.0":
|
||||
version: 2.9.2
|
||||
resolution: "cli-spinners@npm:2.9.2"
|
||||
checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3
|
||||
@@ -34157,13 +34148,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-east-asian-width@npm:^1.0.0":
|
||||
version: 1.4.0
|
||||
resolution: "get-east-asian-width@npm:1.4.0"
|
||||
checksum: 10c0/4e481d418e5a32061c36fbb90d1b225a254cc5b2df5f0b25da215dcd335a3c111f0c2023ffda43140727a9cafb62dac41d022da82c08f31083ee89f714ee3b83
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"get-func-name@npm:^2.0.1, get-func-name@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "get-func-name@npm:2.0.2"
|
||||
@@ -37083,13 +37067,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-interactive@npm:^2.0.0":
|
||||
version: 2.0.0
|
||||
resolution: "is-interactive@npm:2.0.0"
|
||||
checksum: 10c0/801c8f6064f85199dc6bf99b5dd98db3282e930c3bc197b32f2c5b89313bb578a07d1b8a01365c4348c2927229234f3681eb861b9c2c92bee72ff397390fa600
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-lambda@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "is-lambda@npm:1.0.1"
|
||||
@@ -37412,20 +37389,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-unicode-supported@npm:^1.2.0, is-unicode-supported@npm:^1.3.0":
|
||||
"is-unicode-supported@npm:^1.2.0":
|
||||
version: 1.3.0
|
||||
resolution: "is-unicode-supported@npm:1.3.0"
|
||||
checksum: 10c0/b8674ea95d869f6faabddc6a484767207058b91aea0250803cbf1221345cb0c56f466d4ecea375dc77f6633d248d33c47bd296fb8f4cdba0b4edba8917e83d8a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-unicode-supported@npm:^2.0.0":
|
||||
version: 2.1.0
|
||||
resolution: "is-unicode-supported@npm:2.1.0"
|
||||
checksum: 10c0/a0f53e9a7c1fdbcf2d2ef6e40d4736fdffff1c9f8944c75e15425118ff3610172c87bf7bc6c34d3903b04be59790bb2212ddbe21ee65b5a97030fc50370545a5
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"is-upper-case@npm:^2.0.2":
|
||||
version: 2.0.2
|
||||
resolution: "is-upper-case@npm:2.0.2"
|
||||
@@ -40214,16 +40184,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"log-symbols@npm:^6.0.0":
|
||||
version: 6.0.0
|
||||
resolution: "log-symbols@npm:6.0.0"
|
||||
dependencies:
|
||||
chalk: "npm:^5.3.0"
|
||||
is-unicode-supported: "npm:^1.3.0"
|
||||
checksum: 10c0/36636cacedba8f067d2deb4aad44e91a89d9efb3ead27e1846e7b82c9a10ea2e3a7bd6ce28a7ca616bebc60954ff25c67b0f92d20a6a746bb3cc52c3701891f6
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"log-update@npm:^4.0.0":
|
||||
version: 4.0.0
|
||||
resolution: "log-update@npm:4.0.0"
|
||||
@@ -42505,13 +42465,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-function@npm:^5.0.0":
|
||||
version: 5.0.1
|
||||
resolution: "mimic-function@npm:5.0.1"
|
||||
checksum: 10c0/f3d9464dd1816ecf6bdf2aec6ba32c0728022039d992f178237d8e289b48764fee4131319e72eedd4f7f094e22ded0af836c3187a7edc4595d28dd74368fd81d
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"mimic-response@npm:^1.0.0, mimic-response@npm:^1.0.1":
|
||||
version: 1.0.1
|
||||
resolution: "mimic-response@npm:1.0.1"
|
||||
@@ -44419,15 +44372,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"onetime@npm:^7.0.0":
|
||||
version: 7.0.0
|
||||
resolution: "onetime@npm:7.0.0"
|
||||
dependencies:
|
||||
mimic-function: "npm:^5.0.0"
|
||||
checksum: 10c0/5cb9179d74b63f52a196a2e7037ba2b9a893245a5532d3f44360012005c9cadb60851d56716ebff18a6f47129dab7168022445df47c2aff3b276d92585ed1221
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"only@npm:~0.0.2":
|
||||
version: 0.0.2
|
||||
resolution: "only@npm:0.0.2"
|
||||
@@ -44569,23 +44513,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"ora@npm:^8.0.0":
|
||||
version: 8.2.0
|
||||
resolution: "ora@npm:8.2.0"
|
||||
dependencies:
|
||||
chalk: "npm:^5.3.0"
|
||||
cli-cursor: "npm:^5.0.0"
|
||||
cli-spinners: "npm:^2.9.2"
|
||||
is-interactive: "npm:^2.0.0"
|
||||
is-unicode-supported: "npm:^2.0.0"
|
||||
log-symbols: "npm:^6.0.0"
|
||||
stdin-discarder: "npm:^0.2.2"
|
||||
string-width: "npm:^7.2.0"
|
||||
strip-ansi: "npm:^7.1.0"
|
||||
checksum: 10c0/7d9291255db22e293ea164f520b6042a3e906576ab06c9cf408bf9ef5664ba0a9f3bd258baa4ada058cfcc2163ef9b6696d51237a866682ce33295349ba02c3a
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"orderedmap@npm:^2.0.0":
|
||||
version: 2.1.1
|
||||
resolution: "orderedmap@npm:2.1.1"
|
||||
@@ -48722,16 +48649,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"restore-cursor@npm:^5.0.0":
|
||||
version: 5.1.0
|
||||
resolution: "restore-cursor@npm:5.1.0"
|
||||
dependencies:
|
||||
onetime: "npm:^7.0.0"
|
||||
signal-exit: "npm:^4.1.0"
|
||||
checksum: 10c0/c2ba89131eea791d1b25205bdfdc86699767e2b88dee2a590b1a6caa51737deac8bad0260a5ded2f7c074b7db2f3a626bcf1fcf3cdf35974cbeea5e2e6764f60
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"restructure@npm:^3.0.0":
|
||||
version: 3.0.2
|
||||
resolution: "restructure@npm:3.0.2"
|
||||
@@ -50476,13 +50393,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stdin-discarder@npm:^0.2.2":
|
||||
version: 0.2.2
|
||||
resolution: "stdin-discarder@npm:0.2.2"
|
||||
checksum: 10c0/c78375e82e956d7a64be6e63c809c7f058f5303efcaf62ea48350af072bacdb99c06cba39209b45a071c1acbd49116af30df1df9abb448df78a6005b72f10537
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"stop-iteration-iterator@npm:^1.0.0, stop-iteration-iterator@npm:^1.1.0":
|
||||
version: 1.1.0
|
||||
resolution: "stop-iteration-iterator@npm:1.1.0"
|
||||
@@ -50737,17 +50647,6 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string-width@npm:^7.2.0":
|
||||
version: 7.2.0
|
||||
resolution: "string-width@npm:7.2.0"
|
||||
dependencies:
|
||||
emoji-regex: "npm:^10.3.0"
|
||||
get-east-asian-width: "npm:^1.0.0"
|
||||
strip-ansi: "npm:^7.1.0"
|
||||
checksum: 10c0/eb0430dd43f3199c7a46dcbf7a0b34539c76fe3aa62763d0b0655acdcbdf360b3f66f3d58ca25ba0205f42ea3491fa00f09426d3b7d3040e506878fc7664c9b9
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"string.prototype.includes@npm:^2.0.1":
|
||||
version: 2.0.1
|
||||
resolution: "string.prototype.includes@npm:2.0.1"
|
||||
@@ -52435,6 +52334,7 @@ __metadata:
|
||||
"@types/fs-extra": "npm:^11.0.0"
|
||||
"@types/inquirer": "npm:^9.0.0"
|
||||
"@types/jest": "npm:^29.5.0"
|
||||
"@types/lodash.kebabcase": "npm:^4.1.7"
|
||||
"@types/node": "npm:^20.0.0"
|
||||
ajv: "npm:^8.12.0"
|
||||
ajv-formats: "npm:^2.1.1"
|
||||
@@ -52447,11 +52347,9 @@ __metadata:
|
||||
inquirer: "npm:^10.0.0"
|
||||
jest: "npm:^29.5.0"
|
||||
jsonc-parser: "npm:^3.2.0"
|
||||
ora: "npm:^8.0.0"
|
||||
lodash.kebabcase: "npm:^4.1.1"
|
||||
tsx: "npm:^4.7.0"
|
||||
typescript: "npm:^5.3.0"
|
||||
yaml: "npm:^2.4.0"
|
||||
zod: "npm:^4.1.11"
|
||||
bin:
|
||||
twenty: dist/cli.js
|
||||
languageName: unknown
|
||||
@@ -55947,7 +55845,7 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.4.0, yaml@npm:^2.4.5, yaml@npm:^2.6.0":
|
||||
"yaml@npm:^2.2.1, yaml@npm:^2.2.2, yaml@npm:^2.4.5, yaml@npm:^2.6.0":
|
||||
version: 2.8.1
|
||||
resolution: "yaml@npm:2.8.1"
|
||||
bin:
|
||||
|
||||
Reference in New Issue
Block a user