Files
twenty/packages/twenty-sdk/src/cli/services/generate.service.ts
T
martmull e289f3056e 1895 extensibility v1 application tokens 3 (#16504)
- moves applicationRoleId to application entity
- add new `APPLICATION` FieldActorSource and `APPLICATION`
JwtTokenTypeEnum value
- create a new token with applicationId when executing a function
- when applicationId is in token, check for application.defaultRole
permissions
-use twenty-shared types in `twenty-sdk/application`
- create a new import from generate called "Twenty" that you can use
directly without having to set TWENTY_API_KEY AND TWENTY_API_URL (keep
metadata or core parameter only)
- provide to serverless unique one time BEARER TOKEN to run it

Result
<img width="977" height="566" alt="image"
src="https://github.com/user-attachments/assets/e78428a0-5b13-4975-aa13-58ee3b32450c"
/>

<img width="910" height="596" alt="image"
src="https://github.com/user-attachments/assets/6ec72bf5-7655-4093-a45e-ad269595a324"
/>

<img width="741" height="568" alt="image"
src="https://github.com/user-attachments/assets/7683944c-fd79-4417-8fb2-8e4815cc112f"
/>
2025-12-15 17:44:23 +01:00

117 lines
2.9 KiB
TypeScript

import { generate } from '@genql/cli';
import chalk from 'chalk';
import { join, resolve } from 'path';
import { ApiService } from '@/cli/services/api.service';
import { ConfigService } from '@/cli/services/config.service';
import * as fs from 'fs-extra';
import {
DEFAULT_API_URL_NAME,
DEFAULT_API_KEY_NAME,
} from 'twenty-shared/application';
export const GENERATED_FOLDER_NAME = 'generated';
export class GenerateService {
private configService: ConfigService;
private apiService: ApiService;
constructor() {
this.configService = new ConfigService();
this.apiService = new ApiService();
}
async generateClient(appPath: string): Promise<void> {
const outputPath = join(appPath, GENERATED_FOLDER_NAME);
console.log(chalk.blue('📦 Generating Twenty client...'));
console.log(chalk.gray(`📁 Output Path: ${outputPath}`));
console.log('');
const config = await this.configService.getConfig();
const url = config.apiUrl;
const token = config.apiKey;
if (!url || !token) {
console.log(
chalk.yellow(
'⚠️ Skipping Client generation: API URL or token not configured',
),
);
return;
}
console.log(chalk.gray(`API URL: ${url}`));
console.log(chalk.gray(`Output: ${outputPath}`));
const getSchemaResponse = await this.apiService.getSchema();
if (!getSchemaResponse.success) {
return;
}
const { data: schema } = getSchemaResponse;
const output = resolve(outputPath);
await generate({
schema,
output,
scalarTypes: {
DateTime: 'string',
JSON: 'Record<string, unknown>',
UUID: 'string',
},
});
await this.injectTwentyClient(output);
console.log(chalk.green('✓ Client generated successfully!'));
console.log(chalk.gray(`Generated files at: ${outputPath}`));
}
private async injectTwentyClient(output: string) {
const twentyClientContent = `
// ----------------------------------------------------
// ✨ Custom Twenty client (auto-injected)
// ----------------------------------------------------
const defaultOptions: ClientOptions = {
url: \`\${process.env.${DEFAULT_API_URL_NAME}}/graphql\`,
headers: {
'Content-Type': 'application/json',
Authorization: \`Bearer \${process.env.${DEFAULT_API_KEY_NAME}}\`,
},
}
export default class Twenty {
private client: Client;
constructor(options?: ClientOptions) {
const merged: ClientOptions = {
...defaultOptions,
...options,
headers: {
...defaultOptions.headers,
...(options?.headers ?? {}),
},
};
this.client = createClient(merged);
}
query<R extends QueryGenqlSelection>(request: R & { __name?: string }) {
return this.client.query(request);
}
mutation<R extends MutationGenqlSelection>(request: R & { __name?: string }) {
return this.client.mutation(request);
}
}
`;
await fs.appendFile(join(output, 'index.ts'), twentyClientContent);
}
}