Deterministic system field universal identifier (#22565)
# Introduction Close twentyhq/core-team-issues#2641 Auto-provisioned field metadata used to get its `universalIdentifier` from three unrelated sources: random `v4()` on the server when creating custom objects, hardcoded values in `STANDARD_OBJECTS`, and an ad-hoc `v5` derivation in the SDK manifest build. This PR unifies all of them behind the shared `getFieldUniversalIdentifier` derivation: ``` universalIdentifier = f(applicationUniversalIdentifier, objectUniversalIdentifier, fieldName) ``` ## Ownership model The rollout is built on an explicit split of who owns a field's universal identifier: - **The 8 system fields** (`id`, `createdAt`, `updatedAt`, `deletedAt`, `createdBy`, `updatedBy`, `position`, `searchVector`) are **server-owned**. Their universal identifiers are always the deterministic derivation, on **every** application (standard, workspace-custom, installed). Clients cannot provide custom values: a temporary check in `validateObjectMetadataSystemFieldsIntegrity` rejects any non-derived system field identifier at migration build time. This check stands in until system fields are generated exclusively server side by the metadata side-effect engine and stripped from client inputs — at which point it becomes structurally impossible to send one. - **`name` is a default field, not a system field**: it is auto-provisioned when absent (server side for custom objects, SDK side for application objects) but authors can define their own. It is only derived where it is guaranteed to be auto-provisioned. In particular, standard objects keep their **historical hardcoded** `name` identifiers: the standard app authors its `name` fields like any installed app would, and moving those identifiers would break every installed application referencing them (e.g. views on `opportunity.name`). - **User-created and author-provided fields** keep random / explicit identifiers, untouched. ## Server - `validateObjectMetadataSystemFieldsIntegrity` now validates, on top of the existing type/`isSystem` checks, that each system field's `universalIdentifier` equals the deterministic derivation. Runs for every object creation going through the migration orchestrator: app sync, custom object creation, standard provisioning - `build-default-flat-field-metadatas-for-custom-object.util.ts` derives the system field identifiers (and the auto-provisioned `name`) with `getFieldUniversalIdentifier` instead of `v4()` - `build-default-relation-flat-field-metadatas-for-custom-object.util.ts` derives both the forward and the reverse default relation field identifiers deterministically - `generateMorphOrRelationFlatFieldMetadataPair` accepts optional `sourceFieldUniversalIdentifier` / `targetFieldUniversalIdentifier` so callers can inject deterministic values; user-created relations still default to `v4()` ## twenty-shared - `STANDARD_OBJECTS` system field identifiers (the 8) are now computed at module load via `buildStandardObjectSystemFields`; `name` and every other identifier keep their hardcoded values - New snapshot test pinning **every** universal identifier of `STANDARD_OBJECTS`: any identifier change now requires an explicit snapshot update and should ship with a coordinated backfill ## SDK (breaking, pre-GA) - `generateDefaultFieldUniversalIdentifier` delegates to `getFieldUniversalIdentifier` and now requires `applicationUniversalIdentifier` - Reverse default relation field identifiers are derived from the field's real coordinates (standard object UID + actual field name, e.g. `targetRocket` on `attachment`) instead of the legacy custom-object UID + synthetic `${fieldName}Inverse` hash input. Field *names* are unchanged - The manifest build threads the application universal identifier through default field injection (two-pass over object configs) - `twenty dev:add` now resolves the application universal identifier upfront and refuses to scaffold anything until `defineApplication` declares one — no more `fill-later` placeholder for the app UID in generated files ## Upgrade A 2.19 **workspace command** backfills existing `fieldMetadata.universalIdentifier` rows to the deterministic derivation. Coverage follows the ownership model: - **The 8 system fields**: taken over for **every application**, whatever value they currently hold. This is both safe and required now that sync rejects non-derived values — leaving a row unconverged would make its application unsyncable - **`name`**: workspace-custom app → always taken over (server-generated, no author to clobber); installed applications → only rows still carrying the legacy SDK derivation are recomputed, author-provided identifiers are never touched; standard app → never touched (hardcoded in `STANDARD_OBJECTS`) - **Default relation fields**: workspace-custom app → forward fields on custom objects and reverse fields on the standard relation objects; installed applications → legacy-derivation probe only All identifiers of a workspace are updated inside a single transaction, then the command flushes the field-metadata-related workspace caches and bumps the metadata version. Stored `applicationRegistration.manifest` snapshots are intentionally **not** rewritten: installs and upgrades always sync from the `manifest.json` inside the resolved package (npm/tarball), the stored column is only used for display/marketplace purposes. ## Breaking behavior for old packages (fail closed) Packages built with an older SDK carry legacy system field identifiers in their tarball `manifest.json`. Installing or upgrading such a package now fails with an explicit `INVALID_SYSTEM_FIELD` validation error ("universal identifier is not deterministic") instead of silently mismatching against the backfilled rows and triggering a destructive delete+create. The remediation is to rebuild the package with the new SDK; the backfill has already converged the installed rows, so the rebuilt manifest syncs cleanly. ## Test plan - [x] `twenty-sdk` unit tests (526 tests) and typecheck - [x] `twenty-shared` unit tests (1635 tests) including the `STANDARD_OBJECTS` snapshot; `name` identifiers verified byte-for-byte identical to `main` - [x] Lint and typecheck clean on all touched packages - [x] Integration: create a custom object and verify system + default relation field identifiers match the deterministic derivation (`create-one-object-metadata-deterministic-field-universal-identifiers`, 13 assertions passing) - [x] Integration: `failing-sync-application-object-system-fields` extended with a non-derived system field identifier case; all identifiers in the spec pinned deterministically so snapshots embedding expected/actual values are stable across runs (verified with a double run) - [x] Integration: all application sync suites pass with the derived system field identifiers now required by the `buildDefaultObjectManifest` test helper (9 suites, 20 tests) - [x] Full test-database reset: standard app provisioning and seeded workspaces pass the new validation - [x] SDK manifest build verified on the postcard example app: all auto-generated default field identifiers match the derivation - [ ] Run `upgrade:2-19:backfill-deterministic-field-universal-identifiers` (dry-run then real) on a seeded workspace and verify identifier convergence with a rebuilt app manifest
This commit is contained in:
+25
-25
@@ -33,10 +33,10 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetMyNoteId',
|
||||
},
|
||||
universalIdentifier: '7e28b1ea-9a94-5d36-bc03-3f12f476867a',
|
||||
universalIdentifier: '55153e55-1bdc-590d-8fb1-40eafec88ef0',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35',
|
||||
'559713af-d9c0-5894-a972-9a3951661a2b',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
@@ -53,10 +53,10 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetMyNoteId',
|
||||
},
|
||||
universalIdentifier: '721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5',
|
||||
universalIdentifier: '80596d88-f896-5108-8cff-302a2eb61668',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'56a872c8-7a1e-563d-8772-3978c2c5c220',
|
||||
'8f8e42e3-77de-5b0a-b444-d96b6d3c7600',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
@@ -73,10 +73,10 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetMyNoteId',
|
||||
},
|
||||
universalIdentifier: '94b6f269-fc9a-5bcd-af5b-2bdac69f276b',
|
||||
universalIdentifier: '67bf90bd-f3d0-58ce-a96e-648364249812',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'011595e8-7524-5ec7-9fbc-4bd3c42d7d24',
|
||||
'0cbc9a5c-34b2-5d39-97a2-878662cb13d5',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
@@ -93,10 +93,10 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
joinColumnName: 'targetMyNoteId',
|
||||
},
|
||||
universalIdentifier: '50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3',
|
||||
universalIdentifier: '2689365b-e6ce-5b14-a2b5-6daafbfe91f8',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'dd734fa3-26c3-53dd-91aa-577c313e1fa4',
|
||||
'affdd37c-4460-53fc-998b-ffb04bb3a470',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1e2e3e4-e5e6-4000-8000-000000000030',
|
||||
type: FieldMetadataType.MORPH_RELATION,
|
||||
@@ -129,7 +129,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
universalIdentifier: '5bdb4f57-a349-56d0-b421-845fd55b3026',
|
||||
universalIdentifier: 'a717a3ba-e257-5269-95c4-1b8ca4cb8bcf',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -139,7 +139,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Name',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
universalIdentifier: '86cc7b06-26de-5b7b-bf4a-31fa246dfd90',
|
||||
universalIdentifier: '9a7f457a-6c15-581e-8f28-e2c680f75f97',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -149,7 +149,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Creation date',
|
||||
name: 'createdAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: 'abe3bc69-1a51-5814-a722-74f5a025e92a',
|
||||
universalIdentifier: 'f24a236a-e36a-5bab-91db-d43e3f4b8247',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -159,7 +159,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Last update',
|
||||
name: 'updatedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: 'c296d408-9720-5a86-8586-3490fbf04b86',
|
||||
universalIdentifier: '875c1f2e-19f0-5200-96ba-ac1cb4c1fd33',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -169,7 +169,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Deleted at',
|
||||
name: 'deletedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '488333a2-f4ff-5790-88ef-21f07f8f1985',
|
||||
universalIdentifier: 'cc58b328-40e1-5087-b550-ad700e53490b',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -182,7 +182,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Created by',
|
||||
name: 'createdBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: 'fba6e01b-6579-554e-ad64-a676003c8e68',
|
||||
universalIdentifier: '6f34f940-966a-52c2-a8f2-f2f04115d88e',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -195,7 +195,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Updated by',
|
||||
name: 'updatedBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: 'fa60f87d-15c8-5ff7-9da0-191fa14a922f',
|
||||
universalIdentifier: '37515dfb-8ae0-5326-a391-9e66205f6530',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
@@ -205,7 +205,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '4a5d7791-ddc9-53ee-bc46-699da7373290',
|
||||
universalIdentifier: 'a418b503-74d0-500d-b4c0-75f364394e20',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -215,7 +215,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: '0f00bc3b-f313-5206-8496-842af20a538a',
|
||||
universalIdentifier: '45718beb-e0d7-5bde-811c-29ff01b0fcfd',
|
||||
},
|
||||
{
|
||||
name: 'timelineActivities',
|
||||
@@ -227,9 +227,9 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: '12c84c1c-6e3f-54c5-a9a8-e93c7b46ac35',
|
||||
universalIdentifier: '559713af-d9c0-5894-a972-9a3951661a2b',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'7e28b1ea-9a94-5d36-bc03-3f12f476867a',
|
||||
'55153e55-1bdc-590d-8fb1-40eafec88ef0',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
},
|
||||
@@ -243,9 +243,9 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: '56a872c8-7a1e-563d-8772-3978c2c5c220',
|
||||
universalIdentifier: '8f8e42e3-77de-5b0a-b444-d96b6d3c7600',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'721b12d8-7a3d-5e13-b8d5-62b3ad8cb8b5',
|
||||
'80596d88-f896-5108-8cff-302a2eb61668',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
},
|
||||
@@ -259,9 +259,9 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: '011595e8-7524-5ec7-9fbc-4bd3c42d7d24',
|
||||
universalIdentifier: '0cbc9a5c-34b2-5d39-97a2-878662cb13d5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'94b6f269-fc9a-5bcd-af5b-2bdac69f276b',
|
||||
'67bf90bd-f3d0-58ce-a96e-648364249812',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
},
|
||||
@@ -275,9 +275,9 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
universalIdentifier: 'dd734fa3-26c3-53dd-91aa-577c313e1fa4',
|
||||
universalIdentifier: 'affdd37c-4460-53fc-998b-ffb04bb3a470',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'50da3cfa-dea5-5c9b-b1b7-cb615e1df4c3',
|
||||
'2689365b-e6ce-5b14-a2b5-6daafbfe91f8',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
},
|
||||
|
||||
+102
-102
@@ -292,11 +292,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetRootNote',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'1cdb0b3a-0ae1-59f6-b3f0-59ff99e40525',
|
||||
'e1478d59-c947-5926-af8b-4b2f0f842c91',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'a0f6ffdc-a570-58a6-973d-a9bc9a527de8',
|
||||
universalIdentifier: '7f891268-9376-5d00-b8ec-bc32a023154d',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRootNoteId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -312,11 +312,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetRootNote',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'aa05ee89-bd8a-5911-a4d5-3355926186bc',
|
||||
'34a6b8e9-0187-57e1-86c1-41bacf1b2479',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '95cad10a-bff6-524d-b6d8-dbc6db7cbf18',
|
||||
universalIdentifier: 'd72bb002-9952-5651-983e-649436949113',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRootNoteId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -332,11 +332,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetRootNote',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'762ad37e-d624-51d8-989c-3a119eef9e7f',
|
||||
'7a62bbd6-e697-54b0-8c1c-163ac47c0305',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'c327faff-76eb-5baf-8b35-1c31e57306fb',
|
||||
universalIdentifier: '61252ad6-2311-5054-ae51-b318dfd5fbd8',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRootNoteId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -352,11 +352,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetRootNote',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'd4f1b0c8-8cd9-581e-9bf9-917f8c918d76',
|
||||
'10c1af7e-e541-56bc-b5b3-d0297df3e23d',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'b0b1b2b3-b4b5-4000-8000-000000000001',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '36843050-e6e3-5759-99b6-e975426bad73',
|
||||
universalIdentifier: '2f4f3d69-b78e-5465-9e87-17059959fca8',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRootNoteId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -480,11 +480,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetPostCardRecipient',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'02f5efba-4f94-5cc3-9e17-ca7b1fe5f71b',
|
||||
'8f95b434-ee92-5c0f-95de-9d489af4d40c',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '18b6354d-8b0f-5be6-9473-d635aed670a1',
|
||||
universalIdentifier: 'a865f403-3843-5850-9a60-04e832220954',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -500,11 +500,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetPostCardRecipient',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'82497ec7-0121-53d6-9518-ae7434ceaf8b',
|
||||
'0c20df83-8cef-5cd6-9752-b98fb7780904',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'ba74dfd1-e7ee-502d-abad-82e14d595874',
|
||||
universalIdentifier: '501c46b7-7140-59a0-b703-9844bbcb34b9',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -520,11 +520,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetPostCardRecipient',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'6de8b57e-ed0a-5d35-bd39-bd8874445810',
|
||||
'b9642a90-2df5-555b-ab83-23cdce7a4142',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '9d5a9f4b-5431-5b90-810a-1e9d8873b302',
|
||||
universalIdentifier: '721418ff-1b53-58ef-8dd7-b58927c085c6',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -540,11 +540,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetPostCardRecipient',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'00459e7e-8ef1-5a9b-b180-26bbb54a2696',
|
||||
'd938a2d0-1279-5c48-b758-ac52f9460a76',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'e1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5e',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '85353e69-1e70-51ef-91c3-334d85013a4b',
|
||||
universalIdentifier: 'feeba797-7ebb-5a36-91cd-7279ff92a2b6',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -561,11 +561,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetPostCard',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'3f8c70b5-abb0-51ec-9109-0a94ce9c04a6',
|
||||
'9e58820c-78d7-539d-807c-d84362e1eab6',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'54b589ca-eeed-4950-a176-358418b85c05',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '1bdc52a0-0339-5360-ba17-b615250ef730',
|
||||
universalIdentifier: '47d21bb5-075f-5ed4-ba89-c0e7322a57cd',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -581,11 +581,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetPostCard',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'547c93e4-c9e7-5880-b251-46230fb59bfa',
|
||||
'3f0d38c8-a50f-51c7-bdcb-84a715f4b92d',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'54b589ca-eeed-4950-a176-358418b85c05',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'fecb8484-7e3c-59f8-a5ef-30882c5c1904',
|
||||
universalIdentifier: '4d3cf1bd-d87d-5325-8fbc-19f72c252050',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -601,11 +601,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetPostCard',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'47deea5c-8559-5ae7-991a-60c9f7102f86',
|
||||
'def1f10a-847b-574e-a0a2-177caf02129c',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'54b589ca-eeed-4950-a176-358418b85c05',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'ca687361-ac2b-5722-9ad9-cd37427d087a',
|
||||
universalIdentifier: 'bc1f9abe-e27b-574a-9290-a1c9c8255a6b',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -621,11 +621,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetPostCard',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'ecdccc0d-7c31-5126-89d5-0bdf3372257c',
|
||||
'b1238c2d-bc9c-53d6-8bee-eb045b9b9086',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'54b589ca-eeed-4950-a176-358418b85c05',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '17058c96-d5a4-53b1-96ca-786f8c5fc8bd',
|
||||
universalIdentifier: 'fb610de1-db20-5e78-8e4a-7630e57148eb',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetPostCardId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -642,11 +642,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetRecipient',
|
||||
objectUniversalIdentifier: '20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'51d6d2d7-2fa8-5f77-b6cf-a96dbb801fa5',
|
||||
'1118dd8a-9781-5254-b625-2d0f05eb46fe',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: 'b01f6937-4859-587b-b3df-92e0a6f65e93',
|
||||
universalIdentifier: '1ce06ca1-6e9a-53ce-9888-9152b1b2b628',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -662,11 +662,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetRecipient',
|
||||
objectUniversalIdentifier: '20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'244a7841-9e45-5ed1-a830-83e73c1f13fa',
|
||||
'15714c1a-d354-5407-8a15-25c117d605f8',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '9c87e1b8-74a2-5833-9237-281d561d2a7d',
|
||||
universalIdentifier: '2081700d-7ce6-5698-a7da-a013feda0eb6',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -682,11 +682,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetRecipient',
|
||||
objectUniversalIdentifier: '20202020-fff0-4b44-be82-bda313884400',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'eab71eb1-b457-555a-8a2b-f292cef1671d',
|
||||
'167f6de7-835e-51e1-b751-34055eef6aa5',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '23508618-7ed8-519e-9fee-a57b94dc98e9',
|
||||
universalIdentifier: 'a93c479c-6206-5714-94ce-affa6e9886aa',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -702,11 +702,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
name: 'targetRecipient',
|
||||
objectUniversalIdentifier: '20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'305f1890-04ba-5101-b18f-a1ca25a1d8a7',
|
||||
'd0db1844-4fde-5a5a-b037-ebeabd4467af',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'd1a2b3c4-5e6f-4a7b-8c9d-0e1f2a3b4c5d',
|
||||
type: FieldType.MORPH_RELATION,
|
||||
universalIdentifier: '20500ff3-2410-5bfc-b2e7-804785c07770',
|
||||
universalIdentifier: '45752445-c4b6-5e80-8aa1-f5321a838b43',
|
||||
universalSettings: {
|
||||
joinColumnName: 'targetRecipientId',
|
||||
onDelete: RelationOnDeleteAction.SET_NULL,
|
||||
@@ -749,7 +749,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
universalIdentifier: 'b5830f92-1abe-5ae7-b689-7821dda00b15',
|
||||
universalIdentifier: 'a4429f96-5745-511a-b246-67da9f920452',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -759,7 +759,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Name',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
universalIdentifier: 'ae2b2e5a-6c8b-5324-b639-8f17e3bc0420',
|
||||
universalIdentifier: '10452ca3-30cb-56fa-9e58-73f7b1c9fd65',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -769,7 +769,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Creation date',
|
||||
name: 'createdAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '7c84a501-41a6-531e-bc5e-8062843f6cba',
|
||||
universalIdentifier: '777fa853-2198-5623-96f3-119ef5707ad3',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -779,7 +779,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Last update',
|
||||
name: 'updatedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '72c2903a-91c9-5f36-8668-b53b083ae4cf',
|
||||
universalIdentifier: 'e1715fe9-34e1-572c-b62c-f39d737a17df',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -789,7 +789,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Deleted at',
|
||||
name: 'deletedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '007f5d8e-d376-519e-bd07-aea41a53f7d9',
|
||||
universalIdentifier: 'e3b13370-7241-55f1-ab9a-346b57bf4dcf',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -802,7 +802,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Created by',
|
||||
name: 'createdBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '44053354-0d8b-5d66-bfa8-7e7d221de3db',
|
||||
universalIdentifier: 'd2e3e238-9bfd-5845-9fe8-3e58df09f246',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -815,7 +815,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Updated by',
|
||||
name: 'updatedBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '38e2c671-cbf5-5773-a04a-e5a2d6c323c8',
|
||||
universalIdentifier: '690003ae-f649-537e-b124-d21ef0ee86a1',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
@@ -825,7 +825,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '3b56cdea-cf85-51d4-a7a0-cd701f76141a',
|
||||
universalIdentifier: '4b6d383d-4e84-592e-b1ae-30ae27139302',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -835,7 +835,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: '7c6d88f5-a348-51dd-8d0b-74a17e626754',
|
||||
universalIdentifier: 'a92d59aa-23a1-5191-b840-933183c14846',
|
||||
},
|
||||
{
|
||||
description: 'Root notes tied to the RootNote',
|
||||
@@ -844,11 +844,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Timeline Activities',
|
||||
name: 'timelineActivities',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'a0f6ffdc-a570-58a6-973d-a9bc9a527de8',
|
||||
'7f891268-9376-5d00-b8ec-bc32a023154d',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '1cdb0b3a-0ae1-59f6-b3f0-59ff99e40525',
|
||||
universalIdentifier: 'e1478d59-c947-5926-af8b-4b2f0f842c91',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -860,11 +860,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Attachments',
|
||||
name: 'attachments',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'95cad10a-bff6-524d-b6d8-dbc6db7cbf18',
|
||||
'd72bb002-9952-5651-983e-649436949113',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'aa05ee89-bd8a-5911-a4d5-3355926186bc',
|
||||
universalIdentifier: '34a6b8e9-0187-57e1-86c1-41bacf1b2479',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -876,11 +876,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Note Targets',
|
||||
name: 'noteTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'c327faff-76eb-5baf-8b35-1c31e57306fb',
|
||||
'61252ad6-2311-5054-ae51-b318dfd5fbd8',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '762ad37e-d624-51d8-989c-3a119eef9e7f',
|
||||
universalIdentifier: '7a62bbd6-e697-54b0-8c1c-163ac47c0305',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -892,11 +892,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Task Targets',
|
||||
name: 'taskTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'36843050-e6e3-5759-99b6-e975426bad73',
|
||||
'2f4f3d69-b78e-5465-9e87-17059959fca8',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'd4f1b0c8-8cd9-581e-9bf9-917f8c918d76',
|
||||
universalIdentifier: '10c1af7e-e541-56bc-b5b3-d0297df3e23d',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -931,7 +931,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
universalIdentifier: '2809d745-deb3-5ac0-8328-ea6256ec22b3',
|
||||
universalIdentifier: 'fe2cec74-a43b-5910-8dd6-7a737fb869ae',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -941,7 +941,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Name',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
universalIdentifier: 'a6ee6cc8-b9a0-5e23-a39e-bc3fee2aa3d3',
|
||||
universalIdentifier: 'f485cd3a-4c55-5b8f-b926-a83e5e6ba651',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -951,7 +951,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Creation date',
|
||||
name: 'createdAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '87ad23b6-b04e-5dc1-9907-c1eca54ea6d6',
|
||||
universalIdentifier: '729b0d02-9030-5440-bd4e-f34b46d5d031',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -961,7 +961,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Last update',
|
||||
name: 'updatedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '6d7523dc-c097-5b75-96eb-37502d09cc19',
|
||||
universalIdentifier: '42381c24-6337-5654-8c44-6649fc6b06a8',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -971,7 +971,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Deleted at',
|
||||
name: 'deletedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: 'bd4fe2fa-373a-5fdd-ba09-cb5435781e4c',
|
||||
universalIdentifier: '5117f25c-d3a2-533b-b3d6-39a424e88d2a',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -984,7 +984,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Created by',
|
||||
name: 'createdBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: 'c5026f20-ab7a-54e0-900e-37a2d5381aee',
|
||||
universalIdentifier: '48df9616-8671-522f-87c7-efcd3f2e2604',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -997,7 +997,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Updated by',
|
||||
name: 'updatedBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '1dd75ff5-5fd0-51df-b3a3-a12e3aff30df',
|
||||
universalIdentifier: '5c8eb640-8c91-5712-8b73-85ce5e71abb9',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
@@ -1007,7 +1007,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '6c5e91f5-e323-555b-84e0-5a1dd7f36b1b',
|
||||
universalIdentifier: '5a5800dd-743f-533d-ad5c-f983a4bb6324',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -1017,7 +1017,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: '9fa18b19-41b6-5082-b3e2-4e73f7461164',
|
||||
universalIdentifier: 'c4d0beaf-9035-514b-977c-5059b544c1bb',
|
||||
},
|
||||
{
|
||||
description: 'Post Card Recipients tied to the PostCardRecipient',
|
||||
@@ -1026,11 +1026,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Timeline Activities',
|
||||
name: 'timelineActivities',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'18b6354d-8b0f-5be6-9473-d635aed670a1',
|
||||
'a865f403-3843-5850-9a60-04e832220954',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '02f5efba-4f94-5cc3-9e17-ca7b1fe5f71b',
|
||||
universalIdentifier: '8f95b434-ee92-5c0f-95de-9d489af4d40c',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1042,11 +1042,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Attachments',
|
||||
name: 'attachments',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'ba74dfd1-e7ee-502d-abad-82e14d595874',
|
||||
'501c46b7-7140-59a0-b703-9844bbcb34b9',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '82497ec7-0121-53d6-9518-ae7434ceaf8b',
|
||||
universalIdentifier: '0c20df83-8cef-5cd6-9752-b98fb7780904',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1058,11 +1058,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Note Targets',
|
||||
name: 'noteTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'9d5a9f4b-5431-5b90-810a-1e9d8873b302',
|
||||
'721418ff-1b53-58ef-8dd7-b58927c085c6',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '6de8b57e-ed0a-5d35-bd39-bd8874445810',
|
||||
universalIdentifier: 'b9642a90-2df5-555b-ab83-23cdce7a4142',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1074,11 +1074,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Task Targets',
|
||||
name: 'taskTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'85353e69-1e70-51ef-91c3-334d85013a4b',
|
||||
'feeba797-7ebb-5a36-91cd-7279ff92a2b6',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '00459e7e-8ef1-5a9b-b180-26bbb54a2696',
|
||||
universalIdentifier: 'd938a2d0-1279-5c48-b758-ac52f9460a76',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1086,7 +1086,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
],
|
||||
icon: 'IconLink',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
'a6ee6cc8-b9a0-5e23-a39e-bc3fee2aa3d3',
|
||||
'f485cd3a-4c55-5b8f-b926-a83e5e6ba651',
|
||||
labelPlural: 'Post Card Recipients',
|
||||
labelSingular: 'Post Card Recipient',
|
||||
namePlural: 'postCardRecipients',
|
||||
@@ -1180,7 +1180,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
universalIdentifier: '56e652a3-fac2-54fd-a379-f01b72758268',
|
||||
universalIdentifier: 'e61745a2-969c-53d1-a809-6c583e9192d5',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -1190,7 +1190,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Name',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
universalIdentifier: 'c59a149f-5861-51a3-a924-2bd2fa31daaa',
|
||||
universalIdentifier: '3e14e30f-8271-5a13-9c1f-ce5edb932325',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -1200,7 +1200,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Creation date',
|
||||
name: 'createdAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '236e2ec5-3636-5b97-b19d-f7799cb88d99',
|
||||
universalIdentifier: 'b60eaf02-b008-5ebc-8fe5-123f8e92cc9e',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -1210,7 +1210,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Last update',
|
||||
name: 'updatedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '2cf640eb-09a7-52f2-820b-419071c5ec24',
|
||||
universalIdentifier: 'd76f15e3-e877-5b62-af97-9dac7c71065b',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -1220,7 +1220,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Deleted at',
|
||||
name: 'deletedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '2eb62b4a-6888-58ab-9887-a879321fff31',
|
||||
universalIdentifier: 'a5ec9b5d-90ed-587e-8a9b-cd9d1ba43116',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -1233,7 +1233,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Created by',
|
||||
name: 'createdBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '36e95d21-9c86-5b6f-8133-a66d09fc8952',
|
||||
universalIdentifier: '4c72ab36-ca4a-5870-b8cc-652385020c45',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -1246,7 +1246,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Updated by',
|
||||
name: 'updatedBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: 'dc5231a4-e128-57b0-ac9d-723678d9b239',
|
||||
universalIdentifier: '2f481c85-025c-51cf-bccd-3375d252bf26',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
@@ -1256,7 +1256,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '3ad26df7-6345-5f85-aac1-2306cf10eaed',
|
||||
universalIdentifier: 'e1178a62-d1a5-5e9c-b730-9118c640711d',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -1266,7 +1266,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: 'b49b1a1c-2f6b-52a3-836b-ece89c698627',
|
||||
universalIdentifier: 'e8569449-73b0-5f19-abc0-f6ad06c40341',
|
||||
},
|
||||
{
|
||||
description: 'Post cards tied to the PostCard',
|
||||
@@ -1275,11 +1275,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Timeline Activities',
|
||||
name: 'timelineActivities',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'1bdc52a0-0339-5360-ba17-b615250ef730',
|
||||
'47d21bb5-075f-5ed4-ba89-c0e7322a57cd',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '3f8c70b5-abb0-51ec-9109-0a94ce9c04a6',
|
||||
universalIdentifier: '9e58820c-78d7-539d-807c-d84362e1eab6',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1291,11 +1291,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Attachments',
|
||||
name: 'attachments',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'fecb8484-7e3c-59f8-a5ef-30882c5c1904',
|
||||
'4d3cf1bd-d87d-5325-8fbc-19f72c252050',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '547c93e4-c9e7-5880-b251-46230fb59bfa',
|
||||
universalIdentifier: '3f0d38c8-a50f-51c7-bdcb-84a715f4b92d',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1307,11 +1307,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Note Targets',
|
||||
name: 'noteTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'ca687361-ac2b-5722-9ad9-cd37427d087a',
|
||||
'bc1f9abe-e27b-574a-9290-a1c9c8255a6b',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '47deea5c-8559-5ae7-991a-60c9f7102f86',
|
||||
universalIdentifier: 'def1f10a-847b-574e-a0a2-177caf02129c',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1323,11 +1323,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Task Targets',
|
||||
name: 'taskTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'17058c96-d5a4-53b1-96ca-786f8c5fc8bd',
|
||||
'fb610de1-db20-5e78-8e4a-7630e57148eb',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'ecdccc0d-7c31-5126-89d5-0bdf3372257c',
|
||||
universalIdentifier: 'b1238c2d-bc9c-53d6-8bee-eb045b9b9086',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1367,7 +1367,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Id',
|
||||
name: 'id',
|
||||
type: FieldMetadataType.UUID,
|
||||
universalIdentifier: '71aa41d7-54d0-5447-bf98-f37435b62bc4',
|
||||
universalIdentifier: '6c8d489d-f871-598c-92b7-929b5883ae0f',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -1377,7 +1377,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Name',
|
||||
name: 'name',
|
||||
type: FieldMetadataType.TEXT,
|
||||
universalIdentifier: 'ff07c707-df79-58fa-91a4-c60c31f90e6e',
|
||||
universalIdentifier: 'c400ccb8-cd91-5ae1-81e3-2df8b6f2cf53',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -1387,7 +1387,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Creation date',
|
||||
name: 'createdAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '8dd42a57-7aca-5e60-89c1-6e0e430dad43',
|
||||
universalIdentifier: '88b7a0a5-f2d2-5dce-89a6-e0f27577dd3f',
|
||||
},
|
||||
{
|
||||
defaultValue: 'now',
|
||||
@@ -1397,7 +1397,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Last update',
|
||||
name: 'updatedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: 'a81e915c-f21d-5163-a547-f04029655bf0',
|
||||
universalIdentifier: '411c5a73-100a-5255-a54d-de20a5c2ae54',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -1407,7 +1407,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Deleted at',
|
||||
name: 'deletedAt',
|
||||
type: FieldMetadataType.DATE_TIME,
|
||||
universalIdentifier: '33ba5fa8-dbc3-5a08-b0e0-d85b18ff15e0',
|
||||
universalIdentifier: 'd3d59a1e-2873-594a-a7de-630cde6980a9',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -1420,7 +1420,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Created by',
|
||||
name: 'createdBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '7c377262-63b7-5a79-ba54-f7b8acd804dc',
|
||||
universalIdentifier: 'f358b82e-1254-581e-8629-4fc4302fdbdf',
|
||||
},
|
||||
{
|
||||
defaultValue: {
|
||||
@@ -1433,7 +1433,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Updated by',
|
||||
name: 'updatedBy',
|
||||
type: FieldMetadataType.ACTOR,
|
||||
universalIdentifier: '8da8e149-b60e-52e2-878d-73d05c8312b2',
|
||||
universalIdentifier: '8186fca8-89da-5542-99fe-f0ddf90e1fc1',
|
||||
},
|
||||
{
|
||||
defaultValue: 0,
|
||||
@@ -1443,7 +1443,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Position',
|
||||
name: 'position',
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: '824e930c-ce9f-5267-bebd-f6db215c3c14',
|
||||
universalIdentifier: '9a81ba2c-e4b2-56f8-a610-015decebd3aa',
|
||||
},
|
||||
{
|
||||
defaultValue: null,
|
||||
@@ -1453,7 +1453,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Search vector',
|
||||
name: 'searchVector',
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: '1f5df24b-8248-59cf-8bb6-362de121d2e4',
|
||||
universalIdentifier: '41eed58f-1016-5483-b3ff-979f270dd9a6',
|
||||
},
|
||||
{
|
||||
description: 'Recipients tied to the Recipient',
|
||||
@@ -1462,11 +1462,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Timeline Activities',
|
||||
name: 'timelineActivities',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'b01f6937-4859-587b-b3df-92e0a6f65e93',
|
||||
'1ce06ca1-6e9a-53ce-9888-9152b1b2b628',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-6736-4337-b5c4-8b39fae325a5',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '51d6d2d7-2fa8-5f77-b6cf-a96dbb801fa5',
|
||||
universalIdentifier: '1118dd8a-9781-5254-b625-2d0f05eb46fe',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1478,11 +1478,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Attachments',
|
||||
name: 'attachments',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'9c87e1b8-74a2-5833-9237-281d561d2a7d',
|
||||
'2081700d-7ce6-5698-a7da-a013feda0eb6',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-bd3d-4c60-8dca-571c71d4447a',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '244a7841-9e45-5ed1-a830-83e73c1f13fa',
|
||||
universalIdentifier: '15714c1a-d354-5407-8a15-25c117d605f8',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1494,11 +1494,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Note Targets',
|
||||
name: 'noteTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'23508618-7ed8-519e-9fee-a57b94dc98e9',
|
||||
'a93c479c-6206-5714-94ce-affa6e9886aa',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-fff0-4b44-be82-bda313884400',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: 'eab71eb1-b457-555a-8a2b-f292cef1671d',
|
||||
universalIdentifier: '167f6de7-835e-51e1-b751-34055eef6aa5',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1510,11 +1510,11 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
label: 'Task Targets',
|
||||
name: 'taskTargets',
|
||||
relationTargetFieldMetadataUniversalIdentifier:
|
||||
'20500ff3-2410-5bfc-b2e7-804785c07770',
|
||||
'45752445-c4b6-5e80-8aa1-f5321a838b43',
|
||||
relationTargetObjectMetadataUniversalIdentifier:
|
||||
'20202020-5a9a-44e8-95df-771cd06d0fb1',
|
||||
type: FieldType.RELATION,
|
||||
universalIdentifier: '305f1890-04ba-5101-b18f-a1ca25a1d8a7',
|
||||
universalIdentifier: 'd0db1844-4fde-5a5a-b037-ebeabd4467af',
|
||||
universalSettings: {
|
||||
relationType: RelationType.ONE_TO_MANY,
|
||||
},
|
||||
@@ -1522,7 +1522,7 @@ export const EXPECTED_MANIFEST: Manifest = {
|
||||
],
|
||||
icon: 'IconUser',
|
||||
labelIdentifierFieldMetadataUniversalIdentifier:
|
||||
'ff07c707-df79-58fa-91a4-c60c31f90e6e',
|
||||
'c400ccb8-cd91-5ae1-81e3-2df8b6f2cf53',
|
||||
labelPlural: 'Recipients',
|
||||
labelSingular: 'Recipient',
|
||||
namePlural: 'recipients',
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
import { assertUnreachable } from 'twenty-shared/utils';
|
||||
import { v4 } from 'uuid';
|
||||
|
||||
import { getApplicationUniversalIdentifierOrThrow } from '@/cli/utilities/application/get-application-universal-identifier-or-throw';
|
||||
import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory';
|
||||
import { convertToLabel } from '@/cli/utilities/entity/entity-label';
|
||||
import { appendServerVariablesToAppConfig } from '@/cli/utilities/file/append-server-variables.util';
|
||||
@@ -45,6 +46,13 @@ export class EntityAddCommand {
|
||||
|
||||
async execute(entityType?: SyncableEntity, path?: string): Promise<void> {
|
||||
try {
|
||||
// Generated files embed identifiers derived from the application
|
||||
// universal identifier, so scaffolding is blocked until it is defined.
|
||||
const applicationUniversalIdentifier =
|
||||
await getApplicationUniversalIdentifierOrThrow(
|
||||
CURRENT_EXECUTION_DIRECTORY,
|
||||
);
|
||||
|
||||
const entity = entityType ?? (await this.getEntity());
|
||||
|
||||
const entityName = this.getFolderName(entity);
|
||||
@@ -55,7 +63,10 @@ export class EntityAddCommand {
|
||||
|
||||
await ensureDir(appPath);
|
||||
|
||||
const { name, file } = await this.getEntityData(entity);
|
||||
const { name, file } = await this.getEntityData(
|
||||
entity,
|
||||
applicationUniversalIdentifier,
|
||||
);
|
||||
|
||||
const filePath = join(appPath, this.getFileName(name, entity));
|
||||
|
||||
@@ -74,7 +85,11 @@ export class EntityAddCommand {
|
||||
);
|
||||
|
||||
if (entity === SyncableEntity.Object) {
|
||||
await this.promptAndCreateObjectCompanions(name, path);
|
||||
await this.promptAndCreateObjectCompanions({
|
||||
objectName: name,
|
||||
applicationUniversalIdentifier,
|
||||
customPath: path,
|
||||
});
|
||||
}
|
||||
|
||||
if (entity === SyncableEntity.ConnectionProvider) {
|
||||
@@ -89,7 +104,10 @@ export class EntityAddCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private async getEntityData(entity: SyncableEntity) {
|
||||
private async getEntityData(
|
||||
entity: SyncableEntity,
|
||||
applicationUniversalIdentifier: string,
|
||||
) {
|
||||
switch (entity) {
|
||||
case SyncableEntity.Object: {
|
||||
const entityData = await this.getObjectData();
|
||||
@@ -192,6 +210,7 @@ export class EntityAddCommand {
|
||||
|
||||
const file = getViewBaseFile({
|
||||
name,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
return { name, file };
|
||||
@@ -259,10 +278,15 @@ export class EntityAddCommand {
|
||||
}
|
||||
}
|
||||
|
||||
private async promptAndCreateObjectCompanions(
|
||||
objectName: string,
|
||||
customPath?: string,
|
||||
): Promise<void> {
|
||||
private async promptAndCreateObjectCompanions({
|
||||
objectName,
|
||||
applicationUniversalIdentifier,
|
||||
customPath,
|
||||
}: {
|
||||
objectName: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
customPath?: string;
|
||||
}): Promise<void> {
|
||||
const { createCompanions } = await inquirer.prompt<{
|
||||
createCompanions: boolean;
|
||||
}>([
|
||||
@@ -286,6 +310,7 @@ export class EntityAddCommand {
|
||||
name: `all-${kebabCase(objectName)}`,
|
||||
universalIdentifier: viewUniversalIdentifier,
|
||||
objectUniversalIdentifier: this.lastObjectUniversalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
fields: this.lastNameFieldUniversalIdentifier
|
||||
? [
|
||||
{
|
||||
@@ -335,6 +360,7 @@ export class EntityAddCommand {
|
||||
name: `${kebabCase(objectName)}-record-page-fields`,
|
||||
universalIdentifier: fieldsWidgetViewUniversalIdentifier,
|
||||
objectUniversalIdentifier: this.lastObjectUniversalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
type: ViewType.FIELDS_WIDGET,
|
||||
fields: recordPageFieldsViewFields,
|
||||
});
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
import { join } from 'path';
|
||||
|
||||
import { pathExists } from '@/cli/utilities/file/fs-utils';
|
||||
|
||||
export const APP_CONFIG_CANDIDATE_PATHS = [
|
||||
'src/application.config.ts',
|
||||
'src/application-config.ts',
|
||||
'src/applicationConfig.ts',
|
||||
];
|
||||
|
||||
export const findAppConfigPath = async (
|
||||
projectRoot: string,
|
||||
): Promise<string | null> => {
|
||||
for (const candidate of APP_CONFIG_CANDIDATE_PATHS) {
|
||||
const absolute = join(projectRoot, candidate);
|
||||
|
||||
if (await pathExists(absolute)) {
|
||||
return absolute;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
+38
@@ -0,0 +1,38 @@
|
||||
import {
|
||||
APP_CONFIG_CANDIDATE_PATHS,
|
||||
findAppConfigPath,
|
||||
} from '@/cli/utilities/application/find-app-config-path';
|
||||
import { extractManifestFromFile } from '@/cli/utilities/build/manifest/manifest-extract-config-from-file';
|
||||
|
||||
type ApplicationConfigWithUniversalIdentifier = {
|
||||
universalIdentifier?: string;
|
||||
};
|
||||
|
||||
// Scaffolding derives deterministic identifiers from the application
|
||||
// universal identifier, so nothing can be generated before defineApplication
|
||||
// declares one.
|
||||
export const getApplicationUniversalIdentifierOrThrow = async (
|
||||
appPath: string,
|
||||
): Promise<string> => {
|
||||
const configPath = await findAppConfigPath(appPath);
|
||||
|
||||
if (configPath === null) {
|
||||
throw new Error(
|
||||
`Could not find the application config file (expected one of: ${APP_CONFIG_CANDIDATE_PATHS.join(', ')}). Create it with defineApplication({ universalIdentifier: ... }) before generating entities.`,
|
||||
);
|
||||
}
|
||||
|
||||
const { config } =
|
||||
await extractManifestFromFile<ApplicationConfigWithUniversalIdentifier>({
|
||||
appPath,
|
||||
filePath: configPath,
|
||||
});
|
||||
|
||||
if (!config?.universalIdentifier) {
|
||||
throw new Error(
|
||||
`defineApplication in ${configPath} must declare a universalIdentifier before generating entities.`,
|
||||
);
|
||||
}
|
||||
|
||||
return config.universalIdentifier;
|
||||
};
|
||||
@@ -14,10 +14,10 @@ import { validateViewFilterOperands } from '@/cli/utilities/build/manifest/utils
|
||||
import { type ApplicationConfig, type LogicFunctionConfig } from '@/sdk/define';
|
||||
import { type CommandMenuItemConfig } from '@/sdk/define/command-menu-items/command-menu-item-config';
|
||||
import { type FrontComponentConfig } from '@/sdk/define/front-component/front-component-config';
|
||||
import { type IndexConfig } from '@/sdk/define/indexes/index-config';
|
||||
import { type PostInstallLogicFunctionConfig } from '@/sdk/define/logic-functions/post-install-logic-function-config';
|
||||
import { type PreInstallLogicFunctionConfig } from '@/sdk/define/logic-functions/pre-install-logic-function-config';
|
||||
import { type ObjectConfig } from '@/sdk/define/objects/object-config';
|
||||
import { type IndexConfig } from '@/sdk/define/indexes/index-config';
|
||||
import { type PageLayoutConfig } from '@/sdk/define/page-layouts/page-layout-config';
|
||||
import { type PageLayoutTabConfig } from '@/sdk/define/page-layouts/page-layout-tab-config';
|
||||
import { type RoleConfig } from '@/sdk/define/roles/role-config';
|
||||
@@ -96,6 +96,7 @@ export const buildManifest = async (
|
||||
const warnings: string[] = [];
|
||||
|
||||
let applicationConfig: ApplicationConfig | undefined;
|
||||
const objectConfigs: ObjectConfig[] = [];
|
||||
const objects: ObjectManifest[] = [];
|
||||
const fields: FieldManifest[] = [];
|
||||
const indexes: IndexManifest[] = [];
|
||||
@@ -172,31 +173,7 @@ export const buildManifest = async (
|
||||
filePath,
|
||||
});
|
||||
|
||||
const {
|
||||
objectFields: objectFieldsWithDefaults,
|
||||
fields: reverseRelationFields,
|
||||
} = getDefaultFieldsInObjectFields(extract.config);
|
||||
|
||||
const labelIdentifierFieldMetadataUniversalIdentifier =
|
||||
extract.config.labelIdentifierFieldMetadataUniversalIdentifier ??
|
||||
objectFieldsWithDefaults.find((field) => field.name === 'name')
|
||||
?.universalIdentifier;
|
||||
|
||||
if (!labelIdentifierFieldMetadataUniversalIdentifier) {
|
||||
errors.push(
|
||||
`No label identifier field found for object ${extract.config.nameSingular}. Please add a field with name "name" to your object.`,
|
||||
);
|
||||
break;
|
||||
}
|
||||
|
||||
const objectManifest: ObjectManifest = {
|
||||
...extract.config,
|
||||
fields: objectFieldsWithDefaults.map(addMissingFieldOptionIds),
|
||||
labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
};
|
||||
|
||||
objects.push(objectManifest);
|
||||
fields.push(...reverseRelationFields);
|
||||
objectConfigs.push(extract.config);
|
||||
|
||||
errors.push(...extract.errors);
|
||||
warnings.push(...(extract.warnings ?? []));
|
||||
@@ -522,6 +499,39 @@ export const buildManifest = async (
|
||||
);
|
||||
}
|
||||
|
||||
if (applicationConfig) {
|
||||
for (const objectConfig of objectConfigs) {
|
||||
const {
|
||||
objectFields: objectFieldsWithDefaults,
|
||||
fields: reverseRelationFields,
|
||||
} = getDefaultFieldsInObjectFields({
|
||||
objectConfig,
|
||||
applicationUniversalIdentifier: applicationConfig.universalIdentifier,
|
||||
});
|
||||
|
||||
const labelIdentifierFieldMetadataUniversalIdentifier =
|
||||
objectConfig.labelIdentifierFieldMetadataUniversalIdentifier ??
|
||||
objectFieldsWithDefaults.find((field) => field.name === 'name')
|
||||
?.universalIdentifier;
|
||||
|
||||
if (!labelIdentifierFieldMetadataUniversalIdentifier) {
|
||||
errors.push(
|
||||
`No label identifier field found for object ${objectConfig.nameSingular}. Please add a field with name "name" to your object.`,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
const objectManifest: ObjectManifest = {
|
||||
...objectConfig,
|
||||
fields: objectFieldsWithDefaults.map(addMissingFieldOptionIds),
|
||||
labelIdentifierFieldMetadataUniversalIdentifier,
|
||||
};
|
||||
|
||||
objects.push(objectManifest);
|
||||
fields.push(...reverseRelationFields);
|
||||
}
|
||||
}
|
||||
|
||||
if (postInstallLogicFunctions.length > 1) {
|
||||
errors.push(
|
||||
'Only one post install logic function is allowed per application',
|
||||
|
||||
+30
-13
@@ -14,15 +14,32 @@ const baseObjectConfig: ObjectConfig = {
|
||||
fields: [],
|
||||
};
|
||||
|
||||
const applicationUniversalIdentifier = 'b7f8a9c0-1d2e-4f40-8a6b-7c8d9e0f1a2b';
|
||||
|
||||
const getMockDefaultFieldsInObjectFields = (objectConfig: ObjectConfig) =>
|
||||
getDefaultFieldsInObjectFields({
|
||||
objectConfig,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
const getMockDefaultObjectFields = (objectConfig: ObjectConfig) =>
|
||||
getDefaultObjectFields({ objectConfig, applicationUniversalIdentifier });
|
||||
|
||||
const getMockDefaultRelationObjectFields = (objectConfig: ObjectConfig) =>
|
||||
getDefaultRelationObjectFields({
|
||||
objectConfig,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
describe('getDefaultFieldsInObjectFields', () => {
|
||||
it('should return all default fields when objectConfig has no fields', () => {
|
||||
const { objectFields, fields } =
|
||||
getDefaultFieldsInObjectFields(baseObjectConfig);
|
||||
const defaultFields = getDefaultObjectFields(baseObjectConfig);
|
||||
getMockDefaultFieldsInObjectFields(baseObjectConfig);
|
||||
const defaultFields = getMockDefaultObjectFields(baseObjectConfig);
|
||||
const {
|
||||
objectFields: defaultRelationObjectFields,
|
||||
fields: expectedReverseFields,
|
||||
} = getDefaultRelationObjectFields(baseObjectConfig);
|
||||
} = getMockDefaultRelationObjectFields(baseObjectConfig);
|
||||
|
||||
expect(objectFields).toEqual([
|
||||
...defaultFields,
|
||||
@@ -56,10 +73,10 @@ describe('getDefaultFieldsInObjectFields', () => {
|
||||
fields: [customField],
|
||||
};
|
||||
|
||||
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
|
||||
const defaultFields = getDefaultObjectFields(objectConfig);
|
||||
const { objectFields } = getMockDefaultFieldsInObjectFields(objectConfig);
|
||||
const defaultFields = getMockDefaultObjectFields(objectConfig);
|
||||
const { objectFields: defaultRelationObjectFields } =
|
||||
getDefaultRelationObjectFields(objectConfig);
|
||||
getMockDefaultRelationObjectFields(objectConfig);
|
||||
|
||||
expect(objectFields[0]).toEqual(customField);
|
||||
expect(objectFields).toHaveLength(
|
||||
@@ -80,7 +97,7 @@ describe('getDefaultFieldsInObjectFields', () => {
|
||||
fields: [customIdField],
|
||||
};
|
||||
|
||||
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
|
||||
const { objectFields } = getMockDefaultFieldsInObjectFields(objectConfig);
|
||||
|
||||
const idFields = objectFields.filter((f) => f.name === 'id');
|
||||
|
||||
@@ -115,10 +132,10 @@ describe('getDefaultFieldsInObjectFields', () => {
|
||||
fields: customFields,
|
||||
};
|
||||
|
||||
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
|
||||
const defaultFields = getDefaultObjectFields(objectConfig);
|
||||
const { objectFields } = getMockDefaultFieldsInObjectFields(objectConfig);
|
||||
const defaultFields = getMockDefaultObjectFields(objectConfig);
|
||||
const { objectFields: defaultRelationObjectFields } =
|
||||
getDefaultRelationObjectFields(objectConfig);
|
||||
getMockDefaultRelationObjectFields(objectConfig);
|
||||
const overriddenCount = defaultFields.filter((df) =>
|
||||
customFields.some((cf) => cf.name === df.name),
|
||||
).length;
|
||||
@@ -148,7 +165,7 @@ describe('getDefaultFieldsInObjectFields', () => {
|
||||
fields: [customField],
|
||||
};
|
||||
|
||||
const { objectFields } = getDefaultFieldsInObjectFields(objectConfig);
|
||||
const { objectFields } = getMockDefaultFieldsInObjectFields(objectConfig);
|
||||
|
||||
expect(objectFields[0]).toEqual(customField);
|
||||
});
|
||||
@@ -168,13 +185,13 @@ describe('getDefaultFieldsInObjectFields', () => {
|
||||
|
||||
const originalLength = objectConfig.fields.length;
|
||||
|
||||
getDefaultFieldsInObjectFields(objectConfig);
|
||||
getMockDefaultFieldsInObjectFields(objectConfig);
|
||||
|
||||
expect(objectConfig.fields).toHaveLength(originalLength);
|
||||
});
|
||||
|
||||
it('should return reverse relation fields for each default relation', () => {
|
||||
const { fields } = getDefaultFieldsInObjectFields(baseObjectConfig);
|
||||
const { fields } = getMockDefaultFieldsInObjectFields(baseObjectConfig);
|
||||
|
||||
expect(fields).toHaveLength(4);
|
||||
|
||||
|
||||
+22
-12
@@ -12,21 +12,31 @@ const mockObjectConfig: ObjectConfig = {
|
||||
fields: [],
|
||||
};
|
||||
|
||||
const mockApplicationUniversalIdentifier =
|
||||
'3d05dbc0-4bd8-4041-a944-8cd0e26c2be1';
|
||||
|
||||
const getMockDefaultObjectFields = (objectConfig: ObjectConfig) =>
|
||||
getDefaultObjectFields({
|
||||
objectConfig,
|
||||
applicationUniversalIdentifier: mockApplicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
const expectedUniversalId = (fieldName: string) =>
|
||||
generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier: mockApplicationUniversalIdentifier,
|
||||
objectUniversalIdentifier: mockObjectConfig.universalIdentifier,
|
||||
fieldName,
|
||||
});
|
||||
|
||||
describe('getDefaultObjectFields', () => {
|
||||
it('should return an array of 9 default fields', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const fields = getMockDefaultObjectFields(mockObjectConfig);
|
||||
|
||||
expect(fields).toHaveLength(9);
|
||||
});
|
||||
|
||||
it('should include an id field with UUID type', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const fields = getMockDefaultObjectFields(mockObjectConfig);
|
||||
const idField = fields.find((field) => field.name === 'id');
|
||||
|
||||
expect(idField).toBeDefined();
|
||||
@@ -43,7 +53,7 @@ describe('getDefaultObjectFields', () => {
|
||||
});
|
||||
|
||||
it('should include a name field with TEXT type', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const fields = getMockDefaultObjectFields(mockObjectConfig);
|
||||
const nameField = fields.find((field) => field.name === 'name');
|
||||
|
||||
expect(nameField).toBeDefined();
|
||||
@@ -60,7 +70,7 @@ describe('getDefaultObjectFields', () => {
|
||||
});
|
||||
|
||||
it('should include createdAt, updatedAt and deletedAt fields with DATE_TIME type', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const fields = getMockDefaultObjectFields(mockObjectConfig);
|
||||
const createdAtField = fields.find((field) => field.name === 'createdAt');
|
||||
const updatedAtField = fields.find((field) => field.name === 'updatedAt');
|
||||
const deletedAtField = fields.find((field) => field.name === 'deletedAt');
|
||||
@@ -103,7 +113,7 @@ describe('getDefaultObjectFields', () => {
|
||||
});
|
||||
|
||||
it('should include createdBy and updatedBy fields with ACTOR type', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const fields = getMockDefaultObjectFields(mockObjectConfig);
|
||||
const createdByField = fields.find((field) => field.name === 'createdBy');
|
||||
const updatedByField = fields.find((field) => field.name === 'updatedBy');
|
||||
|
||||
@@ -133,7 +143,7 @@ describe('getDefaultObjectFields', () => {
|
||||
});
|
||||
|
||||
it('should include a position field with POSITION type', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const fields = getMockDefaultObjectFields(mockObjectConfig);
|
||||
const positionField = fields.find((field) => field.name === 'position');
|
||||
|
||||
expect(positionField).toBeDefined();
|
||||
@@ -150,7 +160,7 @@ describe('getDefaultObjectFields', () => {
|
||||
});
|
||||
|
||||
it('should include a searchVector field with TS_VECTOR type', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const fields = getMockDefaultObjectFields(mockObjectConfig);
|
||||
const searchVectorField = fields.find(
|
||||
(field) => field.name === 'searchVector',
|
||||
);
|
||||
@@ -169,8 +179,8 @@ describe('getDefaultObjectFields', () => {
|
||||
});
|
||||
|
||||
it('should generate deterministic universalIdentifiers based on objectConfig', () => {
|
||||
const firstResult = getDefaultObjectFields(mockObjectConfig);
|
||||
const secondResult = getDefaultObjectFields(mockObjectConfig);
|
||||
const firstResult = getMockDefaultObjectFields(mockObjectConfig);
|
||||
const secondResult = getMockDefaultObjectFields(mockObjectConfig);
|
||||
|
||||
firstResult.forEach((field, index) => {
|
||||
expect(field.universalIdentifier).toBe(
|
||||
@@ -185,8 +195,8 @@ describe('getDefaultObjectFields', () => {
|
||||
universalIdentifier: 'ffffffff-ffff-ffff-ffff-ffffffffffff',
|
||||
};
|
||||
|
||||
const fieldsA = getDefaultObjectFields(mockObjectConfig);
|
||||
const fieldsB = getDefaultObjectFields(otherObjectConfig);
|
||||
const fieldsA = getMockDefaultObjectFields(mockObjectConfig);
|
||||
const fieldsB = getMockDefaultObjectFields(otherObjectConfig);
|
||||
|
||||
fieldsA.forEach((fieldA, index) => {
|
||||
expect(fieldA.universalIdentifier).not.toBe(
|
||||
@@ -196,7 +206,7 @@ describe('getDefaultObjectFields', () => {
|
||||
});
|
||||
|
||||
it('should return fields in the expected order', () => {
|
||||
const fields = getDefaultObjectFields(mockObjectConfig);
|
||||
const fields = getMockDefaultObjectFields(mockObjectConfig);
|
||||
const fieldNames = fields.map((field) => field.name);
|
||||
|
||||
expect(fieldNames).toEqual([
|
||||
|
||||
+15
-5
@@ -6,12 +6,22 @@ import {
|
||||
type ObjectFieldManifest,
|
||||
} from 'twenty-shared/application';
|
||||
|
||||
export const getDefaultFieldsInObjectFields = (
|
||||
objectConfig: ObjectConfig,
|
||||
): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
|
||||
const defaultObjectFields = getDefaultObjectFields(objectConfig);
|
||||
export const getDefaultFieldsInObjectFields = ({
|
||||
objectConfig,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
objectConfig: ObjectConfig;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
|
||||
const defaultObjectFields = getDefaultObjectFields({
|
||||
objectConfig,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
const { objectFields: defaultRelationObjectFields, fields: reverseFields } =
|
||||
getDefaultRelationObjectFields(objectConfig);
|
||||
getDefaultRelationObjectFields({
|
||||
objectConfig,
|
||||
applicationUniversalIdentifier,
|
||||
});
|
||||
|
||||
const objectConfigFieldNames = (objectConfig.fields ?? []).map((f) => f.name);
|
||||
|
||||
|
||||
+16
-3
@@ -3,9 +3,13 @@ import { FieldMetadataType } from 'twenty-shared/types';
|
||||
import { generateDefaultFieldUniversalIdentifier } from '@/sdk/define/objects/generate-default-field-universal-identifier';
|
||||
import { type ObjectFieldManifest } from 'twenty-shared/application';
|
||||
|
||||
export const getDefaultObjectFields = (
|
||||
objectConfig: ObjectConfig,
|
||||
): ObjectFieldManifest[] => {
|
||||
export const getDefaultObjectFields = ({
|
||||
objectConfig,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
objectConfig: ObjectConfig;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): ObjectFieldManifest[] => {
|
||||
const objectUniversalIdentifier = objectConfig.universalIdentifier;
|
||||
|
||||
const idField: ObjectFieldManifest = {
|
||||
@@ -17,6 +21,7 @@ export const getDefaultObjectFields = (
|
||||
defaultValue: 'uuid',
|
||||
type: FieldMetadataType.UUID as const,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier,
|
||||
fieldName: 'id',
|
||||
}),
|
||||
@@ -31,6 +36,7 @@ export const getDefaultObjectFields = (
|
||||
defaultValue: null,
|
||||
type: FieldMetadataType.TEXT as const,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier,
|
||||
fieldName: 'name',
|
||||
}),
|
||||
@@ -45,6 +51,7 @@ export const getDefaultObjectFields = (
|
||||
defaultValue: 'now',
|
||||
type: FieldMetadataType.DATE_TIME as const,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier,
|
||||
fieldName: 'createdAt',
|
||||
}),
|
||||
@@ -59,6 +66,7 @@ export const getDefaultObjectFields = (
|
||||
defaultValue: 'now',
|
||||
type: FieldMetadataType.DATE_TIME as const,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier,
|
||||
fieldName: 'updatedAt',
|
||||
}),
|
||||
@@ -73,6 +81,7 @@ export const getDefaultObjectFields = (
|
||||
defaultValue: null,
|
||||
type: FieldMetadataType.DATE_TIME as const,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier,
|
||||
fieldName: 'deletedAt',
|
||||
}),
|
||||
@@ -87,6 +96,7 @@ export const getDefaultObjectFields = (
|
||||
defaultValue: { name: "''", source: "'MANUAL'" },
|
||||
type: FieldMetadataType.ACTOR as const,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier,
|
||||
fieldName: 'createdBy',
|
||||
}),
|
||||
@@ -101,6 +111,7 @@ export const getDefaultObjectFields = (
|
||||
defaultValue: { name: "''", source: "'MANUAL'" },
|
||||
type: FieldMetadataType.ACTOR as const,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier,
|
||||
fieldName: 'updatedBy',
|
||||
}),
|
||||
@@ -115,6 +126,7 @@ export const getDefaultObjectFields = (
|
||||
defaultValue: 0,
|
||||
type: FieldMetadataType.POSITION,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier,
|
||||
fieldName: 'position',
|
||||
}),
|
||||
@@ -129,6 +141,7 @@ export const getDefaultObjectFields = (
|
||||
defaultValue: null,
|
||||
type: FieldMetadataType.TS_VECTOR,
|
||||
universalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier,
|
||||
fieldName: 'searchVector',
|
||||
}),
|
||||
|
||||
+12
-6
@@ -1,5 +1,5 @@
|
||||
import { generateDefaultFieldUniversalIdentifier } from '@/sdk/define/objects/generate-default-field-universal-identifier';
|
||||
import { RelationType } from '@/sdk/define';
|
||||
import { generateDefaultFieldUniversalIdentifier } from '@/sdk/define/objects/generate-default-field-universal-identifier';
|
||||
import type { ObjectConfig } from '@/sdk/define/objects/object-config';
|
||||
import {
|
||||
type FieldManifest,
|
||||
@@ -119,9 +119,13 @@ const buildReverseField = ({
|
||||
};
|
||||
};
|
||||
|
||||
export const getDefaultRelationObjectFields = (
|
||||
objectConfig: ObjectConfig,
|
||||
): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
|
||||
export const getDefaultRelationObjectFields = ({
|
||||
objectConfig,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
objectConfig: ObjectConfig;
|
||||
applicationUniversalIdentifier: string;
|
||||
}): { objectFields: ObjectFieldManifest[]; fields: FieldManifest[] } => {
|
||||
const objectFields: ObjectFieldManifest[] = [];
|
||||
const fields: FieldManifest[] = [];
|
||||
|
||||
@@ -130,14 +134,16 @@ export const getDefaultRelationObjectFields = (
|
||||
|
||||
const forwardFieldUniversalIdentifier =
|
||||
generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier: objectConfig.universalIdentifier,
|
||||
fieldName: config.fieldName,
|
||||
});
|
||||
|
||||
const reverseFieldUniversalIdentifier =
|
||||
generateDefaultFieldUniversalIdentifier({
|
||||
objectUniversalIdentifier: objectConfig.universalIdentifier,
|
||||
fieldName: `${config.fieldName}Inverse`,
|
||||
applicationUniversalIdentifier,
|
||||
objectUniversalIdentifier: standardObject.universalIdentifier,
|
||||
fieldName: config.targetFieldName(objectConfig),
|
||||
});
|
||||
|
||||
const forwardField: ObjectFieldManifest = {
|
||||
|
||||
@@ -1,8 +1,21 @@
|
||||
import { getViewBaseFile } from '@/cli/utilities/entity/entity-view-template';
|
||||
|
||||
const APPLICATION_UNIVERSAL_IDENTIFIER = 'app-abc-123';
|
||||
|
||||
const getTestViewBaseFile = (
|
||||
overrides: Omit<
|
||||
Parameters<typeof getViewBaseFile>[0],
|
||||
'applicationUniversalIdentifier'
|
||||
>,
|
||||
) =>
|
||||
getViewBaseFile({
|
||||
applicationUniversalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
||||
...overrides,
|
||||
});
|
||||
|
||||
describe('getViewBaseFile', () => {
|
||||
it('should render proper file using defineView', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'my-view',
|
||||
universalIdentifier: '71e45a58-41da-4ae4-8b73-a543c0a9d3d4',
|
||||
objectUniversalIdentifier: 'obj-abc-123',
|
||||
@@ -20,7 +33,7 @@ describe('getViewBaseFile', () => {
|
||||
});
|
||||
|
||||
it('should use default objectUniversalIdentifier when not provided', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'default-view',
|
||||
});
|
||||
|
||||
@@ -28,7 +41,7 @@ describe('getViewBaseFile', () => {
|
||||
});
|
||||
|
||||
it('should include commented fields, filters and sorts when no fields provided', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'empty-view',
|
||||
});
|
||||
|
||||
@@ -38,7 +51,7 @@ describe('getViewBaseFile', () => {
|
||||
});
|
||||
|
||||
it('should render fields block when fields are provided', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'view-with-fields',
|
||||
fields: [
|
||||
{
|
||||
@@ -61,7 +74,7 @@ describe('getViewBaseFile', () => {
|
||||
});
|
||||
|
||||
it('should apply default values to fields', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'view-defaults',
|
||||
fields: [
|
||||
{
|
||||
@@ -77,7 +90,7 @@ describe('getViewBaseFile', () => {
|
||||
});
|
||||
|
||||
it('should generate unique UUID when not provided', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'auto-uuid-view',
|
||||
});
|
||||
|
||||
@@ -87,7 +100,7 @@ describe('getViewBaseFile', () => {
|
||||
});
|
||||
|
||||
it('should use kebab-case for name', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'all active contacts',
|
||||
});
|
||||
|
||||
@@ -95,7 +108,7 @@ describe('getViewBaseFile', () => {
|
||||
});
|
||||
|
||||
it('should generate UUIDs for fields when not provided', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'view-auto-field-uuids',
|
||||
fields: [
|
||||
{
|
||||
@@ -115,7 +128,7 @@ describe('getViewBaseFile', () => {
|
||||
});
|
||||
|
||||
it('should emit a generateDefaultFieldUniversalIdentifier call for fields using defaultFieldName', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'view-default-field',
|
||||
objectUniversalIdentifier: 'obj-abc-123',
|
||||
fields: [
|
||||
@@ -132,12 +145,13 @@ describe('getViewBaseFile', () => {
|
||||
expect(result).toContain(
|
||||
'fieldMetadataUniversalIdentifier: generateDefaultFieldUniversalIdentifier({',
|
||||
);
|
||||
expect(result).toContain("applicationUniversalIdentifier: 'app-abc-123'");
|
||||
expect(result).toContain("objectUniversalIdentifier: 'obj-abc-123'");
|
||||
expect(result).toContain("fieldName: 'createdAt'");
|
||||
});
|
||||
|
||||
it('should not import generateDefaultFieldUniversalIdentifier when no field uses defaultFieldName', () => {
|
||||
const result = getViewBaseFile({
|
||||
const result = getTestViewBaseFile({
|
||||
name: 'view-literal-only',
|
||||
fields: [
|
||||
{
|
||||
|
||||
@@ -17,10 +17,12 @@ const renderFieldEntry = ({
|
||||
field,
|
||||
index,
|
||||
objectUniversalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
}: {
|
||||
field: ViewFieldTemplate;
|
||||
index: number;
|
||||
objectUniversalIdentifier: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
}) => {
|
||||
const universalIdentifier = field.universalIdentifier ?? v4();
|
||||
const position = field.position ?? index;
|
||||
@@ -30,6 +32,7 @@ const renderFieldEntry = ({
|
||||
const fieldMetadataUniversalIdentifierLine =
|
||||
'defaultFieldName' in field
|
||||
? ` fieldMetadataUniversalIdentifier: generateDefaultFieldUniversalIdentifier({
|
||||
applicationUniversalIdentifier: '${applicationUniversalIdentifier}',
|
||||
objectUniversalIdentifier: '${objectUniversalIdentifier}',
|
||||
fieldName: '${field.defaultFieldName}',
|
||||
})`
|
||||
@@ -48,12 +51,14 @@ export const getViewBaseFile = ({
|
||||
name,
|
||||
universalIdentifier = v4(),
|
||||
objectUniversalIdentifier = 'fill-later',
|
||||
applicationUniversalIdentifier,
|
||||
fields = [],
|
||||
type,
|
||||
}: {
|
||||
name: string;
|
||||
universalIdentifier?: string;
|
||||
objectUniversalIdentifier?: string;
|
||||
applicationUniversalIdentifier: string;
|
||||
fields?: ViewFieldTemplate[];
|
||||
type?: ViewType;
|
||||
}) => {
|
||||
@@ -77,7 +82,12 @@ export const getViewBaseFile = ({
|
||||
? ` fields: [
|
||||
${fields
|
||||
.map((field, index) =>
|
||||
renderFieldEntry({ field, index, objectUniversalIdentifier }),
|
||||
renderFieldEntry({
|
||||
field,
|
||||
index,
|
||||
objectUniversalIdentifier,
|
||||
applicationUniversalIdentifier,
|
||||
}),
|
||||
)
|
||||
.join(',\n')},
|
||||
],`
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
|
||||
import { pathExists } from '@/cli/utilities/file/fs-utils';
|
||||
import { findAppConfigPath } from '@/cli/utilities/application/find-app-config-path';
|
||||
|
||||
// One name + description pair to add to defineApplication.serverVariables.
|
||||
// `isSecret` defaults to false; set true on credentials that should be
|
||||
@@ -11,12 +11,6 @@ export type ServerVariableSpec = {
|
||||
isSecret: boolean;
|
||||
};
|
||||
|
||||
const APP_CONFIG_CANDIDATES = [
|
||||
'src/application.config.ts',
|
||||
'src/application-config.ts',
|
||||
'src/applicationConfig.ts',
|
||||
];
|
||||
|
||||
const SERVER_VARIABLES_PATTERN = /serverVariables\s*:\s*\{/;
|
||||
const DEFINE_APPLICATION_PATTERN = /defineApplication\s*\(\s*\{/;
|
||||
|
||||
@@ -102,20 +96,6 @@ export const appendServerVariablesToAppConfig = async ({
|
||||
return { status: 'created', file: configPath };
|
||||
};
|
||||
|
||||
const findAppConfigPath = async (
|
||||
projectRoot: string,
|
||||
): Promise<string | null> => {
|
||||
for (const candidate of APP_CONFIG_CANDIDATES) {
|
||||
const absolute = `${projectRoot}/${candidate}`;
|
||||
|
||||
if (await pathExists(absolute)) {
|
||||
return absolute;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
const renderServerVariableEntries = (variables: ServerVariableSpec[]): string =>
|
||||
variables
|
||||
.map(
|
||||
|
||||
Reference in New Issue
Block a user