diff --git a/package.json b/package.json
index f0159cdba9..e679aa7a04 100644
--- a/package.json
+++ b/package.json
@@ -12,6 +12,7 @@
"concurrently": "^8.2.2",
"http-server": "^14.1.1",
"nx": "22.5.4",
+ "tsx": "^4.17.0",
"verdaccio": "^6.3.1"
},
"engines": {
diff --git a/packages/twenty-docs/developers/extend/apps/publishing.mdx b/packages/twenty-docs/developers/extend/apps/publishing.mdx
index 475f15d468..3077b5d0f9 100644
--- a/packages/twenty-docs/developers/extend/apps/publishing.mdx
+++ b/packages/twenty-docs/developers/extend/apps/publishing.mdx
@@ -77,6 +77,39 @@ Pre-release tags work as expected: bumping `1.0.0-rc.1` → `1.0.0-rc.2` is allo
{/* TODO: add screenshot of the Upgrade button */}
+### Server version compatibility
+
+If your app uses a feature introduced in a specific Twenty server version (for example, OAuth providers added in v2.3.0), you should declare the minimum server version your app requires using the `engines.twenty` field in `package.json`:
+
+```json filename="package.json"
+{
+ "name": "twenty-my-app",
+ "version": "1.0.0",
+ "engines": {
+ "node": "^24.5.0",
+ "twenty": ">=2.3.0"
+ }
+}
+```
+
+The value is a standard [semver range](https://github.com/npm/node-semver#ranges). Common patterns:
+
+| Range | Meaning |
+|-------|---------|
+| `>=2.3.0` | Any server from 2.3.0 onward |
+| `>=2.3.0 <3.0.0` | 2.3.0 or later, but below the next major |
+| `^2.3.0` | Same as `>=2.3.0 <3.0.0` |
+
+**What happens at deploy and install time:**
+
+- If `engines.twenty` is set and the target server's version does not satisfy the range, the deploy (tarball upload) or install is rejected with a `SERVER_VERSION_INCOMPATIBLE` error and a message indicating both the required range and the actual server version.
+- If `engines.twenty` is **not set**, the app is accepted on any server version (backward-compatible with existing apps).
+- If the server has no `APP_VERSION` configured, the check is skipped.
+
+
+The server is the authoritative check — it validates `engines.twenty` on both tarball upload and workspace install. If you deploy a tarball out-of-band or install from the marketplace, the server still enforces compatibility.
+
+
## Automated CI/CD (scaffolded workflows)
Apps generated with `create-twenty-app` ship with two GitHub Actions workflows out of the box, under `.github/workflows/`. They are ready to run as soon as you push the repo to GitHub — no extra setup is needed for CI, and CD only requires a single secret.
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-exception-filter.ts b/packages/twenty-server/src/engine/core-modules/application/application-exception-filter.ts
index 12ffc47a7d..f0d555223d 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-exception-filter.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-exception-filter.ts
@@ -31,12 +31,15 @@ export class ApplicationExceptionFilter implements ExceptionFilter {
case ApplicationExceptionCode.SOURCE_CHANNEL_MISMATCH:
case ApplicationExceptionCode.APP_ALREADY_INSTALLED:
case ApplicationExceptionCode.CANNOT_DOWNGRADE_APPLICATION:
+ case ApplicationExceptionCode.SERVER_VERSION_INCOMPATIBLE:
+ case ApplicationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT:
throw new UserInputError(exception);
case ApplicationExceptionCode.PACKAGE_RESOLUTION_FAILED:
case ApplicationExceptionCode.POST_INSTALL_ERROR:
case ApplicationExceptionCode.PRE_INSTALL_ERROR:
case ApplicationExceptionCode.TARBALL_EXTRACTION_FAILED:
case ApplicationExceptionCode.UPGRADE_FAILED:
+ case ApplicationExceptionCode.INVALID_SERVER_VERSION:
throw new InternalServerError(exception);
default: {
assertUnreachable(exception.code);
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-install/application-install.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-install/application-install.service.ts
index 55ada635fe..7ea3cfd97d 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-install/application-install.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-install/application-install.service.ts
@@ -21,6 +21,10 @@ import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/appli
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { ApplicationPackageFetcherService } from 'src/engine/core-modules/application/application-package/application-package-fetcher.service';
+import {
+ ApplicationVersionValidationService,
+ type VersionValidationFailureReason,
+} from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import { ApplicationSyncService } from 'src/engine/core-modules/application/application-manifest/application-sync.service';
import { CacheLockService } from 'src/engine/core-modules/cache-lock/cache-lock.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
@@ -39,11 +43,22 @@ import { LogicFunctionExecutorService } from 'src/engine/core-modules/logic-func
export class ApplicationInstallService {
private readonly logger = new Logger(ApplicationInstallService.name);
+ private static readonly VERSION_REASON_TO_EXCEPTION_CODE: Record<
+ VersionValidationFailureReason,
+ ApplicationExceptionCode
+ > = {
+ INVALID_REQUIRED_VERSION:
+ ApplicationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT,
+ INVALID_SERVER_VERSION: ApplicationExceptionCode.INVALID_SERVER_VERSION,
+ INCOMPATIBLE: ApplicationExceptionCode.SERVER_VERSION_INCOMPATIBLE,
+ };
+
constructor(
@InjectRepository(ApplicationRegistrationEntity)
private readonly appRegistrationRepository: Repository,
private readonly applicationService: ApplicationService,
private readonly applicationPackageFetcherService: ApplicationPackageFetcherService,
+ private readonly applicationVersionValidationService: ApplicationVersionValidationService,
private readonly applicationSyncService: ApplicationSyncService,
private readonly fileStorageService: FileStorageService,
private readonly logicFunctionExecutorService: LogicFunctionExecutorService,
@@ -118,6 +133,27 @@ export class ApplicationInstallService {
return true;
}
+ const requiredServerVersion =
+ resolvedPackage.packageJson.engines?.['twenty'];
+
+ const versionValidation =
+ this.applicationVersionValidationService.validateServerCompatibility(
+ requiredServerVersion,
+ );
+
+ if (!versionValidation.compatible) {
+ await this.applicationPackageFetcherService.cleanupExtractedDir(
+ resolvedPackage.cleanupDir,
+ );
+
+ throw new ApplicationException(
+ versionValidation.message,
+ ApplicationInstallService.VERSION_REASON_TO_EXCEPTION_CODE[
+ versionValidation.reason
+ ],
+ );
+ }
+
const universalIdentifier = appRegistration.universalIdentifier;
const existingApplication =
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-package/application-package.module.ts b/packages/twenty-server/src/engine/core-modules/application/application-package/application-package.module.ts
index 2f7c6239fb..90cf2241f3 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-package/application-package.module.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-package/application-package.module.ts
@@ -2,6 +2,7 @@ import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { ApplicationPackageFetcherService } from 'src/engine/core-modules/application/application-package/application-package-fetcher.service';
+import { ApplicationVersionValidationService } from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
import { FileEntity } from 'src/engine/core-modules/file/entities/file.entity';
@@ -13,7 +14,13 @@ import { TwentyConfigModule } from 'src/engine/core-modules/twenty-config/twenty
TwentyConfigModule,
TypeOrmModule.forFeature([FileEntity, ApplicationEntity]),
],
- providers: [ApplicationPackageFetcherService],
- exports: [ApplicationPackageFetcherService],
+ providers: [
+ ApplicationPackageFetcherService,
+ ApplicationVersionValidationService,
+ ],
+ exports: [
+ ApplicationPackageFetcherService,
+ ApplicationVersionValidationService,
+ ],
})
export class ApplicationPackageModule {}
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-package/application-version-validation.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-package/application-version-validation.service.ts
new file mode 100644
index 0000000000..f144bce3b8
--- /dev/null
+++ b/packages/twenty-server/src/engine/core-modules/application/application-package/application-version-validation.service.ts
@@ -0,0 +1,60 @@
+import { Injectable } from '@nestjs/common';
+
+import semver from 'semver';
+import { isDefined } from 'twenty-shared/utils';
+
+import { TwentyConfigService } from 'src/engine/core-modules/twenty-config/twenty-config.service';
+
+export type VersionValidationFailureReason =
+ | 'INVALID_REQUIRED_VERSION'
+ | 'INVALID_SERVER_VERSION'
+ | 'INCOMPATIBLE';
+
+export type VersionValidationResult =
+ | { compatible: true }
+ | {
+ compatible: false;
+ reason: VersionValidationFailureReason;
+ message: string;
+ };
+
+@Injectable()
+export class ApplicationVersionValidationService {
+ constructor(private readonly twentyConfigService: TwentyConfigService) {}
+
+ validateServerCompatibility(
+ requiredServerVersion: string | undefined,
+ ): VersionValidationResult {
+ if (!isDefined(requiredServerVersion)) {
+ return { compatible: true };
+ }
+
+ if (!isDefined(semver.validRange(requiredServerVersion))) {
+ return {
+ compatible: false,
+ reason: 'INVALID_REQUIRED_VERSION',
+ message: `App manifest declares invalid engines.twenty value "${requiredServerVersion}". Must be a valid semver range.`,
+ };
+ }
+
+ const serverVersion = this.twentyConfigService.get('APP_VERSION');
+
+ if (!isDefined(serverVersion) || !isDefined(semver.valid(serverVersion))) {
+ return {
+ compatible: false,
+ reason: 'INVALID_SERVER_VERSION',
+ message: `Cannot verify server compatibility: APP_VERSION "${serverVersion ?? 'undefined'}" is not a valid semver version. Self-hosted instances must set a valid APP_VERSION.`,
+ };
+ }
+
+ if (!semver.satisfies(serverVersion, requiredServerVersion)) {
+ return {
+ compatible: false,
+ reason: 'INCOMPATIBLE',
+ message: `App requires Twenty server ${requiredServerVersion} but this server is ${serverVersion}.`,
+ };
+ }
+
+ return { compatible: true };
+ }
+}
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-exception-filter.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-exception-filter.ts
index 41da4f8278..fb89d3bcb5 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-exception-filter.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration-exception-filter.ts
@@ -22,6 +22,9 @@ export class ApplicationRegistrationExceptionFilter implements ExceptionFilter {
case ApplicationRegistrationExceptionCode.INVALID_REDIRECT_URI:
case ApplicationRegistrationExceptionCode.SOURCE_CHANNEL_MISMATCH:
case ApplicationRegistrationExceptionCode.UNIVERSAL_IDENTIFIER_ALREADY_CLAIMED:
+ case ApplicationRegistrationExceptionCode.SERVER_VERSION_INCOMPATIBLE:
+ case ApplicationRegistrationExceptionCode.VERSION_ALREADY_EXISTS:
+ case ApplicationRegistrationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT:
throw new UserInputError(exception);
default:
throw new InternalServerError(exception);
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.exception.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.exception.ts
index bbe24fdcaf..e7d7c7efdc 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.exception.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.exception.ts
@@ -13,6 +13,9 @@ export enum ApplicationRegistrationExceptionCode {
SOURCE_CHANNEL_MISMATCH = 'SOURCE_CHANNEL_MISMATCH',
VARIABLE_NOT_FOUND = 'VARIABLE_NOT_FOUND',
VERSION_ALREADY_EXISTS = 'VERSION_ALREADY_EXISTS',
+ SERVER_VERSION_INCOMPATIBLE = 'SERVER_VERSION_INCOMPATIBLE',
+ INVALID_APP_ENGINE_REQUIREMENT = 'INVALID_APP_ENGINE_REQUIREMENT',
+ INVALID_SERVER_VERSION = 'INVALID_SERVER_VERSION',
}
const getExceptionUserFriendlyMessage = (
@@ -35,6 +38,12 @@ const getExceptionUserFriendlyMessage = (
return msg`Application registration variable not found.`;
case ApplicationRegistrationExceptionCode.VERSION_ALREADY_EXISTS:
return msg`This version is not higher than the currently deployed version. Please bump the version in package.json before deploying again.`;
+ case ApplicationRegistrationExceptionCode.SERVER_VERSION_INCOMPATIBLE:
+ return msg`This app requires a newer version of the Twenty server. Please upgrade your server or use a compatible app version.`;
+ case ApplicationRegistrationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT:
+ return msg`The app manifest declares an invalid server version requirement.`;
+ case ApplicationRegistrationExceptionCode.INVALID_SERVER_VERSION:
+ return msg`The server's APP_VERSION is not a valid semver version. Self-hosted instances must configure a valid APP_VERSION.`;
default:
assertUnreachable(code);
}
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.module.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.module.ts
index ae2a7bc679..2d1837b753 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.module.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-registration.module.ts
@@ -6,6 +6,7 @@ import { ApplicationRegistrationResolver } from 'src/engine/core-modules/applica
import { ApplicationRegistrationService } from 'src/engine/core-modules/application/application-registration/application-registration.service';
import { ApplicationRegistrationVariableModule } from 'src/engine/core-modules/application/application-registration-variable/application-registration-variable.module';
import { ApplicationTarballService } from 'src/engine/core-modules/application/application-registration/application-tarball.service';
+import { ApplicationPackageModule } from 'src/engine/core-modules/application/application-package/application-package.module';
import { ApplicationEntity } from 'src/engine/core-modules/application/application.entity';
import { ApplicationModule } from 'src/engine/core-modules/application/application.module';
import { DomainServerConfigModule } from 'src/engine/core-modules/domain/domain-server-config/domain-server-config.module';
@@ -25,6 +26,7 @@ import { WorkspaceCacheStorageModule } from 'src/engine/workspace-cache-storage/
]),
ApplicationRegistrationVariableModule,
ApplicationModule,
+ ApplicationPackageModule,
DomainServerConfigModule,
FeatureFlagModule,
PermissionsModule,
diff --git a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts
index a51b1d2ffd..5b0d21ce1d 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application-registration/application-tarball.service.ts
@@ -20,6 +20,10 @@ import { ApplicationRegistrationSourceType } from 'src/engine/core-modules/appli
import { extractTarballSecurely } from 'src/engine/core-modules/application/application-package/utils/extract-tarball-securely.util';
import { readJsonFile } from 'src/engine/core-modules/application/application-package/utils/read-json-file.util';
import { resolvePackageContentDir } from 'src/engine/core-modules/application/application-package/utils/tarball-utils';
+import {
+ ApplicationVersionValidationService,
+ type VersionValidationFailureReason,
+} from 'src/engine/core-modules/application/application-package/application-version-validation.service';
import { ApplicationService } from 'src/engine/core-modules/application/application.service';
import { FileStorageService } from 'src/engine/core-modules/file-storage/file-storage.service';
import type { ApplicationManifest } from 'twenty-shared/application';
@@ -31,12 +35,25 @@ export const MAX_TARBALL_UPLOAD_SIZE_BYTES = 50 * 1024 * 1024;
export class ApplicationTarballService {
private readonly logger = new Logger(ApplicationTarballService.name);
+ private static readonly VERSION_REASON_TO_EXCEPTION_CODE: Record<
+ VersionValidationFailureReason,
+ ApplicationRegistrationExceptionCode
+ > = {
+ INVALID_REQUIRED_VERSION:
+ ApplicationRegistrationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT,
+ INVALID_SERVER_VERSION:
+ ApplicationRegistrationExceptionCode.INVALID_SERVER_VERSION,
+ INCOMPATIBLE:
+ ApplicationRegistrationExceptionCode.SERVER_VERSION_INCOMPATIBLE,
+ };
+
constructor(
@InjectRepository(ApplicationRegistrationEntity)
private readonly appRegistrationRepository: Repository,
private readonly fileStorageService: FileStorageService,
private readonly applicationService: ApplicationService,
private readonly applicationRegistrationVariableService: ApplicationRegistrationVariableService,
+ private readonly applicationVersionValidationService: ApplicationVersionValidationService,
) {}
async uploadTarball(params: {
@@ -66,6 +83,7 @@ export class ApplicationTarballService {
const packageJson = await readJsonFile<{
version: string;
+ engines?: { twenty?: string };
}>(contentDir, 'package.json');
if (manifest === null) {
@@ -75,6 +93,22 @@ export class ApplicationTarballService {
);
}
+ const requiredServerVersion = packageJson?.engines?.twenty;
+
+ const versionValidation =
+ this.applicationVersionValidationService.validateServerCompatibility(
+ requiredServerVersion,
+ );
+
+ if (!versionValidation.compatible) {
+ throw new ApplicationRegistrationException(
+ versionValidation.message,
+ ApplicationTarballService.VERSION_REASON_TO_EXCEPTION_CODE[
+ versionValidation.reason
+ ],
+ );
+ }
+
const universalIdentifier =
params.universalIdentifier ?? manifest.application?.universalIdentifier;
diff --git a/packages/twenty-server/src/engine/core-modules/application/application.exception.ts b/packages/twenty-server/src/engine/core-modules/application/application.exception.ts
index f5d4cf52c8..ba124be317 100644
--- a/packages/twenty-server/src/engine/core-modules/application/application.exception.ts
+++ b/packages/twenty-server/src/engine/core-modules/application/application.exception.ts
@@ -22,6 +22,9 @@ export enum ApplicationExceptionCode {
POST_INSTALL_ERROR = 'POST_INSTALL_ERROR',
APP_ALREADY_INSTALLED = 'APP_ALREADY_INSTALLED',
CANNOT_DOWNGRADE_APPLICATION = 'CANNOT_DOWNGRADE_APPLICATION',
+ SERVER_VERSION_INCOMPATIBLE = 'SERVER_VERSION_INCOMPATIBLE',
+ INVALID_APP_ENGINE_REQUIREMENT = 'INVALID_APP_ENGINE_REQUIREMENT',
+ INVALID_SERVER_VERSION = 'INVALID_SERVER_VERSION',
}
const getApplicationExceptionUserFriendlyMessage = (
@@ -62,6 +65,12 @@ const getApplicationExceptionUserFriendlyMessage = (
return msg`This version of the application is already installed in this workspace.`;
case ApplicationExceptionCode.CANNOT_DOWNGRADE_APPLICATION:
return msg`A higher version of this application is already installed. Downgrading is not allowed.`;
+ case ApplicationExceptionCode.SERVER_VERSION_INCOMPATIBLE:
+ return msg`This app requires a newer version of the Twenty server. Please upgrade your server or use a compatible app version.`;
+ case ApplicationExceptionCode.INVALID_APP_ENGINE_REQUIREMENT:
+ return msg`The app manifest declares an invalid server version requirement.`;
+ case ApplicationExceptionCode.INVALID_SERVER_VERSION:
+ return msg`The server's APP_VERSION is not a valid semver version. Self-hosted instances must configure a valid APP_VERSION.`;
default:
assertUnreachable(code);
}
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts
index 103e3773f4..867620bf40 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-object-metadata-related-entity-ids.util.spec.ts
@@ -1,3 +1,5 @@
+import { getStandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util';
+
let uuidCounter = 0;
jest.mock('uuid', () => ({
@@ -6,8 +8,6 @@ jest.mock('uuid', () => ({
),
}));
-import { getStandardObjectMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-object-metadata-related-entity-ids.util';
-
describe('getStandardObjectMetadataRelatedEntityIds', () => {
beforeEach(() => {
uuidCounter = 0;
diff --git a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts
index ac40a48e85..5325317ea4 100644
--- a/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts
+++ b/packages/twenty-server/src/engine/workspace-manager/twenty-standard-application/utils/__tests__/get-standard-page-layout-metadata-related-entity-ids.util.spec.ts
@@ -1,16 +1,20 @@
-let uuidCounter = 0;
-
-jest.mock('uuid', () => ({
- v4: jest.fn(
- () => `00000000-0000-0000-0000-${String(++uuidCounter).padStart(12, '0')}`,
- ),
-}));
+import { v4 } from 'uuid';
import { getStandardPageLayoutMetadataRelatedEntityIds } from 'src/engine/workspace-manager/twenty-standard-application/utils/get-standard-page-layout-metadata-related-entity-ids.util';
+jest.mock('uuid', () => ({
+ v4: jest.fn(),
+}));
+
describe('getStandardPageLayoutMetadataRelatedEntityIds', () => {
+ let uuidCounter = 0;
+
beforeEach(() => {
uuidCounter = 0;
+ (v4 as jest.Mock).mockImplementation(
+ () =>
+ `00000000-0000-0000-0000-${String(++uuidCounter).padStart(12, '0')}`,
+ );
});
afterAll(() => {
diff --git a/yarn.lock b/yarn.lock
index 4425fc16a0..a5c52481c4 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -57593,7 +57593,7 @@ __metadata:
languageName: node
linkType: hard
-"tsx@npm:^4.19.3, tsx@npm:^4.7.0":
+"tsx@npm:^4.17.0, tsx@npm:^4.19.3, tsx@npm:^4.7.0":
version: 4.21.0
resolution: "tsx@npm:4.21.0"
dependencies:
@@ -58490,6 +58490,7 @@ __metadata:
concurrently: "npm:^8.2.2"
http-server: "npm:^14.1.1"
nx: "npm:22.5.4"
+ tsx: "npm:^4.17.0"
verdaccio: "npm:^6.3.1"
languageName: unknown
linkType: soft