Dynamic grql api wrapper on application sync (#15791)

# Introduction

Important note: for the moment testing this locally will require some
hack due to latest twenty-sdk not being published.
You will need to build twenty-cli and `cd packages/twenty-cli && yarn
link`
To finally sync the app in your app folder as `cd app-folder && twenty
app sync`

close https://github.com/twentyhq/core-team-issues/issues/1863

In this PR is introduced the generate sdk programmatic call to
[genql](https://genql.dev/) exposed in a `client` barrel of `twenty-sdk`
located in this package as there's high chances that will add a codegen
layer above it at some point ?

The cli calls this method after a sync application and writes a client
in a generated folder. It will make a graql introspection query on the
whole workspace. We should later improve that and only filter by current
applicationId and its dependencies ( when twenty-standard application is
introduced )

Fully typesafe ( input, output, filters etc ) auto-completed client

## Hello-world app serverless refactor

<img width="2480" height="1326" alt="image"
src="https://github.com/user-attachments/assets/b18ea372-b21d-4560-8fbc-1dc348427a95"
/>

---------

Co-authored-by: martmull <martmull@hotmail.fr>
This commit is contained in:
Paul Rastoin 2025-11-17 14:46:59 +01:00 committed by GitHub
parent a39efeb1ab
commit 2a44bde848
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
31 changed files with 874 additions and 242 deletions

1
packages/twenty-apps/.gitignore vendored Normal file
View file

@ -0,0 +1 @@
generated

View file

@ -1 +1,2 @@
TWENTY_API_KEY=<SET_YOUR_TWENTY_API>
TWENTY_API_KEY=<SET_YOUR_TWENTY_API_KEY>
TWENTY_API_URL=<SET_YOUR_TWENTY_API_URL>

View file

@ -18,7 +18,7 @@ This example will gradually gain complexity and capabilities as the twenty-cli m
cp .env.example .env
```
- replace `<SET_YOUR_TWENTY_API>` with your apiKey
- replace `<SET_YOUR_TWENTY_API_KEY>` and `<SET_YOUR_TWENTY_API_URL>` accordingly
```bash
twenty auth login

View file

@ -11,6 +11,11 @@ const config: ApplicationConfig = {
description: 'Twenty API Key',
isSecret: true,
},
TWENTY_API_URL: {
universalIdentifier: 'ef8ab489-e68a-4841-b402-261f440e6185',
description: 'Twenty API Url',
isSecret: false,
},
},
};

View file

@ -9,7 +9,6 @@
},
"packageManager": "yarn@4.9.2",
"dependencies": {
"axios": "^1.12.2",
"twenty-sdk": "0.0.5"
},
"devDependencies": {

View file

@ -1,25 +1,27 @@
import axios from 'axios';
import { type ServerlessFunctionConfig } from 'twenty-sdk/application';
import { createClient } from '../../generated';
export const main = async (params: { recipient: string }): Promise<string> => {
const { recipient } = params;
const options = {
method: 'POST',
url: 'http://localhost:3000/rest/postCards',
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.TWENTY_API_KEY}`,
},
data: { name: recipient ?? 'Unknown' },
};
export const main = async (params: { recipient?: string }) => {
try {
const { data } = await axios.request(options);
console.log(`New post card to "${recipient}" created`);
return data;
const client = createClient({
url: `${process.env.TWENTY_API_URL}/graphql`,
headers: {
'Content-Type': 'application/json',
Authorization: `Bearer ${process.env.TWENTY_API_KEY}`,
},
});
const createPostCard = await client.mutation({
createPostCard: {
__args: {
data: {
name: params.recipient ?? 'Hello-world',
},
},
name: true,
id: true,
},
});
return createPostCard;
} catch (error) {
console.error(error);
throw error;

View file

@ -19,12 +19,7 @@
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true
"resolveJsonModule": true,
},
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts"
]
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}

View file

@ -224,7 +224,6 @@ __metadata:
dependencies:
"@types/node": "npm:^24.7.2"
axios: "npm:^1.12.2"
twenty-sdk: "npm:^0.0.4"
languageName: unknown
linkType: soft
@ -258,13 +257,6 @@ __metadata:
languageName: node
linkType: hard
"twenty-sdk@npm:^0.0.4":
version: 0.0.4
resolution: "twenty-sdk@npm:0.0.4"
checksum: 10c0/550f1d85bf0701396c9dd2d4c6bc55ba1b067fce13636f8540eec60ab6a4257c6d7cd86cb3f62e0974bf99467bc31270d92b17b9681a1b7a6281b7ef97224080
languageName: node
linkType: hard
"undici-types@npm:~7.16.0":
version: 7.16.0
resolution: "undici-types@npm:7.16.0"

View file

@ -36,7 +36,7 @@ yarn add axios
# Start dev mode: automatically syncs changes to your Twenty workspace, so you can test new functions/objects instantly.
twenty app dev
# Or use one time sync
# Or use one time sync (also generates SDK automatically)
twenty app sync
# List all available commands
@ -82,5 +82,5 @@ Our team reviews contributions for quality, security, and reusability before mer
## Contributing
- see our [Hacktoberfest 2025 notion page](https://twentycrm.notion.site/Hacktoberfest-27711d8417038037a149d4638a9cc510)
- see our [Hacktoberfest 2025 notion page](https://twentycrm.notion.site/Hacktoberfest-27711d8417038037a149d4638a9cc510)
- our [Discord](https://discord.gg/cx5n4Jzs57)

View file

@ -1,6 +1,6 @@
{
"name": "twenty-cli",
"version": "0.2.1",
"version": "0.2.2",
"description": "Command-line interface for Twenty application development",
"main": "dist/cli.js",
"bin": {
@ -25,6 +25,7 @@
],
"license": "AGPL-3.0",
"dependencies": {
"@genql/cli": "^3.0.3",
"ajv": "^8.12.0",
"ajv-formats": "^2.1.1",
"axios": "^1.6.0",
@ -33,6 +34,7 @@
"commander": "^12.0.0",
"dotenv": "^16.4.0",
"fs-extra": "^11.2.0",
"graphql": "^16.8.1",
"inquirer": "^10.0.0",
"jsonc-parser": "^3.2.0",
"lodash.camelcase": "^4.3.0",
@ -51,7 +53,6 @@
"@types/node": "^20.0.0",
"jest": "^29.5.0",
"tsx": "^4.7.0",
"twenty-sdk": "workspace:*",
"wait-on": "^7.2.0"
},
"engines": {

View file

@ -10,7 +10,8 @@
"options": {
"cwd": "packages/twenty-cli",
"commands": ["rimraf dist", "tsc --project tsconfig.lib.json"]
}
},
"dependsOn": ["^build"]
},
"build": {
"executor": "nx:run-commands",
@ -22,7 +23,7 @@
"cp -R src/constants/schemas dist/constants"
]
},
"dependsOn": ["^build", "before-build"]
"dependsOn": ["before-build"]
},
"dev": {
"executor": "nx:run-commands",
@ -45,7 +46,8 @@
"options": {
"cwd": "packages/twenty-cli",
"command": "tsc --noEmit --project tsconfig.lib.json"
}
},
"dependsOn": ["build"]
},
"lint": {
"options": {

View file

@ -1,11 +1,10 @@
import chalk from 'chalk';
import * as chokidar from 'chokidar';
import { ApiService } from '../services/api.service';
import { CURRENT_EXECUTION_DIRECTORY } from '../constants/current-execution-directory';
import { loadManifest } from '../utils/load-manifest';
import { AppSyncCommand } from './app-sync.command';
export class AppDevCommand {
private apiService = new ApiService();
private syncCommand = new AppSyncCommand();
async execute(options: {
appPath?: string;
@ -18,13 +17,7 @@ export class AppDevCommand {
this.logStartupInfo(appPath, debounceMs);
const { manifest, packageJson, yarnLock } = await loadManifest(appPath);
await this.apiService.syncApplication({
manifest,
packageJson,
yarnLock,
});
await this.syncCommand.execute(appPath);
const watcher = this.setupFileWatcher(appPath, debounceMs);
@ -64,13 +57,7 @@ export class AppDevCommand {
timeout = setTimeout(async () => {
console.log(chalk.blue('🔄 Changes detected, syncing...'));
const { manifest, packageJson, yarnLock } = await loadManifest(appPath);
await this.apiService.syncApplication({
manifest,
packageJson,
yarnLock,
});
await this.syncCommand.execute(appPath);
console.log(
chalk.gray('👀 Watching for changes... (Press Ctrl+C to stop)'),

View file

@ -0,0 +1,19 @@
import chalk from 'chalk';
import { GenerateService } from '../services/generate.service';
import { CURRENT_EXECUTION_DIRECTORY } from '../constants/current-execution-directory';
export class AppGenerateCommand {
private generateService = new GenerateService();
async execute(appPath: string = CURRENT_EXECUTION_DIRECTORY) {
try {
await this.generateService.generateClient(appPath);
} catch (error) {
console.error(
chalk.red('Generate Twenty client failed:'),
error instanceof Error ? error.message : error,
);
throw error;
}
}
}

View file

@ -3,11 +3,12 @@ import { CURRENT_EXECUTION_DIRECTORY } from '../constants/current-execution-dire
import { ApiService } from '../services/api.service';
import { ApiResponse } from '../types/config.types';
import { loadManifest } from '../utils/load-manifest';
import { GenerateService } from '../services/generate.service';
export class AppSyncCommand {
private apiService = new ApiService();
private generateService = new GenerateService();
// TODO improve typing
async execute(
appPath: string = CURRENT_EXECUTION_DIRECTORY,
): Promise<ApiResponse<any>> {
@ -16,21 +17,7 @@ export class AppSyncCommand {
console.log(chalk.gray(`📁 App Path: ${appPath}`));
console.log('');
const { manifest, packageJson, yarnLock } = await loadManifest(appPath);
const result = await this.apiService.syncApplication({
manifest,
packageJson,
yarnLock,
});
if (!result.success) {
console.error(chalk.red('❌ Sync failed:'), result.error);
} else {
console.log(chalk.green('✅ Application synced successfully'));
}
return result;
return await this.synchronize({ appPath });
} catch (error) {
console.error(
chalk.red('Sync failed:'),
@ -39,4 +26,38 @@ export class AppSyncCommand {
throw error;
}
}
private async synchronize({ appPath }: { appPath: string }) {
const { manifest, packageJson, yarnLock, isTwentyClientUsed } =
await loadManifest(appPath);
let serverlessSyncResult = await this.apiService.syncApplication({
manifest,
packageJson,
yarnLock,
});
if (isTwentyClientUsed) {
await this.generateService.generateClient(appPath);
const { manifest: manifestWithClient } = await loadManifest(appPath);
serverlessSyncResult = await this.apiService.syncApplication({
manifest: manifestWithClient,
packageJson,
yarnLock,
});
}
if (!serverlessSyncResult.success) {
console.error(
chalk.red('❌ Serverless functions Sync failed:'),
serverlessSyncResult.error,
);
} else {
console.log(chalk.green('✅ Serverless functions synced successfully'));
}
return serverlessSyncResult;
}
}

View file

@ -10,6 +10,7 @@ import { AppDevCommand } from './app-dev.command';
import { AppInitCommand } from './app-init.command';
import { AppSyncCommand } from './app-sync.command';
import { formatPath } from '../utils/format-path';
import { AppGenerateCommand } from './app-generate.command';
export class AppCommand {
private devCommand = new AppDevCommand();
@ -17,6 +18,7 @@ export class AppCommand {
private deleteCommand = new AppDeleteCommand();
private initCommand = new AppInitCommand();
private addCommand = new AppAddCommand();
private generateCommand = new AppGenerateCommand();
getCommand(): Command {
const appCommand = new Command('app');
@ -96,6 +98,13 @@ export class AppCommand {
await this.addCommand.execute(entityType as SyncableEntity);
});
appCommand
.command('generate [outputPath]')
.description('Generate Twenty client')
.action(async (appPath?: string) => {
await this.generateCommand.execute(formatPath(appPath));
});
return appCommand;
}
}

View file

@ -20,12 +20,8 @@
"lib": ["es2020", "dom"],
"skipLibCheck": true,
"skipDefaultLibCheck": true,
"resolveJsonModule": true
"resolveJsonModule": true,
},
"exclude": [
"node_modules",
"dist",
"**/*.test.ts",
"**/*.spec.ts"
]
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts"]
}

View file

@ -6,6 +6,11 @@ import {
type PackageJson,
} from '../types/config.types';
import { ConfigService } from './config.service';
import {
buildClientSchema,
getIntrospectionQuery,
printSchema,
} from 'graphql/index';
export class ApiService {
private client: AxiosInstance;
@ -188,4 +193,48 @@ export class ApiService {
throw error;
}
}
async getSchema(): Promise<ApiResponse<string>> {
try {
const introspectionQuery = getIntrospectionQuery();
const response = await this.client.post(
'/graphql',
{
query: introspectionQuery,
},
{
headers: {
'Content-Type': 'application/json',
Accept: '*/*',
},
},
);
if (response.data.errors) {
return {
success: false,
error: `GraphQL introspection errors: ${JSON.stringify(response.data.errors)}`,
};
}
const schema = buildClientSchema(response.data.data);
return {
success: true,
data: printSchema(schema),
message: 'Successfully load schema',
};
} catch (error) {
if (axios.isAxiosError(error) && error.response) {
return {
success: false,
error:
error.response.data.errors[0]?.message ||
'Failed to load graphql Schema',
};
}
throw error;
}
}
}

View file

@ -0,0 +1,57 @@
import chalk from 'chalk';
import { generate } from '@genql/cli';
import { join, resolve } from 'path';
import { ConfigService } from './config.service';
import { ApiService } from './api.service';
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 { data: schema } = await this.apiService.getSchema();
await generate({
schema,
output: resolve(outputPath),
scalarTypes: {
DateTime: 'string',
JSON: 'Record<string, unknown>',
UUID: 'string',
},
verbose: true,
});
console.log(chalk.green('✓ Client generated successfully!'));
console.log(chalk.gray(`Generated files at: ${outputPath}`));
}
}

View file

@ -1,6 +1,6 @@
import { ensureDirSync, removeSync, writeFileSync } from 'fs-extra';
import { tmpdir } from 'node:os';
import { join, resolve } from 'node:path';
import { ensureDirSync, writeFileSync, removeSync } from 'fs-extra';
import { copyBaseApplicationProject } from '../app-template';
import { loadManifest } from '../load-manifest';
@ -24,13 +24,13 @@ const tsLibMock = `declare module 'tslib' {
const twentySdkTypesMock = `
declare module 'twenty-sdk/application' {
export type SyncableEntityOptions = { universalIdentifier: string };
type ApplicationVariable = SyncableEntityOptions & {
value?: string;
description?: string;
isSecret?: boolean;
};
export type ApplicationConfig = SyncableEntityOptions & {
displayName?: string;
description?: string;
@ -44,27 +44,27 @@ declare module 'twenty-sdk/application' {
httpMethod: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE';
isAuthRequired: boolean;
};
type CronTrigger = {
type: 'cron';
pattern: string;
};
type DatabaseEventTrigger = {
type: 'databaseEvent';
eventName: string;
};
type ServerlessFunctionTrigger = SyncableEntityOptions &
(RouteTrigger | CronTrigger | DatabaseEventTrigger);
export type ServerlessFunctionConfig = SyncableEntityOptions & {
name?: string;
description?: string;
timeoutSeconds?: number;
triggers?: ServerlessFunctionTrigger[];
};
type ObjectMetadataOptions = SyncableEntityOptions & {
nameSingular: string;
namePlural: string;
@ -73,7 +73,7 @@ declare module 'twenty-sdk/application' {
description?: string;
icon?: string;
};
export const ObjectMetadata = (_: ObjectMetadataOptions): ClassDecorator => {
return () => {};
};
@ -412,11 +412,8 @@ export const format = async (params: any): Promise<any> => {
]);
});
it('fails fast if TS validation fails', async () => {
write(appDirectory, 'src/utils/broken.ts', `const x: number = 'oops';`);
await expect(loadManifest(appDirectory)).rejects.toThrow(
/TypeScript validation failed/,
);
it('manifest should contains typescript sources', async () => {
const { isTwentyClientUsed } = await loadManifest(appDirectory);
expect(isTwentyClientUsed).toBe(false);
});
});

View file

@ -0,0 +1,20 @@
import ts, { formatDiagnosticsWithColorAndContext, sys } from 'typescript';
export const formatAndWarnTsDiagnostics = ({
diagnostics,
}: {
diagnostics: ts.Diagnostic[];
}) => {
if (diagnostics.length > 0) {
const formattedDiagnostics = formatDiagnosticsWithColorAndContext(
diagnostics,
{
getCanonicalFileName: (f) => f,
getCurrentDirectory: sys.getCurrentDirectory,
getNewLine: () => sys.newLine,
},
);
console.warn(formattedDiagnostics);
}
};

View file

@ -0,0 +1,58 @@
import ts from 'typescript';
import { join } from 'path';
import {
createProgram,
formatDiagnosticsWithColorAndContext,
parseJsonConfigFileContent,
readConfigFile,
sys,
} from 'typescript';
const getProgramFromTsconfig = ({
appPath,
tsconfigPath = 'tsconfig.json',
}: {
appPath: string;
tsconfigPath?: string;
}) => {
const configFile = readConfigFile(join(appPath, tsconfigPath), sys.readFile);
if (configFile.error)
throw new Error(
formatDiagnosticsWithColorAndContext([configFile.error], {
getCanonicalFileName: (f) => f,
getCurrentDirectory: sys.getCurrentDirectory,
getNewLine: () => sys.newLine,
}),
);
const parsed = parseJsonConfigFileContent(configFile.config, sys, appPath);
if (parsed.errors.length) {
throw new Error(
formatDiagnosticsWithColorAndContext(parsed.errors, {
getCanonicalFileName: (f) => f,
getCurrentDirectory: sys.getCurrentDirectory,
getNewLine: () => sys.newLine,
}),
);
}
return createProgram(parsed.fileNames, parsed.options);
};
export const getTsProgramAndDiagnostics = async ({
appPath,
}: {
appPath: string;
}): Promise<{ program: ts.Program; diagnostics: ts.Diagnostic[] }> => {
const program = getProgramFromTsconfig({
appPath,
tsconfigPath: 'tsconfig.json',
});
return {
diagnostics: [
...program.getSyntacticDiagnostics(),
...program.getSemanticDiagnostics(),
...program.getGlobalDiagnostics(),
],
program,
};
};

View file

@ -1,52 +1,52 @@
import * as fs from 'fs-extra';
import { posix, relative, sep } from 'path';
import {
sys,
getDecorators,
readConfigFile,
parseJsonConfigFileContent,
formatDiagnosticsWithColorAndContext,
createProgram,
Decorator,
isPropertyAccessExpression,
isNumericLiteral,
SyntaxKind,
isArrayLiteralExpression,
Expression,
isPropertyAssignment,
isComputedPropertyName,
isStringLiteralLike,
isShorthandPropertyAssignment,
isIdentifier,
FunctionDeclaration,
VariableDeclaration,
Program,
Node,
isClassDeclaration,
isCallExpression,
isObjectLiteralExpression,
forEachChild,
SourceFile,
isVariableStatement,
isArrowFunction,
isFunctionExpression,
isExportAssignment,
Modifier,
isPropertyDeclaration,
Node,
Program,
SourceFile,
SyntaxKind,
VariableDeclaration,
forEachChild,
getDecorators,
isArrayLiteralExpression,
isArrowFunction,
isCallExpression,
isClassDeclaration,
isComputedPropertyName,
isExportAssignment,
isFunctionExpression,
isIdentifier,
isNoSubstitutionTemplateLiteral,
isNumericLiteral,
isObjectLiteralExpression,
isPropertyAccessExpression,
isPropertyAssignment,
isPropertyDeclaration,
isShorthandPropertyAssignment,
isStringLiteralLike,
isTemplateExpression,
isVariableStatement,
isImportDeclaration,
NamedImports,
} from 'typescript';
import {
AppManifest,
Application,
FieldMetadata,
ObjectManifest,
PackageJson,
ServerlessFunctionManifest,
Sources,
FieldMetadata,
} from '../types/config.types';
import { posix, relative, sep, resolve, join } from 'path';
import { parseJsoncFile, parseTextFile } from '../utils/jsonc-parser';
import { findPathFile } from '../utils/find-path-file';
import { parseJsoncFile, parseTextFile } from '../utils/jsonc-parser';
import { formatAndWarnTsDiagnostics } from './format-and-warn-ts-diagnostics';
import { getTsProgramAndDiagnostics } from '../utils/get-ts-program-and-diagnostics';
import { GENERATED_FOLDER_NAME } from '../services/generate.service';
type JSONValue =
| string
@ -56,32 +56,6 @@ type JSONValue =
| JSONValue[]
| { [k: string]: JSONValue };
const getProgramFromTsconfig = (
appPath: string,
tsconfigPath = 'tsconfig.json',
) => {
const configFile = readConfigFile(join(appPath, tsconfigPath), sys.readFile);
if (configFile.error)
throw new Error(
formatDiagnosticsWithColorAndContext([configFile.error], {
getCanonicalFileName: (f) => f,
getCurrentDirectory: sys.getCurrentDirectory,
getNewLine: () => sys.newLine,
}),
);
const parsed = parseJsonConfigFileContent(configFile.config, sys, appPath);
if (parsed.errors.length) {
throw new Error(
formatDiagnosticsWithColorAndContext(parsed.errors, {
getCanonicalFileName: (f) => f,
getCurrentDirectory: sys.getCurrentDirectory,
getNewLine: () => sys.newLine,
}),
);
}
return createProgram(parsed.fileNames, parsed.options);
};
const isDecoratorNamed = (node: Decorator, name: string): node is Decorator => {
const expr = node.expression;
if (isCallExpression(expr)) {
@ -392,23 +366,6 @@ const collectServerlessFunctions = (program: Program, appPath: string) => {
return serverlessFunctions;
};
const validateProgram = (program: Program) => {
const diagnostics = [
...program.getSyntacticDiagnostics(),
...program.getSemanticDiagnostics(),
...program.getGlobalDiagnostics(),
];
if (diagnostics.length > 0) {
const formatted = formatDiagnosticsWithColorAndContext(diagnostics, {
getCanonicalFileName: (f) => f,
getCurrentDirectory: sys.getCurrentDirectory,
getNewLine: () => sys.newLine,
});
throw new Error(`TypeScript validation failed:\n${formatted}`);
}
};
const setNested = (root: Sources, parts: string[], value: string) => {
let cur: Sources = root;
for (let i = 0; i < parts.length; i++) {
@ -423,14 +380,10 @@ const setNested = (root: Sources, parts: string[], value: string) => {
};
const loadFolderContentIntoJson = async (
sourcePath = '.',
tsconfigPath = 'tsconfig.json',
program: Program,
appPath: string,
): Promise<Sources> => {
const sources: Sources = {};
const baseAbs = resolve(sourcePath);
// Build the program from tsconfig (uses your getProgramFromTsconfig)
const program: Program = getProgramFromTsconfig(baseAbs, tsconfigPath);
// Iterate only files the TS program knows about.
for (const sf of program.getSourceFiles()) {
@ -438,7 +391,7 @@ const loadFolderContentIntoJson = async (
// Skip .d.ts and anything outside sourcePath
if (sf.isDeclarationFile) continue;
if (!abs.startsWith(baseAbs + sep) && abs !== baseAbs) continue;
if (!abs.startsWith(appPath + sep) && abs !== appPath) continue;
// Keep only TS/TSX files
if (!(abs.endsWith('.ts') || abs.endsWith('.tsx'))) continue;
@ -446,7 +399,7 @@ const loadFolderContentIntoJson = async (
// Optional extra guard (usually unnecessary if tsconfig excludes node_modules)
if (abs.includes(`${sep}node_modules${sep}`)) continue;
const relFromRoot = relative(baseAbs, abs);
const relFromRoot = relative(appPath, abs);
const parts = relFromRoot.split(sep);
const content = await fs.readFile(abs, 'utf8');
@ -495,15 +448,67 @@ export const extractTwentyAppConfig = (program: Program): Application => {
throw new Error('Could not find default exported ApplicationConfig');
};
const isTwentyClientUsedInProgram = (program: Program): boolean => {
for (const sf of program.getSourceFiles()) {
if (sf.isDeclarationFile) continue;
let found = false;
const visit = (node: Node): void => {
if (found) return;
if (isImportDeclaration(node)) {
const moduleSpecifier = node.moduleSpecifier;
if (isStringLiteralLike(moduleSpecifier)) {
const moduleText = moduleSpecifier.text;
// Match ../../generated, ../generated, ./foo/generated, etc.
const isGeneratedModule =
moduleText === GENERATED_FOLDER_NAME ||
moduleText.endsWith(`/${GENERATED_FOLDER_NAME}`);
if (
isGeneratedModule &&
node.importClause &&
node.importClause.namedBindings &&
node.importClause.namedBindings.kind === SyntaxKind.NamedImports
) {
const namedImports = node.importClause
.namedBindings as NamedImports;
for (const element of namedImports.elements) {
const importedName =
element.propertyName?.text ?? element.name.text;
if (importedName === 'createClient') {
found = true;
return;
}
}
}
}
}
forEachChild(node, visit);
};
visit(sf);
if (found) return true;
}
return false;
};
export const loadManifest = async (
path?: string,
appPath: string,
): Promise<{
packageJson: PackageJson;
yarnLock: string;
manifest: AppManifest;
isTwentyClientUsed: boolean;
}> => {
const appPath = path ?? process.cwd();
const packageJson = await parseJsoncFile(
await findPathFile(appPath, 'package.json'),
);
@ -512,17 +517,22 @@ export const loadManifest = async (
await findPathFile(appPath, 'yarn.lock'),
);
const program = getProgramFromTsconfig(appPath, 'tsconfig.json');
const { diagnostics, program } = await getTsProgramAndDiagnostics({
appPath,
});
validateProgram(program);
formatAndWarnTsDiagnostics({
diagnostics,
});
const [objects, serverlessFunctions, application, sources] =
await Promise.all([
Promise.resolve(collectObjects(program)),
Promise.resolve(collectServerlessFunctions(program, appPath)),
Promise.resolve(extractTwentyAppConfig(program)),
loadFolderContentIntoJson(appPath),
]);
const [objects, serverlessFunctions, application, sources] = [
collectObjects(program),
collectServerlessFunctions(program, appPath),
extractTwentyAppConfig(program),
await loadFolderContentIntoJson(program, appPath),
];
const isTwentyClientUsed = isTwentyClientUsedInProgram(program);
return {
packageJson,
@ -533,5 +543,6 @@ export const loadManifest = async (
serverlessFunctions,
sources,
},
isTwentyClientUsed,
};
};

View file

@ -3,6 +3,6 @@
"compilerOptions": {
"composite": true
},
"include": ["src/**/*"],
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"]
}

View file

@ -16,6 +16,6 @@
"declarationMap": true,
"sourceMap": true
},
"include": ["src/**/*"],
"include": ["src"],
"exclude": ["node_modules", "dist", "**/*.test.ts", "**/*.spec.ts", "**/*.e2e-spec.ts", "**/__tests__/**"]
}

View file

@ -0,0 +1,14 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"types": ["jest", "node"]
},
"include": [
"src/**/*",
"src/**/__tests__/**/*.spec.ts"
],
"exclude": [
"node_modules",
"dist",
]
}

View file

@ -13,9 +13,14 @@
"build": "vite build"
},
"devDependencies": {
"@types/node": "^24.0.0",
"typescript": "5.9.2",
"vite": "^7.0.0",
"vite-plugin-dts": "3.8.1"
"vite-plugin-dts": "3.8.1",
"vite-tsconfig-paths": "^4.2.1"
},
"dependencies": {
"twenty-shared": "workspace:*"
},
"exports": {
".": {

View file

@ -3,15 +3,10 @@
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "packages/twenty-sdk/src",
"projectType": "library",
"tags": [
"scope:sdk"
],
"tags": ["scope:sdk"],
"targets": {
"build": {
"dependsOn": [
"generateBarrels",
"^build"
],
"dependsOn": ["generateBarrels", "^build"],
"outputs": [
"{projectRoot}/dist",
"{projectRoot}/application/package.json",
@ -21,10 +16,7 @@
"generateBarrels": {
"executor": "nx:run-commands",
"cache": true,
"inputs": [
"production",
"{projectRoot}/scripts/generateBarrels.ts"
],
"inputs": ["production", "{projectRoot}/scripts/generateBarrels.ts"],
"outputs": [
"{projectRoot}/src/index.ts",
"{projectRoot}/src/*/index.ts",

View file

@ -1,4 +1,4 @@
export {
export type {
ActorMetadata as ActorField,
AddressMetadata as AddressField,
CurrencyMetadata as CurrencyField,
@ -6,5 +6,6 @@ export {
FullNameMetadata as FullNameField,
LinksMetadata as LinksField,
PhonesMetadata as PhonesField,
RichTextV2Metadata as RichTextField,
RichTextV2Metadata as RichTextField
} from 'twenty-shared/types';

View file

@ -8,7 +8,7 @@
*/
export type { ApplicationConfig } from './application-config';
export {
export type {
ActorField,
AddressField,
CurrencyField,

View file

@ -1,25 +1,77 @@
// @ts-ignore
import path from 'path';
import { defineConfig } from 'vite';
import dts from 'vite-plugin-dts';
import tsconfigPaths from 'vite-tsconfig-paths';
import packageJson from './package.json';
export default defineConfig({
resolve: {
alias: {
'@': path.resolve(__dirname, 'src'),
const moduleEntries = Object.keys((packageJson as any).exports || {})
.filter(
(key) => key !== './style.css' && key !== '.' && !key.startsWith('./src/'),
)
.map((module) => `src/${module.replace(/^\.\//, '')}/index.ts`);
const entries = ['src/index.ts', ...moduleEntries];
const entryFileNames = (chunk: any, extension: 'cjs' | 'mjs') => {
if (!chunk.isEntry) {
throw new Error(
`Should never occurs, encountered a non entry chunk ${chunk.facadeModuleId}`,
);
}
const splitFaceModuleId = chunk.facadeModuleId?.split('/');
if (splitFaceModuleId === undefined) {
throw new Error(
`Should never occurs splitFaceModuleId is undefined ${chunk.facadeModuleId}`,
);
}
const moduleDirectory = splitFaceModuleId[splitFaceModuleId?.length - 2];
if (moduleDirectory === 'src') {
return `${chunk.name}.${extension}`;
}
return `${moduleDirectory}.${extension}`;
};
export default defineConfig(() => {
const tsConfigPath = path.resolve(__dirname, './tsconfig.json');
return {
root: __dirname,
cacheDir: '../../node_modules/.vite/packages/twenty-sdk',
plugins: [
tsconfigPaths({
root: __dirname,
}),
dts({ entryRoot: './src', tsconfigPath: tsConfigPath }),
],
build: {
outDir: 'dist',
lib: { entry: entries, name: 'twenty-sdk' },
rollupOptions: {
external: [
...Object.keys((packageJson as any).dependencies || {}),
'path',
'fs',
'crypto',
'stream',
'util',
'os',
],
output: [
{
format: 'es',
entryFileNames: (chunk) => entryFileNames(chunk, 'mjs'),
},
{
format: 'cjs',
interop: 'auto',
esModule: true,
exports: 'named',
entryFileNames: (chunk) => entryFileNames(chunk, 'cjs'),
},
],
},
},
},
build: {
lib: {
entry: 'src/index.ts',
name: 'twenty-sdk',
formats: ['es', 'cjs'],
fileName: (format) => `index.${format === 'es' ? 'mjs' : 'cjs'}`,
},
},
plugins: [
dts({
entryRoot: 'src',
}),
],
logLevel: 'warn',
};
});

372
yarn.lock
View file

@ -6325,6 +6325,13 @@ __metadata:
languageName: node
linkType: hard
"@fastify/busboy@npm:^2.0.0":
version: 2.1.1
resolution: "@fastify/busboy@npm:2.1.1"
checksum: 10c0/6f8027a8cba7f8f7b736718b013f5a38c0476eea67034c94a0d3c375e2b114366ad4419e6a6fa7ffc2ef9c6d3e0435d76dd584a7a1cbac23962fda7650b579e3
languageName: node
linkType: hard
"@fig/complete-commander@npm:^3.0.0":
version: 3.2.0
resolution: "@fig/complete-commander@npm:3.2.0"
@ -6471,6 +6478,30 @@ __metadata:
languageName: node
linkType: hard
"@genql/cli@npm:^3.0.3":
version: 3.0.5
resolution: "@genql/cli@npm:3.0.5"
dependencies:
"@graphql-tools/graphql-file-loader": "npm:^7.5.11"
"@graphql-tools/load": "npm:^7.8.6"
fs-extra: "npm:^10.1.0"
graphql: "npm:^16.6.0"
kleur: "npm:^4.1.5"
listr: "npm:^0.14.3"
lodash: "npm:^4.17.21"
mkdirp: "npm:^0.5.1"
native-fetch: "npm:^4.0.2"
prettier: "npm:^2.8.0"
qs: "npm:^6.11.0"
rimraf: "npm:^2.6.3"
undici: "npm:^5.18.0"
yargs: "npm:^15.3.1"
bin:
genql: dist/cli.js
checksum: 10c0/646d4da8986741a8f1b610bd8cfb5bc26cd9a856de671461e4ca87fb05443f37dd0edfb3b30ceaa34cd049077873afa07febfd4714dd2e0681fb07484e19a080
languageName: node
linkType: hard
"@gitbeaker/core@npm:^38.12.1":
version: 38.12.1
resolution: "@gitbeaker/core@npm:38.12.1"
@ -7125,7 +7156,7 @@ __metadata:
languageName: node
linkType: hard
"@graphql-tools/graphql-file-loader@npm:^7.3.7, @graphql-tools/graphql-file-loader@npm:^7.5.0":
"@graphql-tools/graphql-file-loader@npm:^7.3.7, @graphql-tools/graphql-file-loader@npm:^7.5.0, @graphql-tools/graphql-file-loader@npm:^7.5.11":
version: 7.5.17
resolution: "@graphql-tools/graphql-file-loader@npm:7.5.17"
dependencies:
@ -7183,7 +7214,7 @@ __metadata:
languageName: node
linkType: hard
"@graphql-tools/load@npm:^7.5.5, @graphql-tools/load@npm:^7.8.0":
"@graphql-tools/load@npm:^7.5.5, @graphql-tools/load@npm:^7.8.0, @graphql-tools/load@npm:^7.8.6":
version: 7.8.14
resolution: "@graphql-tools/load@npm:7.8.14"
dependencies:
@ -19141,6 +19172,20 @@ __metadata:
languageName: node
linkType: hard
"@samverschueren/stream-to-observable@npm:^0.3.0":
version: 0.3.1
resolution: "@samverschueren/stream-to-observable@npm:0.3.1"
dependencies:
any-observable: "npm:^0.3.0"
peerDependenciesMeta:
rxjs:
optional: true
zen-observable:
optional: true
checksum: 10c0/0d874453f6bc2460d71783292291f52feb36c2a75314b1072a6ffe6206562f33e9d664a554348d565a6b54da9041d75070371052545bc329caaa52f64216987f
languageName: node
linkType: hard
"@scalar/api-client@npm:2.2.62":
version: 2.2.62
resolution: "@scalar/api-client@npm:2.2.62"
@ -27147,7 +27192,7 @@ __metadata:
languageName: node
linkType: hard
"ansi-escapes@npm:^3.1.0":
"ansi-escapes@npm:^3.0.0, ansi-escapes@npm:^3.1.0":
version: 3.2.0
resolution: "ansi-escapes@npm:3.2.0"
checksum: 10c0/084e1ce38139ad2406f18a8e7efe2b850ddd06ce3c00f633392d1ce67756dab44fe290e573d09ef3c9a0cb13c12881e0e35a8f77a017d39a0a4ab85ae2fae04f
@ -27186,6 +27231,13 @@ __metadata:
languageName: node
linkType: hard
"ansi-regex@npm:^3.0.0":
version: 3.0.1
resolution: "ansi-regex@npm:3.0.1"
checksum: 10c0/d108a7498b8568caf4a46eea4f1784ab4e0dfb2e3f3938c697dee21443d622d765c958f2b7e2b9f6b9e55e2e2af0584eaa9915d51782b89a841c28e744e7a167
languageName: node
linkType: hard
"ansi-regex@npm:^4.1.0":
version: 4.1.1
resolution: "ansi-regex@npm:4.1.1"
@ -27207,6 +27259,13 @@ __metadata:
languageName: node
linkType: hard
"ansi-styles@npm:^2.2.1":
version: 2.2.1
resolution: "ansi-styles@npm:2.2.1"
checksum: 10c0/7c68aed4f1857389e7a12f85537ea5b40d832656babbf511cc7ecd9efc52889b9c3e5653a71a6aade783c3c5e0aa223ad4ff8e83c27ac8a666514e6c79068cab
languageName: node
linkType: hard
"ansi-styles@npm:^3.2.1":
version: 3.2.1
resolution: "ansi-styles@npm:3.2.1"
@ -27267,6 +27326,13 @@ __metadata:
languageName: node
linkType: hard
"any-observable@npm:^0.3.0":
version: 0.3.0
resolution: "any-observable@npm:0.3.0"
checksum: 10c0/104c2b79c2ac7e6c75b35f8fd62babf73015668f22bd25336c6f848350d91f9e7daf2fddbf1c1b76fe795e89fbc91b49f70a2aec5c69f1acf0562c344f36042b
languageName: node
linkType: hard
"any-promise@npm:^1.0.0":
version: 1.3.0
resolution: "any-promise@npm:1.3.0"
@ -29505,6 +29571,19 @@ __metadata:
languageName: node
linkType: hard
"chalk@npm:^1.0.0, chalk@npm:^1.1.3":
version: 1.1.3
resolution: "chalk@npm:1.1.3"
dependencies:
ansi-styles: "npm:^2.2.1"
escape-string-regexp: "npm:^1.0.2"
has-ansi: "npm:^2.0.0"
strip-ansi: "npm:^3.0.0"
supports-color: "npm:^2.0.0"
checksum: 10c0/28c3e399ec286bb3a7111fd4225ebedb0d7b813aef38a37bca7c498d032459c265ef43404201d5fbb8d888d29090899c95335b4c0cda13e8b126ff15c541cef8
languageName: node
linkType: hard
"chalk@npm:^2.3.0, chalk@npm:^2.4.1, chalk@npm:^2.4.2":
version: 2.4.2
resolution: "chalk@npm:2.4.2"
@ -29943,6 +30022,15 @@ __metadata:
languageName: node
linkType: hard
"cli-cursor@npm:^2.0.0, cli-cursor@npm:^2.1.0":
version: 2.1.0
resolution: "cli-cursor@npm:2.1.0"
dependencies:
restore-cursor: "npm:^2.0.0"
checksum: 10c0/09ee6d8b5b818d840bf80ec9561eaf696672197d3a02a7daee2def96d5f52ce6e0bbe7afca754ccf14f04830b5a1b4556273e983507d5029f95bba3016618eda
languageName: node
linkType: hard
"cli-cursor@npm:^4.0.0":
version: 4.0.0
resolution: "cli-cursor@npm:4.0.0"
@ -30026,6 +30114,16 @@ __metadata:
languageName: node
linkType: hard
"cli-truncate@npm:^0.2.1":
version: 0.2.1
resolution: "cli-truncate@npm:0.2.1"
dependencies:
slice-ansi: "npm:0.0.4"
string-width: "npm:^1.0.1"
checksum: 10c0/c6caa5e2b70d841c42f4a2270d6fc7129df915f8911e4afa90c79231ccc857cd819a2c90e0707fde04e51ce56b4d71646b843f6cbaff4f7cdcb3b91ed51f6e89
languageName: node
linkType: hard
"cli-truncate@npm:^2.1.0":
version: 2.1.0
resolution: "cli-truncate@npm:2.1.0"
@ -31809,6 +31907,13 @@ __metadata:
languageName: node
linkType: hard
"date-fns@npm:^1.27.2":
version: 1.30.1
resolution: "date-fns@npm:1.30.1"
checksum: 10c0/bad6ad7c15180121e15d61ad62a4a214c108d66f35b35f5eeb6ade837a3c29aa4444b9528a93a5374b95ba11231c142276351bf52f4d168676f9a1e17ce3726a
languageName: node
linkType: hard
"date-fns@npm:^3.3.1, date-fns@npm:^3.6.0":
version: 3.6.0
resolution: "date-fns@npm:3.6.0"
@ -32929,6 +33034,13 @@ __metadata:
languageName: node
linkType: hard
"elegant-spinner@npm:^1.0.1":
version: 1.0.1
resolution: "elegant-spinner@npm:1.0.1"
checksum: 10c0/df607c83c20fc3ce56c514175dd5d1ee7f667da00cee13d04d32c70d55e76555091fa236689e691cf7dedba17b0020fec635e499cdde84dbea2ef8639314e5f8
languageName: node
linkType: hard
"elliptic@npm:^6.5.3, elliptic@npm:^6.5.5":
version: 6.6.1
resolution: "elliptic@npm:6.6.1"
@ -34062,7 +34174,7 @@ __metadata:
languageName: node
linkType: hard
"escape-string-regexp@npm:^1.0.5":
"escape-string-regexp@npm:^1.0.2, escape-string-regexp@npm:^1.0.5":
version: 1.0.5
resolution: "escape-string-regexp@npm:1.0.5"
checksum: 10c0/a968ad453dd0c2724e14a4f20e177aaf32bb384ab41b674a8454afe9a41c5e6fe8903323e0a1052f56289d04bd600f81278edf140b0fcc02f5cac98d0f5b5371
@ -35436,6 +35548,25 @@ __metadata:
languageName: node
linkType: hard
"figures@npm:^1.7.0":
version: 1.7.0
resolution: "figures@npm:1.7.0"
dependencies:
escape-string-regexp: "npm:^1.0.5"
object-assign: "npm:^4.1.0"
checksum: 10c0/a10942b0eec3372bf61822ab130d2bbecdf527d551b0b013fbe7175b7a0238ead644ee8930a1a3cb872fb9ab2ec27df30e303765a3b70b97852e2e9ee43bdff3
languageName: node
linkType: hard
"figures@npm:^2.0.0":
version: 2.0.0
resolution: "figures@npm:2.0.0"
dependencies:
escape-string-regexp: "npm:^1.0.5"
checksum: 10c0/5dc5a75fec3e7e04ae65d6ce51d28b3e70d4656c51b06996b6fdb2cb5b542df512e3b3c04482f5193a964edddafa5521479ff948fa84e12ff556e53e094ab4ce
languageName: node
linkType: hard
"file-entry-cache@npm:^8.0.0":
version: 8.0.0
resolution: "file-entry-cache@npm:8.0.0"
@ -36087,7 +36218,7 @@ __metadata:
languageName: node
linkType: hard
"fs-extra@npm:^10.0.0":
"fs-extra@npm:^10.0.0, fs-extra@npm:^10.1.0":
version: 10.1.0
resolution: "fs-extra@npm:10.1.0"
dependencies:
@ -37310,6 +37441,15 @@ __metadata:
languageName: node
linkType: hard
"has-ansi@npm:^2.0.0":
version: 2.0.0
resolution: "has-ansi@npm:2.0.0"
dependencies:
ansi-regex: "npm:^2.0.0"
checksum: 10c0/f54e4887b9f8f3c4bfefd649c48825b3c093987c92c27880ee9898539e6f01aed261e82e73153c3f920fde0db5bf6ebd58deb498ed1debabcb4bc40113ccdf05
languageName: node
linkType: hard
"has-bigints@npm:^1.0.2":
version: 1.0.2
resolution: "has-bigints@npm:1.0.2"
@ -38553,7 +38693,7 @@ __metadata:
languageName: node
linkType: hard
"indent-string@npm:^3.2.0":
"indent-string@npm:^3.0.0, indent-string@npm:^3.2.0":
version: 3.2.0
resolution: "indent-string@npm:3.2.0"
checksum: 10c0/91b6d61621d24944c5c4d365d6f1ff4a490264ccaf1162a602faa0d323e69231db2180ad4ccc092c2f49cf8888cdb3da7b73e904cc0fdeec40d0bfb41ceb9478
@ -39199,6 +39339,13 @@ __metadata:
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^2.0.0":
version: 2.0.0
resolution: "is-fullwidth-code-point@npm:2.0.0"
checksum: 10c0/e58f3e4a601fc0500d8b2677e26e9fe0cd450980e66adb29d85b6addf7969731e38f8e43ed2ec868a09c101a55ac3d8b78902209269f38c5286bc98f5bc1b4d9
languageName: node
linkType: hard
"is-fullwidth-code-point@npm:^3.0.0":
version: 3.0.0
resolution: "is-fullwidth-code-point@npm:3.0.0"
@ -39394,6 +39541,15 @@ __metadata:
languageName: node
linkType: hard
"is-observable@npm:^1.1.0":
version: 1.1.0
resolution: "is-observable@npm:1.1.0"
dependencies:
symbol-observable: "npm:^1.1.0"
checksum: 10c0/cf3166b0822f70ad06e7851e09430166ce658349d54aaa64c93a03320420b9285735821b23164bdce741ff83a86730ac3e53035ce4e2511ed843dbff4105bfa2
languageName: node
linkType: hard
"is-online@npm:^10.0.0":
version: 10.0.0
resolution: "is-online@npm:10.0.0"
@ -39464,6 +39620,13 @@ __metadata:
languageName: node
linkType: hard
"is-promise@npm:^2.1.0":
version: 2.2.2
resolution: "is-promise@npm:2.2.2"
checksum: 10c0/2dba959812380e45b3df0fb12e7cb4d4528c989c7abb03ececb1d1fd6ab1cbfee956ca9daa587b9db1d8ac3c1e5738cf217bdb3dfd99df8c691be4c00ae09069
languageName: node
linkType: hard
"is-promise@npm:^4.0.0":
version: 4.0.0
resolution: "is-promise@npm:4.0.0"
@ -41636,7 +41799,7 @@ __metadata:
languageName: node
linkType: hard
"kleur@npm:^4.0.3":
"kleur@npm:^4.0.3, kleur@npm:^4.1.5":
version: 4.1.5
resolution: "kleur@npm:4.1.5"
checksum: 10c0/e9de6cb49657b6fa70ba2d1448fd3d691a5c4370d8f7bbf1c2f64c24d461270f2117e1b0afe8cb3114f13bbd8e51de158c2a224953960331904e636a5e4c0f2a
@ -41924,6 +42087,43 @@ __metadata:
languageName: node
linkType: hard
"listr-silent-renderer@npm:^1.1.1":
version: 1.1.1
resolution: "listr-silent-renderer@npm:1.1.1"
checksum: 10c0/a13e08ebf863516a757bce4887f05290070772113d89095e9f51a07cf0b11a43a7563a67ff3b287c752c08f6d781fdb2123b02957534e3e0675fb564f2a42e1b
languageName: node
linkType: hard
"listr-update-renderer@npm:^0.5.0":
version: 0.5.0
resolution: "listr-update-renderer@npm:0.5.0"
dependencies:
chalk: "npm:^1.1.3"
cli-truncate: "npm:^0.2.1"
elegant-spinner: "npm:^1.0.1"
figures: "npm:^1.7.0"
indent-string: "npm:^3.0.0"
log-symbols: "npm:^1.0.2"
log-update: "npm:^2.3.0"
strip-ansi: "npm:^3.0.1"
peerDependencies:
listr: ^0.14.2
checksum: 10c0/8ade44bf3dc6146c8e0178000619439e8889792c4689b66be6ce82bd459f5fe462ecb34b05147fb206a8ad60e6d4e6f34c9f48038e18366f867fd972688b8edc
languageName: node
linkType: hard
"listr-verbose-renderer@npm:^0.5.0":
version: 0.5.0
resolution: "listr-verbose-renderer@npm:0.5.0"
dependencies:
chalk: "npm:^2.4.1"
cli-cursor: "npm:^2.1.0"
date-fns: "npm:^1.27.2"
figures: "npm:^2.0.0"
checksum: 10c0/041cd1e82da7054f27ae0a914e98b40d15faf9f950ef850578fc6241d3fff3c2d7158a4f6226006e566b4c47bf445be2d254dd1ce5c16569a3a5dcd575bec656
languageName: node
linkType: hard
"listr2@npm:^4.0.5":
version: 4.0.5
resolution: "listr2@npm:4.0.5"
@ -41945,6 +42145,23 @@ __metadata:
languageName: node
linkType: hard
"listr@npm:^0.14.3":
version: 0.14.3
resolution: "listr@npm:0.14.3"
dependencies:
"@samverschueren/stream-to-observable": "npm:^0.3.0"
is-observable: "npm:^1.1.0"
is-promise: "npm:^2.1.0"
is-stream: "npm:^1.1.0"
listr-silent-renderer: "npm:^1.1.1"
listr-update-renderer: "npm:^0.5.0"
listr-verbose-renderer: "npm:^0.5.0"
p-map: "npm:^2.0.0"
rxjs: "npm:^6.3.3"
checksum: 10c0/753d518218c423f46bee8eeacccecadfd2e414ba9c0f602e7f85fe3f6fa18404dfab0812433aeda4683ee2548358488f597ac1a3d321196baec5d3149b200b10
languageName: node
linkType: hard
"load-esm@npm:1.0.3":
version: 1.0.3
resolution: "load-esm@npm:1.0.3"
@ -42366,6 +42583,26 @@ __metadata:
languageName: node
linkType: hard
"log-symbols@npm:^1.0.2":
version: 1.0.2
resolution: "log-symbols@npm:1.0.2"
dependencies:
chalk: "npm:^1.0.0"
checksum: 10c0/c64e1fe41d0d043840f8b592d043b8607a836b846506f525a53d99d578561f02f97b2cba1d2b3c30bae5311d64b308d5a392a9930d252b906a9042fc2877da7a
languageName: node
linkType: hard
"log-update@npm:^2.3.0":
version: 2.3.0
resolution: "log-update@npm:2.3.0"
dependencies:
ansi-escapes: "npm:^3.0.0"
cli-cursor: "npm:^2.0.0"
wrap-ansi: "npm:^3.0.1"
checksum: 10c0/9bf21b138801ab4770a2bfa735161cf005b869360eaf5003a84ba64ddc5f5c3ce7217f4f1fa79d9c1f510d792213b2c9800327228e94df05859d19b716215d90
languageName: node
linkType: hard
"log-update@npm:^4.0.0":
version: 4.0.0
resolution: "log-update@npm:4.0.0"
@ -44593,6 +44830,13 @@ __metadata:
languageName: node
linkType: hard
"mimic-fn@npm:^1.0.0":
version: 1.2.0
resolution: "mimic-fn@npm:1.2.0"
checksum: 10c0/ad55214aec6094c0af4c0beec1a13787556f8116ed88807cf3f05828500f21f93a9482326bcd5a077ae91e3e8795b4e76b5b4c8bb12237ff0e4043a365516cba
languageName: node
linkType: hard
"mimic-fn@npm:^2.1.0":
version: 2.1.0
resolution: "mimic-fn@npm:2.1.0"
@ -44916,7 +45160,7 @@ __metadata:
languageName: node
linkType: hard
"mkdirp@npm:^0.5.6":
"mkdirp@npm:^0.5.1, mkdirp@npm:^0.5.6":
version: 0.5.6
resolution: "mkdirp@npm:0.5.6"
dependencies:
@ -45270,6 +45514,15 @@ __metadata:
languageName: node
linkType: hard
"native-fetch@npm:^4.0.2":
version: 4.0.2
resolution: "native-fetch@npm:4.0.2"
peerDependencies:
undici: "*"
checksum: 10c0/e3b824721daaa628086d9dcd02e8eb12f0a6c5e13a1d182682bae238d80c9bbf3dfd6314a94692ebe20316aa354476804b4df148201d066b46fc552a5794cfab
languageName: node
linkType: hard
"natural-compare@npm:^1.4.0":
version: 1.4.0
resolution: "natural-compare@npm:1.4.0"
@ -46532,6 +46785,15 @@ __metadata:
languageName: node
linkType: hard
"onetime@npm:^2.0.0":
version: 2.0.1
resolution: "onetime@npm:2.0.1"
dependencies:
mimic-fn: "npm:^1.0.0"
checksum: 10c0/b4e44a8c34e70e02251bfb578a6e26d6de6eedbed106cd78211d2fd64d28b6281d54924696554e4e966559644243753ac5df73c87f283b0927533d3315696215
languageName: node
linkType: hard
"onetime@npm:^5.1.0, onetime@npm:^5.1.2":
version: 5.1.2
resolution: "onetime@npm:5.1.2"
@ -46887,6 +47149,13 @@ __metadata:
languageName: node
linkType: hard
"p-map@npm:^2.0.0":
version: 2.1.0
resolution: "p-map@npm:2.1.0"
checksum: 10c0/735dae87badd4737a2dd582b6d8f93e49a1b79eabbc9815a4d63a528d5e3523e978e127a21d784cccb637010e32103a40d2aaa3ab23ae60250b1a820ca752043
languageName: node
linkType: hard
"p-map@npm:^3.0.0":
version: 3.0.0
resolution: "p-map@npm:3.0.0"
@ -48249,7 +48518,7 @@ __metadata:
languageName: node
linkType: hard
"prettier@npm:2.8.8, prettier@npm:^2.0.0":
"prettier@npm:2.8.8, prettier@npm:^2.0.0, prettier@npm:^2.8.0":
version: 2.8.8
resolution: "prettier@npm:2.8.8"
bin:
@ -50986,6 +51255,16 @@ __metadata:
languageName: node
linkType: hard
"restore-cursor@npm:^2.0.0":
version: 2.0.0
resolution: "restore-cursor@npm:2.0.0"
dependencies:
onetime: "npm:^2.0.0"
signal-exit: "npm:^3.0.2"
checksum: 10c0/f5b335bee06f440445e976a7031a3ef53691f9b7c4a9d42a469a0edaf8a5508158a0d561ff2b26a1f4f38783bcca2c0e5c3a44f927326f6694d5b44d7a4993e6
languageName: node
linkType: hard
"restore-cursor@npm:^3.1.0":
version: 3.1.0
resolution: "restore-cursor@npm:3.1.0"
@ -51086,6 +51365,17 @@ __metadata:
languageName: node
linkType: hard
"rimraf@npm:^2.6.3":
version: 2.7.1
resolution: "rimraf@npm:2.7.1"
dependencies:
glob: "npm:^7.1.3"
bin:
rimraf: ./bin.js
checksum: 10c0/4eef73d406c6940927479a3a9dee551e14a54faf54b31ef861250ac815172bade86cc6f7d64a4dc5e98b65e4b18a2e1c9ff3b68d296be0c748413f092bb0dd40
languageName: node
linkType: hard
"rimraf@npm:^3.0.0, rimraf@npm:^3.0.2":
version: 3.0.2
resolution: "rimraf@npm:3.0.2"
@ -51374,7 +51664,7 @@ __metadata:
languageName: node
linkType: hard
"rxjs@npm:^6.6.0":
"rxjs@npm:^6.3.3, rxjs@npm:^6.6.0":
version: 6.6.7
resolution: "rxjs@npm:6.6.7"
dependencies:
@ -52331,6 +52621,13 @@ __metadata:
languageName: node
linkType: hard
"slice-ansi@npm:0.0.4":
version: 0.0.4
resolution: "slice-ansi@npm:0.0.4"
checksum: 10c0/997d4cc73e34aa8c0f60bdb71701b16c062cc4acd7a95e3b10e8c05d790eb5e735d9b470270dc6f443b1ba21492db7ceb849d5c93011d1256061bf7ed7216c7a
languageName: node
linkType: hard
"slice-ansi@npm:^3.0.0":
version: 3.0.0
resolution: "slice-ansi@npm:3.0.0"
@ -53061,6 +53358,16 @@ __metadata:
languageName: node
linkType: hard
"string-width@npm:^2.1.1":
version: 2.1.1
resolution: "string-width@npm:2.1.1"
dependencies:
is-fullwidth-code-point: "npm:^2.0.0"
strip-ansi: "npm:^4.0.0"
checksum: 10c0/e5f2b169fcf8a4257a399f95d069522f056e92ec97dbdcb9b0cdf14d688b7ca0b1b1439a1c7b9773cd79446cbafd582727279d6bfdd9f8edd306ea5e90e5b610
languageName: node
linkType: hard
"string-width@npm:^5.0.1, string-width@npm:^5.1.2":
version: 5.1.2
resolution: "string-width@npm:5.1.2"
@ -53231,6 +53538,15 @@ __metadata:
languageName: node
linkType: hard
"strip-ansi@npm:^4.0.0":
version: 4.0.0
resolution: "strip-ansi@npm:4.0.0"
dependencies:
ansi-regex: "npm:^3.0.0"
checksum: 10c0/d75d9681e0637ea316ddbd7d4d3be010b1895a17e885155e0ed6a39755ae0fd7ef46e14b22162e66a62db122d3a98ab7917794e255532ab461bb0a04feb03e7d
languageName: node
linkType: hard
"strip-ansi@npm:^5.0.0, strip-ansi@npm:^5.2.0":
version: 5.2.0
resolution: "strip-ansi@npm:5.2.0"
@ -53591,6 +53907,13 @@ __metadata:
languageName: node
linkType: hard
"supports-color@npm:^2.0.0":
version: 2.0.0
resolution: "supports-color@npm:2.0.0"
checksum: 10c0/570e0b63be36cccdd25186350a6cb2eaad332a95ff162fa06d9499982315f2fe4217e69dd98e862fbcd9c81eaff300a825a1fe7bf5cc752e5b84dfed042b0dda
languageName: node
linkType: hard
"supports-color@npm:^5.0.0, supports-color@npm:^5.3.0, supports-color@npm:^5.4.0, supports-color@npm:^5.5.0":
version: 5.5.0
resolution: "supports-color@npm:5.5.0"
@ -53728,7 +54051,7 @@ __metadata:
languageName: node
linkType: hard
"symbol-observable@npm:^1.0.4":
"symbol-observable@npm:^1.0.4, symbol-observable@npm:^1.1.0":
version: 1.2.0
resolution: "symbol-observable@npm:1.2.0"
checksum: 10c0/009fee50798ef80ed4b8195048288f108b03de162db07493f2e1fd993b33fafa72d659e832b584da5a2427daa78e5a738fb2a9ab027ee9454252e0bedbcd1fdc
@ -54783,6 +55106,7 @@ __metadata:
version: 0.0.0-use.local
resolution: "twenty-cli@workspace:packages/twenty-cli"
dependencies:
"@genql/cli": "npm:^3.0.3"
"@types/fs-extra": "npm:^11.0.0"
"@types/inquirer": "npm:^9.0.0"
"@types/jest": "npm:^29.5.0"
@ -54798,6 +55122,7 @@ __metadata:
commander: "npm:^12.0.0"
dotenv: "npm:^16.4.0"
fs-extra: "npm:^11.2.0"
graphql: "npm:^16.8.1"
inquirer: "npm:^10.0.0"
jest: "npm:^29.5.0"
jsonc-parser: "npm:^3.2.0"
@ -54805,7 +55130,6 @@ __metadata:
lodash.capitalize: "npm:^4.2.1"
lodash.kebabcase: "npm:^4.1.1"
tsx: "npm:^4.7.0"
twenty-sdk: "workspace:*"
typescript: "npm:^5.9.2"
uuid: "npm:^13.0.0"
wait-on: "npm:^7.2.0"
@ -54972,13 +55296,16 @@ __metadata:
languageName: unknown
linkType: soft
"twenty-sdk@workspace:*, twenty-sdk@workspace:packages/twenty-sdk":
"twenty-sdk@workspace:packages/twenty-sdk":
version: 0.0.0-use.local
resolution: "twenty-sdk@workspace:packages/twenty-sdk"
dependencies:
"@types/node": "npm:^24.0.0"
twenty-shared: "workspace:*"
typescript: "npm:5.9.2"
vite: "npm:^7.0.0"
vite-plugin-dts: "npm:3.8.1"
vite-tsconfig-paths: "npm:^4.2.1"
languageName: unknown
linkType: soft
@ -55946,6 +56273,15 @@ __metadata:
languageName: node
linkType: hard
"undici@npm:^5.18.0":
version: 5.29.0
resolution: "undici@npm:5.29.0"
dependencies:
"@fastify/busboy": "npm:^2.0.0"
checksum: 10c0/e4e4d631ca54ee0ad82d2e90e7798fa00a106e27e6c880687e445cc2f13b4bc87c5eba2a88c266c3eecffb18f26e227b778412da74a23acc374fca7caccec49b
languageName: node
linkType: hard
"unhead@npm:1.11.20":
version: 1.11.20
resolution: "unhead@npm:1.11.20"
@ -57905,6 +58241,16 @@ __metadata:
languageName: node
linkType: hard
"wrap-ansi@npm:^3.0.1":
version: 3.0.1
resolution: "wrap-ansi@npm:3.0.1"
dependencies:
string-width: "npm:^2.1.1"
strip-ansi: "npm:^4.0.0"
checksum: 10c0/ad6fed8f242c26755badaf452da154122d0d862f8b7aab56e758466857f230efafdc5fbffca026650b947ac3fc0eb563df5c05b9e2190a52a4a68f4eef3d4555
languageName: node
linkType: hard
"wrap-ansi@npm:^6.0.1, wrap-ansi@npm:^6.2.0":
version: 6.2.0
resolution: "wrap-ansi@npm:6.2.0"