Source app About description from README and improve internal app READMEs (#22012)

## What

- The SDK manifest build now sources an app's `aboutDescription` (the
long-form "About" tab content) from its `README.md`. An explicit
`aboutDescription` in the config still wins, matching the existing
marketplace CDN fallback.
- Removed the now-duplicated `aboutDescription` from internal app
configs and deleted the standalone `ABOUT_DESCRIPTION` constant files.
- Rewrote internal app READMEs to read as user-facing About content:
stripped developer/build/source-path noise, and expanded the thin ones.
`call-recording` and `self-hosting` (one-liners over substantial apps)
and `people-data-labs` were rewritten from a close reading of the code;
`twenty-exa` was verified for accuracy.
- Added a unit test (and a fixture README) covering README →
`aboutDescription` in the build.

## Why

The README and the About description were maintained separately and
drifted. Making the README the single source keeps the About tab
accurate and removes duplicated copy.

## Notes for reviewers

- Internal apps depend on the published `twenty-sdk`, so the build
change takes effect for them after an SDK release + dependency bump.
Until then, published apps still get README → `aboutDescription` via the
marketplace CDN sync.
- Standard/Custom app descriptions are unchanged (they are resolved in
the frontend, not via the manifest).

<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22012?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://www.cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://www.cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
This commit is contained in:
Raphaël Bosi
2026-06-23 15:27:52 +02:00
committed by GitHub
parent 558f2509c2
commit c52c983b90
25 changed files with 232 additions and 752 deletions
@@ -42,27 +42,10 @@ export class MarketplaceCatalogSyncService {
const universalIdentifier =
fetchedManifest.application.universalIdentifier;
const aboutDescription =
fetchedManifest.application.aboutDescription ??
(await this.marketplaceService.fetchReadmeFromRegistryCdn(
pkg.name,
pkg.version,
));
const manifest = aboutDescription
? {
...fetchedManifest,
application: {
...fetchedManifest.application,
aboutDescription,
},
}
: fetchedManifest;
const cdnBaseUrl = this.twentyConfigService.get('APP_REGISTRY_CDN_URL');
const manifestWithResolvedUrls = resolveManifestAssetUrls(
manifest,
fetchedManifest,
(filePath) =>
buildRegistryCdnUrl({
cdnBaseUrl,
@@ -74,7 +57,7 @@ export class MarketplaceCatalogSyncService {
await this.applicationRegistrationService.upsertFromCatalog({
universalIdentifier,
name: manifest.application.displayName ?? pkg.name,
name: fetchedManifest.application.displayName ?? pkg.name,
sourceType: ApplicationRegistrationSourceType.NPM,
sourcePackage: pkg.name,
latestAvailableVersion: pkg.version ?? null,
@@ -73,39 +73,6 @@ export class MarketplaceService {
}
}
async fetchReadmeFromRegistryCdn(
packageName: string,
version: string,
): Promise<string | null> {
const cdnBaseUrl = this.twentyConfigService.get('APP_REGISTRY_CDN_URL');
const url = buildRegistryCdnUrl({
cdnBaseUrl,
packageName,
version,
filePath: 'README.md',
});
try {
const { data } = await axios.get(url, {
headers: { 'User-Agent': 'Twenty-Marketplace' },
timeout: 5_000,
responseType: 'text',
});
if (!data || data.trim().length === 0) {
return null;
}
return data;
} catch {
this.logger.debug(
`Could not fetch README from CDN for ${packageName}@${version}`,
);
return null;
}
}
async fetchAppsFromRegistry(): Promise<RegistryPackageInfo[]> {
const registryUrl = this.twentyConfigService.get('APP_REGISTRY_URL');