twenty 0.2.0 → 2.46.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CONTRIBUTING.md +166 -0
- package/LICENSE +21 -0
- package/README.md +64 -57
- package/dist/app-template/.nvmrc +1 -0
- package/dist/app-template/.oxlintrc.json +37 -0
- package/dist/app-template/AGENTS.md +70 -0
- package/dist/app-template/CHANGELOG.md +7 -0
- package/dist/app-template/README.md +34 -0
- package/dist/app-template/SETUP.md +47 -0
- package/dist/app-template/github/workflows/cd.yml +42 -0
- package/dist/app-template/github/workflows/ci.yml +60 -0
- package/dist/app-template/github/workflows/publish.yml +52 -0
- package/dist/app-template/gitignore +43 -0
- package/dist/app-template/locales/en.json +1 -0
- package/dist/app-template/package.json +36 -0
- package/dist/app-template/public/logo.svg +7 -0
- package/dist/app-template/src/__tests__/application-config.test.ts +14 -0
- package/dist/app-template/src/__tests__/global-setup.ts +56 -0
- package/dist/app-template/src/__tests__/run-twenty.ts +66 -0
- package/dist/app-template/src/__tests__/schema.integration-test.ts +46 -0
- package/dist/app-template/src/application-config.ts +13 -0
- package/dist/app-template/src/constants/universal-identifiers.ts +12 -0
- package/dist/app-template/src/default-role.ts +16 -0
- package/dist/app-template/src/front-components/main-page.tsx +287 -0
- package/dist/app-template/src/logic-functions/health-check.ts +11 -0
- package/dist/app-template/src/navigation-menu-items/main-page.navigation-menu-item.ts +19 -0
- package/dist/app-template/src/page-layouts/main-page.page-layout.ts +42 -0
- package/dist/app-template/tsconfig.json +37 -0
- package/dist/app-template/tsconfig.spec.json +9 -0
- package/dist/app-template/vitest.config.ts +31 -0
- package/dist/app-template/vitest.unit.config.ts +14 -0
- package/dist/app-template/yarn.lock +3374 -0
- package/dist/app-template/yarnrc.yml +1 -0
- package/dist/app-worker.cjs +1 -0
- package/dist/chunks/application-DuIJE2qG.cjs +5 -0
- package/dist/chunks/assert-pull-paths-BuQ1ZRvQ.cjs +1 -0
- package/dist/chunks/build-CX6FBrbu.cjs +1 -0
- package/dist/chunks/build-pull-entities-zwArrVMk.cjs +5 -0
- package/dist/chunks/build-source-manifest-CLKH5vzc.cjs +2 -0
- package/dist/chunks/build-source-snapshot-CbGsy1-v.cjs +206 -0
- package/dist/chunks/check-node-requirement-mqwNPktb.cjs +1 -0
- package/dist/chunks/cli-error-CSsayo0u.cjs +1 -0
- package/dist/chunks/cli-version.constant-aSp8WZDh.cjs +1 -0
- package/dist/chunks/collect-watch-inputs-B9jWG4W0.cjs +1 -0
- package/dist/chunks/compile-application-translations-B59jYVWc.cjs +1 -0
- package/dist/chunks/config-file-mode.constant-DLBzKKUl.cjs +1 -0
- package/dist/chunks/confirm-in-terminal-BxEM5lxE.cjs +1 -0
- package/dist/chunks/constants-CL6mrTMT-B748cNNG.cjs +23 -0
- package/dist/chunks/constants-Cf5-CwMu.cjs +1 -0
- package/dist/chunks/convert-to-label-BPBLtc_O.cjs +1 -0
- package/dist/chunks/create-app-not-installed-error-Cc8YZqI7.cjs +1 -0
- package/dist/chunks/create-cancelled-error-DgrNfOv2.cjs +1 -0
- package/dist/chunks/create-http-status-error-D3gy3DzB.cjs +1 -0
- package/dist/chunks/create-metadata-client-ChjDalSi.cjs +6 -0
- package/dist/chunks/create-pull-base-write-rxjcKtD-.cjs +1 -0
- package/dist/chunks/create-rest-client-eAfOFfDA.cjs +58 -0
- package/dist/chunks/create-tooling-failure-DLI5FlQP.cjs +1 -0
- package/dist/chunks/credential-kind-labels.constant-CKdoQF8o.cjs +1 -0
- package/dist/chunks/extract-manifest-from-file-j-Y9wNas.cjs +1 -0
- package/dist/chunks/fetch-metadata-inspection-Dp034TpK.cjs +1 -0
- package/dist/chunks/fetch-signed-in-identity-BhvaL3zp.cjs +1 -0
- package/dist/chunks/fetch-workspace-id-DJStmf_V.cjs +1 -0
- package/dist/chunks/find-remote-D48r20g0.cjs +1 -0
- package/dist/chunks/format-app-duration-gu2oRLB4.cjs +1 -0
- package/dist/chunks/format-body-VcKL4WXt.cjs +1 -0
- package/dist/chunks/format-data-value-B6rIbpJv.cjs +1 -0
- package/dist/chunks/format-details-COEwKz_M.cjs +2 -0
- package/dist/chunks/format-metadata-fields-DJnTg1R9.cjs +2 -0
- package/dist/chunks/format-metadata-owner-yUeocnwY.cjs +1 -0
- package/dist/chunks/format-table-4drtI5sP.cjs +2 -0
- package/dist/chunks/fs-utils-Bl1fQcaJ.cjs +2 -0
- package/dist/chunks/generate-application-client-CCVLjxTj.cjs +1 -0
- package/dist/chunks/get-app-plan-summary-B0uwHXbO.cjs +8 -0
- package/dist/chunks/get-config-path-C-D-QHuX.cjs +1 -0
- package/dist/chunks/get-credential-kind-JI9heHe2.cjs +1 -0
- package/dist/chunks/get-function-input-schema-GNk3NRLJ-Bfph1o9z.cjs +1 -0
- package/dist/chunks/get-tooling-error-context-C69TBSej.cjs +1 -0
- package/dist/chunks/global-flags.constant-8GfW0sNU.cjs +1 -0
- package/dist/chunks/has-yarn-plug-and-play-X3ay5Xl0.cjs +1 -0
- package/dist/chunks/is-access-token-expiring-gICPXsbO.cjs +1 -0
- package/dist/chunks/is-application-not-found-error-mNeGbxmT.cjs +1 -0
- package/dist/chunks/is-exported-manifest-CriNOWvx.cjs +1 -0
- package/dist/chunks/is-inside-directory-t3ixLQDr.cjs +1 -0
- package/dist/chunks/is-interaction-allowed-ycbAFMIj.cjs +1 -0
- package/dist/chunks/is-same-universal-identifier-BCBib3RP.cjs +1 -0
- package/dist/chunks/is-supported-locale-BAjnKnKj.cjs +20 -0
- package/dist/chunks/kebab-case-D0usAzG-.cjs +1 -0
- package/dist/chunks/list-application-source-files-jEau5Rie.cjs +1 -0
- package/dist/chunks/metadata-D9sQOTRK.cjs +1 -0
- package/dist/chunks/metadata-page.constant-BkG4ndrO.cjs +1 -0
- package/dist/chunks/oauth-timing.constant-BClMQcKn.cjs +1 -0
- package/dist/chunks/open-browser-LPuVspT7.cjs +1 -0
- package/dist/chunks/package-CdYv4LEx.cjs +1 -0
- package/dist/chunks/parse-api-url-BllxRdpE.cjs +1 -0
- package/dist/chunks/parse-app-export-BkfflyUY.cjs +1 -0
- package/dist/chunks/parse-graphql-response-CtP9KTS2.cjs +1 -0
- package/dist/chunks/parse-json-input-CvMTKAWw.cjs +1 -0
- package/dist/chunks/print-typescript-value-DhElcFzI.cjs +3 -0
- package/dist/chunks/pull-source-eoiJwFb9.cjs +1 -0
- package/dist/chunks/read-app-identity-SeleQ_D6.cjs +1 -0
- package/dist/chunks/read-application-identity-BFg_p84F.cjs +1 -0
- package/dist/chunks/read-command-values-uSJqBG_E.cjs +1 -0
- package/dist/chunks/read-config-DUJ27809.cjs +1 -0
- package/dist/chunks/read-input-value-Cxgh_r2g.cjs +1 -0
- package/dist/chunks/read-source-identity-C8LG1v6U.cjs +1 -0
- package/dist/chunks/read-watch-input-stamp-BxeOb0vI.cjs +1 -0
- package/dist/chunks/record-pull-base-BbxvlnU8.cjs +1 -0
- package/dist/chunks/record-typecheck-config-inputs-BDqkKCrj.cjs +1 -0
- package/dist/chunks/request-oauth-tokens-C4KKlTQT.cjs +1 -0
- package/dist/chunks/request-timeout-milliseconds.constant-DtyQHrll.cjs +1 -0
- package/dist/chunks/require-approval-fhegB1ap.cjs +1 -0
- package/dist/chunks/resolve-app-project-D5ZXIpsG.cjs +1 -0
- package/dist/chunks/resolve-inside-sdk-p-16gKb4.cjs +1 -0
- package/dist/chunks/resolve-metadata-object-QoDi-Aik.cjs +1 -0
- package/dist/chunks/resolve-project-typescript-CDY8BbmP.cjs +1 -0
- package/dist/chunks/resolve-request-url-jC5kYwkn.cjs +1 -0
- package/dist/chunks/resolve-source-sdk-mK-f4q55.cjs +1 -0
- package/dist/chunks/result-item-limit.constant-CDrOhiWD.cjs +1 -0
- package/dist/chunks/run-api-graphql-command-Biy0WqXa.cjs +1 -0
- package/dist/chunks/run-api-rest-command-D9kdQceV.cjs +1 -0
- package/dist/chunks/run-app-add-command-DcTQg7Si.cjs +163 -0
- package/dist/chunks/run-app-apply-command-DwLPPJ-Y.cjs +2 -0
- package/dist/chunks/run-app-build-command-BRYb0one.cjs +2 -0
- package/dist/chunks/run-app-dev-command-CVeh9Gab.cjs +2 -0
- package/dist/chunks/run-app-exec-command-DKLNLbL5.cjs +3 -0
- package/dist/chunks/run-app-init-command-CJj8_spC.cjs +2 -0
- package/dist/chunks/run-app-logs-command-Dusb8K-W.cjs +9 -0
- package/dist/chunks/run-app-operation-BWrY9YZK.cjs +1 -0
- package/dist/chunks/run-app-plan-command-C0_3e332.cjs +1 -0
- package/dist/chunks/run-app-pull-command-DcZqs8gR.cjs +3 -0
- package/dist/chunks/run-app-typecheck-command-C8LqpOej.cjs +1 -0
- package/dist/chunks/run-app-uninstall-command-ncWS_k-p.cjs +2 -0
- package/dist/chunks/run-auth-login-command-_zDgRcth.cjs +1 -0
- package/dist/chunks/run-auth-logout-command-BO6O7GVi.cjs +2 -0
- package/dist/chunks/run-auth-status-command-Cj-vqoHg.cjs +1 -0
- package/dist/chunks/run-auth-token-command-D4geJ-rx.cjs +1 -0
- package/dist/chunks/run-cli-c9TOR0zj.cjs +23 -0
- package/dist/chunks/run-commands-command-C6gvMpQ_.cjs +2 -0
- package/dist/chunks/run-data-get-command-jDEU4EG1.cjs +1 -0
- package/dist/chunks/run-data-list-command-EnCdbuzc.cjs +1 -0
- package/dist/chunks/run-doctor-command-DnJ6io-m.cjs +2 -0
- package/dist/chunks/run-metadata-field-describe-command-vJngpw2S.cjs +3 -0
- package/dist/chunks/run-metadata-field-list-command-CR4w7fI6.cjs +3 -0
- package/dist/chunks/run-metadata-object-describe-command-CHP8K-W2.cjs +3 -0
- package/dist/chunks/run-metadata-object-list-command-BqXMcAp6.cjs +2 -0
- package/dist/chunks/run-open-command-DKl8acw0.cjs +1 -0
- package/dist/chunks/run-remote-list-command-DeAzr5Mn.cjs +1 -0
- package/dist/chunks/run-remote-remove-command-D0C5sWjP.cjs +2 -0
- package/dist/chunks/run-remote-rename-command-Byq9WRHd.cjs +1 -0
- package/dist/chunks/run-remote-use-command-B6VVDlMG.cjs +1 -0
- package/dist/chunks/run-version-command-BmSk8PUV.cjs +1 -0
- package/dist/chunks/select-target-C1VZjf-A.cjs +1 -0
- package/dist/chunks/sign-in-to-remote-again-CW8HPSTP.cjs +1 -0
- package/dist/chunks/sign-in-with-browser-C3kC6uP2.cjs +1 -0
- package/dist/chunks/style-BR52fK4V.cjs +1 -0
- package/dist/chunks/target-environment-variable.constant-D7z8AihP.cjs +1 -0
- package/dist/chunks/to-cli-error-BBYf_wqo.cjs +1 -0
- package/dist/chunks/to-public-target-CIAwoOKL.cjs +1 -0
- package/dist/chunks/to-worker-output-diagnostics-lprh9b0l.cjs +2 -0
- package/dist/chunks/typecheck-application-DGrovgX9.cjs +2 -0
- package/dist/chunks/undici-D3mKdfwl.cjs +95 -0
- package/dist/chunks/update-config-BkKBEfAp.cjs +1 -0
- package/dist/chunks/utils-B8EXJeaX-DK3gCslL.cjs +33 -0
- package/dist/chunks/validate-app-path-Dau-PC0a.cjs +1 -0
- package/dist/chunks/validate-remote-name-w-qnr43w.cjs +1 -0
- package/dist/chunks/write-config-atomically-DRWTukU-.cjs +1 -0
- package/dist/cli.cjs +2 -0
- package/docs/commands.md +564 -0
- package/package.json +41 -18
- package/src/app/README.md +43 -0
- package/src/app/add/README.md +69 -0
- package/src/app/bundles/README.md +85 -0
- package/src/app/client/README.md +62 -0
- package/src/app/dev/README.md +35 -0
- package/src/app/exec/README.md +48 -0
- package/src/app/function-logs/README.md +44 -0
- package/src/app/manifest/README.md +74 -0
- package/src/app/pull/README.md +28 -0
- package/src/app/source/README.md +33 -0
- package/src/app/typecheck/README.md +67 -0
- package/.npmignore +0 -2
- package/bin/twenty +0 -2
- package/index.js +0 -118
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Publishes the app to npm with provenance (npm trusted publishing).
|
|
2
|
+
#
|
|
3
|
+
# One-time setup:
|
|
4
|
+
# 1. On npmjs.com open your package > Settings > Trusted Publisher and register
|
|
5
|
+
# this repository and this workflow file (publish.yml).
|
|
6
|
+
# See https://docs.npmjs.com/trusted-publishers
|
|
7
|
+
# 2. Push a version tag (e.g. `git tag v1.0.0 && git push --tags`) or run the
|
|
8
|
+
# workflow manually from the Actions tab.
|
|
9
|
+
#
|
|
10
|
+
# Publishing with provenance certifies which GitHub repository the package is
|
|
11
|
+
# built from. This is also how you claim ownership of your app in a Twenty
|
|
12
|
+
# marketplace: the claim flow verifies you own the GitHub account or
|
|
13
|
+
# organization the provenance points to.
|
|
14
|
+
name: Publish
|
|
15
|
+
|
|
16
|
+
on:
|
|
17
|
+
push:
|
|
18
|
+
tags:
|
|
19
|
+
- 'v*'
|
|
20
|
+
workflow_dispatch:
|
|
21
|
+
|
|
22
|
+
permissions:
|
|
23
|
+
contents: read
|
|
24
|
+
# Required for npm trusted publishing (OIDC provenance).
|
|
25
|
+
id-token: write
|
|
26
|
+
|
|
27
|
+
jobs:
|
|
28
|
+
publish:
|
|
29
|
+
runs-on: ubuntu-latest
|
|
30
|
+
steps:
|
|
31
|
+
- name: Checkout
|
|
32
|
+
uses: actions/checkout@v4
|
|
33
|
+
|
|
34
|
+
- name: Setup Node.js
|
|
35
|
+
uses: actions/setup-node@v4
|
|
36
|
+
with:
|
|
37
|
+
node-version: 24
|
|
38
|
+
registry-url: https://registry.npmjs.org
|
|
39
|
+
|
|
40
|
+
- name: Update npm
|
|
41
|
+
# Trusted publishing requires npm 11.5.1 or later.
|
|
42
|
+
run: npm install -g npm@latest
|
|
43
|
+
|
|
44
|
+
- name: Install dependencies
|
|
45
|
+
run: yarn install --immutable
|
|
46
|
+
|
|
47
|
+
- name: Publish to npm
|
|
48
|
+
# Uncomment if publishing from a private source repo: npm rejects
|
|
49
|
+
# OIDC provenance for private repos
|
|
50
|
+
# env:
|
|
51
|
+
# TWENTY_APP_PUBLISH_DISABLE_PROVENANCE: 'true'
|
|
52
|
+
run: yarn twenty app:publish
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
|
2
|
+
|
|
3
|
+
# dependencies
|
|
4
|
+
/node_modules
|
|
5
|
+
/.pnp
|
|
6
|
+
.pnp.*
|
|
7
|
+
.yarn/*
|
|
8
|
+
!.yarn/patches
|
|
9
|
+
!.yarn/plugins
|
|
10
|
+
!.yarn/releases
|
|
11
|
+
!.yarn/sdks
|
|
12
|
+
!.yarn/versions
|
|
13
|
+
|
|
14
|
+
# codegen
|
|
15
|
+
generated
|
|
16
|
+
|
|
17
|
+
# testing
|
|
18
|
+
/coverage
|
|
19
|
+
|
|
20
|
+
# dev
|
|
21
|
+
/dist/
|
|
22
|
+
|
|
23
|
+
.twenty
|
|
24
|
+
|
|
25
|
+
# production
|
|
26
|
+
/build
|
|
27
|
+
|
|
28
|
+
# misc
|
|
29
|
+
.DS_Store
|
|
30
|
+
*.pem
|
|
31
|
+
|
|
32
|
+
# debug
|
|
33
|
+
npm-debug.log*
|
|
34
|
+
yarn-debug.log*
|
|
35
|
+
yarn-error.log*
|
|
36
|
+
.pnpm-debug.log*
|
|
37
|
+
|
|
38
|
+
# env files (can opt-in for committing if needed)
|
|
39
|
+
.env*
|
|
40
|
+
|
|
41
|
+
# typescript
|
|
42
|
+
*.tsbuildinfo
|
|
43
|
+
*.d.ts
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "TO-BE-GENERATED",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"license": "MIT",
|
|
5
|
+
"engines": {
|
|
6
|
+
"node": "^24.5.0",
|
|
7
|
+
"npm": "please-use-yarn",
|
|
8
|
+
"yarn": ">=4.0.2"
|
|
9
|
+
},
|
|
10
|
+
"keywords": [],
|
|
11
|
+
"packageManager": "yarn@4.13.0",
|
|
12
|
+
"scripts": {
|
|
13
|
+
"twenty": "twenty",
|
|
14
|
+
"lint": "oxlint -c .oxlintrc.json .",
|
|
15
|
+
"lint:fix": "oxlint --fix -c .oxlintrc.json .",
|
|
16
|
+
"typecheck": "tsgo --noEmit -p tsconfig.spec.json",
|
|
17
|
+
"test": "vitest run",
|
|
18
|
+
"test:watch": "vitest",
|
|
19
|
+
"test:unit": "vitest run --config vitest.unit.config.ts"
|
|
20
|
+
},
|
|
21
|
+
"dependencies": {},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^24.7.2",
|
|
24
|
+
"@types/react": "^19.0.0",
|
|
25
|
+
"@typescript/native-preview": "^7.0.0-dev.20260116.1",
|
|
26
|
+
"oxlint": "^0.16.0",
|
|
27
|
+
"react": "^19.0.0",
|
|
28
|
+
"react-dom": "^19.0.0",
|
|
29
|
+
"twenty-client-sdk": "TO-BE-GENERATED",
|
|
30
|
+
"twenty-sdk": "TO-BE-GENERATED",
|
|
31
|
+
"twenty-ui": "TO-BE-GENERATED",
|
|
32
|
+
"typescript": "^5.9.3",
|
|
33
|
+
"vite-tsconfig-paths": "^4.2.1",
|
|
34
|
+
"vitest": "^4.0.0"
|
|
35
|
+
}
|
|
36
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 80 80" fill="none">
|
|
2
|
+
<rect width="80" height="80" rx="16" fill="#141414"/>
|
|
3
|
+
<rect x="20" y="20" width="16" height="16" rx="4" fill="#FAFAFA"/>
|
|
4
|
+
<rect x="44" y="20" width="16" height="16" rx="4" fill="#FAFAFA" opacity="0.6"/>
|
|
5
|
+
<rect x="20" y="44" width="16" height="16" rx="4" fill="#FAFAFA" opacity="0.6"/>
|
|
6
|
+
<rect x="44" y="44" width="16" height="16" rx="4" fill="#FAFAFA" opacity="0.3"/>
|
|
7
|
+
</svg>
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import {
|
|
2
|
+
APP_DESCRIPTION,
|
|
3
|
+
APP_DISPLAY_NAME,
|
|
4
|
+
APPLICATION_UNIVERSAL_IDENTIFIER,
|
|
5
|
+
} from 'src/constants/universal-identifiers';
|
|
6
|
+
import { describe, expect, it } from 'vitest';
|
|
7
|
+
|
|
8
|
+
describe('application identifiers', () => {
|
|
9
|
+
it('should expose the application metadata constants', () => {
|
|
10
|
+
expect(APP_DISPLAY_NAME).toBeTruthy();
|
|
11
|
+
expect(typeof APP_DESCRIPTION).toBe('string');
|
|
12
|
+
expect(APPLICATION_UNIVERSAL_IDENTIFIER).toBeTruthy();
|
|
13
|
+
});
|
|
14
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { runTwenty } from './run-twenty';
|
|
2
|
+
|
|
3
|
+
function validateEnv(): { apiUrl: string; apiKey: string } {
|
|
4
|
+
const apiUrl = process.env.TWENTY_API_URL;
|
|
5
|
+
const apiKey = process.env.TWENTY_API_KEY;
|
|
6
|
+
|
|
7
|
+
if (!apiUrl || !apiKey) {
|
|
8
|
+
throw new Error(
|
|
9
|
+
'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' +
|
|
10
|
+
'Start a local server: yarn twenty docker:start\n' +
|
|
11
|
+
'Or set them in vitest env config.',
|
|
12
|
+
);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
return { apiUrl, apiKey };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
async function checkServer(apiUrl: string) {
|
|
19
|
+
let response: Response;
|
|
20
|
+
|
|
21
|
+
try {
|
|
22
|
+
response = await fetch(`${apiUrl}/healthz`);
|
|
23
|
+
} catch {
|
|
24
|
+
throw new Error(
|
|
25
|
+
`Twenty server is not reachable at ${apiUrl}. ` +
|
|
26
|
+
'Make sure the server is running before executing integration tests.',
|
|
27
|
+
);
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
if (!response.ok) {
|
|
31
|
+
throw new Error(`Server at ${apiUrl} returned ${response.status}`);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function setup() {
|
|
36
|
+
const { apiUrl } = validateEnv();
|
|
37
|
+
|
|
38
|
+
await checkServer(apiUrl);
|
|
39
|
+
await runTwenty(['app', 'uninstall', '--yes']);
|
|
40
|
+
|
|
41
|
+
const result = await runTwenty(['app', 'apply', '--create']);
|
|
42
|
+
|
|
43
|
+
if (!result.ok) {
|
|
44
|
+
throw new Error(
|
|
45
|
+
`App apply failed: ${result.error.code} ${result.error.message}`,
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export async function teardown() {
|
|
51
|
+
const result = await runTwenty(['app', 'uninstall', '--yes']);
|
|
52
|
+
|
|
53
|
+
if (!result.ok) {
|
|
54
|
+
console.warn(`App uninstall failed: ${result.error.message}`);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { spawn } from 'child_process';
|
|
2
|
+
import { delimiter, join } from 'path';
|
|
3
|
+
|
|
4
|
+
type TwentyCliError = {
|
|
5
|
+
code: string;
|
|
6
|
+
message: string;
|
|
7
|
+
hint?: string;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export type TwentyCliResult =
|
|
11
|
+
| { ok: true; data: unknown }
|
|
12
|
+
| { ok: false; error: TwentyCliError };
|
|
13
|
+
|
|
14
|
+
function withoutProjectBinaries(pathValue: string) {
|
|
15
|
+
return pathValue
|
|
16
|
+
.split(delimiter)
|
|
17
|
+
.filter(
|
|
18
|
+
(entry) =>
|
|
19
|
+
entry !== process.env.BERRY_BIN_FOLDER &&
|
|
20
|
+
!entry.endsWith(join('node_modules', '.bin')),
|
|
21
|
+
)
|
|
22
|
+
.join(delimiter);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export function runTwenty(args: string[]): Promise<TwentyCliResult> {
|
|
26
|
+
const executable = process.env.TWENTY_CLI ?? 'twenty';
|
|
27
|
+
|
|
28
|
+
return new Promise((resolve, reject) => {
|
|
29
|
+
const child = spawn(executable, [...args, '--json'], {
|
|
30
|
+
env: {
|
|
31
|
+
...process.env,
|
|
32
|
+
PATH: withoutProjectBinaries(process.env.PATH ?? ''),
|
|
33
|
+
},
|
|
34
|
+
stdio: ['ignore', 'pipe', 'inherit'],
|
|
35
|
+
});
|
|
36
|
+
let stdout = '';
|
|
37
|
+
|
|
38
|
+
child.stdout.setEncoding('utf8');
|
|
39
|
+
child.stdout.on('data', (chunk: string) => {
|
|
40
|
+
stdout += chunk;
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
child.on('error', (error: NodeJS.ErrnoException) => {
|
|
44
|
+
reject(
|
|
45
|
+
error.code === 'ENOENT'
|
|
46
|
+
? new Error(
|
|
47
|
+
`${executable} was not found. Install the Twenty CLI globally, or set TWENTY_CLI to its path.`,
|
|
48
|
+
)
|
|
49
|
+
: error,
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
child.on('close', (exitCode) => {
|
|
54
|
+
try {
|
|
55
|
+
resolve(JSON.parse(stdout));
|
|
56
|
+
} catch {
|
|
57
|
+
reject(
|
|
58
|
+
new Error(
|
|
59
|
+
`${executable} ${args.join(' ')} exited with code ${exitCode} without a JSON result. ` +
|
|
60
|
+
'Check that it is the Twenty CLI (twenty --version), or set TWENTY_CLI to its path.',
|
|
61
|
+
),
|
|
62
|
+
);
|
|
63
|
+
}
|
|
64
|
+
});
|
|
65
|
+
});
|
|
66
|
+
}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { CoreApiClient } from 'twenty-client-sdk/core';
|
|
2
|
+
import { MetadataApiClient } from 'twenty-client-sdk/metadata';
|
|
3
|
+
import { APPLICATION_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
|
4
|
+
import { describe, expect, it } from 'vitest';
|
|
5
|
+
|
|
6
|
+
describe('App installation', () => {
|
|
7
|
+
it('should find the installed app in the applications list', async () => {
|
|
8
|
+
const client = new MetadataApiClient();
|
|
9
|
+
|
|
10
|
+
const result = await client.query({
|
|
11
|
+
findManyApplications: {
|
|
12
|
+
id: true,
|
|
13
|
+
name: true,
|
|
14
|
+
universalIdentifier: true,
|
|
15
|
+
},
|
|
16
|
+
});
|
|
17
|
+
|
|
18
|
+
const app = result.findManyApplications.find(
|
|
19
|
+
(a: { universalIdentifier: string }) =>
|
|
20
|
+
a.universalIdentifier === APPLICATION_UNIVERSAL_IDENTIFIER,
|
|
21
|
+
);
|
|
22
|
+
|
|
23
|
+
expect(app).toBeDefined();
|
|
24
|
+
});
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
describe('CoreApiClient', () => {
|
|
28
|
+
it('should support CRUD on standard objects', async () => {
|
|
29
|
+
const client = new CoreApiClient();
|
|
30
|
+
|
|
31
|
+
const created = await client.mutation({
|
|
32
|
+
createNote: {
|
|
33
|
+
__args: { data: { title: 'Integration test note' } },
|
|
34
|
+
id: true,
|
|
35
|
+
},
|
|
36
|
+
});
|
|
37
|
+
expect(created.createNote.id).toBeDefined();
|
|
38
|
+
|
|
39
|
+
await client.mutation({
|
|
40
|
+
destroyNote: {
|
|
41
|
+
__args: { id: created.createNote.id },
|
|
42
|
+
id: true,
|
|
43
|
+
},
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
});
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
import { defineApplication } from 'twenty-sdk/define';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
APP_DESCRIPTION,
|
|
5
|
+
APP_DISPLAY_NAME,
|
|
6
|
+
APPLICATION_UNIVERSAL_IDENTIFIER,
|
|
7
|
+
} from 'src/constants/universal-identifiers';
|
|
8
|
+
|
|
9
|
+
export default defineApplication({
|
|
10
|
+
universalIdentifier: APPLICATION_UNIVERSAL_IDENTIFIER,
|
|
11
|
+
displayName: APP_DISPLAY_NAME,
|
|
12
|
+
description: APP_DESCRIPTION,
|
|
13
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export const APP_DISPLAY_NAME = 'DISPLAY-NAME-TO-BE-GENERATED';
|
|
2
|
+
export const APP_DESCRIPTION = 'DESCRIPTION-TO-BE-GENERATED';
|
|
3
|
+
export const APPLICATION_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
|
|
4
|
+
export const DEFAULT_ROLE_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
|
|
5
|
+
export const HEALTH_CHECK_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
|
|
6
|
+
export const MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER =
|
|
7
|
+
'UUID-TO-BE-GENERATED';
|
|
8
|
+
export const MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
|
|
9
|
+
export const MAIN_PAGE_LAYOUT_TAB_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
|
|
10
|
+
export const MAIN_PAGE_WIDGET_UNIVERSAL_IDENTIFIER = 'UUID-TO-BE-GENERATED';
|
|
11
|
+
export const MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER =
|
|
12
|
+
'UUID-TO-BE-GENERATED';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { defineApplicationRole } from 'twenty-sdk/define';
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
APP_DISPLAY_NAME,
|
|
5
|
+
DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
|
6
|
+
} from 'src/constants/universal-identifiers';
|
|
7
|
+
|
|
8
|
+
export default defineApplicationRole({
|
|
9
|
+
universalIdentifier: DEFAULT_ROLE_UNIVERSAL_IDENTIFIER,
|
|
10
|
+
label: `${APP_DISPLAY_NAME} default function role`,
|
|
11
|
+
description: `${APP_DISPLAY_NAME} default function role`,
|
|
12
|
+
canReadAllObjectRecords: true,
|
|
13
|
+
canUpdateAllObjectRecords: true,
|
|
14
|
+
canSoftDeleteAllObjectRecords: true,
|
|
15
|
+
canDestroyAllObjectRecords: false,
|
|
16
|
+
});
|
|
@@ -0,0 +1,287 @@
|
|
|
1
|
+
import { defineFrontComponent } from 'twenty-sdk/define';
|
|
2
|
+
import { useState } from 'react';
|
|
3
|
+
import { Avatar } from 'twenty-ui/primitives/data-display';
|
|
4
|
+
import {
|
|
5
|
+
IconBox,
|
|
6
|
+
IconHierarchy,
|
|
7
|
+
IconLayout,
|
|
8
|
+
IconSettingsAutomation,
|
|
9
|
+
} from 'twenty-ui/icon';
|
|
10
|
+
|
|
11
|
+
import {
|
|
12
|
+
APP_DISPLAY_NAME,
|
|
13
|
+
MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
|
14
|
+
} from 'src/constants/universal-identifiers';
|
|
15
|
+
|
|
16
|
+
const DOCS_BASE_URL = 'https://docs.twenty.com/developers/extend/apps';
|
|
17
|
+
|
|
18
|
+
const CATEGORIES = [
|
|
19
|
+
{
|
|
20
|
+
title: 'Data model',
|
|
21
|
+
color: '#73D08D',
|
|
22
|
+
items: [
|
|
23
|
+
{ label: 'CUSTOM OBJECT', href: `${DOCS_BASE_URL}/data/objects` },
|
|
24
|
+
{
|
|
25
|
+
label: 'CUSTOM FIELDS',
|
|
26
|
+
href: `${DOCS_BASE_URL}/data/extending-objects`,
|
|
27
|
+
},
|
|
28
|
+
],
|
|
29
|
+
rotation: '2.4deg',
|
|
30
|
+
},
|
|
31
|
+
{
|
|
32
|
+
title: 'Logic',
|
|
33
|
+
color: '#F4D345',
|
|
34
|
+
items: [
|
|
35
|
+
{
|
|
36
|
+
label: 'TOOLS',
|
|
37
|
+
href: `${DOCS_BASE_URL}/logic/logic-functions`,
|
|
38
|
+
},
|
|
39
|
+
{
|
|
40
|
+
label: 'LOGIC FUNCTION',
|
|
41
|
+
href: `${DOCS_BASE_URL}/logic/logic-functions`,
|
|
42
|
+
},
|
|
43
|
+
{
|
|
44
|
+
label: 'SKILLS',
|
|
45
|
+
href: `${DOCS_BASE_URL}/logic/skills-and-agents`,
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
rotation: '0deg',
|
|
49
|
+
},
|
|
50
|
+
{
|
|
51
|
+
title: 'Layout',
|
|
52
|
+
color: '#C4A2E0',
|
|
53
|
+
items: [
|
|
54
|
+
{ label: 'VIEWS', href: `${DOCS_BASE_URL}/layout/views` },
|
|
55
|
+
{ label: 'WIDGETS', href: `${DOCS_BASE_URL}/layout/page-layouts` },
|
|
56
|
+
{
|
|
57
|
+
label: 'LAYOUT PAGES',
|
|
58
|
+
href: `${DOCS_BASE_URL}/layout/page-layouts`,
|
|
59
|
+
},
|
|
60
|
+
{
|
|
61
|
+
label: 'COMMANDS',
|
|
62
|
+
href: `${DOCS_BASE_URL}/layout/command-menu-items`,
|
|
63
|
+
},
|
|
64
|
+
],
|
|
65
|
+
rotation: '-2.8deg',
|
|
66
|
+
},
|
|
67
|
+
] as const;
|
|
68
|
+
|
|
69
|
+
const ArrowUpRight = ({ color = '#999' }: { color?: string }) => (
|
|
70
|
+
<svg width="14" height="14" viewBox="0 0 14 14" fill="none">
|
|
71
|
+
<path
|
|
72
|
+
d="M4.5 3.5H10.5V9.5M10.5 3.5L3.5 10.5"
|
|
73
|
+
stroke={color}
|
|
74
|
+
strokeWidth="1.2"
|
|
75
|
+
strokeLinecap="round"
|
|
76
|
+
strokeLinejoin="round"
|
|
77
|
+
/>
|
|
78
|
+
</svg>
|
|
79
|
+
);
|
|
80
|
+
|
|
81
|
+
const CategoryCard = ({
|
|
82
|
+
title,
|
|
83
|
+
color,
|
|
84
|
+
items,
|
|
85
|
+
rotation,
|
|
86
|
+
}: {
|
|
87
|
+
title: string;
|
|
88
|
+
color: string;
|
|
89
|
+
items: ReadonlyArray<{ label: string; href: string }>;
|
|
90
|
+
rotation: string;
|
|
91
|
+
}) => {
|
|
92
|
+
const [hoveredItem, setHoveredItem] = useState<string | null>(null);
|
|
93
|
+
|
|
94
|
+
const CategoryIcon = () => {
|
|
95
|
+
if (title === 'Data model') {
|
|
96
|
+
return <IconHierarchy color={color} size="20px" />;
|
|
97
|
+
}
|
|
98
|
+
if (title === 'Logic') {
|
|
99
|
+
return <IconSettingsAutomation color={color} size="20px" />;
|
|
100
|
+
}
|
|
101
|
+
if (title === 'Layout') {
|
|
102
|
+
return <IconLayout color={color} size="20px" />;
|
|
103
|
+
}
|
|
104
|
+
};
|
|
105
|
+
|
|
106
|
+
return (
|
|
107
|
+
<div
|
|
108
|
+
style={{
|
|
109
|
+
display: 'flex',
|
|
110
|
+
flexDirection: 'column',
|
|
111
|
+
border: `1px solid ${color}80`,
|
|
112
|
+
borderRadius: '12px',
|
|
113
|
+
overflow: 'hidden',
|
|
114
|
+
width: '240px',
|
|
115
|
+
background: '#FFFFFF',
|
|
116
|
+
transform: `rotate(${rotation})`,
|
|
117
|
+
boxShadow: '0 2px 8px rgba(0, 0, 0, 0.04)',
|
|
118
|
+
}}
|
|
119
|
+
>
|
|
120
|
+
<div
|
|
121
|
+
style={{
|
|
122
|
+
padding: '16px 20px',
|
|
123
|
+
background: `${color}22`,
|
|
124
|
+
display: 'flex',
|
|
125
|
+
alignItems: 'center',
|
|
126
|
+
gap: '12px',
|
|
127
|
+
}}
|
|
128
|
+
>
|
|
129
|
+
<CategoryIcon />
|
|
130
|
+
<span
|
|
131
|
+
style={{
|
|
132
|
+
fontSize: '16px',
|
|
133
|
+
fontWeight: 600,
|
|
134
|
+
color: color,
|
|
135
|
+
}}
|
|
136
|
+
>
|
|
137
|
+
{title}
|
|
138
|
+
</span>
|
|
139
|
+
</div>
|
|
140
|
+
<div
|
|
141
|
+
style={{
|
|
142
|
+
display: 'flex',
|
|
143
|
+
flexDirection: 'column',
|
|
144
|
+
padding: '8px',
|
|
145
|
+
gap: '4px',
|
|
146
|
+
}}
|
|
147
|
+
>
|
|
148
|
+
{items.map((item) => {
|
|
149
|
+
const isHovered = hoveredItem === item.label;
|
|
150
|
+
|
|
151
|
+
return (
|
|
152
|
+
<a
|
|
153
|
+
key={item.label}
|
|
154
|
+
href={item.href}
|
|
155
|
+
target="_blank"
|
|
156
|
+
rel="noopener noreferrer"
|
|
157
|
+
onMouseEnter={() => setHoveredItem(item.label)}
|
|
158
|
+
onMouseLeave={() => setHoveredItem(null)}
|
|
159
|
+
style={{
|
|
160
|
+
display: 'flex',
|
|
161
|
+
alignItems: 'center',
|
|
162
|
+
gap: '10px',
|
|
163
|
+
textDecoration: 'none',
|
|
164
|
+
cursor: 'pointer',
|
|
165
|
+
padding: '10px 12px',
|
|
166
|
+
borderRadius: '8px',
|
|
167
|
+
background: isHovered ? '#0000000A' : 'transparent',
|
|
168
|
+
transition: 'background 0.15s',
|
|
169
|
+
}}
|
|
170
|
+
>
|
|
171
|
+
<IconBox color={color} size="20px" />
|
|
172
|
+
<span
|
|
173
|
+
style={{
|
|
174
|
+
fontSize: '13px',
|
|
175
|
+
fontWeight: 300,
|
|
176
|
+
color: '#333',
|
|
177
|
+
letterSpacing: '0.5px',
|
|
178
|
+
flex: 1,
|
|
179
|
+
}}
|
|
180
|
+
>
|
|
181
|
+
{item.label}
|
|
182
|
+
</span>
|
|
183
|
+
{isHovered && <ArrowUpRight />}
|
|
184
|
+
</a>
|
|
185
|
+
);
|
|
186
|
+
})}
|
|
187
|
+
</div>
|
|
188
|
+
</div>
|
|
189
|
+
);
|
|
190
|
+
};
|
|
191
|
+
|
|
192
|
+
const MainPage = () => {
|
|
193
|
+
return (
|
|
194
|
+
<div
|
|
195
|
+
style={{
|
|
196
|
+
display: 'flex',
|
|
197
|
+
flexDirection: 'column',
|
|
198
|
+
alignItems: 'center',
|
|
199
|
+
justifyContent: 'center',
|
|
200
|
+
height: '100%',
|
|
201
|
+
fontFamily:
|
|
202
|
+
'Inter, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif',
|
|
203
|
+
gap: '8px',
|
|
204
|
+
padding: '40px',
|
|
205
|
+
}}
|
|
206
|
+
>
|
|
207
|
+
<Avatar name={APP_DISPLAY_NAME} colorSeed={APP_DISPLAY_NAME} size="xl" />
|
|
208
|
+
<span
|
|
209
|
+
style={{
|
|
210
|
+
fontSize: '24px',
|
|
211
|
+
fontWeight: 600,
|
|
212
|
+
color: '#333',
|
|
213
|
+
marginTop: '8px',
|
|
214
|
+
}}
|
|
215
|
+
>
|
|
216
|
+
{APP_DISPLAY_NAME}
|
|
217
|
+
</span>
|
|
218
|
+
<span
|
|
219
|
+
style={{
|
|
220
|
+
fontSize: '13px',
|
|
221
|
+
color: '#888',
|
|
222
|
+
textAlign: 'center',
|
|
223
|
+
lineHeight: '1.5',
|
|
224
|
+
}}
|
|
225
|
+
>
|
|
226
|
+
Was installed successfully.
|
|
227
|
+
<br />
|
|
228
|
+
You can now add content to your app.
|
|
229
|
+
</span>
|
|
230
|
+
<a
|
|
231
|
+
href="/settings/applications#installed"
|
|
232
|
+
style={{
|
|
233
|
+
display: 'inline-flex',
|
|
234
|
+
alignItems: 'center',
|
|
235
|
+
gap: '6px',
|
|
236
|
+
marginTop: '16px',
|
|
237
|
+
fontSize: '13px',
|
|
238
|
+
color: '#333',
|
|
239
|
+
textDecoration: 'none',
|
|
240
|
+
padding: '8px 16px',
|
|
241
|
+
borderRadius: '8px',
|
|
242
|
+
border: '1px solid #e0e0e0',
|
|
243
|
+
background: '#fafafa',
|
|
244
|
+
transition: 'background 0.15s, border-color 0.15s',
|
|
245
|
+
}}
|
|
246
|
+
onMouseEnter={(e) => {
|
|
247
|
+
e.currentTarget.style.background = '#f0f0f0';
|
|
248
|
+
e.currentTarget.style.borderColor = '#ccc';
|
|
249
|
+
}}
|
|
250
|
+
onMouseLeave={(e) => {
|
|
251
|
+
e.currentTarget.style.background = '#fafafa';
|
|
252
|
+
e.currentTarget.style.borderColor = '#e0e0e0';
|
|
253
|
+
}}
|
|
254
|
+
>
|
|
255
|
+
Open app settings
|
|
256
|
+
<ArrowUpRight color="#333" />
|
|
257
|
+
</a>
|
|
258
|
+
<div
|
|
259
|
+
style={{
|
|
260
|
+
display: 'flex',
|
|
261
|
+
gap: '16px',
|
|
262
|
+
marginTop: '32px',
|
|
263
|
+
flexWrap: 'wrap',
|
|
264
|
+
justifyContent: 'center',
|
|
265
|
+
alignItems: 'flex-start',
|
|
266
|
+
}}
|
|
267
|
+
>
|
|
268
|
+
{CATEGORIES.map((category) => (
|
|
269
|
+
<CategoryCard
|
|
270
|
+
key={category.title}
|
|
271
|
+
title={category.title}
|
|
272
|
+
color={category.color}
|
|
273
|
+
items={category.items}
|
|
274
|
+
rotation={category.rotation}
|
|
275
|
+
/>
|
|
276
|
+
))}
|
|
277
|
+
</div>
|
|
278
|
+
</div>
|
|
279
|
+
);
|
|
280
|
+
};
|
|
281
|
+
|
|
282
|
+
export default defineFrontComponent({
|
|
283
|
+
universalIdentifier: MAIN_PAGE_FRONT_COMPONENT_UNIVERSAL_IDENTIFIER,
|
|
284
|
+
name: APP_DISPLAY_NAME,
|
|
285
|
+
description: `${APP_DISPLAY_NAME} front component displaying the app logo and name`,
|
|
286
|
+
component: MainPage,
|
|
287
|
+
});
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { defineHealthCheck } from 'twenty-sdk/define';
|
|
2
|
+
|
|
3
|
+
import { HEALTH_CHECK_UNIVERSAL_IDENTIFIER } from 'src/constants/universal-identifiers';
|
|
4
|
+
|
|
5
|
+
export default defineHealthCheck({
|
|
6
|
+
universalIdentifier: HEALTH_CHECK_UNIVERSAL_IDENTIFIER,
|
|
7
|
+
name: 'health-check',
|
|
8
|
+
handler: async () => {
|
|
9
|
+
return { status: 'OK' };
|
|
10
|
+
},
|
|
11
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import {
|
|
2
|
+
defineNavigationMenuItem,
|
|
3
|
+
NavigationMenuItemType,
|
|
4
|
+
} from 'twenty-sdk/define';
|
|
5
|
+
|
|
6
|
+
import {
|
|
7
|
+
APP_DISPLAY_NAME,
|
|
8
|
+
MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
|
|
9
|
+
MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
|
|
10
|
+
} from 'src/constants/universal-identifiers';
|
|
11
|
+
|
|
12
|
+
export default defineNavigationMenuItem({
|
|
13
|
+
universalIdentifier: MAIN_PAGE_NAVIGATION_MENU_ITEM_UNIVERSAL_IDENTIFIER,
|
|
14
|
+
name: APP_DISPLAY_NAME,
|
|
15
|
+
icon: 'IconFile',
|
|
16
|
+
position: -1,
|
|
17
|
+
type: NavigationMenuItemType.PAGE_LAYOUT,
|
|
18
|
+
pageLayoutUniversalIdentifier: MAIN_PAGE_LAYOUT_UNIVERSAL_IDENTIFIER,
|
|
19
|
+
});
|