From 237a943947c8dccfe6b73fd06bd75676535dbfc7 Mon Sep 17 00:00:00 2001 From: martmull Date: Wed, 20 May 2026 17:12:39 +0200 Subject: [PATCH] Update twenty sdk commands (#20735) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Performs twenty-sdk cli command migration: Summary ``` ┌─────┬──────────────────────────┬────────────────────────────┬───────────────────────┐ │ # │ Old command │ New command │ Status │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 1 │ twenty dev [appPath] │ twenty dev [appPath] │ Unchanged (now also │ │ │ │ │ DEFAULT) │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 2 │ twenty dev --once │ twenty dev --once │ Unchanged │ │ │ [appPath] │ [appPath] │ │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 3 │ twenty dev --watch │ twenty dev [appPath] │ --watch flag removed │ │ │ [appPath] │ │ (was default) │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 4 │ twenty dev --verbose │ twenty dev --verbose │ Unchanged │ │ │ [appPath] │ [appPath] │ │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 5 │ twenty dev --debug │ twenty dev --debug │ Unchanged │ │ │ [appPath] │ [appPath] │ │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 6 │ twenty dev --debounceMs │ twenty dev --debounceMs │ Unchanged │ │ │ [appPath] │ [appPath] │ │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 7 │ twenty build [appPath] │ twenty dev:build [appPath] │ Deprecated → colon │ │ │ │ │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 8 │ twenty build --tarball │ twenty dev:build --tarball │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 9 │ twenty typecheck │ twenty dev:typecheck │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 10 │ twenty logs [appPath] │ twenty dev:fn-logs │ Deprecated → colon │ │ │ │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 11 │ twenty logs -n │ twenty dev:fn-logs -n │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 12 │ twenty logs -u │ twenty dev:fn-logs -u │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 13 │ twenty exec [appPath] │ twenty dev:fn-exec │ Deprecated → colon │ │ │ │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 14 │ twenty exec -n │ twenty dev:fn-exec -n │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 15 │ twenty exec -u │ twenty dev:fn-exec -u │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 16 │ twenty exec -p │ twenty dev:fn-exec -p │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 17 │ twenty exec │ twenty dev:fn-exec │ Deprecated → colon │ │ │ --postInstall [appPath] │ --postInstall [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 18 │ twenty exec --preInstall │ twenty dev:fn-exec │ Deprecated → colon │ │ │ [appPath] │ --preInstall [appPath] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 19 │ twenty add [entityType] │ twenty dev:add │ Deprecated → colon │ │ │ │ [entityType] │ command │ ├─────┼──────────────────────────┼────────────────────────────┼───────────────────────┤ │ 20 │ twenty add --path │ twenty dev:add --path │ Deprecated → colon │ │ │ [entityType] │ [entityType] │ command │ └─────┴──────────────────────────┴────────────────────────────┴───────────────────────┘ App lifecycle commands ┌─────┬────────────────────────┬────────────────────────────┬─────────────────────────┐ │ # │ Old command │ New command │ Status │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 21 │ twenty publish │ twenty app:publish │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 22 │ twenty publish --tag │ twenty app:publish --tag │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 23 │ twenty deploy │ twenty app:publish │ Deprecated → colon │ │ │ [appPath] │ --private [appPath] │ command + --private │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 24 │ twenty install │ twenty app:install │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 25 │ twenty uninstall │ twenty app:uninstall │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ ├─────┼────────────────────────┼────────────────────────────┼─────────────────────────┤ │ 26 │ twenty uninstall -y │ twenty app:uninstall -y │ Deprecated → colon │ │ │ [appPath] │ [appPath] │ command │ └─────┴────────────────────────┴────────────────────────────┴─────────────────────────┘ Server commands ┌─────┬─────────────────────────┬─────────────────────────────┬──────────────────────┐ │ # │ Old command │ New command │ Status │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 27 │ twenty server start │ twenty docker:start │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 28 │ twenty server start -p │ twenty docker:start -p │ Deprecated → colon │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 29 │ twenty server start │ twenty docker:start --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 30 │ twenty server stop │ twenty docker:stop │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 31 │ twenty server stop │ twenty docker:stop --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 32 │ twenty server status │ twenty docker:status │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 33 │ twenty server status │ twenty docker:status --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 34 │ twenty server logs │ twenty docker:logs │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 35 │ twenty server logs -n │ twenty docker:logs -n │ Deprecated → colon │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 36 │ twenty server logs │ twenty docker:logs --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 37 │ twenty server reset │ twenty docker:reset │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 38 │ twenty server reset │ twenty docker:reset --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 39 │ twenty server upgrade │ twenty docker:upgrade │ Deprecated → colon │ │ │ [version] │ [version] │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 40 │ twenty server upgrade │ twenty docker:upgrade │ Deprecated → colon │ │ │ --test [version] │ --test [version] │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 41 │ twenty server │ twenty app:catalog-sync │ Deprecated → colon │ │ │ catalog-sync │ │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 42 │ twenty server │ twenty app:catalog-sync │ Deprecated → colon │ │ │ catalog-sync -r │ -r │ syntax │ ├─────┼─────────────────────────┼─────────────────────────────┼──────────────────────┤ │ 43 │ twenty catalog-sync │ (removed) │ Removed (was already │ │ │ │ │ deprecated) │ └─────┴─────────────────────────┴─────────────────────────────┴──────────────────────┘ Remote commands ┌─────┬────────────────────────┬──────────────────────────┬──────────────────────────┐ │ # │ Old command │ New command │ Status │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 44 │ twenty remote add │ twenty remote:add │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 45 │ twenty remote add --as │ twenty remote:add --as │ Deprecated → colon │ │ │ │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 46 │ twenty remote add │ twenty remote:add │ Deprecated → colon │ │ │ --api-key │ --api-key │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 47 │ twenty remote add │ twenty remote:add │ Deprecated → colon │ │ │ --api-url │ --api-url │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 48 │ twenty remote add │ twenty remote:add │ Deprecated → colon │ │ │ --local │ --local │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 49 │ twenty remote add │ twenty remote:add --test │ Deprecated → colon │ │ │ --test │ │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 50 │ twenty remote list │ twenty remote:list │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 51 │ twenty remote switch │ twenty remote:use [name] │ Deprecated → colon │ │ │ [name] │ │ syntax + renamed │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 52 │ twenty remote status │ twenty remote:status │ Deprecated → colon │ │ │ │ │ syntax │ ├─────┼────────────────────────┼──────────────────────────┼──────────────────────────┤ │ 53 │ twenty remote remove │ twenty remote:remove │ Deprecated → colon │ │ │ │ syntax │ └─────┴────────────────────────┴──────────────────────────┴──────────────────────────┘ ``` --------- Co-authored-by: cubic-dev-ai[bot] <191113872+cubic-dev-ai[bot]@users.noreply.github.com> --- .github/actions/deploy-twenty-app/action.yml | 2 +- .github/actions/install-twenty-app/action.yml | 2 +- .../workflows/ci-create-app-e2e-minimal.yaml | 4 +- README.md | 2 +- packages/create-twenty-app/README.md | 6 +- packages/create-twenty-app/package.json | 2 +- packages/create-twenty-app/src/cli.ts | 20 +- .../src/constants/template/AGENTS.md | 26 +- .../src/constants/template/README.md | 4 +- .../template/src/__tests__/global-setup.ts | 2 +- .../src/create-app.command.ts | 70 ++-- .../src/utils/docker-install.ts | 2 +- .../community/github-connector/README.md | 6 +- .../examples/hello-world/README.md | 22 +- .../hello-world/src/__tests__/setup-test.ts | 2 +- .../postcard/src/__tests__/global-setup.ts | 2 +- .../function-execute-app/package.json | 4 +- .../internal/call-recording/README.md | 22 +- .../self-hosting/src/__tests__/setup-test.ts | 2 +- .../src/__tests__/global-setup.ts | 2 +- .../internal/twenty-linear/README.md | 12 +- packages/twenty-client-sdk/package.json | 2 +- .../src/core/generated/schema.ts | 2 +- .../extend/apps/config/install-hooks.mdx | 8 +- .../extend/apps/config/public-assets.mdx | 2 +- .../developers/extend/apps/data/objects.mdx | 2 +- .../extend/apps/getting-started/concepts.mdx | 4 +- .../apps/getting-started/local-server.mdx | 42 +- .../apps/getting-started/quick-start.mdx | 4 +- .../apps/getting-started/scaffolding.mdx | 34 +- .../apps/getting-started/troubleshooting.mdx | 2 +- .../extend/apps/logic/logic-functions.mdx | 10 +- .../developers/extend/apps/operations/cli.mdx | 40 +- .../extend/apps/operations/overview.mdx | 4 +- .../extend/apps/operations/publishing.mdx | 32 +- .../extend/apps/operations/testing.mdx | 2 +- .../getting-started/core-concepts/apps.mdx | 2 +- ...ApplicationRegistrationDistributionTab.tsx | 2 +- packages/twenty-sdk/README.md | 4 +- packages/twenty-sdk/package.json | 2 +- .../src/cli/__tests__/constants/setupTest.ts | 4 +- .../utils/run-app-dev-in-process.util.ts | 2 +- packages/twenty-sdk/src/cli/cli.ts | 4 +- .../src/cli/commands/app-command.ts | 252 ------------ .../src/cli/commands/{ => app}/deploy.ts | 35 +- .../twenty-sdk/src/cli/commands/app/index.ts | 56 +++ .../src/cli/commands/{ => app}/install.ts | 0 .../src/cli/commands/{ => app}/publish.ts | 0 .../src/cli/commands/{ => app}/uninstall.ts | 0 .../twenty-sdk/src/cli/commands/deprecated.ts | 200 ++++++++++ .../src/cli/commands/{ => dev}/add.ts | 2 +- .../src/cli/commands/{ => dev}/build.ts | 0 .../cli/commands/{ => dev}/catalog-sync.ts | 0 .../src/cli/commands/{ => dev}/dev-once.ts | 0 .../src/cli/commands/{ => dev}/dev.ts | 0 .../src/cli/commands/{ => dev}/exec.ts | 0 .../src/cli/commands/dev/function/index.ts | 86 +++++ .../twenty-sdk/src/cli/commands/dev/index.ts | 97 +++++ .../src/cli/commands/{ => dev}/logs.ts | 0 .../src/cli/commands/{ => dev}/typecheck.ts | 0 .../src/cli/commands/docker/index.ts | 288 ++++++++++++++ packages/twenty-sdk/src/cli/commands/index.ts | 14 + .../twenty-sdk/src/cli/commands/remote.ts | 298 -------------- .../src/cli/commands/remote/index.ts | 365 ++++++++++++++++++ .../twenty-sdk/src/cli/commands/server.ts | 215 ----------- .../twenty-sdk/src/cli/operations/dev-once.ts | 6 +- .../src/cli/operations/server-start.ts | 10 +- .../src/cli/operations/server-upgrade.ts | 2 +- .../src/cli/utilities/api/api-client.ts | 2 +- .../cli/utilities/config/config-service.ts | 2 +- .../steps/check-server-orchestrator-step.ts | 6 +- .../file/append-server-variables.util.ts | 2 +- .../check-server-version-compatibility.ts | 2 +- 73 files changed, 1364 insertions(+), 1002 deletions(-) delete mode 100644 packages/twenty-sdk/src/cli/commands/app-command.ts rename packages/twenty-sdk/src/cli/commands/{ => app}/deploy.ts (67%) create mode 100644 packages/twenty-sdk/src/cli/commands/app/index.ts rename packages/twenty-sdk/src/cli/commands/{ => app}/install.ts (100%) rename packages/twenty-sdk/src/cli/commands/{ => app}/publish.ts (100%) rename packages/twenty-sdk/src/cli/commands/{ => app}/uninstall.ts (100%) create mode 100644 packages/twenty-sdk/src/cli/commands/deprecated.ts rename packages/twenty-sdk/src/cli/commands/{ => dev}/add.ts (99%) rename packages/twenty-sdk/src/cli/commands/{ => dev}/build.ts (100%) rename packages/twenty-sdk/src/cli/commands/{ => dev}/catalog-sync.ts (100%) rename packages/twenty-sdk/src/cli/commands/{ => dev}/dev-once.ts (100%) rename packages/twenty-sdk/src/cli/commands/{ => dev}/dev.ts (100%) rename packages/twenty-sdk/src/cli/commands/{ => dev}/exec.ts (100%) create mode 100644 packages/twenty-sdk/src/cli/commands/dev/function/index.ts create mode 100644 packages/twenty-sdk/src/cli/commands/dev/index.ts rename packages/twenty-sdk/src/cli/commands/{ => dev}/logs.ts (100%) rename packages/twenty-sdk/src/cli/commands/{ => dev}/typecheck.ts (100%) create mode 100644 packages/twenty-sdk/src/cli/commands/docker/index.ts create mode 100644 packages/twenty-sdk/src/cli/commands/index.ts delete mode 100644 packages/twenty-sdk/src/cli/commands/remote.ts create mode 100644 packages/twenty-sdk/src/cli/commands/remote/index.ts delete mode 100644 packages/twenty-sdk/src/cli/commands/server.ts diff --git a/.github/actions/deploy-twenty-app/action.yml b/.github/actions/deploy-twenty-app/action.yml index 6f8a1030d25..d2025d4758e 100644 --- a/.github/actions/deploy-twenty-app/action.yml +++ b/.github/actions/deploy-twenty-app/action.yml @@ -50,4 +50,4 @@ runs: - name: Deploy shell: bash working-directory: ${{ inputs.app-path }} - run: yarn twenty deploy --remote target + run: yarn twenty app:publish --private --remote target diff --git a/.github/actions/install-twenty-app/action.yml b/.github/actions/install-twenty-app/action.yml index bf96a16c004..785cc578fc9 100644 --- a/.github/actions/install-twenty-app/action.yml +++ b/.github/actions/install-twenty-app/action.yml @@ -50,4 +50,4 @@ runs: - name: Install shell: bash working-directory: ${{ inputs.app-path }} - run: yarn twenty install --remote target + run: yarn twenty app:install --remote target diff --git a/.github/workflows/ci-create-app-e2e-minimal.yaml b/.github/workflows/ci-create-app-e2e-minimal.yaml index 564d54a1bc1..a78b0ecc6c7 100644 --- a/.github/workflows/ci-create-app-e2e-minimal.yaml +++ b/.github/workflows/ci-create-app-e2e-minimal.yaml @@ -105,7 +105,7 @@ jobs: create-twenty-app --version mkdir -p /tmp/e2e-test-workspace cd /tmp/e2e-test-workspace - create-twenty-app test-app --display-name "Test scaffolded app" --description "E2E test scaffolded app" --workspace-url http://localhost:3000 + create-twenty-app test-app --display-name "Test scaffolded app" --description "E2E test scaffolded app" --url http://localhost:3000 - name: Install scaffolded app dependencies run: | @@ -153,7 +153,7 @@ jobs: - name: Authenticate with twenty-server run: | cd /tmp/e2e-test-workspace/test-app - npx --no-install twenty remote add --api-key ${{ env.TWENTY_API_KEY }} --api-url ${{ env.TWENTY_API_URL }} + npx --no-install twenty remote:add --api-key ${{ env.TWENTY_API_KEY }} --url ${{ env.TWENTY_API_URL }} - name: Run scaffolded app integration test (deploys, installs, and verifies the app) run: | diff --git a/README.md b/README.md index c60c97fb290..69a3d893d94 100644 --- a/README.md +++ b/README.md @@ -63,7 +63,7 @@ export default defineObject({ Then ship it to your workspace: ```bash -npx twenty deploy +npx twenty app:publish --private ``` See the [app development guide](https://docs.twenty.com/developers/extend/apps/getting-started) for objects, views, agents, and logic functions. diff --git a/packages/create-twenty-app/README.md b/packages/create-twenty-app/README.md index 722cd37e977..64c8c2b5245 100644 --- a/packages/create-twenty-app/README.md +++ b/packages/create-twenty-app/README.md @@ -35,7 +35,7 @@ The scaffolder will: | `--name ` | Set the app name | | `--display-name ` | Set the display name | | `--description ` | Set the description | -| `--workspace-url ` | Twenty workspace URL (default: `http://localhost:2020`) | +| `--url ` | Twenty workspace URL (default: `http://localhost:2020`) | | `--authentication-method ` | `oauth` or `apiKey` (default: `apiKey` for local, `oauth` for remote) | ## Documentation @@ -48,8 +48,8 @@ Full documentation is available at **[docs.twenty.com/developers/extend/apps](ht ## Troubleshooting -- Server not starting: check Docker is running (`docker info`), then try `yarn twenty server logs`. -- Auth not working: run `yarn twenty remote add --local` to re-authenticate. +- Server not starting: check Docker is running (`docker info`), then try `yarn twenty docker:logs`. +- Auth not working: run `yarn twenty remote:add --local` to re-authenticate. - Types not generated: ensure `yarn twenty dev` is running — it auto-generates the typed client. ## Contributing diff --git a/packages/create-twenty-app/package.json b/packages/create-twenty-app/package.json index d6bd8028a0c..490b0f1cfa7 100644 --- a/packages/create-twenty-app/package.json +++ b/packages/create-twenty-app/package.json @@ -1,6 +1,6 @@ { "name": "create-twenty-app", - "version": "2.6.0", + "version": "2.7.0", "description": "Command-line interface to create Twenty application", "main": "dist/cli.cjs", "bin": "dist/cli.cjs", diff --git a/packages/create-twenty-app/src/cli.ts b/packages/create-twenty-app/src/cli.ts index 05026ea2e4e..f040706d52f 100644 --- a/packages/create-twenty-app/src/cli.ts +++ b/packages/create-twenty-app/src/cli.ts @@ -18,11 +18,8 @@ const program = new Command(packageJson.name) .option('-n, --name ', 'Application name') .option('-d, --display-name ', 'Application display name') .option('--description ', 'Application description') - .option( - '--workspace-url ', - 'Twenty workspace URL (default: http://localhost:2020)', - ) - .option('--api-url ', '[deprecated: use --workspace-url]') + .option('--url ', 'Twenty server URL (default: http://localhost:2020)') + .option('--api-url ', '[deprecated: use --url]') .option( '--authentication-method ', 'Authentication method: oauth or apiKey (default: apiKey for local, oauth for remote)', @@ -35,7 +32,7 @@ const program = new Command(packageJson.name) name?: string; displayName?: string; description?: string; - workspaceUrl?: string; + url?: string; apiUrl?: string; authenticationMethod?: AuthenticationMethod; }, @@ -68,23 +65,18 @@ const program = new Command(packageJson.name) if (options?.apiUrl) { console.warn( - chalk.yellow( - 'Warning: --api-url is deprecated. Use --workspace-url instead.', - ), + chalk.yellow('Warning: --api-url is deprecated. Use --url instead.'), ); } - const workspaceUrl = (options?.workspaceUrl ?? options?.apiUrl)?.replace( - /\/+$/, - '', - ); + const serverUrl = (options?.url ?? options?.apiUrl)?.replace(/\/+$/, ''); await new CreateAppCommand().execute({ directory, name: options?.name, displayName: options?.displayName, description: options?.description, - workspaceUrl, + serverUrl, authenticationMethod: options?.authenticationMethod, }); }, diff --git a/packages/create-twenty-app/src/constants/template/AGENTS.md b/packages/create-twenty-app/src/constants/template/AGENTS.md index d4865d21a6c..fc6e7f8a514 100644 --- a/packages/create-twenty-app/src/constants/template/AGENTS.md +++ b/packages/create-twenty-app/src/constants/template/AGENTS.md @@ -49,19 +49,19 @@ ## Best practice -It's highly recommended to create new app entities using `yarn twenty add`. These are the options: +It's highly recommended to create new app entities using `yarn twenty dev:add`. These are the options: -| Entity type | Command | Generated file | -| -------------------- | ------------------------------------ | ------------------------------------- | -| Object | `yarn twenty add object` | `src/objects/.ts` | -| Field | `yarn twenty add field` | `src/fields/.ts` | -| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | -| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | -| Role | `yarn twenty add role` | `src/roles/.ts` | -| Skill | `yarn twenty add skill` | `src/skills/.ts` | -| Agent | `yarn twenty add agent` | `src/agents/.ts` | -| View | `yarn twenty add view` | `src/views/.ts` | -| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | -| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | +| Entity type | Command | Generated file | +| -------------------- | ---------------------------------------- | ------------------------------------- | +| Object | `yarn twenty dev:add object` | `src/objects/.ts` | +| Field | `yarn twenty dev:add field` | `src/fields/.ts` | +| Logic function | `yarn twenty dev:add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty dev:add frontComponent` | `src/front-components/.tsx` | +| Role | `yarn twenty dev:add role` | `src/roles/.ts` | +| Skill | `yarn twenty dev:add skill` | `src/skills/.ts` | +| Agent | `yarn twenty dev:add agent` | `src/agents/.ts` | +| View | `yarn twenty dev:add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty dev:add pageLayout` | `src/page-layouts/.ts` | This helps automatically generate required IDs etc. diff --git a/packages/create-twenty-app/src/constants/template/README.md b/packages/create-twenty-app/src/constants/template/README.md index ffee43df9a2..4728d9ffa09 100644 --- a/packages/create-twenty-app/src/constants/template/README.md +++ b/packages/create-twenty-app/src/constants/template/README.md @@ -11,8 +11,8 @@ Run `yarn twenty help` to list all available commands. ## Useful Commands - `yarn twenty dev` - Start the development server and sync your app -- `yarn twenty server status` - Check the local Twenty server status -- `yarn twenty server start` - Start the local Twenty server +- `yarn twenty docker:status` - Check the local Twenty server status +- `yarn twenty docker:start` - Start the local Twenty server - `yarn test` - Run integration tests ## Learn More diff --git a/packages/create-twenty-app/src/constants/template/src/__tests__/global-setup.ts b/packages/create-twenty-app/src/constants/template/src/__tests__/global-setup.ts index 76d5993737c..e0f8c23b054 100644 --- a/packages/create-twenty-app/src/constants/template/src/__tests__/global-setup.ts +++ b/packages/create-twenty-app/src/constants/template/src/__tests__/global-setup.ts @@ -14,7 +14,7 @@ function validateEnv(): { apiUrl: string; apiKey: string } { if (!apiUrl || !apiKey) { throw new Error( 'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' + - 'Start a local server: yarn twenty server start\n' + + 'Start a local server: yarn twenty docker:start\n' + 'Or set them in vitest env config.', ); } diff --git a/packages/create-twenty-app/src/create-app.command.ts b/packages/create-twenty-app/src/create-app.command.ts index 9e2f9c7e656..0d6291005ae 100644 --- a/packages/create-twenty-app/src/create-app.command.ts +++ b/packages/create-twenty-app/src/create-app.command.ts @@ -33,7 +33,7 @@ type CreateAppOptions = { name?: string; displayName?: string; description?: string; - workspaceUrl?: string; + serverUrl?: string; authenticationMethod?: AuthenticationMethod; }; @@ -45,9 +45,9 @@ export class CreateAppCommand { const { appName, appDisplayName, appDirectory, appDescription } = this.getAppInfos(options); - const workspaceUrl = options.workspaceUrl ?? DEV_API_URL; + const serverUrl = options.serverUrl ?? DEV_API_URL; - const skipLocalInstance = workspaceUrl !== DEV_API_URL; + const skipLocalInstance = serverUrl !== DEV_API_URL; if (!skipLocalInstance && !isDockerInstalled()) { console.log(chalk.yellow('\n' + getDockerInstallInstructions() + '\n')); @@ -118,7 +118,7 @@ export class CreateAppCommand { console.log(''); let authSucceeded = false; - let resolvedWorkspaceUrl = workspaceUrl; + let resolvedServerUrl = serverUrl; let serverReady = skipLocalInstance; if (!skipLocalInstance) { @@ -126,7 +126,7 @@ export class CreateAppCommand { const serverResult = await this.ensureDockerServer(dockerPullPromise); if (isDefined(serverResult.url)) { - resolvedWorkspaceUrl = serverResult.url; + resolvedServerUrl = serverResult.url; serverReady = true; } } @@ -134,18 +134,16 @@ export class CreateAppCommand { if (serverReady) { this.logNextStep('Authenticating'); - authSucceeded = await this.tryExistingAuth(resolvedWorkspaceUrl); + authSucceeded = await this.tryExistingAuth(resolvedServerUrl); if (authSucceeded) { this.logDetail('Reusing existing credentials'); } else if (authenticationMethod === 'oauth') { this.logDetail('Starting OAuth flow'); - authSucceeded = - await this.authenticateWithOAuth(resolvedWorkspaceUrl); + authSucceeded = await this.authenticateWithOAuth(resolvedServerUrl); } else { this.logDetail('Using development API key'); - authSucceeded = - await this.authenticateWithDevKey(resolvedWorkspaceUrl); + authSucceeded = await this.authenticateWithDevKey(resolvedServerUrl); } } @@ -165,10 +163,10 @@ export class CreateAppCommand { } if (syncSucceeded) { - await this.openMainPage(appDirectory, resolvedWorkspaceUrl); + await this.openMainPage(appDirectory, resolvedServerUrl); } - this.logSuccess(appDirectory, resolvedWorkspaceUrl, authSucceeded); + this.logSuccess(appDirectory, resolvedServerUrl, authSucceeded); } catch (error) { console.error( chalk.red('\nCreate application failed:'), @@ -315,7 +313,7 @@ export class CreateAppCommand { private async openMainPage( appDirectory: string, - workspaceUrl: string, + serverUrl: string, ): Promise { try { const configService = new ConfigService(); @@ -328,7 +326,7 @@ export class CreateAppCommand { const [universalIdentifier, frontUrl] = await Promise.all([ this.readMainPageLayoutUniversalIdentifier(appDirectory), - this.resolveWorkspaceFrontUrl(workspaceUrl, token), + this.resolveWorkspaceFrontUrl(serverUrl, token), ]); if (!universalIdentifier || !frontUrl) { @@ -336,7 +334,7 @@ export class CreateAppCommand { } const pageLayoutId = await this.resolvePageLayoutId( - workspaceUrl, + serverUrl, universalIdentifier, token, ); @@ -355,12 +353,12 @@ export class CreateAppCommand { } private async resolveWorkspaceFrontUrl( - workspaceUrl: string, + serverUrl: string, token: string, ): Promise { const query = `{ currentWorkspace { workspaceUrls { subdomainUrl customUrl } } }`; - const response = await fetch(`${workspaceUrl}/metadata`, { + const response = await fetch(`${serverUrl}/metadata`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -410,13 +408,13 @@ export class CreateAppCommand { } private async resolvePageLayoutId( - workspaceUrl: string, + serverUrl: string, universalIdentifier: string, token: string, ): Promise { const query = `{ getPageLayouts { id universalIdentifier } }`; - const response = await fetch(`${workspaceUrl}/metadata`, { + const response = await fetch(`${serverUrl}/metadata`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -503,7 +501,7 @@ export class CreateAppCommand { }); } - private async tryExistingAuth(workspaceUrl: string): Promise { + private async tryExistingAuth(serverUrl: string): Promise { try { const configService = new ConfigService(); const remoteNames = await configService.getRemotes(); @@ -511,7 +509,7 @@ export class CreateAppCommand { for (const remoteName of remoteNames) { const remoteConfig = await configService.getConfigForRemote(remoteName); - if (remoteConfig.apiUrl !== workspaceUrl) { + if (remoteConfig.apiUrl !== serverUrl) { continue; } @@ -521,7 +519,7 @@ export class CreateAppCommand { continue; } - const response = await fetch(`${workspaceUrl}/metadata`, { + const response = await fetch(`${serverUrl}/metadata`, { method: 'POST', headers: { 'Content-Type': 'application/json', @@ -555,11 +553,11 @@ export class CreateAppCommand { } } - private async authenticateWithDevKey(workspaceUrl: string): Promise { + private async authenticateWithDevKey(serverUrl: string): Promise { try { const result = await authLogin({ apiKey: DEV_API_KEY, - apiUrl: workspaceUrl, + apiUrl: serverUrl, remote: 'local', }); @@ -574,7 +572,7 @@ export class CreateAppCommand { console.log( chalk.yellow( - ' Authentication failed. Run `yarn twenty remote add --local` manually.', + ' Authentication failed. Run `yarn twenty remote:add --local` manually.', ), ); @@ -582,7 +580,7 @@ export class CreateAppCommand { } catch { console.log( chalk.yellow( - ' Authentication failed. Run `yarn twenty remote add --local` manually.', + ' Authentication failed. Run `yarn twenty remote:add --local` manually.', ), ); @@ -598,21 +596,21 @@ export class CreateAppCommand { } } - private async authenticateWithOAuth(workspaceUrl: string): Promise { + private async authenticateWithOAuth(serverUrl: string): Promise { try { - const remoteName = this.deriveRemoteName(workspaceUrl); + const remoteName = this.deriveRemoteName(serverUrl); ConfigService.setActiveRemote(remoteName); this.logDetail('Opening browser for OAuth...'); - const result = await authLoginOAuth({ apiUrl: workspaceUrl }); + const result = await authLoginOAuth({ apiUrl: serverUrl }); if (result.success) { const configService = new ConfigService(); await configService.setDefaultRemote(remoteName); - this.logDetail(`Authenticated via OAuth to ${workspaceUrl}`); + this.logDetail(`Authenticated via OAuth to ${serverUrl}`); return true; } @@ -620,7 +618,7 @@ export class CreateAppCommand { console.log( chalk.yellow( ` OAuth failed: ${result.error.message}\n` + - ` Run \`yarn twenty remote add --api-url ${workspaceUrl}\` manually.`, + ` Run \`yarn twenty remote:add --url ${serverUrl}\` manually.`, ), ); @@ -628,7 +626,7 @@ export class CreateAppCommand { } catch { console.log( chalk.yellow( - ` Authentication failed. Run \`yarn twenty remote add --api-url ${workspaceUrl}\` manually.`, + ` Authentication failed. Run \`yarn twenty remote:add --url ${serverUrl}\` manually.`, ), ); @@ -638,7 +636,7 @@ export class CreateAppCommand { private logSuccess( appDirectory: string, - workspaceUrl: string, + serverUrl: string, authSucceeded: boolean, ): void { const dirName = basename(appDirectory); @@ -656,9 +654,7 @@ export class CreateAppCommand { if (!authSucceeded) { console.log(chalk.white(` ${stepNumber}. Connect to a Twenty instance`)); console.log( - chalk.cyan( - ' yarn twenty remote add --api-url \n', - ), + chalk.cyan(' yarn twenty remote:add --url \n'), ); stepNumber++; } @@ -668,7 +664,7 @@ export class CreateAppCommand { stepNumber++; console.log(chalk.white(` ${stepNumber}. Open your twenty instance`)); - console.log(chalk.cyan(` ${workspaceUrl}\n`)); + console.log(chalk.cyan(` ${serverUrl}\n`)); console.log( chalk.gray( diff --git a/packages/create-twenty-app/src/utils/docker-install.ts b/packages/create-twenty-app/src/utils/docker-install.ts index 4b8c56d55d6..d7754dfaad9 100644 --- a/packages/create-twenty-app/src/utils/docker-install.ts +++ b/packages/create-twenty-app/src/utils/docker-install.ts @@ -28,6 +28,6 @@ export const getDockerInstallInstructions = (): string => { ' Then run this command again.', '', ' Alternatively, connect to an existing Twenty instance:', - ' npx create-twenty-app@latest my-twenty-app --workspace-url ', + ' npx create-twenty-app@latest my-twenty-app --url ', ].join('\n'); }; diff --git a/packages/twenty-apps/community/github-connector/README.md b/packages/twenty-apps/community/github-connector/README.md index a7383c4da05..0b2e68c8545 100644 --- a/packages/twenty-apps/community/github-connector/README.md +++ b/packages/twenty-apps/community/github-connector/README.md @@ -93,7 +93,7 @@ yarn install # Register your local Twenty server as a remote (interactive prompt). # When asked for the URL use http://localhost:2021 and paste an API key # from Settings -> Developers in the Twenty UI. -yarn twenty remote add +yarn twenty remote:add # Build, install, and watch for changes. yarn twenty dev @@ -107,8 +107,8 @@ watching `src/`. Edit any file and the change is re-synced within seconds. ```bash cd packages/twenty-apps/community/github-connector yarn install -yarn twenty remote add # same prompts as above -yarn twenty install # builds and installs once +yarn twenty remote:add # same prompts as above +yarn twenty app:install # builds and installs once ``` ## Configure authentication diff --git a/packages/twenty-apps/examples/hello-world/README.md b/packages/twenty-apps/examples/hello-world/README.md index 4901275bfab..a864a490d7a 100644 --- a/packages/twenty-apps/examples/hello-world/README.md +++ b/packages/twenty-apps/examples/hello-world/README.md @@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c First, authenticate to your workspace: ```bash -yarn twenty remote add --api-url http://localhost:2020 --as local +yarn twenty remote:add --api-url http://localhost:2020 --as local ``` Then, start development mode to sync your app and watch for changes: @@ -22,18 +22,18 @@ Run `yarn twenty help` to list all available commands. Common commands: ```bash # Remotes & Authentication -yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty -yarn twenty remote status # Check auth status -yarn twenty remote switch # Switch default remote -yarn twenty remote list # List all configured remotes -yarn twenty remote remove # Remove a remote +yarn twenty remote:add --api-url http://localhost:2020 --as local # Authenticate with Twenty +yarn twenty remote:status # Check auth status +yarn twenty remote:use # Set default remote +yarn twenty remote:list # List all configured remotes +yarn twenty remote:remove # Remove a remote # Application -yarn twenty dev # Start dev mode (watch, build, sync, and auto-generate typed client) -yarn twenty add # Add a new entity (object, field, function, front-component, role, view, navigation-menu-item) -yarn twenty logs # Stream function logs -yarn twenty exec # Execute a function with JSON payload -yarn twenty uninstall # Uninstall app from workspace +yarn twenty dev # Start dev mode (watch, build, sync, and auto-generate typed client) +yarn twenty dev:add # Scaffold a new entity (object, field, function, front-component, role, view, navigation-menu-item) +yarn twenty dev:function:logs # Stream function logs +yarn twenty dev:function:exec # Execute a function with JSON payload +yarn twenty app:uninstall # Uninstall app from workspace ``` ## Integration Tests diff --git a/packages/twenty-apps/examples/hello-world/src/__tests__/setup-test.ts b/packages/twenty-apps/examples/hello-world/src/__tests__/setup-test.ts index 40cb3888dbb..bfc05cb8002 100644 --- a/packages/twenty-apps/examples/hello-world/src/__tests__/setup-test.ts +++ b/packages/twenty-apps/examples/hello-world/src/__tests__/setup-test.ts @@ -13,7 +13,7 @@ beforeAll(async () => { if (!apiUrl || !token) { throw new Error( 'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' + - 'Start a local server: yarn twenty server start\n' + + 'Start a local server: yarn twenty docker:start\n' + 'Or set them in vitest env config.', ); } diff --git a/packages/twenty-apps/examples/postcard/src/__tests__/global-setup.ts b/packages/twenty-apps/examples/postcard/src/__tests__/global-setup.ts index 76d5993737c..e0f8c23b054 100644 --- a/packages/twenty-apps/examples/postcard/src/__tests__/global-setup.ts +++ b/packages/twenty-apps/examples/postcard/src/__tests__/global-setup.ts @@ -14,7 +14,7 @@ function validateEnv(): { apiUrl: string; apiKey: string } { if (!apiUrl || !apiKey) { throw new Error( 'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' + - 'Start a local server: yarn twenty server start\n' + + 'Start a local server: yarn twenty docker:start\n' + 'Or set them in vitest env config.', ); } diff --git a/packages/twenty-apps/fixtures/function-execute-app/package.json b/packages/twenty-apps/fixtures/function-execute-app/package.json index a733871e37c..3cf46866cbb 100644 --- a/packages/twenty-apps/fixtures/function-execute-app/package.json +++ b/packages/twenty-apps/fixtures/function-execute-app/package.json @@ -10,8 +10,8 @@ "packageManager": "yarn@4.9.2", "scripts": { "dev": "twenty dev", - "exec": "twenty exec", - "uninstall": "twenty uninstall", + "exec": "twenty dev:fn-exec", + "uninstall": "twenty app:uninstall", "lint": "oxlint -c .oxlintrc.json .", "lint:fix": "oxlint --fix -c .oxlintrc.json ." }, diff --git a/packages/twenty-apps/internal/call-recording/README.md b/packages/twenty-apps/internal/call-recording/README.md index 47a33a89142..f59d4673f21 100644 --- a/packages/twenty-apps/internal/call-recording/README.md +++ b/packages/twenty-apps/internal/call-recording/README.md @@ -5,7 +5,7 @@ This is a [Twenty](https://twenty.com) application project bootstrapped with [`c First, authenticate to your workspace: ```bash -yarn twenty remote add --api-url http://localhost:2020 --as local +yarn twenty remote:add --api-url http://localhost:2020 --as local ``` Then, start development mode to sync your app and watch for changes: @@ -22,18 +22,18 @@ Run `yarn twenty help` to list all available commands. Common commands: ```bash # Remotes & Authentication -yarn twenty remote add --api-url http://localhost:2020 --as local # Authenticate with Twenty -yarn twenty remote status # Check auth status -yarn twenty remote switch # Switch default remote -yarn twenty remote list # List all configured remotes -yarn twenty remote remove # Remove a remote +yarn twenty remote:add --api-url http://localhost:2020 --as local # Authenticate with Twenty +yarn twenty remote:status # Check auth status +yarn twenty remote:use # Set default remote +yarn twenty remote:list # List all configured remotes +yarn twenty remote:remove # Remove a remote # Application -yarn twenty dev # Start dev mode (watch, build, sync, and auto-generate typed client) -yarn twenty add # Add a new entity (object, field, function, front-component, role, view, navigation-menu-item) -yarn twenty logs # Stream function logs -yarn twenty exec # Execute a function with JSON payload -yarn twenty uninstall # Uninstall app from workspace +yarn twenty dev # Start dev mode (watch, build, sync, and auto-generate typed client) +yarn twenty dev:add # Scaffold a new entity (object, field, function, front-component, role, view, navigation-menu-item) +yarn twenty dev:function:logs # Stream function logs +yarn twenty dev:function:exec # Execute a function with JSON payload +yarn twenty app:uninstall # Uninstall app from workspace ``` ## LLMs instructions diff --git a/packages/twenty-apps/internal/self-hosting/src/__tests__/setup-test.ts b/packages/twenty-apps/internal/self-hosting/src/__tests__/setup-test.ts index 40cb3888dbb..bfc05cb8002 100644 --- a/packages/twenty-apps/internal/self-hosting/src/__tests__/setup-test.ts +++ b/packages/twenty-apps/internal/self-hosting/src/__tests__/setup-test.ts @@ -13,7 +13,7 @@ beforeAll(async () => { if (!apiUrl || !token) { throw new Error( 'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' + - 'Start a local server: yarn twenty server start\n' + + 'Start a local server: yarn twenty docker:start\n' + 'Or set them in vitest env config.', ); } diff --git a/packages/twenty-apps/internal/twenty-for-twenty/src/__tests__/global-setup.ts b/packages/twenty-apps/internal/twenty-for-twenty/src/__tests__/global-setup.ts index 76d5993737c..e0f8c23b054 100644 --- a/packages/twenty-apps/internal/twenty-for-twenty/src/__tests__/global-setup.ts +++ b/packages/twenty-apps/internal/twenty-for-twenty/src/__tests__/global-setup.ts @@ -14,7 +14,7 @@ function validateEnv(): { apiUrl: string; apiKey: string } { if (!apiUrl || !apiKey) { throw new Error( 'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' + - 'Start a local server: yarn twenty server start\n' + + 'Start a local server: yarn twenty docker:start\n' + 'Or set them in vitest env config.', ); } diff --git a/packages/twenty-apps/internal/twenty-linear/README.md b/packages/twenty-apps/internal/twenty-linear/README.md index fd35e9a5d91..d26c756ddeb 100644 --- a/packages/twenty-apps/internal/twenty-linear/README.md +++ b/packages/twenty-apps/internal/twenty-linear/README.md @@ -64,15 +64,15 @@ cd packages/twenty-apps/internal/twenty-linear # For day-to-day development (publish + install + watch in one): yarn twenty dev -# Manual publish flow (deploy registers the app, install activates it): -yarn twenty deploy -yarn twenty install +# Manual publish flow (publish registers the app, install activates it): +yarn twenty app:publish --private +yarn twenty app:install ``` `twenty dev` is recommended for iteration — it publishes, installs, and -watches for changes in one command. Use `twenty deploy` + `twenty install` -when you want to control each step separately (e.g. deploying to a -production server without auto-installing). +watches for changes in one command. Use `twenty app:publish --private` + +`twenty app:install` when you want to control each step separately (e.g. +deploying to a production server without auto-installing). This serves as the reference implementation for Twenty's `defineConnectionProvider({ type: 'oauth' })` flow — useful as a template diff --git a/packages/twenty-client-sdk/package.json b/packages/twenty-client-sdk/package.json index 1e3e9c173cc..d986cc3cae2 100644 --- a/packages/twenty-client-sdk/package.json +++ b/packages/twenty-client-sdk/package.json @@ -1,6 +1,6 @@ { "name": "twenty-client-sdk", - "version": "2.6.0", + "version": "2.7.0", "sideEffects": false, "license": "AGPL-3.0", "scripts": { diff --git a/packages/twenty-client-sdk/src/core/generated/schema.ts b/packages/twenty-client-sdk/src/core/generated/schema.ts index 905326723cf..040312c36cf 100644 --- a/packages/twenty-client-sdk/src/core/generated/schema.ts +++ b/packages/twenty-client-sdk/src/core/generated/schema.ts @@ -1,2 +1,2 @@ -// Stub — overwritten by `twenty build` or `twenty dev` +// Stub — overwritten by `twenty dev:build` or `twenty dev` export type CoreSchema = {}; diff --git a/packages/twenty-docs/developers/extend/apps/config/install-hooks.mdx b/packages/twenty-docs/developers/extend/apps/config/install-hooks.mdx index 1763d6bcb59..b0be535144e 100644 --- a/packages/twenty-docs/developers/extend/apps/config/install-hooks.mdx +++ b/packages/twenty-docs/developers/extend/apps/config/install-hooks.mdx @@ -45,7 +45,7 @@ export default definePostInstallLogicFunction({ You can also manually execute the post-install function at any time using the CLI: ```bash filename="Terminal" -yarn twenty exec --postInstall +yarn twenty dev:function:exec --postInstall ``` Key points: @@ -60,7 +60,7 @@ Key points: - Only one post-install function is allowed per application. The manifest build will error if more than one is detected. - The function's `universalIdentifier`, `shouldRunOnVersionUpgrade`, and `shouldRunSynchronously` are automatically attached to the application manifest under the `postInstallLogicFunction` field during the build — you do not need to reference them in [`defineApplication()`](/developers/extend/apps/config/application). - The default timeout is set to 300 seconds (5 minutes) to allow for longer setup tasks like data seeding. -- **Not executed in dev mode**: when an app is registered locally (via `yarn twenty dev`), the server skips the install flow entirely and syncs files directly through the CLI watcher — so post-install never runs in dev mode, regardless of `shouldRunSynchronously`. Use `yarn twenty exec --postInstall` to trigger it manually against a running workspace. +- **Not executed in dev mode**: when an app is registered locally (via `yarn twenty dev`), the server skips the install flow entirely and syncs files directly through the CLI watcher — so post-install never runs in dev mode, regardless of `shouldRunSynchronously`. Use `yarn twenty dev:function:exec --postInstall` to trigger it manually against a running workspace. @@ -87,7 +87,7 @@ export default definePreInstallLogicFunction({ You can also manually execute the pre-install function at any time using the CLI: ```bash filename="Terminal" -yarn twenty exec --preInstall +yarn twenty dev:function:exec --preInstall ``` Key points: @@ -96,7 +96,7 @@ Key points: - **When the hook runs**: positioned just before the workspace metadata migration (`synchronizeFromManifest`). Before executing, the server runs a purely additive "pared-down sync" that registers the **new** version's pre-install function in the workspace metadata — nothing else is touched — and then executes it. Because this sync is additive-only, the previous version's objects, fields, and data are still intact when your handler runs: you can safely read and back up pre-migration state. - **Execution model**: pre-install is executed **synchronously** and **blocks the install**. If the handler throws, the install is aborted before any schema changes are applied — the workspace stays on the previous version in a consistent state. This is intentional: pre-install is your last chance to refuse a risky upgrade. - As with post-install, only one pre-install function is allowed per application. It is attached to the application manifest under `preInstallLogicFunction` automatically during the build. -- **Not executed in dev mode**: same as post-install — the install flow is skipped entirely for locally-registered apps, so pre-install never runs under `yarn twenty dev`. Use `yarn twenty exec --preInstall` to trigger it manually. +- **Not executed in dev mode**: same as post-install — the install flow is skipped entirely for locally-registered apps, so pre-install never runs under `yarn twenty dev`. Use `yarn twenty dev:function:exec --preInstall` to trigger it manually. diff --git a/packages/twenty-docs/developers/extend/apps/config/public-assets.mdx b/packages/twenty-docs/developers/extend/apps/config/public-assets.mdx index 939135b8ac4..8350077407b 100644 --- a/packages/twenty-docs/developers/extend/apps/config/public-assets.mdx +++ b/packages/twenty-docs/developers/extend/apps/config/public-assets.mdx @@ -13,7 +13,7 @@ Files placed in `public/` are: - **Available in logic functions** — reference asset URLs in emails, API responses, or any server-side logic. - **Used for marketplace metadata** — the `logoUrl` and `screenshots` fields in `defineApplication()` reference files from this folder (e.g., `public/logo.png`). These are displayed in the marketplace when your app is published. - **Auto-synced in dev mode** — when you add, update, or delete a file in `public/`, it is synced to the server automatically. No restart needed. -- **Included in builds** — `yarn twenty build` bundles all public assets into the distribution output. +- **Included in builds** — `yarn twenty dev:build` bundles all public assets into the distribution output. ## Accessing public assets with `getPublicAssetUrl` diff --git a/packages/twenty-docs/developers/extend/apps/data/objects.mdx b/packages/twenty-docs/developers/extend/apps/data/objects.mdx index ab24c3744f0..b6f3f83ee66 100644 --- a/packages/twenty-docs/developers/extend/apps/data/objects.mdx +++ b/packages/twenty-docs/developers/extend/apps/data/objects.mdx @@ -80,7 +80,7 @@ export default defineObject({ - Each field requires a `name`, `type`, `label`, and its own stable `universalIdentifier`. - The `fields` array is optional — you can define objects without custom fields. - Inline fields defined here do **not** need an `objectUniversalIdentifier` — it's inherited from the parent object. Use [`defineField()`](/developers/extend/apps/data/extending-objects) to add fields to objects you don't own. -- You can scaffold new objects with `yarn twenty add object`, which guides you through naming, fields, and relationships. See [Architecture → Scaffolding entities](/developers/extend/apps/getting-started/scaffolding). +- You can scaffold new objects with `yarn twenty dev:add object`, which guides you through naming, fields, and relationships. See [Architecture → Scaffolding entities](/developers/extend/apps/getting-started/scaffolding). **Base fields are added automatically.** When you define a custom object, Twenty creates standard fields like `id`, `name`, `createdAt`, `updatedAt`, `createdBy`, `updatedBy`, and `deletedAt` for you. You don't need to declare them in your `fields` array — only your custom fields. You can override a default field by declaring one with the same name, but this is rarely a good idea. diff --git a/packages/twenty-docs/developers/extend/apps/getting-started/concepts.mdx b/packages/twenty-docs/developers/extend/apps/getting-started/concepts.mdx index 26e33675720..b3137ef37dd 100644 --- a/packages/twenty-docs/developers/extend/apps/getting-started/concepts.mdx +++ b/packages/twenty-docs/developers/extend/apps/getting-started/concepts.mdx @@ -65,7 +65,7 @@ your-app/ │ npx create-twenty-app → yarn twenty dev (live sync) │ ├─────────────────────────────────────────────────────────┤ │ Build & Deploy │ -│ yarn twenty build → yarn twenty deploy │ +│ yarn twenty dev:build → yarn twenty app:publish │ ├─────────────────────────────────────────────────────────┤ │ Install flow │ │ upload → [pre-install] → metadata migration → │ @@ -77,7 +77,7 @@ your-app/ ``` - **`yarn twenty dev`** — watches your source files and live-syncs changes to a connected Twenty server. The typed API client is regenerated automatically when the schema changes. -- **`yarn twenty build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. +- **`yarn twenty dev:build`** — compiles TypeScript, bundles logic functions and front components with esbuild, and produces a manifest. - **Pre/post-install hooks** — optional functions that run during installation. See [Install Hooks](/developers/extend/apps/config/install-hooks) for details. ## Next steps diff --git a/packages/twenty-docs/developers/extend/apps/getting-started/local-server.mdx b/packages/twenty-docs/developers/extend/apps/getting-started/local-server.mdx index d3c5be7bfb7..ea5dd9e8d03 100644 --- a/packages/twenty-docs/developers/extend/apps/getting-started/local-server.mdx +++ b/packages/twenty-docs/developers/extend/apps/getting-started/local-server.mdx @@ -6,44 +6,44 @@ icon: "server" ## Managing the local server -Use `yarn twenty server` to control the local Twenty container: +Use `yarn twenty docker:*` to control the local Twenty container: | Command | What it does | |---------|--------------| -| `yarn twenty server start` | Start the server (pulls the image if needed) | -| `yarn twenty server start --port 3030` | Start on a custom port | -| `yarn twenty server stop` | Stop the server (preserves data) | -| `yarn twenty server status` | Show URL, version, and login credentials | -| `yarn twenty server logs` | Stream server logs | -| `yarn twenty server reset` | Wipe data and start fresh | -| `yarn twenty server upgrade` | Pull the latest `twenty-app-dev` image | -| `yarn twenty server upgrade 2.2.0` | Upgrade to a specific version | +| `yarn twenty docker:start` | Start the server (pulls the image if needed) | +| `yarn twenty docker:start --port 3030` | Start on a custom port | +| `yarn twenty docker:stop` | Stop the server (preserves data) | +| `yarn twenty docker:status` | Show URL, version, and login credentials | +| `yarn twenty docker:logs` | Stream server logs | +| `yarn twenty docker:reset` | Wipe data and start fresh | +| `yarn twenty docker:upgrade` | Pull the latest `twenty-app-dev` image | +| `yarn twenty docker:upgrade 2.2.0` | Upgrade to a specific version | Data persists across restarts in two Docker volumes (`twenty-app-dev-data` for PostgreSQL, `twenty-app-dev-storage` for files). Use `reset` to wipe everything. ## Upgrading the server image -`yarn twenty server upgrade` pulls the latest image, compares digests, and only recreates the container if anything actually changed. Volumes are preserved — only the container is replaced. If a new image was pulled and the container was running, the upgrade automatically starts a new container; run `yarn twenty server start` afterward to wait for it to become healthy. +`yarn twenty docker:upgrade` pulls the latest image, compares digests, and only recreates the container if anything actually changed. Volumes are preserved — only the container is replaced. If a new image was pulled and the container was running, the upgrade automatically starts a new container; run `yarn twenty docker:start` afterward to wait for it to become healthy. ```bash filename="Terminal" -yarn twenty server upgrade # Latest -yarn twenty server upgrade 2.2.0 # Specific version +yarn twenty docker:upgrade # Latest +yarn twenty docker:upgrade 2.2.0 # Specific version ``` -Verify the running version with `yarn twenty server status` (it shows the `APP_VERSION` baked into the container). +Verify the running version with `yarn twenty docker:status` (it shows the `APP_VERSION` baked into the container). ## Running a parallel test instance -Pass `--test` to any `server` command to manage a second, fully isolated instance — useful for integration tests or experiments without touching your main dev data: +Pass `--test` to any `docker:*` command to manage a second, fully isolated instance — useful for integration tests or experiments without touching your main dev data: | Command | What it does | |---------|--------------| -| `yarn twenty server start --test` | Start the test instance (defaults to port 2021) | -| `yarn twenty server stop --test` | Stop it | -| `yarn twenty server status --test` | Show its status | -| `yarn twenty server logs --test` | Stream its logs | -| `yarn twenty server reset --test` | Wipe its data | -| `yarn twenty server upgrade --test` | Upgrade its image | +| `yarn twenty docker:start --test` | Start the test instance (defaults to port 2021) | +| `yarn twenty docker:stop --test` | Stop it | +| `yarn twenty docker:status --test` | Show its status | +| `yarn twenty docker:logs --test` | Stream its logs | +| `yarn twenty docker:reset --test` | Wipe its data | +| `yarn twenty docker:upgrade --test` | Upgrade its image | The test instance has its own container (`twenty-app-dev-test`), volumes (`twenty-app-dev-test-data`, `twenty-app-dev-test-storage`), and config — it runs alongside your main instance without conflicts. Combine `--test` with `--port` to override 2021. @@ -65,7 +65,7 @@ Add the script to `package.json`: } ``` -You can now run `yarn twenty dev`, `yarn twenty server start`, and the rest. +You can now run `yarn twenty dev`, `yarn twenty docker:start`, and the rest. Don't install `twenty-sdk` globally — pin it per project so each app uses its own version. diff --git a/packages/twenty-docs/developers/extend/apps/getting-started/quick-start.mdx b/packages/twenty-docs/developers/extend/apps/getting-started/quick-start.mdx index 71dcbbc7407..a3bf941762e 100644 --- a/packages/twenty-docs/developers/extend/apps/getting-started/quick-start.mdx +++ b/packages/twenty-docs/developers/extend/apps/getting-started/quick-start.mdx @@ -43,7 +43,7 @@ The scaffolder offers to start one for you: > **Would you like to set up a local Twenty instance?** - **Yes (recommended)** — pulls the `twentycrm/twenty-app-dev` Docker image and starts it on port `2020`. Make sure Docker is running first. -- **No** — choose this if you already have a Twenty server you want to connect to. You can wire it up later with `yarn twenty remote add`. +- **No** — choose this if you already have a Twenty server you want to connect to. You can wire it up later with `yarn twenty remote:add`.
Should start local instance? @@ -73,7 +73,7 @@ Your terminal will confirm everything is set up. **After this phase:** you have a running Twenty server at [http://localhost:2020](http://localhost:2020) with your CLI authorized to sync to it. -If Docker isn't installed or running, the scaffolder will tell you the right start command for your OS. Once Docker is up, you can resume with `yarn twenty server start` — no need to re-scaffold. +If Docker isn't installed or running, the scaffolder will tell you the right start command for your OS. Once Docker is up, you can resume with `yarn twenty docker:start` — no need to re-scaffold. --- diff --git a/packages/twenty-docs/developers/extend/apps/getting-started/scaffolding.mdx b/packages/twenty-docs/developers/extend/apps/getting-started/scaffolding.mdx index de505b98acb..720d2ad1cdf 100644 --- a/packages/twenty-docs/developers/extend/apps/getting-started/scaffolding.mdx +++ b/packages/twenty-docs/developers/extend/apps/getting-started/scaffolding.mdx @@ -1,13 +1,13 @@ --- title: Scaffolding -description: Generate entity files interactively with yarn twenty add — objects, fields, views, logic functions, and more. +description: Generate entity files interactively with yarn twenty dev:add — objects, fields, views, logic functions, and more. icon: "wand-magic-sparkles" --- Instead of creating entity files by hand, use the interactive scaffolder: ```bash filename="Terminal" -yarn twenty add +yarn twenty dev:add ``` It prompts you to pick an entity type and walks you through the required fields, then writes a ready-to-use file with a stable `universalIdentifier` and the correct `defineEntity()` call. @@ -15,29 +15,29 @@ It prompts you to pick an entity type and walks you through the required fields, You can also pass the entity type directly to skip the first prompt: ```bash filename="Terminal" -yarn twenty add object -yarn twenty add logicFunction -yarn twenty add frontComponent +yarn twenty dev:add object +yarn twenty dev:add logicFunction +yarn twenty dev:add frontComponent ``` ## Available entity types | Entity type | Command | Generated file | |-------------|---------|----------------| -| Object | `yarn twenty add object` | `src/objects/.ts` | -| Field | `yarn twenty add field` | `src/fields/.ts` | -| Logic function | `yarn twenty add logicFunction` | `src/logic-functions/.ts` | -| Front component | `yarn twenty add frontComponent` | `src/front-components/.tsx` | -| Role | `yarn twenty add role` | `src/roles/.ts` | -| Skill | `yarn twenty add skill` | `src/skills/.ts` | -| Agent | `yarn twenty add agent` | `src/agents/.ts` | -| View | `yarn twenty add view` | `src/views/.ts` | -| Navigation menu item | `yarn twenty add navigationMenuItem` | `src/navigation-menu-items/.ts` | -| Page layout | `yarn twenty add pageLayout` | `src/page-layouts/.ts` | +| Object | `yarn twenty dev:add object` | `src/objects/.ts` | +| Field | `yarn twenty dev:add field` | `src/fields/.ts` | +| Logic function | `yarn twenty dev:add logicFunction` | `src/logic-functions/.ts` | +| Front component | `yarn twenty dev:add frontComponent` | `src/front-components/.tsx` | +| Role | `yarn twenty dev:add role` | `src/roles/.ts` | +| Skill | `yarn twenty dev:add skill` | `src/skills/.ts` | +| Agent | `yarn twenty dev:add agent` | `src/agents/.ts` | +| View | `yarn twenty dev:add view` | `src/views/.ts` | +| Navigation menu item | `yarn twenty dev:add navigationMenuItem` | `src/navigation-menu-items/.ts` | +| Page layout | `yarn twenty dev:add pageLayout` | `src/page-layouts/.ts` | ## What the scaffolder generates -Each entity type has its own template. For example, `yarn twenty add object` asks for: +Each entity type has its own template. For example, `yarn twenty dev:add object` asks for: 1. **Name (singular)** — e.g., `invoice` 2. **Name (plural)** — e.g., `invoices` @@ -54,5 +54,5 @@ The `field` entity type is more detailed: it asks for the field name, label, typ Use the `--path` flag to place the generated file in a custom location: ```bash filename="Terminal" -yarn twenty add logicFunction --path src/custom-folder +yarn twenty dev:add logicFunction --path src/custom-folder ``` diff --git a/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx b/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx index 73bc5a85009..85b991cccdf 100644 --- a/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx +++ b/packages/twenty-docs/developers/extend/apps/getting-started/troubleshooting.mdx @@ -4,7 +4,7 @@ description: Common first-run issues — Docker, Node version, Yarn, dependencie icon: "wrench" --- -- **Docker errors** — Make sure Docker Desktop (or the daemon) is running before `yarn twenty server start`. The error message will show the right start command for your OS. +- **Docker errors** — Make sure Docker Desktop (or the daemon) is running before `yarn twenty docker:start`. The error message will show the right start command for your OS. - **Wrong Node version** — Need 24+. Check with `node -v`. - **Yarn 4 missing** — Run `corepack enable`. - **Dependencies broken** — `rm -rf node_modules && yarn install`. diff --git a/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx b/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx index f0f83e0845d..5503fbffe7a 100644 --- a/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx +++ b/packages/twenty-docs/developers/extend/apps/logic/logic-functions.mdx @@ -61,17 +61,17 @@ Available trigger types: You can also manually execute a function using the CLI: ```bash filename="Terminal" -yarn twenty exec -n create-new-post-card -p '{"key": "value"}' +yarn twenty dev:function:exec -n create-new-post-card -p '{"key": "value"}' ``` ```bash filename="Terminal" -yarn twenty exec -y e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` You can watch logs with: ```bash filename="Terminal" -yarn twenty logs +yarn twenty dev:function:logs ``` @@ -341,7 +341,7 @@ The `twenty-client-sdk` package provides two typed GraphQL clients for interacti -`CoreApiClient` is the main client for querying and mutating workspace data. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty build`, so it is fully typed to match your objects and fields. +`CoreApiClient` is the main client for querying and mutating workspace data. It is **generated from your workspace schema** during `yarn twenty dev` or `yarn twenty dev:build`, so it is fully typed to match your objects and fields. ```ts import { CoreApiClient } from 'twenty-client-sdk/core'; @@ -381,7 +381,7 @@ const { createCompany } = await client.mutation({ The client uses a selection-set syntax: pass `true` to include a field, use `__args` for arguments, and nest objects for relations. You get full autocompletion and type checking based on your workspace schema. -**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. +**CoreApiClient is generated at dev/build time.** If you use it without running `yarn twenty dev` or `yarn twenty dev:build` first, it throws an error. The generation happens automatically — the CLI introspects your workspace's GraphQL schema and generates a typed client using `@genql/cli`. #### Using CoreSchema for type annotations diff --git a/packages/twenty-docs/developers/extend/apps/operations/cli.mdx b/packages/twenty-docs/developers/extend/apps/operations/cli.mdx index 1eff0079023..5e326461aa3 100644 --- a/packages/twenty-docs/developers/extend/apps/operations/cli.mdx +++ b/packages/twenty-docs/developers/extend/apps/operations/cli.mdx @@ -4,54 +4,54 @@ description: yarn twenty commands for executing functions, streaming logs, manag icon: "terminal" --- -Beyond `dev`, `build`, `add`, and `typecheck`, the `yarn twenty` CLI provides commands for executing functions, viewing logs, and managing app installations. +Beyond `dev`, `dev:build`, `dev:add`, and `dev:typecheck`, the `yarn twenty` CLI provides commands for executing functions, viewing logs, and managing app installations. -## Executing functions (`yarn twenty exec`) +## Executing functions (`yarn twenty dev:function:exec`) Run a logic function manually without triggering it via HTTP, cron, or database event: ```bash filename="Terminal" # Execute by function name -yarn twenty exec -n create-new-post-card +yarn twenty dev:function:exec -n create-new-post-card # Execute by universalIdentifier -yarn twenty exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +yarn twenty dev:function:exec -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf # Pass a JSON payload -yarn twenty exec -n create-new-post-card -p '{"name": "Hello"}' +yarn twenty dev:function:exec -n create-new-post-card -p '{"name": "Hello"}' # Execute the post-install function -yarn twenty exec --postInstall +yarn twenty dev:function:exec --postInstall ``` -## Viewing function logs (`yarn twenty logs`) +## Viewing function logs (`yarn twenty dev:function:logs`) Stream execution logs for your app's logic functions: ```bash filename="Terminal" # Stream all function logs -yarn twenty logs +yarn twenty dev:function:logs # Filter by function name -yarn twenty logs -n create-new-post-card +yarn twenty dev:function:logs -n create-new-post-card # Filter by universalIdentifier -yarn twenty logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf +yarn twenty dev:function:logs -u e56d363b-0bdc-4d8a-a393-6f0d1c75bdcf ``` -This is different from `yarn twenty server logs`, which shows the Docker container logs. `yarn twenty logs` shows your app's function execution logs from the Twenty server. +This is different from `yarn twenty docker:logs`, which shows the Docker container logs. `yarn twenty dev:function:logs` shows your app's function execution logs from the Twenty server. -## Uninstalling an app (`yarn twenty uninstall`) +## Uninstalling an app (`yarn twenty app:uninstall`) Remove your app from the active workspace: ```bash filename="Terminal" -yarn twenty uninstall +yarn twenty app:uninstall # Skip the confirmation prompt -yarn twenty uninstall --yes +yarn twenty app:uninstall --yes ``` ## Managing remotes @@ -60,19 +60,19 @@ A **remote** is a Twenty server that your app connects to. During setup, the sca ```bash filename="Terminal" # Add a new remote (opens a browser for OAuth login) -yarn twenty remote add +yarn twenty remote:add # Connect to a local Twenty server (auto-detects port 2020 or 3000) -yarn twenty remote add --local +yarn twenty remote:add --local # Add a remote non-interactively (useful for CI) -yarn twenty remote add --api-url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote +yarn twenty remote:add --url https://your-twenty-server.com --api-key $TWENTY_API_KEY --as my-remote # List all configured remotes -yarn twenty remote list +yarn twenty remote:list -# Switch the active remote -yarn twenty remote switch +# Set the active remote +yarn twenty remote:use ``` Your credentials are stored in `~/.twenty/config.json`. diff --git a/packages/twenty-docs/developers/extend/apps/operations/overview.mdx b/packages/twenty-docs/developers/extend/apps/operations/overview.mdx index 46374ecf441..dacf74948f3 100644 --- a/packages/twenty-docs/developers/extend/apps/operations/overview.mdx +++ b/packages/twenty-docs/developers/extend/apps/operations/overview.mdx @@ -9,9 +9,9 @@ The **operations layer** is everything you do *to* your app rather than *with* i ```text develop ─▶ test ─▶ build ─▶ deploy / publish ─────── ──── ───── ───────────────── - yarn yarn yarn yarn twenty deploy (tarball → one server) + yarn yarn yarn yarn twenty app:publish --private (tarball → one server) twenty test twenty - dev build yarn twenty publish (npm → marketplace) + dev dev:build yarn twenty app:publish (npm → marketplace) ``` ## In this section diff --git a/packages/twenty-docs/developers/extend/apps/operations/publishing.mdx b/packages/twenty-docs/developers/extend/apps/operations/publishing.mdx index e8612608e8c..9589daa7a11 100644 --- a/packages/twenty-docs/developers/extend/apps/operations/publishing.mdx +++ b/packages/twenty-docs/developers/extend/apps/operations/publishing.mdx @@ -18,10 +18,10 @@ Both paths start from the same **build** step. Run the build command to compile your app and generate a distribution-ready `manifest.json`: ```bash filename="Terminal" -yarn twenty build +yarn twenty dev:build ``` -This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the deploy command. +This compiles TypeScript sources, transpiles logic functions and front components, and writes everything to `.twenty/output/`. Add `--tarball` to also produce a `.tgz` package for manual distribution or the publish command. ## Deploying to a server (tarball) @@ -34,7 +34,7 @@ Before deploying, you need a configured remote pointing to the target server. Re Add a remote: ```bash filename="Terminal" -yarn twenty remote add --api-url https://your-twenty-server.com --as production +yarn twenty remote:add --url https://your-twenty-server.com --as production ``` ### Deploying @@ -42,9 +42,9 @@ yarn twenty remote add --api-url https://your-twenty-server.com --as production Build and upload your app to the server in one step: ```bash filename="Terminal" -yarn twenty deploy +yarn twenty app:publish --private # To deploy to a specific remote: -# yarn twenty deploy --remote production +# yarn twenty app:publish --private --remote production ``` ### Sharing a deployed app @@ -68,7 +68,7 @@ When updating an already deployed tarball app, the server requires the `version` To release an update: 1. Bump the `version` field in your `package.json` (e.g. `1.2.3` → `1.2.4`, `1.3.0`, or `2.0.0`) -2. Run `yarn twenty deploy` (or `yarn twenty deploy --remote production`) +2. Run `yarn twenty app:publish --private` (or `yarn twenty app:publish --private --remote production`) 3. Workspaces that have the app installed will see the upgrade available in their settings @@ -121,7 +121,7 @@ Runs integration tests on every push to `main` and every pull request. **What it does:** 1. Checks out your app's source. -2. Spawns an isolated Twenty test instance using the `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` composite action (the CI equivalent of `yarn twenty server start --test`). +2. Spawns an isolated Twenty test instance using the `twentyhq/twenty/.github/actions/spawn-twenty-app-dev-test@main` composite action (the CI equivalent of `yarn twenty docker:start --test`). 3. Enables Corepack, sets up Node.js from your `.nvmrc`, and installs dependencies with `yarn install --immutable`. 4. Runs `yarn test`, passing `TWENTY_API_URL` and `TWENTY_API_KEY` from the spawned instance so your tests can talk to a real server. @@ -139,7 +139,7 @@ Deploys your app to a configured Twenty server on every push to `main`, and opti **What it does:** 1. Checks out the PR head (for labeled PRs) or the pushed commit. -2. Runs `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — the CI equivalent of `yarn twenty deploy`. +2. Runs `twentyhq/twenty/.github/actions/deploy-twenty-app@main` — the CI equivalent of `yarn twenty app:publish --private`. 3. Runs `twentyhq/twenty/.github/actions/install-twenty-app@main` so the newly deployed version is installed into the target workspace. **Required configuration:** @@ -208,13 +208,13 @@ Screenshots of any aspect ratio are displayed in full and are never cropped, but ### Publish ```bash filename="Terminal" -yarn twenty publish +yarn twenty app:publish ``` To publish under a specific dist-tag (e.g., `beta` or `next`): ```bash filename="Terminal" -yarn twenty publish --tag beta +yarn twenty app:publish --tag beta ``` ### How marketplace discovery works @@ -224,9 +224,9 @@ The Twenty server syncs its marketplace catalog from the npm registry **every ho You can trigger the sync immediately instead of waiting: ```bash filename="Terminal" -yarn twenty server catalog-sync +yarn twenty dev:catalog-sync # To target a specific remote: -# yarn twenty server catalog-sync --remote production +# yarn twenty dev:catalog-sync --remote production ``` The metadata shown in the marketplace comes from your `defineApplication()` config — fields like `displayName`, `description`, `author`, `category`, `logoUrl`, `screenshots`, `aboutDescription`, `websiteUrl`, and `termsUrl`. @@ -259,12 +259,12 @@ jobs: node-version: "24" registry-url: https://registry.npmjs.org - run: yarn install --immutable - - run: npx twenty build + - run: npx twenty dev:build - run: npm publish --provenance --access public working-directory: .twenty/output ``` -For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `yarn twenty build`, then `npm publish` from `.twenty/output`. +For other CI systems (GitLab CI, CircleCI, etc.), the same three commands apply: `yarn install`, `yarn twenty dev:build`, then `npm publish` from `.twenty/output`. **npm provenance** is optional but recommended. Publishing with `--provenance` adds a trust badge to your npm listing, letting users verify the package was built from a specific commit in a public CI pipeline. See the [npm provenance docs](https://docs.npmjs.com/generating-provenance-statements) for setup instructions. @@ -281,7 +281,7 @@ Go to the **Settings > Applications** page in Twenty, where both marketplace and You can also install apps from the command line: ```bash filename="Terminal" -yarn twenty install +yarn twenty app:install ``` @@ -290,5 +290,5 @@ The server enforces semver versioning on install, mirroring the rules on deploy: - Installing the same version that is already installed in your workspace is rejected with an `APP_ALREADY_INSTALLED` error. - Installing a lower version than the one currently installed is rejected with a `CANNOT_DOWNGRADE_APPLICATION` error. -To install a newer version, deploy or publish it first, then re-run `yarn twenty install`. +To install a newer version, deploy or publish it first, then re-run `yarn twenty app:install`. diff --git a/packages/twenty-docs/developers/extend/apps/operations/testing.mdx b/packages/twenty-docs/developers/extend/apps/operations/testing.mdx index e77b2716a8f..3d0cc24deb5 100644 --- a/packages/twenty-docs/developers/extend/apps/operations/testing.mdx +++ b/packages/twenty-docs/developers/extend/apps/operations/testing.mdx @@ -235,7 +235,7 @@ yarn test:watch You can also run type checking on your app without running tests: ```bash filename="Terminal" -yarn twenty typecheck +yarn twenty dev:typecheck ``` This runs `tsc --noEmit` and reports any type errors. diff --git a/packages/twenty-docs/getting-started/core-concepts/apps.mdx b/packages/twenty-docs/getting-started/core-concepts/apps.mdx index f5e1392e5d6..722470bb75d 100644 --- a/packages/twenty-docs/getting-started/core-concepts/apps.mdx +++ b/packages/twenty-docs/getting-started/core-concepts/apps.mdx @@ -35,7 +35,7 @@ Everything is detected via AST analysis at build time — no config files, no re ## The developer experience -You write your app as a TypeScript project on your machine. The CLI watches your source files and live-syncs them to a running Twenty server — edit a file, see the change in the UI within a second. The typed API client regenerates automatically when the schema changes. When you're ready, `yarn twenty deploy` pushes to a production server, or `yarn twenty publish` lists your app on npm and the Twenty marketplace. +You write your app as a TypeScript project on your machine. The CLI watches your source files and live-syncs them to a running Twenty server — edit a file, see the change in the UI within a second. The typed API client regenerates automatically when the schema changes. When you're ready, `yarn twenty app:publish --private` pushes to a production server, or `yarn twenty app:publish` lists your app on npm and the Twenty marketplace. Three-phase walkthrough — scaffold, run a local server, sync your changes. diff --git a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationRegistrationDistributionTab.tsx b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationRegistrationDistributionTab.tsx index ec3b79739fe..d309b534755 100644 --- a/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationRegistrationDistributionTab.tsx +++ b/packages/twenty-front/src/pages/settings/applications/tabs/SettingsApplicationRegistrationDistributionTab.tsx @@ -28,7 +28,7 @@ export const SettingsApplicationRegistrationDistributionTab = ({ availableApplicationId: registration.universalIdentifier, }); - const publishCommands = ['yarn twenty publish']; + const publishCommands = ['yarn twenty app:publish']; return ( <> diff --git a/packages/twenty-sdk/README.md b/packages/twenty-sdk/README.md index d5e97341851..436d09ac624 100644 --- a/packages/twenty-sdk/README.md +++ b/packages/twenty-sdk/README.md @@ -54,11 +54,11 @@ Run `yarn twenty help` to see all available commands. ## Configuration -The CLI stores credentials per remote in `~/.twenty/config.json`. Run `yarn twenty remote add` to configure a remote, or `yarn twenty remote list` to see existing ones. +The CLI stores credentials per remote in `~/.twenty/config.json`. Run `yarn twenty remote:add` to configure a remote, or `yarn twenty remote:list` to see existing ones. ## Troubleshooting -- Auth errors: run `yarn twenty remote add` to re-authenticate. +- Auth errors: run `yarn twenty remote:add` to re-authenticate. - Typings out of date: restart `yarn twenty dev` to refresh the client and types. - Not seeing changes in dev: make sure dev mode is running (`yarn twenty dev`). diff --git a/packages/twenty-sdk/package.json b/packages/twenty-sdk/package.json index 07e84112dce..4f07ca5668c 100644 --- a/packages/twenty-sdk/package.json +++ b/packages/twenty-sdk/package.json @@ -1,6 +1,6 @@ { "name": "twenty-sdk", - "version": "2.6.0", + "version": "2.7.0", "sideEffects": false, "bin": { "twenty": "dist/cli.cjs" diff --git a/packages/twenty-sdk/src/cli/__tests__/constants/setupTest.ts b/packages/twenty-sdk/src/cli/__tests__/constants/setupTest.ts index a34159ae212..105e4d75945 100644 --- a/packages/twenty-sdk/src/cli/__tests__/constants/setupTest.ts +++ b/packages/twenty-sdk/src/cli/__tests__/constants/setupTest.ts @@ -14,7 +14,7 @@ beforeAll(async () => { if (!apiUrl || !token) { throw new Error( 'TWENTY_API_URL and TWENTY_API_KEY must be set.\n' + - 'Run: twenty server start --test\n' + + 'Run: twenty docker:start --test\n' + 'Or set them in vitest env config.', ); } @@ -24,7 +24,7 @@ beforeAll(async () => { if (!response?.ok) { throw new Error( `Twenty server not reachable at ${apiUrl}. ` + - 'Run: twenty server start --test', + 'Run: twenty docker:start --test', ); } diff --git a/packages/twenty-sdk/src/cli/__tests__/integration/utils/run-app-dev-in-process.util.ts b/packages/twenty-sdk/src/cli/__tests__/integration/utils/run-app-dev-in-process.util.ts index 8e38167079f..0f851aeb232 100644 --- a/packages/twenty-sdk/src/cli/__tests__/integration/utils/run-app-dev-in-process.util.ts +++ b/packages/twenty-sdk/src/cli/__tests__/integration/utils/run-app-dev-in-process.util.ts @@ -1,7 +1,7 @@ import { join } from 'path'; import { OUTPUT_DIR } from 'twenty-shared/application'; -import { AppDevCommand } from '@/cli/commands/dev'; +import { AppDevCommand } from '@/cli/commands/dev/dev'; import { pathExists } from '@/cli/utilities/file/fs-utils'; export type RunAppDevResult = { diff --git a/packages/twenty-sdk/src/cli/cli.ts b/packages/twenty-sdk/src/cli/cli.ts index a566847a912..3968d7fc48f 100644 --- a/packages/twenty-sdk/src/cli/cli.ts +++ b/packages/twenty-sdk/src/cli/cli.ts @@ -1,5 +1,5 @@ #!/usr/bin/env node -import { registerCommands } from '@/cli/commands/app-command'; +import { registerCommands } from '@/cli/commands'; import { ConfigService } from '@/cli/utilities/config/config-service'; import chalk from 'chalk'; import { Command, CommanderError } from 'commander'; @@ -17,7 +17,7 @@ program program.option( '-r, --remote ', - 'Use a specific remote (overrides the default set by remote switch)', + 'Use a specific remote (overrides the default set by remote:use)', ); program.hook('preAction', async (thisCommand) => { diff --git a/packages/twenty-sdk/src/cli/commands/app-command.ts b/packages/twenty-sdk/src/cli/commands/app-command.ts deleted file mode 100644 index d0ce67e467b..00000000000 --- a/packages/twenty-sdk/src/cli/commands/app-command.ts +++ /dev/null @@ -1,252 +0,0 @@ -import { formatPath } from '@/cli/utilities/file/file-path'; -import chalk from 'chalk'; -import type { Command } from 'commander'; -import { SyncableEntity } from 'twenty-shared/application'; -import { EntityAddCommand } from './add'; -import { AppBuildCommand } from './build'; -import { CatalogSyncCommand } from './catalog-sync'; -import { DeployCommand } from './deploy'; -import { AppDevCommand } from './dev'; -import { LogicFunctionExecuteCommand } from './exec'; -import { AppInstallCommand } from './install'; -import { LogicFunctionLogsCommand } from './logs'; -import { AppPublishCommand } from './publish'; -import { registerRemoteCommands } from './remote'; -import { registerServerCommands } from './server'; -import { AppDevOnceCommand } from './dev-once'; -import { AppTypecheckCommand } from './typecheck'; -import { AppUninstallCommand } from './uninstall'; - -export const registerCommands = (program: Command): void => { - const buildCommand = new AppBuildCommand(); - const devCommand = new AppDevCommand(); - const devOnceCommand = new AppDevOnceCommand(); - const installCommand = new AppInstallCommand(); - const publishCommand = new AppPublishCommand(); - const typecheckCommand = new AppTypecheckCommand(); - const uninstallCommand = new AppUninstallCommand(); - const catalogSyncCommand = new CatalogSyncCommand(); - const deployCommand = new DeployCommand(); - const addCommand = new EntityAddCommand(); - const logsCommand = new LogicFunctionLogsCommand(); - const executeCommand = new LogicFunctionExecuteCommand(); - - program - .command('dev [appPath]') - .description( - 'Build and sync local application changes (watches by default; use --once for a one-shot sync)', - ) - .option( - '-w, --watch', - 'Watch source files and re-sync on every change (default behavior)', - ) - .option( - '-o, --once', - 'Build and sync once, then exit (useful for CI, scripts, and pre-commit hooks)', - ) - .option('--debounceMs ', 'Debounce in ms (default: 2 000)') - .option('-v, --verbose', 'Show detailed logs') - .option('-d, --debug', 'Show detailed logs (alias for --verbose)') - .action(async (appPath, options) => { - if (options.once && options.watch) { - console.error( - chalk.red( - 'Error: --once and --watch are mutually exclusive. Watch mode is the default.', - ), - ); - process.exit(1); - } - - const commonOptions = { - appPath: formatPath(appPath), - verbose: options.verbose || options.debug, - debounceMs: options.debounceMs - ? parseInt(options.debounceMs, 10) - : undefined, - }; - - if (options.once) { - await devOnceCommand.execute(commonOptions); - return; - } - - await devCommand.execute(commonOptions); - }); - - program - .command('build [appPath]') - .description('Build, sync, and generate API client into .twenty/output/') - .option('--tarball', 'Also pack into a .tgz tarball') - .action(async (appPath, options) => { - await buildCommand.execute({ - appPath: formatPath(appPath), - tarball: options.tarball, - }); - }); - - program - .command('install [appPath]') - .description('Install a deployed application on the connected server') - .option('-r, --remote ', 'Install on a specific remote') - .action(async (appPath, options) => { - await installCommand.execute({ - appPath: formatPath(appPath), - remote: options.remote, - }); - }); - - program - .command('deploy [appPath]') - .description("Publish a new version to a Twenty server's registry") - .option('-r, --remote ', 'Deploy to a specific remote') - .action(async (appPath, options) => { - await deployCommand.execute({ - appPath: formatPath(appPath), - remote: options.remote, - }); - }); - - program - .command('publish [appPath]') - .description('Build and publish to npm') - .option('--tag ', 'npm dist-tag (e.g. beta, next)') - .action(async (appPath, options) => { - await publishCommand.execute({ - appPath: formatPath(appPath), - tag: options.tag, - }); - }); - - program - .command('catalog-sync') - .description( - '[Deprecated] Moved under server. Use `yarn twenty server catalog-sync`.', - ) - .option('-r, --remote ', 'Sync on a specific remote') - .action(async (options) => { - console.warn( - chalk.yellow( - '`yarn twenty catalog-sync` is deprecated and will be removed in a future release.\n' + - 'Use `yarn twenty server catalog-sync` instead.\n', - ), - ); - - await catalogSyncCommand.execute({ - remote: options.remote, - }); - }); - - program - .command('typecheck [appPath]') - .description('Run TypeScript type checking on the application') - .action(async (appPath) => { - await typecheckCommand.execute({ - appPath: formatPath(appPath), - }); - }); - - program - .command('uninstall [appPath]') - .description('Uninstall application from Twenty') - .option('-y, --yes', 'Skip confirmation prompt') - .action(async (appPath?: string, options?: { yes?: boolean }) => { - try { - const result = await uninstallCommand.execute({ - appPath: formatPath(appPath), - askForConfirmation: !options?.yes, - }); - process.exit(result.success ? 0 : 1); - } catch { - process.exit(1); - } - }); - - registerRemoteCommands(program); - registerServerCommands(program); - - program - .command('add [entityType]') - .option('--path ', 'Path in which the entity should be created.') - .description( - `Add a new entity to your application (${Object.values(SyncableEntity).join('|')})`, - ) - .action(async (entityType?: string, options?: { path?: string }) => { - await addCommand.execute(entityType as SyncableEntity, options?.path); - }); - - program - .command('exec [appPath]') - .option('--postInstall', 'Execute post-install logic function if defined') - .option('--preInstall', 'Execute pre-install logic function if defined') - .option( - '-p, --payload ', - 'JSON payload to send to the function', - '{}', - ) - .option( - '-u, --functionUniversalIdentifier ', - 'Universal ID of the function to execute', - ) - .option( - '-n, --functionName ', - 'Name of the function to execute', - ) - .description('Execute a logic function with a JSON payload') - .action( - async ( - appPath?: string, - options?: { - postInstall?: boolean; - preInstall?: boolean; - payload?: string; - functionUniversalIdentifier?: string; - functionName?: string; - }, - ) => { - if ( - !options?.postInstall && - !options?.preInstall && - !options?.functionUniversalIdentifier && - !options?.functionName - ) { - console.error( - chalk.red( - 'Error: Either --postInstall, --preInstall, --functionName (-n), or --functionUniversalIdentifier (-u) is required.', - ), - ); - process.exit(1); - } - await executeCommand.execute({ - ...options, - payload: options?.payload ?? '{}', - appPath: formatPath(appPath), - }); - }, - ); - - program - .command('logs [appPath]') - .option( - '-u, --functionUniversalIdentifier ', - 'Only show logs for the function with this universal ID', - ) - .option( - '-n, --functionName ', - 'Only show logs for the function with this name', - ) - .description('Watch application function logs') - .action( - async ( - appPath?: string, - options?: { - functionUniversalIdentifier?: string; - functionName?: string; - }, - ) => { - await logsCommand.execute({ - ...options, - appPath: formatPath(appPath), - }); - }, - ); -}; diff --git a/packages/twenty-sdk/src/cli/commands/deploy.ts b/packages/twenty-sdk/src/cli/commands/app/deploy.ts similarity index 67% rename from packages/twenty-sdk/src/cli/commands/deploy.ts rename to packages/twenty-sdk/src/cli/commands/app/deploy.ts index 89943a0419f..b4035e48054 100644 --- a/packages/twenty-sdk/src/cli/commands/deploy.ts +++ b/packages/twenty-sdk/src/cli/commands/app/deploy.ts @@ -2,6 +2,7 @@ import path from 'path'; import { appBuild } from '@/cli/operations/build'; import { appDeploy } from '@/cli/operations/deploy'; +import { appPublish } from '@/cli/operations/publish'; import { ConfigService } from '@/cli/utilities/config/config-service'; import { CURRENT_EXECUTION_DIRECTORY } from '@/cli/utilities/config/current-execution-directory'; import { readJson } from '@/cli/utilities/file/fs-utils'; @@ -11,10 +12,42 @@ import chalk from 'chalk'; export type DeployCommandOptions = { appPath?: string; remote?: string; + private?: boolean; + tag?: string; }; export class DeployCommand { async execute(options: DeployCommandOptions): Promise { + if (options.private) { + await this.executePrivate(options); + } else { + await this.executeNpm(options); + } + } + + private async executeNpm(options: DeployCommandOptions): Promise { + const appPath = options.appPath ?? CURRENT_EXECUTION_DIRECTORY; + + await checkSdkVersionCompatibility(appPath); + + console.log(chalk.blue('Publishing to npm...')); + console.log(chalk.gray(`App path: ${appPath}\n`)); + + const result = await appPublish({ + appPath, + npmTag: options.tag, + onProgress: (message) => console.log(chalk.gray(message)), + }); + + if (!result.success) { + console.error(chalk.red(result.error.message)); + process.exit(1); + } + + console.log(chalk.green('✓ Published to npm successfully')); + } + + private async executePrivate(options: DeployCommandOptions): Promise { const appPath = options.appPath ?? CURRENT_EXECUTION_DIRECTORY; await checkSdkVersionCompatibility(appPath); @@ -57,6 +90,6 @@ export class DeployCommand { console.log( chalk.green(`\n✓ Published ${appName} v${appVersion} to ${remoteName}\n`), ); - console.log(' To install deployed application: `yarn twenty install`'); + console.log(' To install deployed application: `yarn twenty app:install`'); } } diff --git a/packages/twenty-sdk/src/cli/commands/app/index.ts b/packages/twenty-sdk/src/cli/commands/app/index.ts new file mode 100644 index 00000000000..3fb6664f621 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/app/index.ts @@ -0,0 +1,56 @@ +import { formatPath } from '@/cli/utilities/file/file-path'; +import type { Command } from 'commander'; +import { DeployCommand } from './deploy'; +import { AppInstallCommand } from './install'; +import { AppUninstallCommand } from './uninstall'; + +export const registerAppCommands = (program: Command): void => { + const deployCommand = new DeployCommand(); + const installCommand = new AppInstallCommand(); + const uninstallCommand = new AppUninstallCommand(); + + program + .command('app:publish [appPath]') + .description('Build and publish to npm (default) or server registry') + .option('--private', "Push to a Twenty server's registry instead of npm") + .option( + '-r, --remote ', + 'Publish to a specific remote (with --private)', + ) + .option('--tag ', 'npm dist-tag (e.g. beta, next)') + .action(async (appPath, options) => { + await deployCommand.execute({ + appPath: formatPath(appPath), + private: options.private, + remote: options.remote, + tag: options.tag, + }); + }); + + program + .command('app:install [appPath]') + .description('Install a deployed app on the server') + .option('-r, --remote ', 'Install on a specific remote') + .action(async (appPath, options) => { + await installCommand.execute({ + appPath: formatPath(appPath), + remote: options.remote, + }); + }); + + program + .command('app:uninstall [appPath]') + .description('Uninstall app from server') + .option('-y, --yes', 'Skip confirmation prompt') + .action(async (appPath?: string, options?: { yes?: boolean }) => { + try { + const result = await uninstallCommand.execute({ + appPath: formatPath(appPath), + askForConfirmation: !options?.yes, + }); + process.exit(result.success ? 0 : 1); + } catch { + process.exit(1); + } + }); +}; diff --git a/packages/twenty-sdk/src/cli/commands/install.ts b/packages/twenty-sdk/src/cli/commands/app/install.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/install.ts rename to packages/twenty-sdk/src/cli/commands/app/install.ts diff --git a/packages/twenty-sdk/src/cli/commands/publish.ts b/packages/twenty-sdk/src/cli/commands/app/publish.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/publish.ts rename to packages/twenty-sdk/src/cli/commands/app/publish.ts diff --git a/packages/twenty-sdk/src/cli/commands/uninstall.ts b/packages/twenty-sdk/src/cli/commands/app/uninstall.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/uninstall.ts rename to packages/twenty-sdk/src/cli/commands/app/uninstall.ts diff --git a/packages/twenty-sdk/src/cli/commands/deprecated.ts b/packages/twenty-sdk/src/cli/commands/deprecated.ts new file mode 100644 index 00000000000..12f64aceb18 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/deprecated.ts @@ -0,0 +1,200 @@ +import { formatPath } from '@/cli/utilities/file/file-path'; +import chalk from 'chalk'; +import type { Command } from 'commander'; +import type { SyncableEntity } from 'twenty-shared/application'; +import { EntityAddCommand } from './dev/add'; +import { AppBuildCommand } from './dev/build'; +import { DeployCommand } from './app/deploy'; +import { LogicFunctionExecuteCommand } from './dev/exec'; +import { AppInstallCommand } from './app/install'; +import { LogicFunctionLogsCommand } from './dev/logs'; +import { AppTypecheckCommand } from './dev/typecheck'; +import { AppUninstallCommand } from './app/uninstall'; + +const deprecate = (oldCmd: string, newCmd: string) => + console.warn( + chalk.yellow( + `⚠ \`twenty ${oldCmd}\` is deprecated. Use \`twenty ${newCmd}\` instead.`, + ), + ); + +export const registerDeprecatedCommands = (program: Command): void => { + const buildCommand = new AppBuildCommand(); + const typecheckCommand = new AppTypecheckCommand(); + const logsCommand = new LogicFunctionLogsCommand(); + const executeCommand = new LogicFunctionExecuteCommand(); + const addCommand = new EntityAddCommand(); + const deployCommand = new DeployCommand(); + const installCommand = new AppInstallCommand(); + const uninstallCommand = new AppUninstallCommand(); + + program + .command('build [appPath]', { hidden: true }) + .option('--tarball', 'Also pack into a .tgz tarball') + .action( + async (appPath: string | undefined, options: { tarball?: boolean }) => { + deprecate('build', 'dev:build'); + await buildCommand.execute({ + appPath: formatPath(appPath), + tarball: options.tarball, + }); + }, + ); + + program + .command('typecheck [appPath]', { hidden: true }) + .action(async (appPath: string | undefined) => { + deprecate('typecheck', 'dev:typecheck'); + await typecheckCommand.execute({ + appPath: formatPath(appPath), + }); + }); + + program + .command('logs [appPath]', { hidden: true }) + .option( + '-u, --functionUniversalIdentifier ', + 'Only show logs for the function with this universal ID', + ) + .option( + '-n, --functionName ', + 'Only show logs for the function with this name', + ) + .action( + async ( + appPath?: string, + options?: { + functionUniversalIdentifier?: string; + functionName?: string; + }, + ) => { + deprecate('logs', 'dev:function:logs'); + await logsCommand.execute({ + ...options, + appPath: formatPath(appPath), + }); + }, + ); + + program + .command('exec [appPath]', { hidden: true }) + .option('--postInstall', 'Execute post-install logic function if defined') + .option('--preInstall', 'Execute pre-install logic function if defined') + .option( + '-p, --payload ', + 'JSON payload to send to the function', + '{}', + ) + .option( + '-u, --functionUniversalIdentifier ', + 'Universal ID of the function to execute', + ) + .option( + '-n, --functionName ', + 'Name of the function to execute', + ) + .action( + async ( + appPath?: string, + options?: { + postInstall?: boolean; + preInstall?: boolean; + payload?: string; + functionUniversalIdentifier?: string; + functionName?: string; + }, + ) => { + deprecate('exec', 'dev:function:exec'); + if ( + !options?.postInstall && + !options?.preInstall && + !options?.functionUniversalIdentifier && + !options?.functionName + ) { + console.error( + chalk.red( + 'Error: Either --postInstall, --preInstall, --functionName (-n), or --functionUniversalIdentifier (-u) is required.', + ), + ); + process.exit(1); + } + await executeCommand.execute({ + ...options, + payload: options?.payload ?? '{}', + appPath: formatPath(appPath), + }); + }, + ); + + program + .command('add [entityType]', { hidden: true }) + .option('--path ', 'Path in which the entity should be created.') + .action(async (entityType?: string, options?: { path?: string }) => { + deprecate('add', 'dev:add'); + await addCommand.execute(entityType as SyncableEntity, options?.path); + }); + + program + .command('publish [appPath]', { hidden: true }) + .option('--tag ', 'npm dist-tag (e.g. beta, next)') + .action(async (appPath: string | undefined, options: { tag?: string }) => { + deprecate('publish', 'app:publish'); + await deployCommand.execute({ + appPath: formatPath(appPath), + tag: options.tag, + }); + }); + + program + .command('deploy [appPath]', { hidden: true }) + .option('-r, --remote ', 'Deploy to a specific remote') + .action( + async (appPath: string | undefined, options: { remote?: string }) => { + deprecate('deploy', 'app:publish --private'); + await deployCommand.execute({ + appPath: formatPath(appPath), + private: true, + remote: options.remote, + }); + }, + ); + + program + .command('install [appPath]', { hidden: true }) + .option('-r, --remote ', 'Install on a specific remote') + .action( + async (appPath: string | undefined, options: { remote?: string }) => { + deprecate('install', 'app:install'); + await installCommand.execute({ + appPath: formatPath(appPath), + remote: options.remote, + }); + }, + ); + + program + .command('uninstall [appPath]', { hidden: true }) + .option('-y, --yes', 'Skip confirmation prompt') + .action(async (appPath?: string, options?: { yes?: boolean }) => { + deprecate('uninstall', 'app:uninstall'); + try { + const result = await uninstallCommand.execute({ + appPath: formatPath(appPath), + askForConfirmation: !options?.yes, + }); + process.exit(result.success ? 0 : 1); + } catch { + process.exit(1); + } + }); + + program + .command('catalog-sync', { hidden: true }) + .option('-r, --remote ', 'Sync on a specific remote') + .action(async (options: { remote?: string }) => { + deprecate('catalog-sync', 'dev:catalog-sync'); + const { CatalogSyncCommand } = await import('./dev/catalog-sync'); + const cmd = new CatalogSyncCommand(); + await cmd.execute({ remote: options.remote }); + }); +}; diff --git a/packages/twenty-sdk/src/cli/commands/add.ts b/packages/twenty-sdk/src/cli/commands/dev/add.ts similarity index 99% rename from packages/twenty-sdk/src/cli/commands/add.ts rename to packages/twenty-sdk/src/cli/commands/dev/add.ts index 2b2feaafacc..0646bb7be02 100644 --- a/packages/twenty-sdk/src/cli/commands/add.ts +++ b/packages/twenty-sdk/src/cli/commands/dev/add.ts @@ -412,7 +412,7 @@ export class EntityAddCommand { // Connection providers reference two serverVariables (`_CLIENT_ID` // and `_CLIENT_SECRET`) that the dev needs to declare on // `defineApplication.serverVariables`. Auto-append them so the dev - // doesn't have to remember the wiring after `twenty add connection-provider`. + // doesn't have to remember the wiring after `twenty dev:add connection-provider`. // The util is best-effort: it handles the common file shapes and falls // back to a printed snippet for anything it can't safely modify. private async registerConnectionProviderServerVariables( diff --git a/packages/twenty-sdk/src/cli/commands/build.ts b/packages/twenty-sdk/src/cli/commands/dev/build.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/build.ts rename to packages/twenty-sdk/src/cli/commands/dev/build.ts diff --git a/packages/twenty-sdk/src/cli/commands/catalog-sync.ts b/packages/twenty-sdk/src/cli/commands/dev/catalog-sync.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/catalog-sync.ts rename to packages/twenty-sdk/src/cli/commands/dev/catalog-sync.ts diff --git a/packages/twenty-sdk/src/cli/commands/dev-once.ts b/packages/twenty-sdk/src/cli/commands/dev/dev-once.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/dev-once.ts rename to packages/twenty-sdk/src/cli/commands/dev/dev-once.ts diff --git a/packages/twenty-sdk/src/cli/commands/dev.ts b/packages/twenty-sdk/src/cli/commands/dev/dev.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/dev.ts rename to packages/twenty-sdk/src/cli/commands/dev/dev.ts diff --git a/packages/twenty-sdk/src/cli/commands/exec.ts b/packages/twenty-sdk/src/cli/commands/dev/exec.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/exec.ts rename to packages/twenty-sdk/src/cli/commands/dev/exec.ts diff --git a/packages/twenty-sdk/src/cli/commands/dev/function/index.ts b/packages/twenty-sdk/src/cli/commands/dev/function/index.ts new file mode 100644 index 00000000000..37d0ef03474 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/dev/function/index.ts @@ -0,0 +1,86 @@ +import { formatPath } from '@/cli/utilities/file/file-path'; +import chalk from 'chalk'; +import type { Command } from 'commander'; +import { LogicFunctionExecuteCommand } from '../exec'; +import { LogicFunctionLogsCommand } from '../logs'; + +export const registerDevFunctionCommands = (program: Command): void => { + const logsCommand = new LogicFunctionLogsCommand(); + const executeCommand = new LogicFunctionExecuteCommand(); + + program + .command('dev:function:logs [appPath]') + .description('Stream logic function logs') + .option( + '-u, --functionUniversalIdentifier ', + 'Only show logs for the function with this universal ID', + ) + .option( + '-n, --functionName ', + 'Only show logs for the function with this name', + ) + .action( + async ( + appPath?: string, + options?: { + functionUniversalIdentifier?: string; + functionName?: string; + }, + ) => { + await logsCommand.execute({ + ...options, + appPath: formatPath(appPath), + }); + }, + ); + + program + .command('dev:function:exec [appPath]') + .description('Execute a logic function') + .option('--postInstall', 'Execute post-install logic function if defined') + .option('--preInstall', 'Execute pre-install logic function if defined') + .option( + '-p, --payload ', + 'JSON payload to send to the function', + '{}', + ) + .option( + '-u, --functionUniversalIdentifier ', + 'Universal ID of the function to execute', + ) + .option( + '-n, --functionName ', + 'Name of the function to execute', + ) + .action( + async ( + appPath?: string, + options?: { + postInstall?: boolean; + preInstall?: boolean; + payload?: string; + functionUniversalIdentifier?: string; + functionName?: string; + }, + ) => { + if ( + !options?.postInstall && + !options?.preInstall && + !options?.functionUniversalIdentifier && + !options?.functionName + ) { + console.error( + chalk.red( + 'Error: Either --postInstall, --preInstall, --functionName (-n), or --functionUniversalIdentifier (-u) is required.', + ), + ); + process.exit(1); + } + await executeCommand.execute({ + ...options, + payload: options?.payload ?? '{}', + appPath: formatPath(appPath), + }); + }, + ); +}; diff --git a/packages/twenty-sdk/src/cli/commands/dev/index.ts b/packages/twenty-sdk/src/cli/commands/dev/index.ts new file mode 100644 index 00000000000..4acf4137246 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/dev/index.ts @@ -0,0 +1,97 @@ +import { formatPath } from '@/cli/utilities/file/file-path'; +import type { Command } from 'commander'; +import { SyncableEntity } from 'twenty-shared/application'; +import { EntityAddCommand } from './add'; +import { AppBuildCommand } from './build'; +import { AppDevCommand } from './dev'; +import { AppDevOnceCommand } from './dev-once'; +import { AppTypecheckCommand } from './typecheck'; +import { registerDevFunctionCommands } from './function'; + +export const registerDevCommands = (program: Command): void => { + const buildCommand = new AppBuildCommand(); + const devCommand = new AppDevCommand(); + const devOnceCommand = new AppDevOnceCommand(); + const typecheckCommand = new AppTypecheckCommand(); + const addCommand = new EntityAddCommand(); + + const devAction = async ( + appPath: string | undefined, + options: { + once?: boolean; + verbose?: boolean; + debug?: boolean; + debounceMs?: string; + }, + ) => { + const commonOptions = { + appPath: formatPath(appPath), + verbose: options.verbose || options.debug, + debounceMs: options.debounceMs + ? parseInt(options.debounceMs, 10) + : undefined, + }; + + if (options.once) { + await devOnceCommand.execute(commonOptions); + + return; + } + + await devCommand.execute(commonOptions); + }; + + program + .command('dev [appPath]', { isDefault: true }) + .description('Build and sync local changes (default command)') + .option( + '-o, --once', + 'Build and sync once, then exit (useful for CI, scripts, and pre-commit hooks)', + ) + .option('--debounceMs ', 'Debounce in ms (default: 2 000)') + .option('-v, --verbose', 'Show detailed logs') + .option('-d, --debug', 'Show detailed logs (alias for --verbose)') + .action(devAction); + + program + .command('dev:build [appPath]') + .description('Build and generate API client') + .option('--tarball', 'Also pack into a .tgz tarball') + .action(async (appPath, options) => { + await buildCommand.execute({ + appPath: formatPath(appPath), + tarball: options.tarball, + }); + }); + + program + .command('dev:typecheck [appPath]') + .description('Run TypeScript type checking') + .action(async (appPath) => { + await typecheckCommand.execute({ + appPath: formatPath(appPath), + }); + }); + + program + .command('dev:add [entityType]') + .description( + `Scaffold a new entity (${Object.values(SyncableEntity).join('|')})`, + ) + .option('--path ', 'Path in which the entity should be created.') + .action(async (entityType?: string, options?: { path?: string }) => { + await addCommand.execute(entityType as SyncableEntity, options?.path); + }); + + program + .command('dev:catalog-sync') + .description('Trigger marketplace catalog sync') + .option('-r, --remote ', 'Sync on a specific remote') + .action(async (options: { remote?: string }) => { + const { CatalogSyncCommand } = await import('./catalog-sync'); + const cmd = new CatalogSyncCommand(); + await cmd.execute({ remote: options.remote }); + }); + + registerDevFunctionCommands(program); +}; diff --git a/packages/twenty-sdk/src/cli/commands/logs.ts b/packages/twenty-sdk/src/cli/commands/dev/logs.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/logs.ts rename to packages/twenty-sdk/src/cli/commands/dev/logs.ts diff --git a/packages/twenty-sdk/src/cli/commands/typecheck.ts b/packages/twenty-sdk/src/cli/commands/dev/typecheck.ts similarity index 100% rename from packages/twenty-sdk/src/cli/commands/typecheck.ts rename to packages/twenty-sdk/src/cli/commands/dev/typecheck.ts diff --git a/packages/twenty-sdk/src/cli/commands/docker/index.ts b/packages/twenty-sdk/src/cli/commands/docker/index.ts new file mode 100644 index 00000000000..32b785f0d56 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/docker/index.ts @@ -0,0 +1,288 @@ +import { serverStart } from '@/cli/operations/server-start'; +import { serverUpgrade } from '@/cli/operations/server-upgrade'; +import { + CONTAINER_NAME, + containerExists, + DEFAULT_PORT, + DEFAULT_TEST_PORT, + getContainerEnvVar, + getContainerPort, + isContainerRunning, + TEST_CONTAINER_NAME, +} from '@/cli/utilities/server/docker-container'; +import { checkServerHealth } from '@/cli/utilities/server/detect-local-server'; +import chalk from 'chalk'; +import type { Command } from 'commander'; +import { execSync, spawnSync } from 'node:child_process'; + +const startAction = async (options: { port?: string; test?: boolean }) => { + const defaultPort = options.test ? DEFAULT_TEST_PORT : DEFAULT_PORT; + const port = options.port ? parseInt(options.port, 10) : defaultPort; + + if (isNaN(port) || port < 1 || port > 65535) { + console.error(chalk.red('Invalid port number.')); + process.exit(1); + } + + const result = await serverStart({ + port, + test: options.test, + onProgress: (message) => console.log(chalk.gray(message)), + }); + + if (!result.success) { + console.error(chalk.red(result.error.message)); + process.exit(1); + } +}; + +const stopAction = (options: { test?: boolean }) => { + const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME; + + if (!containerExists(containerName)) { + console.log(chalk.yellow('No Twenty server container found.')); + + return; + } + + execSync(`docker stop ${containerName}`, { stdio: 'ignore' }); + console.log(chalk.green('Twenty server stopped.')); +}; + +const logsAction = (options: { lines: string; test?: boolean }) => { + const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME; + + if (!containerExists(containerName)) { + console.log(chalk.yellow('No Twenty server container found.')); + + return; + } + + try { + spawnSync( + 'docker', + ['logs', '-f', '--tail', options.lines, containerName], + { stdio: 'inherit' }, + ); + } catch { + // User hit Ctrl-C + } +}; + +const statusAction = async (options: { test?: boolean }) => { + const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME; + const defaultPort = options.test ? DEFAULT_TEST_PORT : DEFAULT_PORT; + + if (!containerExists(containerName)) { + console.log(` Status: ${chalk.gray('not created')}`); + console.log( + chalk.gray( + ` Run 'yarn twenty docker:start${options.test ? ' --test' : ''}' to create one.`, + ), + ); + + return; + } + + const running = isContainerRunning(containerName); + const port = running ? getContainerPort(containerName) : defaultPort; + const healthy = running ? await checkServerHealth(port) : false; + + const statusText = healthy + ? chalk.green('running (healthy)') + : running + ? chalk.yellow('running (starting...)') + : chalk.gray('stopped'); + + const appVersion = getContainerEnvVar('APP_VERSION', containerName); + + console.log(` Status: ${statusText}`); + console.log(` URL: http://localhost:${port}`); + + if (appVersion) { + console.log(` Version: ${chalk.gray(appVersion)}`); + } + + if (healthy) { + console.log(chalk.gray(' Login: tim@apple.dev / tim@apple.dev')); + } +}; + +const resetAction = (options: { test?: boolean }) => { + const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME; + const volumeData = options.test + ? 'twenty-app-dev-test-data' + : 'twenty-app-dev-data'; + const volumeStorage = options.test + ? 'twenty-app-dev-test-storage' + : 'twenty-app-dev-storage'; + + if (containerExists(containerName)) { + execSync(`docker rm -f ${containerName}`, { stdio: 'ignore' }); + } + + try { + execSync(`docker volume rm ${volumeData} ${volumeStorage}`, { + stdio: 'ignore', + }); + } catch { + // Volumes may not exist + } + + console.log(chalk.green('Twenty server data reset.')); + console.log( + chalk.gray( + `Run 'yarn twenty docker:start${options.test ? ' --test' : ''}' to start a fresh instance.`, + ), + ); +}; + +const upgradeAction = async ( + version: string | undefined, + options: { test?: boolean }, +) => { + const result = await serverUpgrade({ + version: version ?? 'latest', + test: options.test, + onProgress: (message) => console.log(chalk.gray(message)), + }); + + if (!result.success) { + console.error(chalk.red(result.error.message)); + process.exit(1); + } + + const { data } = result; + + if (!data.imageUpdated) { + console.log(chalk.green(` Already up to date (${data.image}).`)); + + return; + } + + console.log(chalk.green(` Upgraded to: ${data.image}`)); + + if (data.containerRecreated) { + console.log( + chalk.gray( + ` Run 'yarn twenty docker:start${options.test ? ' --test' : ''}' to wait for the server to be ready.`, + ), + ); + } +}; + +export const registerServerCommands = (program: Command): void => { + program + .command('docker:start') + .description('Start the local Twenty container') + .option('-p, --port ', 'HTTP port') + .option('--test', 'Start a separate test instance (port 2021)') + .action(startAction); + + program + .command('docker:stop') + .description('Stop the local Twenty container') + .option('--test', 'Stop the test instance') + .action(stopAction); + + program + .command('docker:logs') + .description('Stream container logs') + .option('-n, --lines ', 'Number of lines to show', '50') + .option('--test', 'Show logs for the test instance') + .action(logsAction); + + program + .command('docker:status') + .description('Show container status') + .option('--test', 'Show status of the test instance') + .action(statusAction); + + program + .command('docker:reset') + .description('Delete all data and start fresh') + .option('--test', 'Reset the test instance') + .action(resetAction); + + program + .command('docker:upgrade [version]') + .description('Upgrade the Docker image') + .option('--test', 'Upgrade the test instance') + .action(upgradeAction); + + // Deprecated: `server ` forwarding to `docker:` + const server = program + .command('server', { hidden: true }) + .description( + 'Manage a Twenty server (local instance and server-side actions)', + ); + + const deprecate = (oldCmd: string, newCmd: string) => + console.warn( + chalk.yellow( + `⚠ \`twenty server ${oldCmd}\` is deprecated. Use \`twenty ${newCmd}\` instead.`, + ), + ); + + server + .command('start') + .option('-p, --port ', 'HTTP port') + .option('--test', 'Start a separate test instance (port 2021)') + .action(async (options: { port?: string; test?: boolean }) => { + deprecate('start', 'docker:start'); + await startAction(options); + }); + + server + .command('stop') + .option('--test', 'Stop the test instance') + .action((options: { test?: boolean }) => { + deprecate('stop', 'docker:stop'); + stopAction(options); + }); + + server + .command('logs') + .option('-n, --lines ', 'Number of lines to show', '50') + .option('--test', 'Show logs for the test instance') + .action((options: { lines: string; test?: boolean }) => { + deprecate('logs', 'docker:logs'); + logsAction(options); + }); + + server + .command('status') + .option('--test', 'Show status of the test instance') + .action(async (options: { test?: boolean }) => { + deprecate('status', 'docker:status'); + await statusAction(options); + }); + + server + .command('reset') + .option('--test', 'Reset the test instance') + .action((options: { test?: boolean }) => { + deprecate('reset', 'docker:reset'); + resetAction(options); + }); + + server + .command('upgrade [version]') + .option('--test', 'Upgrade the test instance') + .action( + async (version: string | undefined, options: { test?: boolean }) => { + deprecate('upgrade', 'docker:upgrade'); + await upgradeAction(version, options); + }, + ); + + server + .command('catalog-sync') + .option('-r, --remote ', 'Sync on a specific remote') + .action(async (options: { remote?: string }) => { + deprecate('catalog-sync', 'dev:catalog-sync'); + const { CatalogSyncCommand } = await import('../dev/catalog-sync'); + const cmd = new CatalogSyncCommand(); + await cmd.execute({ remote: options.remote }); + }); +}; diff --git a/packages/twenty-sdk/src/cli/commands/index.ts b/packages/twenty-sdk/src/cli/commands/index.ts new file mode 100644 index 00000000000..04ff2f1a6ce --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/index.ts @@ -0,0 +1,14 @@ +import type { Command } from 'commander'; +import { registerAppCommands } from './app'; +import { registerDeprecatedCommands } from './deprecated'; +import { registerDevCommands } from './dev'; +import { registerServerCommands } from './docker'; +import { registerRemoteCommands } from './remote'; + +export const registerCommands = (program: Command): void => { + registerDevCommands(program); + registerAppCommands(program); + registerServerCommands(program); + registerRemoteCommands(program); + registerDeprecatedCommands(program); +}; diff --git a/packages/twenty-sdk/src/cli/commands/remote.ts b/packages/twenty-sdk/src/cli/commands/remote.ts deleted file mode 100644 index 1c5e177b09c..00000000000 --- a/packages/twenty-sdk/src/cli/commands/remote.ts +++ /dev/null @@ -1,298 +0,0 @@ -import { authLogin } from '@/cli/operations/login'; -import { authLoginOAuth } from '@/cli/operations/login-oauth'; -import { ApiService } from '@/cli/utilities/api/api-service'; -import { ConfigService } from '@/cli/utilities/config/config-service'; -import { getConfigPath } from '@/cli/utilities/config/get-config-path'; -import { detectLocalServer } from '@/cli/utilities/server/detect-local-server'; -import chalk from 'chalk'; -import type { Command } from 'commander'; -import inquirer from 'inquirer'; -import { normalizeUrl } from 'twenty-shared/utils'; - -const deriveRemoteName = (url: string): string => { - try { - const hostname = new URL(url).hostname; - - return hostname.replace(/\./g, '-'); - } catch { - return 'remote'; - } -}; - -type AuthMethod = 'OAuth' | 'API key'; - -const authenticate = async ( - apiUrl: string, - apiKey?: string, -): Promise => { - if (apiKey) { - const result = await authLogin({ apiKey, apiUrl }); - - if (!result.success) { - console.error(chalk.red('✗ Authentication failed.')); - process.exit(1); - } - - return 'API key'; - } - - return runOAuthWithApiKeyFallback(apiUrl); -}; - -const runOAuthWithApiKeyFallback = async ( - apiUrl: string, -): Promise => { - console.log(chalk.gray('Opening browser for authentication...')); - - const oauthResult = await authLoginOAuth({ apiUrl }); - - if (oauthResult.success) { - return 'OAuth'; - } - - console.log(chalk.yellow(oauthResult.error.message)); - - const keyAnswer = await inquirer.prompt([ - { - type: 'password', - name: 'apiKey', - message: 'API Key:', - mask: '*', - validate: (input: string) => input.length > 0 || 'API key is required', - }, - ]); - - const fallbackResult = await authLogin({ - apiKey: keyAnswer.apiKey, - apiUrl, - }); - - if (!fallbackResult.success) { - console.error(chalk.red('✗ Authentication failed.')); - process.exit(1); - } - - return 'API key'; -}; - -export const registerRemoteCommands = (program: Command): void => { - const remote = program - .command('remote') - .description('Manage remote Twenty servers'); - - remote - .command('add') - .description('Add a new remote or re-authenticate an existing one') - .option('--as ', 'Name for this remote') - .option('--api-key ', 'API key for non-interactive auth') - .option('--api-url ', 'Server URL') - .option('--local', 'Connect to a local Twenty server (auto-detect)') - .option('--test', 'Write to config.test.json (for integration tests)') - .action( - async (options: { - as?: string; - apiKey?: string; - apiUrl?: string; - local?: boolean; - test?: boolean; - }) => { - const configPath = options.test ? getConfigPath(true) : undefined; - const configService = new ConfigService( - configPath ? { configPath } : undefined, - ); - const existingRemotes = await configService.getRemotes(); - - if (options.as !== undefined && existingRemotes.includes(options.as)) { - const config = await configService.getConfigForRemote(options.as); - - ConfigService.setActiveRemote(options.as); - const method = await authenticate(config.apiUrl, options.apiKey); - - console.log( - chalk.green(`✓ Re-authenticated "${options.as}" via ${method}.`), - ); - - await configService.setDefaultRemote(options.as); - console.log(chalk.green(`✓ Default remote set to "${options.as}".`)); - - return; - } - - let apiUrl = options.apiUrl ? normalizeUrl(options.apiUrl) : undefined; - - if (!apiUrl) { - const detectedUrl = await detectLocalServer(); - - if (options.local) { - if (!detectedUrl) { - console.error( - chalk.red( - 'No local Twenty server found.\n' + - 'Start one with: yarn twenty server start', - ), - ); - process.exit(1); - } - - console.log(chalk.gray(`Found local server at ${detectedUrl}`)); - apiUrl = detectedUrl; - } else { - apiUrl = normalizeUrl( - ( - await inquirer.prompt<{ apiUrl: string }>([ - { - type: 'input', - name: 'apiUrl', - message: 'Twenty server URL:', - validate: (input: string) => { - try { - new URL(input); - - return true; - } catch { - return 'Please enter a valid URL'; - } - }, - }, - ]) - ).apiUrl, - ); - } - } - - const name = options.as ?? deriveRemoteName(apiUrl); - - ConfigService.setActiveRemote(name); - const method = await authenticate(apiUrl, options.apiKey); - - console.log( - chalk.green(`✓ Remote "${name}" added (${apiUrl}) via ${method}.`), - ); - - await configService.setDefaultRemote(name); - console.log(chalk.green(`✓ Default remote set to "${name}".`)); - }, - ); - - remote - .command('list') - .description('List all configured remotes') - .action(async () => { - const configService = new ConfigService(); - const remotes = await configService.getRemotes(); - const defaultRemote = await configService.getDefaultRemote(); - - if (remotes.length === 0) { - console.log('No remotes configured.'); - console.log("Use 'twenty remote add' to add one."); - - return; - } - - console.log(''); - - for (const remoteName of remotes) { - const config = await configService.getConfigForRemote(remoteName); - - const authMethod = config.twentyCLIAccessToken - ? 'oauth' - : config.apiKey - ? 'api-key' - : 'none'; - - const isDefault = remoteName === defaultRemote; - const marker = isDefault ? '* ' : ' '; - const nameText = isDefault ? chalk.bold(remoteName) : remoteName; - - console.log( - `${marker}${nameText} ${chalk.gray(config.apiUrl)} [${authMethod}]`, - ); - } - - console.log( - '\n', - chalk.gray("Use 'twenty remote switch ' to change default"), - ); - }); - - remote - .command('switch [name]') - .description('Set the default remote') - .action(async (nameArg?: string) => { - const configService = new ConfigService(); - - const remoteName = - nameArg ?? - ( - await inquirer.prompt<{ remote: string }>([ - { - type: 'list', - name: 'remote', - message: 'Select default remote:', - choices: await configService.getRemotes(), - }, - ]) - ).remote; - - const remotes = await configService.getRemotes(); - - if (!remotes.includes(remoteName)) { - console.error(chalk.red(`Remote "${remoteName}" not found.`)); - process.exit(1); - } - - await configService.setDefaultRemote(remoteName); - console.log(chalk.green(`✓ Default remote set to "${remoteName}".`)); - }); - - remote - .command('status') - .description('Show active remote and authentication status') - .action(async () => { - const configService = new ConfigService(); - const apiService = new ApiService(); - const activeRemote = ConfigService.getActiveRemote(); - const config = await configService.getConfig(); - - const authMethod = config.twentyCLIAccessToken - ? 'oauth' - : config.apiKey - ? 'api-key' - : 'none'; - - console.log(` Remote: ${chalk.bold(activeRemote)}`); - console.log(` Server: ${config.apiUrl}`); - - if (authMethod === 'none') { - console.log(` Auth: ${chalk.yellow('not configured')}`); - - return; - } - - const { authValid } = await apiService.validateAuth(); - - const statusText = authValid - ? chalk.green(`${authMethod} (valid)`) - : chalk.red(`${authMethod} (invalid)`); - - console.log(` Auth: ${statusText}`); - }); - - remote - .command('remove ') - .description('Remove a remote') - .action(async (name: string) => { - const configService = new ConfigService(); - const remotes = await configService.getRemotes(); - - if (!remotes.includes(name)) { - console.error(chalk.red(`Remote "${name}" not found.`)); - process.exit(1); - } - - ConfigService.setActiveRemote(name); - await configService.clearConfig(); - - console.log(chalk.green(`✓ Remote "${name}" removed.`)); - }); -}; diff --git a/packages/twenty-sdk/src/cli/commands/remote/index.ts b/packages/twenty-sdk/src/cli/commands/remote/index.ts new file mode 100644 index 00000000000..f67f25c9159 --- /dev/null +++ b/packages/twenty-sdk/src/cli/commands/remote/index.ts @@ -0,0 +1,365 @@ +import { authLogin } from '@/cli/operations/login'; +import { authLoginOAuth } from '@/cli/operations/login-oauth'; +import { ApiService } from '@/cli/utilities/api/api-service'; +import { ConfigService } from '@/cli/utilities/config/config-service'; +import { getConfigPath } from '@/cli/utilities/config/get-config-path'; +import { detectLocalServer } from '@/cli/utilities/server/detect-local-server'; +import chalk from 'chalk'; +import type { Command } from 'commander'; +import inquirer from 'inquirer'; +import { normalizeUrl } from 'twenty-shared/utils'; + +const deriveRemoteName = (url: string): string => { + try { + const hostname = new URL(url).hostname; + + return hostname.replace(/\./g, '-'); + } catch { + return 'remote'; + } +}; + +type AuthMethod = 'OAuth' | 'API key'; + +const authenticate = async ( + apiUrl: string, + apiKey?: string, +): Promise => { + if (apiKey) { + const result = await authLogin({ apiKey, apiUrl }); + + if (!result.success) { + console.error(chalk.red('✗ Authentication failed.')); + process.exit(1); + } + + return 'API key'; + } + + return runOAuthWithApiKeyFallback(apiUrl); +}; + +const runOAuthWithApiKeyFallback = async ( + apiUrl: string, +): Promise => { + console.log(chalk.gray('Opening browser for authentication...')); + + const oauthResult = await authLoginOAuth({ apiUrl }); + + if (oauthResult.success) { + return 'OAuth'; + } + + console.log(chalk.yellow(oauthResult.error.message)); + + const keyAnswer = await inquirer.prompt([ + { + type: 'password', + name: 'apiKey', + message: 'API Key:', + mask: '*', + validate: (input: string) => input.length > 0 || 'API key is required', + }, + ]); + + const fallbackResult = await authLogin({ + apiKey: keyAnswer.apiKey, + apiUrl, + }); + + if (!fallbackResult.success) { + console.error(chalk.red('✗ Authentication failed.')); + process.exit(1); + } + + return 'API key'; +}; + +const addAction = async (options: { + as?: string; + apiKey?: string; + url?: string; + apiUrl?: string; + local?: boolean; + test?: boolean; +}) => { + if (options.apiUrl) { + console.warn( + chalk.yellow('⚠ --api-url is deprecated. Use --url instead.'), + ); + } + const configPath = options.test ? getConfigPath(true) : undefined; + const configService = new ConfigService( + configPath ? { configPath } : undefined, + ); + const existingRemotes = await configService.getRemotes(); + + if (options.as !== undefined && existingRemotes.includes(options.as)) { + const config = await configService.getConfigForRemote(options.as); + + ConfigService.setActiveRemote(options.as); + const method = await authenticate(config.apiUrl, options.apiKey); + + console.log( + chalk.green(`✓ Re-authenticated "${options.as}" via ${method}.`), + ); + + await configService.setDefaultRemote(options.as); + console.log(chalk.green(`✓ Default remote set to "${options.as}".`)); + + return; + } + + let serverUrl = options.url ?? options.apiUrl; + + if (serverUrl) { + serverUrl = normalizeUrl(serverUrl); + } else { + const detectedUrl = await detectLocalServer(); + + if (options.local) { + if (!detectedUrl) { + console.error( + chalk.red( + 'No local Twenty server found.\n' + + 'Start one with: yarn twenty docker:start', + ), + ); + process.exit(1); + } + + console.log(chalk.gray(`Found local server at ${detectedUrl}`)); + serverUrl = detectedUrl; + } else { + serverUrl = normalizeUrl( + ( + await inquirer.prompt<{ serverUrl: string }>([ + { + type: 'input', + name: 'serverUrl', + message: 'Twenty server URL:', + validate: (input: string) => { + try { + new URL(input); + + return true; + } catch { + return 'Please enter a valid URL'; + } + }, + }, + ]) + ).serverUrl, + ); + } + } + + const name = options.as ?? deriveRemoteName(serverUrl); + + ConfigService.setActiveRemote(name); + const method = await authenticate(serverUrl, options.apiKey); + + console.log( + chalk.green(`✓ Remote "${name}" added (${serverUrl}) via ${method}.`), + ); + + await configService.setDefaultRemote(name); + console.log(chalk.green(`✓ Default remote set to "${name}".`)); +}; + +const listAction = async () => { + const configService = new ConfigService(); + const remotes = await configService.getRemotes(); + const defaultRemote = await configService.getDefaultRemote(); + + if (remotes.length === 0) { + console.log('No remotes configured.'); + console.log("Use 'twenty remote:add' to add one."); + + return; + } + + console.log(''); + + for (const remoteName of remotes) { + const config = await configService.getConfigForRemote(remoteName); + + const authMethod = config.twentyCLIAccessToken + ? 'oauth' + : config.apiKey + ? 'api-key' + : 'none'; + + const isDefault = remoteName === defaultRemote; + const marker = isDefault ? '* ' : ' '; + const nameText = isDefault ? chalk.bold(remoteName) : remoteName; + + console.log( + `${marker}${nameText} ${chalk.gray(config.apiUrl)} [${authMethod}]`, + ); + } + + console.log( + '\n', + chalk.gray("Use 'twenty remote:use ' to change default"), + ); +}; + +const useAction = async (nameArg?: string) => { + const configService = new ConfigService(); + + const remoteName = + nameArg ?? + ( + await inquirer.prompt<{ remote: string }>([ + { + type: 'list', + name: 'remote', + message: 'Select default remote:', + choices: await configService.getRemotes(), + }, + ]) + ).remote; + + const remotes = await configService.getRemotes(); + + if (!remotes.includes(remoteName)) { + console.error(chalk.red(`Remote "${remoteName}" not found.`)); + process.exit(1); + } + + await configService.setDefaultRemote(remoteName); + console.log(chalk.green(`✓ Default remote set to "${remoteName}".`)); +}; + +const statusAction = async () => { + const configService = new ConfigService(); + const apiService = new ApiService(); + const activeRemote = ConfigService.getActiveRemote(); + const config = await configService.getConfig(); + + const authMethod = config.twentyCLIAccessToken + ? 'oauth' + : config.apiKey + ? 'api-key' + : 'none'; + + console.log(` Remote: ${chalk.bold(activeRemote)}`); + console.log(` Server: ${config.apiUrl}`); + + if (authMethod === 'none') { + console.log(` Auth: ${chalk.yellow('not configured')}`); + + return; + } + + const { authValid } = await apiService.validateAuth(); + + const statusText = authValid + ? chalk.green(`${authMethod} (valid)`) + : chalk.red(`${authMethod} (invalid)`); + + console.log(` Auth: ${statusText}`); +}; + +const removeAction = async (name: string) => { + const configService = new ConfigService(); + const remotes = await configService.getRemotes(); + + if (!remotes.includes(name)) { + console.error(chalk.red(`Remote "${name}" not found.`)); + process.exit(1); + } + + ConfigService.setActiveRemote(name); + await configService.clearConfig(); + + console.log(chalk.green(`✓ Remote "${name}" removed.`)); +}; + +export const registerRemoteCommands = (program: Command): void => { + program + .command('remote:add') + .description('Add or re-authenticate a remote') + .option('--as ', 'Name for this remote') + .option('--api-key ', 'API key for non-interactive auth') + .option('--url ', 'Server URL') + .option('--api-url ', '[deprecated: use --url]') + .option('--local', 'Connect to a local Twenty server (auto-detect)') + .option('--test', 'Write to config.test.json (for integration tests)') + .action(addAction); + + program + .command('remote:list') + .description('List all configured remotes') + .action(listAction); + + program + .command('remote:use [name]') + .description('Set the default remote') + .action(useAction); + + program + .command('remote:status') + .description('Show active remote auth status') + .action(statusAction); + + program + .command('remote:remove ') + .description('Remove a remote') + .action(removeAction); + + // Deprecated: `remote ` forwarding to `remote:` + const remote = program + .command('remote', { hidden: true }) + .description('Manage remote Twenty servers'); + + const deprecate = (oldCmd: string, newCmd: string) => + console.warn( + chalk.yellow( + `⚠ \`twenty remote ${oldCmd}\` is deprecated. Use \`twenty ${newCmd}\` instead.`, + ), + ); + + remote + .command('add') + .option('--as ', 'Name for this remote') + .option('--api-key ', 'API key for non-interactive auth') + .option('--url ', 'Server URL') + .option('--api-url ', '[deprecated: use --url]') + .option('--local', 'Connect to a local Twenty server (auto-detect)') + .option('--test', 'Write to config.test.json (for integration tests)') + .action( + async (options: { + as?: string; + apiKey?: string; + url?: string; + apiUrl?: string; + local?: boolean; + test?: boolean; + }) => { + deprecate('add', 'remote:add'); + await addAction(options); + }, + ); + + remote.command('list').action(async () => { + deprecate('list', 'remote:list'); + await listAction(); + }); + + remote.command('switch [name]').action(async (name?: string) => { + deprecate('switch', 'remote:use'); + await useAction(name); + }); + + remote.command('status').action(async () => { + deprecate('status', 'remote:status'); + await statusAction(); + }); + + remote.command('remove ').action(async (name: string) => { + deprecate('remove', 'remote:remove'); + await removeAction(name); + }); +}; diff --git a/packages/twenty-sdk/src/cli/commands/server.ts b/packages/twenty-sdk/src/cli/commands/server.ts deleted file mode 100644 index d2f3fb03522..00000000000 --- a/packages/twenty-sdk/src/cli/commands/server.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { serverStart } from '@/cli/operations/server-start'; -import { serverUpgrade } from '@/cli/operations/server-upgrade'; -import { - CONTAINER_NAME, - containerExists, - DEFAULT_PORT, - DEFAULT_TEST_PORT, - getContainerEnvVar, - getContainerPort, - isContainerRunning, - TEST_CONTAINER_NAME, -} from '@/cli/utilities/server/docker-container'; -import { checkServerHealth } from '@/cli/utilities/server/detect-local-server'; -import chalk from 'chalk'; -import type { Command } from 'commander'; -import { execSync, spawnSync } from 'node:child_process'; -import { CatalogSyncCommand } from './catalog-sync'; - -export const registerServerCommands = (program: Command): void => { - const server = program - .command('server') - .description( - 'Manage a Twenty server (local instance and server-side actions)', - ); - - server - .command('start') - .description('Start a local Twenty server') - .option('-p, --port ', 'HTTP port') - .option('--test', 'Start a separate test instance (port 2021)') - .action(async (options: { port?: string; test?: boolean }) => { - const defaultPort = options.test ? DEFAULT_TEST_PORT : DEFAULT_PORT; - const port = options.port ? parseInt(options.port, 10) : defaultPort; - - if (isNaN(port) || port < 1 || port > 65535) { - console.error(chalk.red('Invalid port number.')); - process.exit(1); - } - - const result = await serverStart({ - port, - test: options.test, - onProgress: (message) => console.log(chalk.gray(message)), - }); - - if (!result.success) { - console.error(chalk.red(result.error.message)); - process.exit(1); - } - }); - - server - .command('stop') - .description('Stop the local Twenty server') - .option('--test', 'Stop the test instance') - .action((options: { test?: boolean }) => { - const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME; - - if (!containerExists(containerName)) { - console.log(chalk.yellow('No Twenty server container found.')); - - return; - } - - execSync(`docker stop ${containerName}`, { stdio: 'ignore' }); - console.log(chalk.green('Twenty server stopped.')); - }); - - server - .command('logs') - .description('Stream Twenty server logs') - .option('-n, --lines ', 'Number of lines to show', '50') - .option('--test', 'Show logs for the test instance') - .action((options: { lines: string; test?: boolean }) => { - const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME; - - if (!containerExists(containerName)) { - console.log(chalk.yellow('No Twenty server container found.')); - - return; - } - - try { - spawnSync( - 'docker', - ['logs', '-f', '--tail', options.lines, containerName], - { stdio: 'inherit' }, - ); - } catch { - // User hit Ctrl-C - } - }); - - server - .command('status') - .description('Show Twenty server status') - .option('--test', 'Show status of the test instance') - .action(async (options: { test?: boolean }) => { - const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME; - const defaultPort = options.test ? DEFAULT_TEST_PORT : DEFAULT_PORT; - - if (!containerExists(containerName)) { - console.log(` Status: ${chalk.gray('not created')}`); - console.log( - chalk.gray( - ` Run 'yarn twenty server start${options.test ? ' --test' : ''}' to create one.`, - ), - ); - - return; - } - - const running = isContainerRunning(containerName); - const port = running ? getContainerPort(containerName) : defaultPort; - const healthy = running ? await checkServerHealth(port) : false; - - const statusText = healthy - ? chalk.green('running (healthy)') - : running - ? chalk.yellow('running (starting...)') - : chalk.gray('stopped'); - - const appVersion = getContainerEnvVar('APP_VERSION', containerName); - - console.log(` Status: ${statusText}`); - console.log(` URL: http://localhost:${port}`); - - if (appVersion) { - console.log(` Version: ${chalk.gray(appVersion)}`); - } - - if (healthy) { - console.log(chalk.gray(' Login: tim@apple.dev / tim@apple.dev')); - } - }); - - server - .command('reset') - .description('Delete all data and start fresh') - .option('--test', 'Reset the test instance') - .action((options: { test?: boolean }) => { - const containerName = options.test ? TEST_CONTAINER_NAME : CONTAINER_NAME; - const volumeData = options.test - ? 'twenty-app-dev-test-data' - : 'twenty-app-dev-data'; - const volumeStorage = options.test - ? 'twenty-app-dev-test-storage' - : 'twenty-app-dev-storage'; - - if (containerExists(containerName)) { - execSync(`docker rm -f ${containerName}`, { stdio: 'ignore' }); - } - - try { - execSync(`docker volume rm ${volumeData} ${volumeStorage}`, { - stdio: 'ignore', - }); - } catch { - // Volumes may not exist - } - - console.log(chalk.green('Twenty server data reset.')); - console.log( - chalk.gray( - `Run 'yarn twenty server start${options.test ? ' --test' : ''}' to start a fresh instance.`, - ), - ); - }); - - server - .command('upgrade [version]') - .description('Upgrade the twenty-app-dev Docker image') - .option('--test', 'Upgrade the test instance') - .action( - async (version: string | undefined, options: { test?: boolean }) => { - const result = await serverUpgrade({ - version: version ?? 'latest', - test: options.test, - onProgress: (message) => console.log(chalk.gray(message)), - }); - - if (!result.success) { - console.error(chalk.red(result.error.message)); - process.exit(1); - } - - const { data } = result; - - if (!data.imageUpdated) { - console.log(chalk.green(` Already up to date (${data.image}).`)); - - return; - } - - console.log(chalk.green(` Upgraded to: ${data.image}`)); - - if (data.containerRecreated) { - console.log( - chalk.gray( - ` Run 'yarn twenty server start${options.test ? ' --test' : ''}' to wait for the server to be ready.`, - ), - ); - } - }, - ); - - server - .command('catalog-sync') - .description('Trigger a marketplace catalog sync on the server') - .option('-r, --remote ', 'Sync on a specific remote') - .action(async (options: { remote?: string }) => { - const command = new CatalogSyncCommand(); - await command.execute({ remote: options.remote }); - }); -}; diff --git a/packages/twenty-sdk/src/cli/operations/dev-once.ts b/packages/twenty-sdk/src/cli/operations/dev-once.ts index b75e994b88c..a308ac45448 100644 --- a/packages/twenty-sdk/src/cli/operations/dev-once.ts +++ b/packages/twenty-sdk/src/cli/operations/dev-once.ts @@ -50,9 +50,9 @@ const innerAppDevOnce = async ( message: 'Cannot reach Twenty server.\n\n' + ' Start a local server:\n' + - ' yarn twenty server start\n\n' + + ' yarn twenty docker:start\n\n' + ' Check server status:\n' + - ' yarn twenty server status', + ' yarn twenty docker:status', }, }; } @@ -63,7 +63,7 @@ const innerAppDevOnce = async ( error: { code: APP_ERROR_CODES.SYNC_FAILED, message: - 'Authentication failed. Run `yarn twenty remote add --local` to authenticate.', + 'Authentication failed. Run `yarn twenty remote:add --local` to authenticate.', }, }; } diff --git a/packages/twenty-sdk/src/cli/operations/server-start.ts b/packages/twenty-sdk/src/cli/operations/server-start.ts index cadf5d1b0d8..00b6733c349 100644 --- a/packages/twenty-sdk/src/cli/operations/server-start.ts +++ b/packages/twenty-sdk/src/cli/operations/server-start.ts @@ -151,8 +151,8 @@ const innerServerStart = async ( if (!checkDockerRunning()) { const retryCommand = isTest - ? 'yarn twenty server start --test' - : 'yarn twenty server start'; + ? 'yarn twenty docker:start --test' + : 'yarn twenty docker:start'; return { success: false, @@ -177,7 +177,7 @@ const innerServerStart = async ( code: SERVER_ERROR_CODES.HEALTH_TIMEOUT, message: 'Twenty server did not become healthy in time.\n' + - "Check: 'yarn twenty server logs'", + "Check: 'yarn twenty docker:logs'", }, }; } @@ -202,7 +202,7 @@ const innerServerStart = async ( if (existingPort !== port) { onProgress?.( - `Existing container uses port ${existingPort}. Run 'yarn twenty server reset${isTest ? ' --test' : ''}' first to change ports.`, + `Existing container uses port ${existingPort}. Run 'yarn twenty docker:reset${isTest ? ' --test' : ''}' first to change ports.`, ); } @@ -257,7 +257,7 @@ const innerServerStart = async ( code: SERVER_ERROR_CODES.HEALTH_TIMEOUT, message: 'Twenty server did not become healthy in time.\n' + - "Check: 'yarn twenty server logs'", + "Check: 'yarn twenty docker:logs'", }, }; } diff --git a/packages/twenty-sdk/src/cli/operations/server-upgrade.ts b/packages/twenty-sdk/src/cli/operations/server-upgrade.ts index 158fdcc29c6..32cc8ead12f 100644 --- a/packages/twenty-sdk/src/cli/operations/server-upgrade.ts +++ b/packages/twenty-sdk/src/cli/operations/server-upgrade.ts @@ -32,7 +32,7 @@ const innerServerUpgrade = async ( if (!checkDockerRunning()) { const retryCommand = [ - 'yarn twenty server upgrade', + 'yarn twenty docker:upgrade', version !== 'latest' ? version : null, isTest ? '--test' : null, ] diff --git a/packages/twenty-sdk/src/cli/utilities/api/api-client.ts b/packages/twenty-sdk/src/cli/utilities/api/api-client.ts index 5f769862ace..6a22d647987 100644 --- a/packages/twenty-sdk/src/cli/utilities/api/api-client.ts +++ b/packages/twenty-sdk/src/cli/utilities/api/api-client.ts @@ -51,7 +51,7 @@ export class ApiClient { if (error.response?.status === 401) { console.error( chalk.red( - 'Authentication failed. Run `yarn twenty remote add` to authenticate.', + 'Authentication failed. Run `yarn twenty remote:add` to authenticate.', ), ); } else if (error.response?.status === 403) { diff --git a/packages/twenty-sdk/src/cli/utilities/config/config-service.ts b/packages/twenty-sdk/src/cli/utilities/config/config-service.ts index a02ab31aece..5dfada1ebac 100644 --- a/packages/twenty-sdk/src/cli/utilities/config/config-service.ts +++ b/packages/twenty-sdk/src/cli/utilities/config/config-service.ts @@ -8,7 +8,7 @@ import { getConfigPath } from '@/cli/utilities/config/get-config-path'; export type RemoteConfig = { apiUrl: string; apiKey?: string; - // CLI OAuth app credentials (from `yarn twenty remote add`) + // CLI OAuth app credentials (from `yarn twenty remote:add`) twentyCLIRegistrationId?: string; twentyCLIRegistrationClientId?: string; twentyCLIAccessToken?: string; diff --git a/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/steps/check-server-orchestrator-step.ts b/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/steps/check-server-orchestrator-step.ts index 655ee24020e..dd3bb9fe639 100644 --- a/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/steps/check-server-orchestrator-step.ts +++ b/packages/twenty-sdk/src/cli/utilities/dev/orchestrator/steps/check-server-orchestrator-step.ts @@ -53,9 +53,9 @@ export class CheckServerOrchestratorStep { message: 'Cannot reach Twenty server.\n\n' + ' Start a local server:\n' + - ' yarn twenty server start\n\n' + + ' yarn twenty docker:start\n\n' + ' Check server status:\n' + - ' yarn twenty server status\n\n' + + ' yarn twenty docker:status\n\n' + ' Waiting for server...', status: 'error', }, @@ -73,7 +73,7 @@ export class CheckServerOrchestratorStep { this.state.applyStepEvents([ { message: - 'Authentication failed. Run `yarn twenty remote add --local` to authenticate.', + 'Authentication failed. Run `yarn twenty remote:add --local` to authenticate.', status: 'error', }, ]); diff --git a/packages/twenty-sdk/src/cli/utilities/file/append-server-variables.util.ts b/packages/twenty-sdk/src/cli/utilities/file/append-server-variables.util.ts index bd9f0371290..3cbd6fff1ae 100644 --- a/packages/twenty-sdk/src/cli/utilities/file/append-server-variables.util.ts +++ b/packages/twenty-sdk/src/cli/utilities/file/append-server-variables.util.ts @@ -22,7 +22,7 @@ const DEFINE_APPLICATION_PATTERN = /defineApplication\s*\(\s*\{/; // Auto-appends OAuth client_id / client_secret entries to the dev's // `defineApplication({ serverVariables: { ... } })` block so they don't -// have to remember the wiring after `twenty add connection-provider`. +// have to remember the wiring after `twenty dev:add connection-provider`. // // Returns one of: // - { status: 'appended', file } — wrote new entries to an existing block diff --git a/packages/twenty-sdk/src/cli/utilities/version/check-server-version-compatibility.ts b/packages/twenty-sdk/src/cli/utilities/version/check-server-version-compatibility.ts index 95e64828cc6..ba3e58284ab 100644 --- a/packages/twenty-sdk/src/cli/utilities/version/check-server-version-compatibility.ts +++ b/packages/twenty-sdk/src/cli/utilities/version/check-server-version-compatibility.ts @@ -25,6 +25,6 @@ export const checkServerVersionCompatibility = async ( `⚠ Local Twenty server is v${info.localServerVersion} (${info.daysBehind} days behind v${info.latestServerVersion}).`, ), ); - console.warn(chalk.dim(' Update with: yarn twenty server upgrade')); + console.warn(chalk.dim(' Update with: yarn twenty docker:upgrade')); console.warn(''); };