feat(server): add application:install command (#23430)
Adds `application:install` to install an application on workspaces that do not have it yet, and moves both application commands onto `WorkspaceIteratorService`. ### Behavior - Iterates provisioned workspaces through `WorkspaceIteratorService` (workspace id resolution, workspace context, per-workspace success/fail report), or the ones passed with `-w`. - Workspaces where the application is already installed are skipped, with a log line pointing at `application:upgrade`. This command never upgrades. - Installed workspaces are detected by `universalIdentifier`, the same identity `ApplicationInstallService` uses to tell a fresh install from a version upgrade, checked per workspace on the `(universalIdentifier, workspaceId)` unique index. - `--workspace-count-limit` caps both the iterator's own selection and an explicitly targeted `-w` list. - Fails fast for `LOCAL` and `OAUTH_ONLY` registrations, which have no code artifacts to install. - Per-workspace failures are collected in the iterator report and never abort the run; the command ends with an installed / skipped / failed summary. ### Options | Flag | Description | | --- | --- | | `-u, --application-registration-universal-identifier` | Application registration universal identifier (required) | | `-w, --workspace-id` | Target a specific workspace, repeatable | | `--workspace-count-limit` | Cap the number of workspaces to iterate over (max 50) | | `-d, --dry-run` | Print the workspaces that would be installed without installing | | `-y, --yes` | Skip the confirmation prompt | ### Example ``` yarn command:prod application:install -u UNIVERSAL_IDENTIFIER --dry-run ``` ### Changes to application:upgrade - `ApplicationUpgradeService.upgradeApplications` iterates through `WorkspaceIteratorService` and returns its report, replacing the hand-rolled parallel batching. - `--batch-size` dropped from the command, and `batchSize` dropped from the service and from `UpgradeApplicationsJobData`, since `iterate()` is sequential. - `parseBoundedPositiveInteger` moved to `src/database/commands/utils/` and is shared by both commands. ### Files - `application-install/commands/install-application.command.ts` (new) - `src/database/commands/utils/parse-bounded-positive-integer.util.ts` (new) - `application-upgrade/application-upgrade.service.ts`, `application-upgrade/commands/upgrade-application.command.ts`, `jobs/upgrade-applications.job*`: iterator instead of batching - `application-install.module.ts` / `application-upgrade.module.ts`: register the command, wire `WorkspaceIteratorModule` - `database-command.module.ts`: import `ApplicationInstallModule` so the command is discovered by the CLI ### Testing - `npx jest src/engine/core-modules/application` (32 suites, 181 tests passing) - `npx nx typecheck twenty-server` - `oxlint --type-aware` and `oxfmt` on the changed files
This commit is contained in:
@@ -17,6 +17,7 @@ import { WorkspaceExportModule } from 'src/database/commands/workspace-export/wo
|
||||
import { TypeORMModule } from 'src/database/typeorm/typeorm.module';
|
||||
import { ApiKeyModule } from 'src/engine/core-modules/api-key/api-key.module';
|
||||
import { GenerateApiKeyCommand } from 'src/engine/core-modules/api-key/commands/generate-api-key.command';
|
||||
import { ApplicationInstallModule } from 'src/engine/core-modules/application/application-install/application-install.module';
|
||||
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';
|
||||
@@ -88,6 +89,7 @@ import { WorkflowCoreConsistencyModule } from 'src/modules/workflow/workflow-cor
|
||||
EnterpriseModule,
|
||||
TwentyConfigModule,
|
||||
MarketplaceModule,
|
||||
ApplicationInstallModule,
|
||||
ApplicationUpgradeModule,
|
||||
StaleRegistrationCleanupModule,
|
||||
PreInstalledAppsModule,
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
export const parseBoundedPositiveInteger = (
|
||||
value: string,
|
||||
optionName: string,
|
||||
maximum: number,
|
||||
): number => {
|
||||
const parsedValue = Number(value);
|
||||
|
||||
if (!Number.isInteger(parsedValue) || parsedValue < 1) {
|
||||
throw new Error(
|
||||
`Invalid ${optionName} "${value}". Expected a positive integer`,
|
||||
);
|
||||
}
|
||||
|
||||
if (parsedValue > maximum) {
|
||||
throw new Error(`Invalid ${optionName} "${value}". Maximum is ${maximum}`);
|
||||
}
|
||||
|
||||
return parsedValue;
|
||||
};
|
||||
Reference in New Issue
Block a user