Refactor application module architecture for clarity and explicitness (#18432)
## Summary - **Module reorganization**: Moved `ApplicationUpgradeService` and cron jobs to `application-upgrade/`, `ApplicationSyncService` to `application-manifest/`, and `runWorkspaceMigration`/`uninstallApplication` mutations to the manifest resolver — each module now has a single clear responsibility. - **Explicit install flow**: Removed implicit `ApplicationEntity` creation from `ApplicationSyncService`. The install service and dev resolver now explicitly create the `ApplicationEntity` before syncing. npm packages are resolved at registration time to extract manifest metadata (universalIdentifier, name, description, etc.), eliminating the `reconcileUniversalIdentifier` hack. - **Better error handling**: Frontend hooks now surface actual server error messages in snackbars instead of swallowing them. Replaced the ugly `ConfirmationModal` for transfer ownership with a proper form modal. Fixed `SettingsAdminTableCard` row height overflow and corrected the `yarn-engine` asset path. ## Test plan - [ ] Register an npm package — verify manifest metadata (name, description, universalIdentifier) is extracted correctly - [ ] Install a registered npm app on a workspace — verify ApplicationEntity is created and sync succeeds - [ ] Test `app:dev` CLI flow — verify local app registration and sync work - [ ] Upload a tarball — verify registration and install flow - [ ] Transfer ownership — verify the new modal UX works - [ ] Verify error messages appear correctly in snackbars when operations fail Made with [Cursor](https://cursor.com)
This commit is contained in:
+1
-1
@@ -4,7 +4,7 @@ import { type CommonResponseBody } from 'test/integration/metadata/types/common-
|
||||
import { warnIfErrorButNotExpectedToFail } from 'test/integration/metadata/utils/warn-if-error-but-not-expected-to-fail.util';
|
||||
import { warnIfNoErrorButExpectedToFail } from 'test/integration/metadata/utils/warn-if-no-error-but-expected-to-fail.util';
|
||||
|
||||
import { type ApplicationTokenPairDTO } from 'src/engine/core-modules/application/dtos/application-token-pair.dto';
|
||||
import { type ApplicationTokenPairDTO } from 'src/engine/core-modules/application/application-oauth/dtos/application-token-pair.dto';
|
||||
|
||||
export const generateApplicationToken = async ({
|
||||
applicationId,
|
||||
|
||||
+9
-10
@@ -1,13 +1,8 @@
|
||||
import gql from 'graphql-tag';
|
||||
|
||||
export type InstallApplicationFactoryInput = {
|
||||
workspaceMigration: {
|
||||
actions: {
|
||||
type: 'delete';
|
||||
metadataName: string;
|
||||
universalIdentifier: string;
|
||||
}[];
|
||||
};
|
||||
appRegistrationId: string;
|
||||
version?: string;
|
||||
};
|
||||
|
||||
export const installApplicationQueryFactory = ({
|
||||
@@ -16,11 +11,15 @@ export const installApplicationQueryFactory = ({
|
||||
input: InstallApplicationFactoryInput;
|
||||
}) => ({
|
||||
query: gql`
|
||||
mutation InstallApplication($workspaceMigration: WorkspaceMigrationInput!) {
|
||||
installApplication(workspaceMigration: $workspaceMigration)
|
||||
mutation InstallApplication($appRegistrationId: String!, $version: String) {
|
||||
installApplication(
|
||||
appRegistrationId: $appRegistrationId
|
||||
version: $version
|
||||
)
|
||||
}
|
||||
`,
|
||||
variables: {
|
||||
workspaceMigration: input.workspaceMigration,
|
||||
appRegistrationId: input.appRegistrationId,
|
||||
version: input.version,
|
||||
},
|
||||
});
|
||||
|
||||
+43
-10
@@ -1,6 +1,9 @@
|
||||
import { createOneApplication } from 'test/integration/metadata/suites/application/utils/create-one-application.util';
|
||||
import crypto from 'crypto';
|
||||
|
||||
import { uploadApplicationFile } from 'test/integration/metadata/suites/application/utils/upload-application-file.util';
|
||||
|
||||
const TEST_WORKSPACE_ID = '20202020-1c25-4d02-bf25-6aeccf7ea419';
|
||||
|
||||
export const setupApplicationForSync = async ({
|
||||
applicationUniversalIdentifier,
|
||||
name,
|
||||
@@ -12,16 +15,46 @@ export const setupApplicationForSync = async ({
|
||||
description: string;
|
||||
sourcePath: string;
|
||||
}) => {
|
||||
await createOneApplication({
|
||||
universalIdentifier: applicationUniversalIdentifier,
|
||||
name,
|
||||
description,
|
||||
version: '1.0.0',
|
||||
sourcePath,
|
||||
expectToFail: false,
|
||||
});
|
||||
const registrationId = crypto.randomUUID();
|
||||
const applicationId = crypto.randomUUID();
|
||||
const oAuthClientId = crypto.randomUUID();
|
||||
|
||||
await globalThis.testDataSource.query(
|
||||
`INSERT INTO core."applicationRegistration"
|
||||
(id, "universalIdentifier", name, description, "oAuthClientId",
|
||||
"oAuthRedirectUris", "oAuthScopes", "workspaceId", "sourceType")
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
[
|
||||
registrationId,
|
||||
applicationUniversalIdentifier,
|
||||
name,
|
||||
description,
|
||||
oAuthClientId,
|
||||
[],
|
||||
[],
|
||||
TEST_WORKSPACE_ID,
|
||||
'local',
|
||||
],
|
||||
);
|
||||
|
||||
await globalThis.testDataSource.query(
|
||||
`INSERT INTO core."application"
|
||||
(id, "universalIdentifier", name, description, version, "sourcePath",
|
||||
"sourceType", "workspaceId", "applicationRegistrationId")
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)`,
|
||||
[
|
||||
applicationId,
|
||||
applicationUniversalIdentifier,
|
||||
name,
|
||||
description,
|
||||
'1.0.0',
|
||||
sourcePath,
|
||||
'local',
|
||||
TEST_WORKSPACE_ID,
|
||||
registrationId,
|
||||
],
|
||||
);
|
||||
|
||||
// File upload uses multipart which requires real timers
|
||||
jest.useRealTimers();
|
||||
|
||||
const packageJson = JSON.stringify({
|
||||
|
||||
Reference in New Issue
Block a user