@@ -25,7 +25,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
task: [lint, typecheck, test:unit, storybook:build, storybook:test]
|
||||
task: [lint, typecheck, test:unit, storybook:build, storybook:test, test:integration]
|
||||
steps:
|
||||
- name: Cancel Previous Runs
|
||||
uses: styfle/cancel-workflow-action@0.11.0
|
||||
@@ -47,7 +47,7 @@ jobs:
|
||||
with:
|
||||
tag: scope:sdk
|
||||
tasks: ${{ matrix.task }}
|
||||
sdk-e2e-integration-test:
|
||||
sdk-e2e-test:
|
||||
timeout-minutes: 30
|
||||
runs-on: ubuntu-latest-8-cores
|
||||
needs: [changed-files-check, sdk-test]
|
||||
@@ -82,23 +82,9 @@ jobs:
|
||||
uses: ./.github/actions/yarn-install
|
||||
- name: Build
|
||||
run: npx nx build twenty-sdk
|
||||
- name: Server / Append billing config to .env.test
|
||||
working-directory: packages/twenty-server
|
||||
run: |
|
||||
echo "" >> .env.test
|
||||
echo "IS_BILLING_ENABLED=true" >> .env.test
|
||||
echo "BILLING_STRIPE_API_KEY=test-api-key" >> .env.test
|
||||
echo "BILLING_STRIPE_BASE_PLAN_PRODUCT_ID=test-base-plan-product-id" >> .env.test
|
||||
echo "BILLING_STRIPE_WEBHOOK_SECRET=test-webhook-secret" >> .env.test
|
||||
echo "BILLING_PLAN_REQUIRED_LINK=http://localhost:3001/stripe-redirection" >> .env.test
|
||||
- name: Server / Create Test DB
|
||||
run: |
|
||||
PGPASSWORD=postgres psql -h localhost -p 5432 -U postgres -d postgres -c 'CREATE DATABASE "test";'
|
||||
- name: SDK / Run integration tests
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
tag: scope:sdk
|
||||
tasks: test:integration
|
||||
- name: SDK / Run e2e Tests
|
||||
uses: ./.github/actions/nx-affected
|
||||
with:
|
||||
@@ -108,7 +94,7 @@ jobs:
|
||||
if: always() && !cancelled()
|
||||
timeout-minutes: 5
|
||||
runs-on: ubuntu-latest
|
||||
needs: [changed-files-check, sdk-test, sdk-e2e-integration-test]
|
||||
needs: [changed-files-check, sdk-test, sdk-e2e-test]
|
||||
steps:
|
||||
- name: Fail job if any needs failed
|
||||
if: contains(needs.*.result, 'failure')
|
||||
|
||||
@@ -63,20 +63,8 @@
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "packages/twenty-sdk",
|
||||
"command": "npx wait-on http://localhost:3000/healthz --timeout 600000 --interval 1000 --log && npx vitest run --config vitest.integration.config.ts"
|
||||
},
|
||||
"parallel": false,
|
||||
"dependsOn": [
|
||||
"build",
|
||||
{
|
||||
"target": "database:reset",
|
||||
"projects": "twenty-server"
|
||||
},
|
||||
{
|
||||
"target": "start:ci-if-needed",
|
||||
"projects": "twenty-server"
|
||||
}
|
||||
]
|
||||
"command": "npx vitest run --config vitest.integration.config.ts"
|
||||
}
|
||||
},
|
||||
"test:e2e": {
|
||||
"executor": "nx:run-commands",
|
||||
|
||||
+6
-4
@@ -1,17 +1,19 @@
|
||||
import { runAppDev } from '@/cli/__tests__/integration/utils/run-app-dev.util';
|
||||
import { runAppDevInProcess } from '@/cli/__tests__/integration/utils/run-app-dev-in-process.util';
|
||||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { OUTPUT_DIR } from 'twenty-shared/application';
|
||||
|
||||
const APP_PATH = join(__dirname, '..');
|
||||
const MANIFEST_OUTPUT_PATH = join(APP_PATH, OUTPUT_DIR, 'ioi', 'manifest.json');
|
||||
const MANIFEST_OUTPUT_PATH = join(APP_PATH, OUTPUT_DIR, 'manifest.json');
|
||||
|
||||
describe('invalid-app manifest', () => {
|
||||
it('should fail to build manifest due to duplicate universalIdentifier', async () => {
|
||||
const result = await runAppDev({ appPath: APP_PATH, timeout: 10000 });
|
||||
const result = await runAppDevInProcess({
|
||||
appPath: APP_PATH,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
expect(result.success).toBe(false);
|
||||
expect(result.output).toContain('Duplicate universal identifiers');
|
||||
|
||||
const manifestExists = await fs.pathExists(MANIFEST_OUTPUT_PATH);
|
||||
|
||||
|
||||
+20
-15
@@ -1,47 +1,52 @@
|
||||
import { runAppDev } from '@/cli/__tests__/integration/utils/run-app-dev.util';
|
||||
import { OUTPUT_DIR } from 'twenty-shared/application';
|
||||
import { AppUninstallCommand } from '@/cli/commands/app/app-uninstall';
|
||||
import { existsSync } from 'fs';
|
||||
import { join, resolve } from 'path';
|
||||
import { inspect } from 'util';
|
||||
import { runCliCommand } from '@/cli/__tests__/integration/utils/run-cli-command.util';
|
||||
|
||||
inspect.defaultOptions.depth = 10;
|
||||
|
||||
describe('Application: install delete and reinstall rich-app', () => {
|
||||
const applicationName = 'rich-app';
|
||||
const deleteCommand = new AppUninstallCommand();
|
||||
const appPath = resolve(__dirname, '../');
|
||||
|
||||
beforeAll(async () => {
|
||||
expect(existsSync(appPath)).toBe(true);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
const result = await deleteCommand.execute({
|
||||
appPath,
|
||||
askForConfirmation: false,
|
||||
const result = await runCliCommand({
|
||||
command: 'auth:status',
|
||||
args: [appPath],
|
||||
timeout: 5_000,
|
||||
waitForOutput: '✓ Valid',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it(`should successfully install ${applicationName} application`, async () => {
|
||||
await runAppDev({ appPath });
|
||||
await runCliCommand({
|
||||
command: 'app:dev',
|
||||
args: [appPath],
|
||||
waitForOutput: '✓ Synced',
|
||||
});
|
||||
|
||||
expect(existsSync(join(appPath, OUTPUT_DIR, 'manifest.json'))).toBe(true);
|
||||
});
|
||||
|
||||
it(`should successfully delete ${applicationName} application`, async () => {
|
||||
const result = await deleteCommand.execute({
|
||||
appPath,
|
||||
askForConfirmation: false,
|
||||
await runCliCommand({
|
||||
command: 'app:uninstall',
|
||||
args: [appPath, '-y'],
|
||||
waitForOutput: 'Application uninstalled successfully',
|
||||
});
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
});
|
||||
|
||||
it(`should successfully re-install ${applicationName} application`, async () => {
|
||||
await runAppDev({ appPath });
|
||||
await runCliCommand({
|
||||
command: 'app:dev',
|
||||
args: [appPath],
|
||||
waitForOutput: '✓ Synced',
|
||||
});
|
||||
|
||||
expect(existsSync(join(appPath, OUTPUT_DIR, 'manifest.json'))).toBe(true);
|
||||
});
|
||||
|
||||
+4
-11
@@ -1,25 +1,18 @@
|
||||
import { runAppDev } from '@/cli/__tests__/integration/utils/run-app-dev.util';
|
||||
import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-cli-command.util';
|
||||
import { runAppDevInProcess } from '@/cli/__tests__/integration/utils/run-app-dev-in-process.util';
|
||||
import { join } from 'path';
|
||||
|
||||
import { defineConsoleOutputTests } from './tests/console-output.tests';
|
||||
import { defineManifestTests } from './tests/manifest.tests';
|
||||
import { defineEntitiesTests } from './tests/entities.tests';
|
||||
import { defineManifestTests } from './tests/manifest.tests';
|
||||
|
||||
const APP_PATH = join(__dirname, '../..');
|
||||
|
||||
describe('rich-app app:dev', () => {
|
||||
let result: RunCliCommandResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runAppDev({ appPath: APP_PATH });
|
||||
if (!result.success) {
|
||||
console.log(result.output.slice(undefined, 20_000));
|
||||
}
|
||||
const result = await runAppDevInProcess({ appPath: APP_PATH });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
}, 60000);
|
||||
|
||||
defineConsoleOutputTests(() => result);
|
||||
defineManifestTests(APP_PATH);
|
||||
defineEntitiesTests(APP_PATH);
|
||||
});
|
||||
|
||||
+3
-10
@@ -1,8 +1,6 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { runAppDev } from '@/cli/__tests__/integration/utils/run-app-dev.util';
|
||||
import { type RunCliCommandResult } from '@/cli/__tests__/integration/utils/run-cli-command.util';
|
||||
import { defineConsoleOutputTests } from './tests/console-output.tests';
|
||||
import { runAppDevInProcess } from '@/cli/__tests__/integration/utils/run-app-dev-in-process.util';
|
||||
import { defineFrontComponentsTests } from './tests/front-components.tests';
|
||||
import { defineLogicFunctionsTests } from './tests/logic-functions.tests';
|
||||
import { defineManifestTests } from './tests/manifest.tests';
|
||||
@@ -10,17 +8,12 @@ import { defineManifestTests } from './tests/manifest.tests';
|
||||
const APP_PATH = join(__dirname, '../..');
|
||||
|
||||
describe('root-app app:dev', () => {
|
||||
let result: RunCliCommandResult;
|
||||
|
||||
beforeAll(async () => {
|
||||
result = await runAppDev({ appPath: APP_PATH });
|
||||
if (!result.success) {
|
||||
console.log(result.output.slice(undefined, 20_000));
|
||||
}
|
||||
const result = await runAppDevInProcess({ appPath: APP_PATH });
|
||||
|
||||
expect(result.success).toBe(true);
|
||||
}, 60000);
|
||||
|
||||
defineConsoleOutputTests(() => result);
|
||||
defineManifestTests(APP_PATH);
|
||||
defineLogicFunctionsTests(APP_PATH);
|
||||
defineFrontComponentsTests(APP_PATH);
|
||||
|
||||
@@ -1,11 +1,19 @@
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { testConfig } from '@/cli/__tests__/constants/testConfig';
|
||||
import { vi, beforeAll, afterAll } from 'vitest';
|
||||
import { getConfigPath } from '@/cli/utilities/config/get-config-path';
|
||||
import * as fs from 'fs-extra';
|
||||
import * as path from 'path';
|
||||
import { beforeAll } from 'vitest';
|
||||
|
||||
beforeAll(() => {
|
||||
vi.spyOn(ConfigService.prototype, 'getConfig').mockResolvedValue(testConfig);
|
||||
});
|
||||
const testConfigPath = getConfigPath();
|
||||
|
||||
afterAll(() => {
|
||||
vi.restoreAllMocks();
|
||||
beforeAll(async () => {
|
||||
await fs.ensureDir(path.dirname(testConfigPath));
|
||||
|
||||
const configFile = {
|
||||
profiles: {
|
||||
default: testConfig,
|
||||
},
|
||||
};
|
||||
|
||||
await fs.writeFile(testConfigPath, JSON.stringify(configFile, null, 2));
|
||||
});
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { type TwentyConfig } from '@/cli/utilities/config/config-service';
|
||||
|
||||
export const testConfig: TwentyConfig = {
|
||||
apiUrl: 'http://localhost:3000',
|
||||
apiUrl: 'http://apple.localhost:3000',
|
||||
apiKey:
|
||||
'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ1c2VySWQiOiIyMDIwMjAyMC1lNmI1LTQ2ODAtOGEzMi1iODIwOTczNzE1NmIiLCJ3b3Jrc3BhY2VJZCI6IjIwMjAyMDIwLTFjMjUtNGQwMi1iZjI1LTZhZWNjZjdlYTQxOSIsIndvcmtzcGFjZU1lbWJlcklkIjoiMjAyMDIwMjAtNDYzZi00MzViLTgyOGMtMTA3ZTAwN2EyNzExIiwidXNlcldvcmtzcGFjZUlkIjoiMjAyMDIwMjAtMWU3Yy00M2Q5LWE1ZGItNjg1YjUwNjlkODE2IiwidHlwZSI6IkFDQ0VTUyIsImF1dGhQcm92aWRlciI6InBhc3N3b3JkIiwiaWF0IjoxNzUxMjgxNzA0LCJleHAiOjIwNjY4NTc3MDR9.HMGqCsVlOAPVUBhKSGlD1X86VoHKt4LIUtET3CGIdik',
|
||||
};
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
import { AppDevCommand } from '@/cli/commands/app/app-dev';
|
||||
import * as fs from 'fs-extra';
|
||||
import { join } from 'path';
|
||||
import { OUTPUT_DIR } from 'twenty-shared/application';
|
||||
|
||||
export type RunAppDevResult = {
|
||||
success: boolean;
|
||||
};
|
||||
|
||||
export const runAppDevInProcess = async (options: {
|
||||
appPath: string;
|
||||
timeout?: number;
|
||||
}): Promise<RunAppDevResult> => {
|
||||
const { appPath, timeout = 30_000 } = options;
|
||||
const manifestPath = join(appPath, OUTPUT_DIR, 'manifest.json');
|
||||
|
||||
const command = new AppDevCommand();
|
||||
|
||||
await command.execute({ appPath });
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < timeout) {
|
||||
if (await fs.pathExists(manifestPath)) {
|
||||
// Small delay to let any pending writes finish
|
||||
await new Promise((resolve) => setTimeout(resolve, 500));
|
||||
await command.close();
|
||||
|
||||
return { success: true };
|
||||
}
|
||||
await new Promise((resolve) => setTimeout(resolve, 100));
|
||||
}
|
||||
|
||||
await command.close();
|
||||
|
||||
return { success: false };
|
||||
};
|
||||
@@ -1,22 +0,0 @@
|
||||
import {
|
||||
runCliCommand,
|
||||
type RunCliCommandResult,
|
||||
} from './run-cli-command.util';
|
||||
|
||||
export type RunAppDevOptions = {
|
||||
appPath: string;
|
||||
timeout?: number;
|
||||
};
|
||||
|
||||
export const runAppDev = (
|
||||
options: RunAppDevOptions,
|
||||
): Promise<RunCliCommandResult> => {
|
||||
const { appPath, timeout = 60_000 } = options;
|
||||
|
||||
return runCliCommand({
|
||||
command: 'app:dev',
|
||||
args: [appPath],
|
||||
waitForOutput: ['✓ Synced'],
|
||||
timeout,
|
||||
});
|
||||
};
|
||||
@@ -23,7 +23,7 @@ export type RunCliCommandResult = {
|
||||
export const runCliCommand = (
|
||||
options: RunCliCommandOptions,
|
||||
): Promise<RunCliCommandResult> => {
|
||||
const { command, args = [], waitForOutput, timeout = 60_000 } = options;
|
||||
const { command, args = [], waitForOutput, timeout = 30_000 } = options;
|
||||
|
||||
return new Promise((resolve) => {
|
||||
// Run from CLI directory to use twenty-sdk's tsconfig paths
|
||||
@@ -43,6 +43,7 @@ export const runCliCommand = (
|
||||
let output = '';
|
||||
const timeoutId = setTimeout(() => {
|
||||
child.kill();
|
||||
console.log(`RunCliCommand ${command} timeout after ${timeout / 1_000}s`);
|
||||
resolve({ success: false, output });
|
||||
}, timeout);
|
||||
|
||||
@@ -76,11 +77,7 @@ export const runCliCommand = (
|
||||
|
||||
child.on('close', (code) => {
|
||||
clearTimeout(timeoutId);
|
||||
if (waitForOutputs.length === 0) {
|
||||
resolve({ success: code === 0, output });
|
||||
} else {
|
||||
resolve({ success: false, output });
|
||||
}
|
||||
resolve({ success: code === 0, output });
|
||||
});
|
||||
|
||||
child.on('error', () => {
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
import { vi } from 'vitest';
|
||||
|
||||
const mockApiService = {
|
||||
validateAuth: vi.fn().mockResolvedValue({ authValid: true, serverUp: true }),
|
||||
checkApplicationExist: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ success: true, data: false }),
|
||||
createApplication: vi
|
||||
.fn()
|
||||
.mockResolvedValue({ success: true, data: { id: 'mock-id' } }),
|
||||
syncApplication: vi.fn().mockResolvedValue({ success: true, data: true }),
|
||||
uploadFile: vi.fn().mockResolvedValue({ success: true, data: true }),
|
||||
};
|
||||
|
||||
vi.mock('@/cli/utilities/api/api-service', () => ({
|
||||
ApiService: class {
|
||||
validateAuth = mockApiService.validateAuth;
|
||||
checkApplicationExist = mockApiService.checkApplicationExist;
|
||||
createApplication = mockApiService.createApplication;
|
||||
syncApplication = mockApiService.syncApplication;
|
||||
uploadFile = mockApiService.uploadFile;
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/cli/utilities/file/file-uploader', () => ({
|
||||
FileUploader: class {
|
||||
uploadFile = vi.fn().mockResolvedValue({ success: true, data: true });
|
||||
},
|
||||
}));
|
||||
|
||||
vi.mock('@/cli/utilities/dev/dev-ui', () => ({
|
||||
renderDevUI: vi.fn().mockResolvedValue({ unmount: vi.fn() }),
|
||||
}));
|
||||
@@ -79,11 +79,12 @@ export const registerCommands = (program: Command): void => {
|
||||
program
|
||||
.command('app:uninstall [appPath]')
|
||||
.description('Uninstall application from Twenty')
|
||||
.action(async (appPath?: string) => {
|
||||
.option('-y, --yes', 'Skip confirmation prompt')
|
||||
.action(async (appPath?: string, options?: { yes?: boolean }) => {
|
||||
try {
|
||||
const result = await uninstallCommand.execute({
|
||||
appPath: formatPath(appPath),
|
||||
askForConfirmation: true,
|
||||
askForConfirmation: !options?.yes,
|
||||
});
|
||||
process.exit(result.success ? 0 : 1);
|
||||
} catch {
|
||||
|
||||
@@ -31,6 +31,18 @@ export class AppDevCommand {
|
||||
private uiStateManager: DevUiStateManager | null = null;
|
||||
private unmountUI: (() => void) | null = null;
|
||||
|
||||
async close(): Promise<void> {
|
||||
this.unmountUI?.();
|
||||
|
||||
await Promise.all([
|
||||
this.manifestWatcher?.close(),
|
||||
this.logicFunctionsWatcher?.close(),
|
||||
this.frontComponentsWatcher?.close(),
|
||||
this.assetWatcher?.close(),
|
||||
this.dependencyWatcher?.close(),
|
||||
]);
|
||||
}
|
||||
|
||||
async execute(options: AppDevOptions): Promise<void> {
|
||||
this.appPath = options.appPath ?? CURRENT_EXECUTION_DIRECTORY;
|
||||
|
||||
@@ -163,21 +175,9 @@ export class AppDevCommand {
|
||||
}
|
||||
|
||||
private setupGracefulShutdown(): void {
|
||||
const shutdown = async () => {
|
||||
this.unmountUI?.();
|
||||
const shutdown = () => void this.close().then(() => process.exit(0));
|
||||
|
||||
await Promise.all([
|
||||
this.manifestWatcher?.close(),
|
||||
this.logicFunctionsWatcher?.close(),
|
||||
this.frontComponentsWatcher?.close(),
|
||||
this.assetWatcher?.close(),
|
||||
this.dependencyWatcher?.close(),
|
||||
]);
|
||||
|
||||
process.exit(0);
|
||||
};
|
||||
|
||||
process.on('SIGINT', () => void shutdown());
|
||||
process.on('SIGTERM', () => void shutdown());
|
||||
process.on('SIGINT', shutdown);
|
||||
process.on('SIGTERM', shutdown);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,7 +79,7 @@ export class LogicFunctionExecuteCommand {
|
||||
} else {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'No functions found for this application. Have you synced your app with `yarn app:sync`?',
|
||||
'No functions found for this application. Have you synced your app with `yarn app:dev`?',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
import * as fs from 'fs-extra';
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
import { getConfigPath } from '@/cli/utilities/config/get-config-path';
|
||||
|
||||
export type TwentyConfig = {
|
||||
apiUrl: string;
|
||||
apiKey?: string;
|
||||
@@ -19,7 +20,7 @@ export class ConfigService {
|
||||
private static activeWorkspace = DEFAULT_WORKSPACE_NAME;
|
||||
|
||||
constructor() {
|
||||
this.configPath = path.join(os.homedir(), '.twenty', 'config.json');
|
||||
this.configPath = getConfigPath();
|
||||
}
|
||||
|
||||
static setActiveWorkspace(name?: string) {
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
import * as os from 'os';
|
||||
import * as path from 'path';
|
||||
|
||||
const TEST_CONFIG_DIR = path.join(os.tmpdir(), '.twenty-sdk-test');
|
||||
|
||||
export const getConfigPath = (): string => {
|
||||
if (process.env.NODE_ENV === 'test') {
|
||||
return path.join(TEST_CONFIG_DIR, 'config.json');
|
||||
}
|
||||
|
||||
return path.join(os.homedir(), '.twenty', 'config.json');
|
||||
};
|
||||
@@ -18,6 +18,9 @@ export default defineConfig({
|
||||
truncateThreshold: 0,
|
||||
},
|
||||
fileParallelism: false,
|
||||
setupFiles: ['src/cli/__tests__/constants/setupTest.ts'],
|
||||
setupFiles: [
|
||||
'src/cli/__tests__/constants/setupTest.ts',
|
||||
'src/cli/__tests__/integration/utils/setup-app-dev-mocks.ts',
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
@@ -2,6 +2,7 @@ PG_DATABASE_URL=postgres://postgres:postgres@localhost:5432/test
|
||||
REDIS_URL=redis://localhost:6379
|
||||
|
||||
APP_SECRET=replace_me_with_a_random_string
|
||||
ACCESS_TOKEN_SECRET=replace_me_with_a_random_string
|
||||
SIGN_IN_PREFILLED=true
|
||||
EXCEPTION_HANDLER_DRIVER=CONSOLE
|
||||
TELEMETRY_ENABLED=false
|
||||
|
||||
@@ -65,7 +65,7 @@
|
||||
"dependsOn": ["build"],
|
||||
"options": {
|
||||
"cwd": "packages/twenty-server",
|
||||
"command": "curl -f -s http://localhost:3000/healthz > /dev/null 2>&1 && echo '✅ Server already running' || (echo '🚀 Server not running, starting...' && nohup nest start > server.log 2>&1 &)"
|
||||
"command": "curl -f -s http://localhost:3000/healthz > /dev/null 2>&1 && echo '✅ Server already running' || (echo '🚀 Server not running, starting...' && nohup nest start &)"
|
||||
}
|
||||
},
|
||||
"start:debug": {
|
||||
|
||||
+11
-2
@@ -1,12 +1,21 @@
|
||||
import { promises as fs } from 'fs';
|
||||
import { resolve, join } from 'path';
|
||||
|
||||
import { getExecutorFilePath } from 'src/engine/core-modules/logic-function/logic-function-drivers/utils/get-executor-file-path';
|
||||
import { ASSET_PATH } from 'src/constants/assets-path';
|
||||
|
||||
const EXECUTOR_FILE_PATH = resolve(
|
||||
__dirname,
|
||||
join(
|
||||
ASSET_PATH,
|
||||
`engine/core-modules/logic-function/logic-function-drivers/constants/executor`,
|
||||
),
|
||||
);
|
||||
|
||||
export const copyExecutor = async (buildDirectory: string) => {
|
||||
await fs.mkdir(buildDirectory, {
|
||||
recursive: true,
|
||||
});
|
||||
await fs.cp(getExecutorFilePath(), buildDirectory, {
|
||||
await fs.cp(EXECUTOR_FILE_PATH, buildDirectory, {
|
||||
recursive: true,
|
||||
});
|
||||
};
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import path from 'path';
|
||||
|
||||
import { ASSET_PATH } from 'src/constants/assets-path';
|
||||
|
||||
export const getExecutorFilePath = (): string => {
|
||||
const baseTypescriptProjectPath = path.join(
|
||||
ASSET_PATH,
|
||||
`engine/core-modules/logic-function/logic-function-drivers/constants/executor`,
|
||||
);
|
||||
|
||||
return path.resolve(__dirname, baseTypescriptProjectPath);
|
||||
};
|
||||
+3
-47
@@ -1,15 +1,6 @@
|
||||
import { Field, HideField, InputType } from '@nestjs/graphql';
|
||||
|
||||
import {
|
||||
IsBoolean,
|
||||
IsNotEmpty,
|
||||
IsNumber,
|
||||
IsObject,
|
||||
IsOptional,
|
||||
IsString,
|
||||
Max,
|
||||
Min,
|
||||
} from 'class-validator';
|
||||
import { IsObject, IsOptional, IsString } from 'class-validator';
|
||||
import graphqlTypeJson from 'graphql-type-json';
|
||||
import {
|
||||
CronTriggerSettings,
|
||||
@@ -18,35 +9,10 @@ import {
|
||||
} from 'twenty-shared/application';
|
||||
|
||||
import type { JsonbProperty } from 'src/engine/workspace-manager/workspace-migration/universal-flat-entity/types/jsonb-property.type';
|
||||
import { CreateDefaultLogicFunctionInput } from 'src/engine/metadata-modules/logic-function/dtos/create-default-logic-function.input';
|
||||
|
||||
@InputType()
|
||||
export class CreateLogicFunctionInput {
|
||||
@IsString()
|
||||
@IsNotEmpty()
|
||||
@Field()
|
||||
name: string;
|
||||
|
||||
@IsString()
|
||||
@IsOptional()
|
||||
@Field({ nullable: true })
|
||||
description?: string;
|
||||
|
||||
@IsNumber()
|
||||
@Field({ nullable: true })
|
||||
@Min(1)
|
||||
@Max(900)
|
||||
@IsOptional()
|
||||
timeoutSeconds?: number;
|
||||
|
||||
@HideField()
|
||||
applicationId: string;
|
||||
|
||||
@HideField()
|
||||
universalIdentifier?: string;
|
||||
|
||||
@HideField()
|
||||
id: string;
|
||||
|
||||
export class CreateLogicFunctionInput extends CreateDefaultLogicFunctionInput {
|
||||
@HideField()
|
||||
checksum: string;
|
||||
|
||||
@@ -62,16 +28,6 @@ export class CreateLogicFunctionInput {
|
||||
@Field({ nullable: false })
|
||||
builtHandlerPath: string;
|
||||
|
||||
@Field(() => graphqlTypeJson, { nullable: true })
|
||||
@IsObject()
|
||||
@IsOptional()
|
||||
toolInputSchema?: object;
|
||||
|
||||
@IsBoolean()
|
||||
@Field({ nullable: true })
|
||||
@IsOptional()
|
||||
isTool?: boolean;
|
||||
|
||||
@IsObject()
|
||||
@Field(() => graphqlTypeJson, { nullable: true })
|
||||
@IsOptional()
|
||||
|
||||
+1
-1
@@ -19,7 +19,7 @@ export const fromCreateLogicFunctionInputToFlatLogicFunction = ({
|
||||
workspaceId,
|
||||
ownerFlatApplication,
|
||||
}: FromCreateLogicFunctionInputToFlatLogicFunctionArgs): FlatLogicFunction => {
|
||||
const id = rawCreateLogicFunctionInput.id;
|
||||
const id = rawCreateLogicFunctionInput.id ?? v4();
|
||||
const currentDate = new Date();
|
||||
|
||||
const sourceHandlerPath = rawCreateLogicFunctionInput.sourceHandlerPath;
|
||||
|
||||
-12
@@ -1,12 +0,0 @@
|
||||
import { dirname } from 'path';
|
||||
|
||||
export const getLogicFunctionBaseFolderPath = (handlerPath: string): string => {
|
||||
return dirname(dirname(handlerPath));
|
||||
};
|
||||
|
||||
export const getRelativePathFromBase = (
|
||||
handlerPath: string,
|
||||
baseFolderPath: string,
|
||||
): string => {
|
||||
return handlerPath.replace(`${baseFolderPath}/`, '');
|
||||
};
|
||||
-45
@@ -1,45 +0,0 @@
|
||||
import fs from 'fs/promises';
|
||||
import path from 'path';
|
||||
|
||||
import { ASSET_PATH } from 'src/constants/assets-path';
|
||||
|
||||
export type CodeStepSeedProjectFile = {
|
||||
name: string;
|
||||
path: string;
|
||||
content: Buffer;
|
||||
};
|
||||
|
||||
const getAllFiles = async (
|
||||
rootDir: string,
|
||||
dir: string = rootDir,
|
||||
files: CodeStepSeedProjectFile[] = [],
|
||||
): Promise<CodeStepSeedProjectFile[]> => {
|
||||
const dirEntries = await fs.readdir(dir, { withFileTypes: true });
|
||||
|
||||
for (const entry of dirEntries) {
|
||||
const fullPath = path.join(dir, entry.name);
|
||||
|
||||
if (entry.isDirectory()) {
|
||||
await getAllFiles(rootDir, fullPath, files);
|
||||
} else {
|
||||
files.push({
|
||||
path: path.relative(rootDir, dir),
|
||||
name: entry.name,
|
||||
content: await fs.readFile(fullPath),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return files;
|
||||
};
|
||||
|
||||
export const getCodeStepSeedProjectFiles = async (): Promise<
|
||||
CodeStepSeedProjectFile[]
|
||||
> => {
|
||||
const seedProjectPath = path.join(
|
||||
ASSET_PATH,
|
||||
'modules/workflow/workflow-builder/workflow-version-step/code-step/constants/seed-project',
|
||||
);
|
||||
|
||||
return getAllFiles(seedProjectPath);
|
||||
};
|
||||
Reference in New Issue
Block a user