feat(app): allow non-admin developers to claim and list marketplace apps (#22621)

## Context

Follow-up to #22609. Lets a non-admin developer claim ownership of a
public Twenty app they published to npm, then request a marketplace
listing that a server admin reviews. Marketplace state is per-instance
for now.

## Claiming

- Developer tab gets a **Claim an application** section: look up an
unclaimed npm app by package name or universal identifier.
- Ownership is proven with GitHub OAuth against the package's npm
provenance (trusted publishing): the connected account must own the
GitHub account or organization the package was published from.
- Errors from the GitHub callback come back as a code and are shown
inline with a link to the relevant documentation.
- The old one-click claim stays admin-only.
- A **Sync catalog** button triggers a catalog refresh instead of
waiting for the hourly cron.
- Gated behind the `IS_APP_CLAIMING_ENABLED` feature flag.

## Listing requests

- Catalog-synced apps are created **unlisted**; a data migration unlists
previously auto-listed unclaimed npm apps (owned or vetted rows are left
untouched).
- Owners request a listing from the Distribution tab (logo + description
required); a server admin approves or rejects it from a **Listing
requests** section in the Admin Panel.

## Screenshots

<img width="1512" height="829" alt="image"
src="https://github.com/user-attachments/assets/788d4362-97c4-4e42-810c-ef1f11517bec"/>
<img width="1512" height="829" alt="image"
src="https://github.com/user-attachments/assets/d6246190-c82a-4f64-87be-3bb668527645"/>
<img width="1512" height="828" alt="image"
src="https://github.com/user-attachments/assets/21a8dad4-610b-4d1f-8948-b9acab40d373"/>
<img width="1512" height="829" alt="image"
src="https://github.com/user-attachments/assets/58246130-41f7-451e-ae7f-57bd21d04bb6"/>

---------

Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com>
This commit is contained in:
martmull
2026-07-20 17:43:40 +02:00
committed by GitHub
parent 1b5e974629
commit 8d84a0b9f3
48 changed files with 2497 additions and 467 deletions
@@ -0,0 +1,36 @@
import { DataSource, QueryRunner } from 'typeorm';
import { RegisteredInstanceCommand } from 'src/engine/core-modules/upgrade/decorators/registered-instance-command.decorator';
import { SlowInstanceCommand } from 'src/engine/core-modules/upgrade/interfaces/slow-instance-command.interface';
// Catalog-synced apps used to be listed automatically. Marketplace listing is
// now curated by server admins, so unlist the auto-synced ones: npm-sourced,
// still unclaimed, and not vetted. Owned or vetted rows are left untouched.
@RegisteredInstanceCommand('2.23.0', 1784322591746, { type: 'slow' })
export class UnlistUnclaimedNpmApplicationRegistrationsSlowInstanceCommand
implements SlowInstanceCommand
{
async runDataMigration(dataSource: DataSource): Promise<void> {
await dataSource.query(
`UPDATE "core"."applicationRegistration"
SET "isListed" = false
WHERE "sourceType" = 'npm'
AND "workspaceId" IS NULL
AND "isVetted" = false`,
);
}
public async up(_queryRunner: QueryRunner): Promise<void> {}
public async down(queryRunner: QueryRunner): Promise<void> {
// Approximate rollback: rows that were manually unlisted before this
// migration cannot be told apart and may be relisted.
await queryRunner.query(
`UPDATE "core"."applicationRegistration"
SET "isListed" = true
WHERE "sourceType" = 'npm'
AND "workspaceId" IS NULL
AND "isVetted" = false`,
);
}
}
@@ -112,6 +112,7 @@ import { BackfillWorkspaceDatabaseSchemaSlowInstanceCommand } from './2-21/2-21-
import { AddLogoFileIdToApplicationRegistrationFastInstanceCommand } from './2-21/2-21-instance-command-fast-1783945979243-add-logo-file-id-to-application-registration';
import { AddCalendarEndFieldMetadataIdToViewFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-22/2-22-instance-command-fast-1783956795000-add-calendar-end-field-metadata-id-to-view';
import { AddCreatedWorkspaceActivationStatusSlowInstanceCommand } from './2-22/2-22-instance-command-slow-1784106205000-add-created-workspace-activation-status';
import { UnlistUnclaimedNpmApplicationRegistrationsSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784322591746-unlist-unclaimed-npm-application-registrations';
import { BackfillCreatedWorkspaceActivationStatusSlowInstanceCommand } from './2-23/2-23-instance-command-slow-1784286705000-backfill-created-workspace-activation-status';
import { AddAutoUpgradeToApplicationFastInstanceCommand } from 'src/database/commands/upgrade-version-command/2-23/2-23-instance-command-fast-1784297307235-add-auto-upgrade-to-application';
@@ -228,6 +229,7 @@ export const INSTANCE_COMMANDS = [
AddLogoFileIdToApplicationRegistrationFastInstanceCommand,
AddCalendarEndFieldMetadataIdToViewFastInstanceCommand,
AddCreatedWorkspaceActivationStatusSlowInstanceCommand,
UnlistUnclaimedNpmApplicationRegistrationsSlowInstanceCommand,
BackfillCreatedWorkspaceActivationStatusSlowInstanceCommand,
AddAutoUpgradeToApplicationFastInstanceCommand,
];