fe442fe5fe
## Summary
- Bumps `twenty-sdk` / `twenty-client-sdk` to the exact `2.19.0-alpha.1`
prerelease for the apps under `packages/twenty-apps` that actually
target a mutated standard identifier, and refreshes their lockfiles.
- Declares `"engines": { "twenty": ">=2.19.0" }` in those apps so
pre-2.19 servers refuse to install or upgrade to the rebuilt packages.
Only apps that reference a standard object's **system-field** universal
identifier, define a **relation into** a standard object, or call the
field-UID derivation helper need 2.19 (the identifiers those touch
changed from hardcoded UUIDs to deterministic hashes). Apps that only
define their own custom objects, or add plain scalar fields to a
standard object via its stable object-level id, were left on their prior
SDK pins. Currently bumped: `postcard`, `self-hosting`,
`twenty-partners`, `call-recorder`, `people-data-labs`,
`twenty-last-contact`.
## Context
Follow-up to #22565 (deterministic system field universal identifiers)
and #22599 (SDK prerelease bump).
Packages built with SDK ≤ 2.18 carry legacy system field identifiers and
are rejected by servers running `main`. Rebuilding with the 2.19 SDK
fixes that — but a rebuilt package must not be *upgraded into* by a 2.18
server, since 2.18 has no deterministic-identifier validation and would
diff the changed system field identifiers as a destructive delete +
create (the 2.19 backfill has not run there yet).
The `engines.twenty` constraint closes that gap: `doInstallApplication`
validates it via `validateServerCompatibility` before any mutation, and
this check has shipped since ~2.10, so every 2.18 server enforces it.
Resulting matrix:
- 2.18 fresh install of a rebuilt app: works, converges as a no-op once
the 2.19 backfill runs
- 2.18 upgrade of an existing install: rejected with
`SERVER_VERSION_INCOMPATIBLE` before any mutation
- 2.19 (post-backfill) install/upgrade: syncs cleanly
## Expected CI failures
**The `CI Twenty Apps` integration-test jobs are expected to fail on
this PR** (e.g. `people-data-labs`, `twenty-partners`). This is a
server-version mismatch, not an app bug — lint, typecheck and unit tests
all pass:
- The integration step spawns a real Twenty server from Docker Hub
`twentycrm/twenty-app-dev:latest` and runs `twenty dev` to sync each
app's metadata into it.
- `latest` currently resolves to **v2.18.5** — no `2.19` image is
published to Docker Hub yet.
- These apps now reference 2.19's **deterministic system-field universal
identifiers** (e.g. `company.createdBy`, `opportunity.createdAt`). A
2.18 server still carries the legacy identifiers, so the sync rejects
every 2.19-derived reference with `INVALID_VIEW_DATA` /
`FIELD_METADATA_NOT_FOUND` ("Field metadata not found").
- The failure surfaces as low-level field errors rather than a clean
`SERVER_VERSION_INCOMPATIBLE` because the `engines.twenty` gate
(`validateServerCompatibility`) only runs on the `app:install` / publish
paths — **not** on the `twenty dev` dev-sync path the integration tests
use.
These jobs will go green automatically once `twenty-app-dev:2.19` is
published to Docker Hub (or once CI pins the spawn action's
`twenty-version` to a 2.19 tag).
## Intentionally not included
- App `version` fields are untouched; they'll be bumped at publish time.
## Test plan
- [x] Refresh each bumped app's `yarn.lock` (`2.19.0-alpha.1` is now on
npm)
- [ ] Rebuild one app manifest and verify default field identifiers
match `getFieldUniversalIdentifier`
- [ ] Verify a 2.18 server rejects an upgrade to a rebuilt package with
`SERVER_VERSION_INCOMPATIBLE`
- [ ] Re-run `CI Twenty Apps` integration jobs once a
`twenty-app-dev:2.19` image is available
Made with [Cursor](https://cursor.com)
<!-- This is an auto-generated description by cubic. -->
<a
href="https://cubic.dev/pr/twentyhq/twenty/pull/22601?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-light.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
118 lines
3.2 KiB
TypeScript
118 lines
3.2 KiB
TypeScript
import { useEffect } from 'react';
|
|
import { defineFrontComponent } from 'twenty-sdk/define';
|
|
import {
|
|
enqueueSnackbar,
|
|
unmountFrontComponent,
|
|
updateProgress,
|
|
useRecordId,
|
|
} from 'twenty-sdk/front-component';
|
|
import { CoreApiClient } from 'twenty-client-sdk/core';
|
|
|
|
const SYSTEM_PROMPT =
|
|
'You are a postcard writing assistant. Write a short, warm postcard message ' +
|
|
'under 150 words. Use a personal tone. Output ONLY the postcard message ' +
|
|
'text, nothing else — no greeting label, no sign-off label, just the message.';
|
|
|
|
const GeneratePostCardEffect = () => {
|
|
const recordId = useRecordId();
|
|
|
|
useEffect(() => {
|
|
if (recordId === null) {
|
|
enqueueSnackbar({
|
|
message: 'Please select exactly one record',
|
|
variant: 'error',
|
|
});
|
|
unmountFrontComponent();
|
|
return;
|
|
}
|
|
|
|
const generate = async () => {
|
|
try {
|
|
const client = new CoreApiClient();
|
|
|
|
const userPrompt = 'Write a postcard message for a friend.';
|
|
|
|
const apiBaseUrl = process.env.TWENTY_API_URL;
|
|
const token =
|
|
process.env.TWENTY_APP_ACCESS_TOKEN ?? process.env.TWENTY_API_KEY;
|
|
|
|
if (!apiBaseUrl || !token) {
|
|
throw new Error('API configuration missing');
|
|
}
|
|
|
|
const response = await fetch(`${apiBaseUrl}/rest/ai/generate-text`, {
|
|
method: 'POST',
|
|
headers: {
|
|
'Content-Type': 'application/json',
|
|
Authorization: `Bearer ${token}`,
|
|
},
|
|
body: JSON.stringify({
|
|
systemPrompt: SYSTEM_PROMPT,
|
|
userPrompt,
|
|
}),
|
|
});
|
|
|
|
if (!response.ok) {
|
|
const errorBody = await response.text();
|
|
|
|
if (errorBody.includes('No AI models are available')) {
|
|
throw new Error(
|
|
'No AI models configured. Go to Settings > Admin Panel > AI to add a provider API key.',
|
|
);
|
|
}
|
|
|
|
throw new Error(`AI request failed with status ${response.status}`);
|
|
}
|
|
|
|
const data = (await response.json()) as { text?: string };
|
|
const generatedContent = data.text ?? '';
|
|
|
|
await updateProgress(0.7);
|
|
|
|
await client.mutation({
|
|
updatePostCard: {
|
|
__args: {
|
|
id: recordId,
|
|
data: { content: generatedContent },
|
|
},
|
|
id: true,
|
|
},
|
|
});
|
|
|
|
await updateProgress(1);
|
|
|
|
await enqueueSnackbar({
|
|
message: 'Post card content generated!',
|
|
variant: 'success',
|
|
});
|
|
|
|
await unmountFrontComponent();
|
|
} catch (error) {
|
|
await enqueueSnackbar({
|
|
message:
|
|
error instanceof Error
|
|
? error.message
|
|
: 'Failed to generate content',
|
|
variant: 'error',
|
|
});
|
|
await unmountFrontComponent();
|
|
}
|
|
};
|
|
|
|
generate();
|
|
}, [recordId]);
|
|
|
|
return null;
|
|
};
|
|
|
|
export const GENERATE_POST_CARD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
|
|
'bd7649c0-7540-4267-ac4b-f062fbd635a3';
|
|
|
|
export default defineFrontComponent({
|
|
universalIdentifier: GENERATE_POST_CARD_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
|
name: 'Generate Post Card',
|
|
description: 'Generates postcard content using AI',
|
|
isHeadless: true,
|
|
component: GeneratePostCardEffect,
|
|
});
|