diff --git a/packages/twenty-server/src/database/commands/database-command.module.ts b/packages/twenty-server/src/database/commands/database-command.module.ts index 94452fc091f..bcfcb56d192 100644 --- a/packages/twenty-server/src/database/commands/database-command.module.ts +++ b/packages/twenty-server/src/database/commands/database-command.module.ts @@ -16,6 +16,10 @@ import { GenerateApiKeyCommand } from 'src/engine/core-modules/api-key/commands/ import { MarketplaceModule } from 'src/engine/core-modules/application/application-marketplace/marketplace.module'; import { StaleRegistrationCleanupModule } from 'src/engine/core-modules/application/application-oauth/stale-registration-cleanup/stale-registration-cleanup.module'; import { ApplicationUpgradeModule } from 'src/engine/core-modules/application/application-upgrade/application-upgrade.module'; +import { RebuildApplicationDefaultDepsCommand } from 'src/database/commands/rebuild-application-default-deps.command'; +import { WorkspaceIteratorModule } from 'src/database/commands/command-runners/workspace-iterator.module'; +import { ApplicationModule } from 'src/engine/core-modules/application/application.module'; +import { WorkspaceCacheModule } from 'src/engine/workspace-cache/workspace-cache.module'; import { EnforceUsageCapCronCommand } from 'src/engine/core-modules/billing/crons/commands/enforce-usage-cap.cron.command'; import { EnterpriseKeyValidationCronCommand } from 'src/engine/core-modules/enterprise/cron/command/enterprise-key-validation.cron.command'; import { EnterpriseModule } from 'src/engine/core-modules/enterprise/enterprise.module'; @@ -73,6 +77,9 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au MarketplaceModule, ApplicationUpgradeModule, StaleRegistrationCleanupModule, + WorkspaceIteratorModule, + ApplicationModule, + WorkspaceCacheModule, WorkspaceVersionModule, UpgradeModule, ], @@ -88,6 +95,7 @@ import { AutomatedTriggerModule } from 'src/modules/workflow/workflow-trigger/au GenerateApiKeyCommand, EnforceUsageCapCronCommand, UpgradeStatusCommand, + RebuildApplicationDefaultDepsCommand, ], }) export class DatabaseCommandModule {} diff --git a/packages/twenty-server/src/database/commands/rebuild-application-default-deps.command.ts b/packages/twenty-server/src/database/commands/rebuild-application-default-deps.command.ts new file mode 100644 index 00000000000..10aee054d8f --- /dev/null +++ b/packages/twenty-server/src/database/commands/rebuild-application-default-deps.command.ts @@ -0,0 +1,94 @@ +import chalk from 'chalk'; +import { Command, CommandRunner, Option } from 'nest-commander'; +import { isDefined } from 'twenty-shared/utils'; + +import { WorkspaceIteratorService } from 'src/database/commands/command-runners/workspace-iterator.service'; +import { CommandLogger } from 'src/database/commands/logger'; +import { ApplicationService } from 'src/engine/core-modules/application/application.service'; +import { type FlatApplication } from 'src/engine/core-modules/application/types/flat-application.type'; +import { WorkspaceCacheService } from 'src/engine/workspace-cache/services/workspace-cache.service'; + +type RebuildDefaultPackageFilesCommandOptions = { + workspaceId?: Set; +}; + +@Command({ + name: 'application:rebuild-default-deps', + description: + 'Re-upload default package.json and yarn.lock to file storage for all applications in the workspace', +}) +export class RebuildApplicationDefaultDepsCommand extends CommandRunner { + protected logger: CommandLogger; + + constructor( + private readonly workspaceIteratorService: WorkspaceIteratorService, + private readonly applicationService: ApplicationService, + private readonly workspaceCacheService: WorkspaceCacheService, + ) { + super(); + this.logger = new CommandLogger({ + verbose: false, + constructorName: this.constructor.name, + }); + } + + @Option({ + flags: '-w, --workspace-id [workspace_id]', + description: + 'workspace id. Command runs on all active/suspended workspaces if not provided.', + required: false, + }) + parseWorkspaceId(val: string, previous?: Set): Set { + const accumulator = previous ?? new Set(); + + accumulator.add(val); + + return accumulator; + } + + override async run( + _passedParams: string[], + options: RebuildDefaultPackageFilesCommandOptions, + ): Promise { + const workspaceIds = isDefined(options.workspaceId) + ? Array.from(options.workspaceId) + : undefined; + const report = await this.workspaceIteratorService.iterate({ + workspaceIds, + callback: async ({ workspaceId }) => { + const { flatApplicationMaps } = + await this.workspaceCacheService.getOrRecompute(workspaceId, [ + 'flatApplicationMaps', + ]); + + const applications = Object.values(flatApplicationMaps.byId).filter( + (application): application is FlatApplication => + isDefined(application) && !isDefined(application.deletedAt), + ); + + this.logger.log( + `Found ${applications.length} application(s) in workspace ${workspaceId}`, + ); + + for (const application of applications) { + await this.applicationService.uploadDefaultPackageFilesAndSetFileIds({ + id: application.id, + universalIdentifier: application.universalIdentifier, + workspaceId, + }); + this.logger.log( + `Rebuilt default package files for application "${application.name}" (${application.id})`, + ); + } + }, + }); + + if (report.fail.length > 0) { + throw new Error( + `Command completed with ${report.fail.length} failure(s)`, + ); + } + + this.logger.log(chalk.blue('Command completed!')); + } +}