mirror of
https://github.com/twentyhq/twenty
synced 2026-04-21 13:37:22 +00:00
Refactor dependency graph for SDK, client-sdk and create-app (#18963)
## Summary
### Externalize `twenty-client-sdk` from `twenty-sdk`
Previously, `twenty-client-sdk` was listed as a `devDependency` of
`twenty-sdk`, which caused Vite to bundle it inline into the dist
output. This meant end-user apps had two copies of `twenty-client-sdk`:
one hidden inside `twenty-sdk`'s bundle, and one installed explicitly in
their `node_modules`. These copies could drift apart since they weren't
guaranteed to be the same version.
**Change:** Moved `twenty-client-sdk` from `devDependencies` to
`dependencies` in `twenty-sdk/package.json`. Vite's `external` function
now recognizes it and keeps it as an external `require`/`import` in the
dist output. End users get a single deduplicated copy resolved by their
package manager.
### Externalize `twenty-sdk` from `create-twenty-app`
Similarly, `create-twenty-app` had `twenty-sdk` as a `devDependency`
(bundled inline). After refactoring `create-twenty-app` to
programmatically import operations from `twenty-sdk` (instead of
shelling out via `execSync`), it became a proper runtime dependency.
**Change:** Moved `twenty-sdk` from `devDependencies` to `dependencies`
in `create-twenty-app/package.json`.
### Switch E2E CI to `yarn npm publish`
The `workspace:*` protocol in `dependencies` is a Yarn-specific feature.
`npm publish` publishes it as-is (which breaks for consumers), while
`yarn npm publish` automatically replaces `workspace:*` with the
resolved version at publish time (e.g., `workspace:*` becomes `=1.2.3`).
**Change:** Replaced `npm publish` with `yarn npm publish` in
`.github/workflows/ci-create-app-e2e.yaml`.
### Replace `execSync` with programmatic SDK calls in
`create-twenty-app`
`create-twenty-app` was shelling out to `yarn twenty remote add` and
`yarn twenty server start` via `execSync`, which assumed the `twenty`
binary was already installed in the scaffolded app. This was fragile and
created an implicit circular dependency.
**Changes:**
- Replaced `execSync('yarn twenty remote add ...')` with a direct call
to `authLoginOAuth()` from `twenty-sdk/cli`
- Replaced `execSync('yarn twenty server start')` with a direct call to
`serverStart()` from `twenty-sdk/cli`
- Deleted the duplicated `setup-local-instance.ts` from
`create-twenty-app`
### Centralize `serverStart` as a dedicated operation
The Docker server start logic was previously inline in the `server
start` CLI command handler (`server.ts`), and `setup-local-instance.ts`
was shelling out to `yarn twenty server start` to invoke it -- meaning
`twenty-sdk` was calling itself via a child process.
**Changes:**
- Extracted the Docker container management logic into a new
`serverStart` operation (`cli/operations/server-start.ts`)
- Merged the detect-or-start flow from `setup-local-instance.ts` into
`serverStart` (detect across multiple ports, start Docker if needed,
poll for health)
- Deleted `setup-local-instance.ts` from `twenty-sdk`
- Added `onProgress` callback (consistent with other operations like
`appBuild`) instead of direct `console.log` calls
- Both the `server start` CLI command and `create-twenty-app` now call
`serverStart()` programmatically
related to https://github.com/twentyhq/twenty-infra/pull/525
This commit is contained in:
parent
b651a74b1f
commit
052aecccc7
26 changed files with 1276 additions and 1415 deletions
18
.github/workflows/ci-create-app-e2e.yaml
vendored
18
.github/workflows/ci-create-app-e2e.yaml
vendored
|
|
@ -53,6 +53,8 @@ jobs:
|
|||
image: redis
|
||||
ports:
|
||||
- 6379:6379
|
||||
env:
|
||||
PUBLISHABLE_PACKAGES: twenty-client-sdk twenty-sdk create-twenty-app
|
||||
steps:
|
||||
- name: Fetch custom Github Actions and base branch history
|
||||
uses: actions/checkout@v4
|
||||
|
|
@ -66,13 +68,13 @@ jobs:
|
|||
run: |
|
||||
CI_VERSION="0.0.0-ci.$(date +%s)"
|
||||
echo "CI_VERSION=$CI_VERSION" >> $GITHUB_ENV
|
||||
npx nx run-many -t set-local-version -p twenty-sdk twenty-client-sdk create-twenty-app --releaseVersion=$CI_VERSION
|
||||
npx nx run-many -t set-local-version -p $PUBLISHABLE_PACKAGES --releaseVersion=$CI_VERSION
|
||||
|
||||
- name: Build packages
|
||||
run: |
|
||||
npx nx build twenty-sdk
|
||||
npx nx build twenty-client-sdk
|
||||
npx nx build create-twenty-app
|
||||
for pkg in $PUBLISHABLE_PACKAGES; do
|
||||
npx nx build $pkg
|
||||
done
|
||||
|
||||
- name: Install and start Verdaccio
|
||||
run: |
|
||||
|
|
@ -89,11 +91,13 @@ jobs:
|
|||
|
||||
- name: Publish packages to local registry
|
||||
run: |
|
||||
npm set //localhost:4873/:_authToken "ci-auth-token"
|
||||
yarn config set npmRegistryServer http://localhost:4873
|
||||
yarn config set unsafeHttpWhitelist --json '["localhost"]'
|
||||
yarn config set npmAuthToken ci-auth-token
|
||||
|
||||
for pkg in twenty-sdk twenty-client-sdk create-twenty-app; do
|
||||
for pkg in $PUBLISHABLE_PACKAGES; do
|
||||
cd packages/$pkg
|
||||
npm publish --registry http://localhost:4873 --tag ci
|
||||
yarn npm publish --tag ci
|
||||
cd ../..
|
||||
done
|
||||
|
||||
|
|
|
|||
940
.yarn/releases/yarn-4.13.0.cjs
vendored
Executable file
940
.yarn/releases/yarn-4.13.0.cjs
vendored
Executable file
File diff suppressed because one or more lines are too long
942
.yarn/releases/yarn-4.9.2.cjs
vendored
942
.yarn/releases/yarn-4.9.2.cjs
vendored
File diff suppressed because one or more lines are too long
|
|
@ -6,4 +6,4 @@ enableInlineHunks: true
|
|||
|
||||
nodeLinker: node-modules
|
||||
|
||||
yarnPath: .yarn/releases/yarn-4.9.2.cjs
|
||||
yarnPath: .yarn/releases/yarn-4.13.0.cjs
|
||||
|
|
|
|||
|
|
@ -175,7 +175,7 @@
|
|||
},
|
||||
"license": "AGPL-3.0",
|
||||
"name": "twenty",
|
||||
"packageManager": "yarn@4.9.2",
|
||||
"packageManager": "yarn@4.13.0",
|
||||
"resolutions": {
|
||||
"graphql": "16.8.1",
|
||||
"type-fest": "4.10.1",
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ cd my-twenty-app
|
|||
|
||||
# Or do it manually:
|
||||
yarn twenty server start # Start local Twenty server
|
||||
yarn twenty remote add --local # Authenticate via OAuth
|
||||
yarn twenty remote add http://localhost:2020 --as local # Authenticate via OAuth
|
||||
|
||||
# Start dev mode: watches, builds, and syncs local changes to your workspace
|
||||
# (also auto-generates typed CoreApiClient — MetadataApiClient ships pre-built — both available via `twenty-client-sdk`)
|
||||
|
|
@ -122,18 +122,10 @@ yarn twenty server reset # Wipe all data and start fresh
|
|||
|
||||
The server is pre-seeded with a workspace and user (`tim@apple.dev` / `tim@apple.dev`).
|
||||
|
||||
### How to use a local Twenty instance
|
||||
|
||||
If you're already running a local Twenty instance, you can connect to it instead of using Docker. Pass the port your local server is listening on (default: `3000`):
|
||||
|
||||
```bash
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
```
|
||||
|
||||
## Next steps
|
||||
|
||||
- Run `yarn twenty help` to see all available commands.
|
||||
- Use `yarn twenty remote add --local` to authenticate with your Twenty workspace via OAuth.
|
||||
- Use `yarn twenty remote add <url>` to authenticate with your Twenty workspace via OAuth.
|
||||
- Explore the generated project and add your first entity with `yarn twenty add` (logic functions, front components, objects, roles, views, navigation menu items, skills).
|
||||
- Use `yarn twenty dev` while you iterate — it watches, builds, and syncs changes to your workspace in real time.
|
||||
- `CoreApiClient` is auto-generated by `yarn twenty dev`. `MetadataApiClient` (for workspace configuration and file uploads via `/metadata`) ships pre-built with the SDK. Both are available via `import { CoreApiClient } from 'twenty-client-sdk/core'` and `import { MetadataApiClient } from 'twenty-client-sdk/metadata'`.
|
||||
|
|
@ -177,7 +169,7 @@ Our team reviews contributions for quality, security, and reusability before mer
|
|||
## Troubleshooting
|
||||
|
||||
- Server not starting: check Docker is running (`docker info`), then try `yarn twenty server logs`.
|
||||
- Auth not working: make sure you're logged in to Twenty in the browser first, then run `yarn twenty remote add --local`.
|
||||
- Auth not working: make sure you're logged in to Twenty in the browser first, then run `yarn twenty remote add <url>`.
|
||||
- Types not generated: ensure `yarn twenty dev` is running — it auto-generates the typed client.
|
||||
|
||||
## Contributing
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "create-twenty-app",
|
||||
"version": "0.8.0-canary.2",
|
||||
"version": "0.8.0-canary.3",
|
||||
"description": "Command-line interface to create Twenty application",
|
||||
"main": "dist/cli.cjs",
|
||||
"bin": "dist/cli.cjs",
|
||||
|
|
@ -36,6 +36,7 @@
|
|||
"lodash.camelcase": "^4.3.0",
|
||||
"lodash.kebabcase": "^4.1.1",
|
||||
"lodash.startcase": "^4.4.0",
|
||||
"twenty-sdk": "workspace:*",
|
||||
"uuid": "^13.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
@ -45,7 +46,6 @@
|
|||
"@types/lodash.kebabcase": "^4.1.7",
|
||||
"@types/lodash.startcase": "^4",
|
||||
"@types/node": "^20.0.0",
|
||||
"twenty-sdk": "workspace:*",
|
||||
"twenty-shared": "workspace:*",
|
||||
"typescript": "^5.9.2",
|
||||
"vite": "^7.0.0",
|
||||
|
|
|
|||
|
|
@ -31,10 +31,6 @@ const program = new Command(packageJson.name)
|
|||
'--skip-local-instance',
|
||||
'Skip the local Twenty instance setup prompt',
|
||||
)
|
||||
.option(
|
||||
'-p, --port <port>',
|
||||
'Port of an existing Twenty server (skips Docker setup)',
|
||||
)
|
||||
.helpOption('-h, --help', 'Display this help message.')
|
||||
.action(
|
||||
async (
|
||||
|
|
@ -46,7 +42,6 @@ const program = new Command(packageJson.name)
|
|||
displayName?: string;
|
||||
description?: string;
|
||||
skipLocalInstance?: boolean;
|
||||
port?: string;
|
||||
},
|
||||
) => {
|
||||
const modeFlags = [options?.exhaustive, options?.minimal].filter(Boolean);
|
||||
|
|
@ -76,8 +71,6 @@ const program = new Command(packageJson.name)
|
|||
|
||||
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
|
||||
|
||||
const port = options?.port ? parseInt(options.port, 10) : undefined;
|
||||
|
||||
await new CreateAppCommand().execute({
|
||||
directory,
|
||||
mode,
|
||||
|
|
@ -85,7 +78,6 @@ const program = new Command(packageJson.name)
|
|||
displayName: options?.displayName,
|
||||
description: options?.description,
|
||||
skipLocalInstance: options?.skipLocalInstance,
|
||||
port,
|
||||
});
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -1,18 +1,18 @@
|
|||
import { basename } from 'path';
|
||||
import { copyBaseApplicationProject } from '@/utils/app-template';
|
||||
import { convertToLabel } from '@/utils/convert-to-label';
|
||||
import { install } from '@/utils/install';
|
||||
import {
|
||||
type LocalInstanceResult,
|
||||
setupLocalInstance,
|
||||
} from '@/utils/setup-local-instance';
|
||||
import { tryGitInit } from '@/utils/try-git-init';
|
||||
import chalk from 'chalk';
|
||||
import * as fs from 'fs-extra';
|
||||
import inquirer from 'inquirer';
|
||||
import kebabCase from 'lodash.kebabcase';
|
||||
import { execSync } from 'node:child_process';
|
||||
import * as path from 'path';
|
||||
import { basename } from 'path';
|
||||
import {
|
||||
authLoginOAuth,
|
||||
serverStart,
|
||||
type ServerStartResult,
|
||||
} from 'twenty-sdk/cli';
|
||||
import { isDefined } from 'twenty-shared/utils';
|
||||
|
||||
import {
|
||||
|
|
@ -29,7 +29,6 @@ type CreateAppOptions = {
|
|||
displayName?: string;
|
||||
description?: string;
|
||||
skipLocalInstance?: boolean;
|
||||
port?: number;
|
||||
};
|
||||
|
||||
export class CreateAppCommand {
|
||||
|
|
@ -60,17 +59,22 @@ export class CreateAppCommand {
|
|||
|
||||
await tryGitInit(appDirectory);
|
||||
|
||||
let localResult: LocalInstanceResult = { running: false };
|
||||
let serverResult: ServerStartResult | undefined;
|
||||
|
||||
if (!options.skipLocalInstance) {
|
||||
localResult = await setupLocalInstance(appDirectory, options.port);
|
||||
const startResult = await serverStart({
|
||||
onProgress: (message: string) => console.log(chalk.gray(message)),
|
||||
});
|
||||
|
||||
if (localResult.running && localResult.serverUrl) {
|
||||
await this.connectToLocal(appDirectory, localResult.serverUrl);
|
||||
if (startResult.success) {
|
||||
serverResult = startResult.data;
|
||||
await this.connectToLocal(serverResult.url);
|
||||
} else {
|
||||
console.log(chalk.yellow(`\n${startResult.error.message}`));
|
||||
}
|
||||
}
|
||||
|
||||
this.logSuccess(appDirectory, localResult);
|
||||
this.logSuccess(appDirectory, serverResult);
|
||||
} catch (error) {
|
||||
console.error(
|
||||
chalk.red('\nCreate application failed:'),
|
||||
|
|
@ -197,15 +201,20 @@ export class CreateAppCommand {
|
|||
);
|
||||
}
|
||||
|
||||
private async connectToLocal(
|
||||
appDirectory: string,
|
||||
serverUrl: string,
|
||||
): Promise<void> {
|
||||
private async connectToLocal(serverUrl: string): Promise<void> {
|
||||
try {
|
||||
execSync(`yarn twenty remote add ${serverUrl} --as local`, {
|
||||
cwd: appDirectory,
|
||||
stdio: 'inherit',
|
||||
const result = await authLoginOAuth({
|
||||
apiUrl: serverUrl,
|
||||
remote: 'local',
|
||||
});
|
||||
|
||||
if (!result.success) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Authentication skipped. Run `yarn twenty remote add --local` manually.',
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
|
|
@ -217,14 +226,14 @@ export class CreateAppCommand {
|
|||
|
||||
private logSuccess(
|
||||
appDirectory: string,
|
||||
localResult: LocalInstanceResult,
|
||||
serverResult?: ServerStartResult,
|
||||
): void {
|
||||
const dirName = basename(appDirectory);
|
||||
|
||||
console.log(chalk.blue('\nApplication created. Next steps:'));
|
||||
console.log(chalk.gray(`- cd ${dirName}`));
|
||||
|
||||
if (!localResult.running) {
|
||||
if (!serverResult) {
|
||||
console.log(
|
||||
chalk.gray(
|
||||
'- yarn twenty remote add --local # Authenticate with Twenty',
|
||||
|
|
|
|||
|
|
@ -1,106 +0,0 @@
|
|||
import chalk from 'chalk';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
const LOCAL_PORTS = [2020, 3000];
|
||||
|
||||
// Minimal health check — the full implementation lives in twenty-sdk
|
||||
const isServerReady = async (port: number): Promise<boolean> => {
|
||||
const controller = new AbortController();
|
||||
const timeoutId = setTimeout(() => controller.abort(), 3000);
|
||||
|
||||
try {
|
||||
const response = await fetch(`http://localhost:${port}/healthz`, {
|
||||
signal: controller.signal,
|
||||
});
|
||||
|
||||
const body = await response.json();
|
||||
|
||||
return body.status === 'ok';
|
||||
} catch {
|
||||
return false;
|
||||
} finally {
|
||||
clearTimeout(timeoutId);
|
||||
}
|
||||
};
|
||||
|
||||
const detectRunningServer = async (
|
||||
preferredPort?: number,
|
||||
): Promise<number | null> => {
|
||||
const ports = preferredPort ? [preferredPort] : LOCAL_PORTS;
|
||||
|
||||
for (const port of ports) {
|
||||
if (await isServerReady(port)) {
|
||||
return port;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
};
|
||||
|
||||
export type LocalInstanceResult = {
|
||||
running: boolean;
|
||||
serverUrl?: string;
|
||||
};
|
||||
|
||||
export const setupLocalInstance = async (
|
||||
appDirectory: string,
|
||||
preferredPort?: number,
|
||||
): Promise<LocalInstanceResult> => {
|
||||
const detectedPort = await detectRunningServer(preferredPort);
|
||||
|
||||
if (detectedPort) {
|
||||
const serverUrl = `http://localhost:${detectedPort}`;
|
||||
|
||||
console.log(chalk.green(`Twenty server detected on ${serverUrl}.\n`));
|
||||
|
||||
return { running: true, serverUrl };
|
||||
}
|
||||
|
||||
if (preferredPort) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`No Twenty server found on port ${preferredPort}.\n` +
|
||||
'Start your server and run `yarn twenty remote add --local` manually.\n',
|
||||
),
|
||||
);
|
||||
|
||||
return { running: false };
|
||||
}
|
||||
|
||||
console.log(chalk.blue('Setting up local Twenty instance...\n'));
|
||||
|
||||
try {
|
||||
execSync('yarn twenty server start', {
|
||||
cwd: appDirectory,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch {
|
||||
return { running: false };
|
||||
}
|
||||
|
||||
console.log(chalk.gray('Waiting for Twenty to be ready...\n'));
|
||||
|
||||
const startTime = Date.now();
|
||||
const timeoutMs = 180 * 1000;
|
||||
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
if (await isServerReady(LOCAL_PORTS[0])) {
|
||||
const serverUrl = `http://localhost:${LOCAL_PORTS[0]}`;
|
||||
|
||||
console.log(chalk.green(`Server running on '${serverUrl}'\n`));
|
||||
|
||||
return { running: true, serverUrl };
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Twenty server did not become healthy in time.\n',
|
||||
"Check: 'yarn twenty server logs'\n",
|
||||
),
|
||||
);
|
||||
|
||||
return { running: false };
|
||||
};
|
||||
|
|
@ -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 --local
|
||||
yarn twenty remote add http://localhost:2020 --as local
|
||||
```
|
||||
|
||||
Then, start development mode to sync your app and watch for changes:
|
||||
|
|
@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
|
|||
|
||||
```bash
|
||||
# Remotes & Authentication
|
||||
yarn twenty remote add --local # Authenticate with Twenty
|
||||
yarn twenty remote add 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
|
||||
|
|
|
|||
|
|
@ -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 --local
|
||||
yarn twenty remote add http://localhost:2020 --as local
|
||||
```
|
||||
|
||||
Then, start development mode to sync your app and watch for changes:
|
||||
|
|
@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
|
|||
|
||||
```bash
|
||||
# Remotes & Authentication
|
||||
yarn twenty remote add --local # Authenticate with Twenty
|
||||
yarn twenty remote add 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
|
||||
|
|
|
|||
|
|
@ -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 --local
|
||||
yarn twenty remote add http://localhost:2020 --as local
|
||||
```
|
||||
|
||||
Then, start development mode to sync your app and watch for changes:
|
||||
|
|
@ -22,7 +22,7 @@ Run `yarn twenty help` to list all available commands. Common commands:
|
|||
|
||||
```bash
|
||||
# Remotes & Authentication
|
||||
yarn twenty remote add --local # Authenticate with Twenty
|
||||
yarn twenty remote add 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
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "twenty-client-sdk",
|
||||
"version": "0.7.0-canary.0",
|
||||
"version": "0.8.0-canary.3",
|
||||
"sideEffects": false,
|
||||
"license": "AGPL-3.0",
|
||||
"scripts": {
|
||||
|
|
|
|||
|
|
@ -124,8 +124,6 @@ Manage remote server connections and authentication.
|
|||
- `--token <token>`: API key for non-interactive auth.
|
||||
- `--url <url>`: Server URL (alternative to positional arg).
|
||||
- `--as <name>`: Name for this remote (otherwise derived from URL hostname).
|
||||
- `--local`: Connect to local development server (`http://localhost:2020`) via OAuth.
|
||||
- `--port <port>`: Port for local server (use with `--local`).
|
||||
- Behavior: If `nameOrUrl` matches an existing remote name, re-authenticates it. Otherwise, creates a new remote and authenticates via OAuth (with API key fallback).
|
||||
|
||||
- `twenty remote remove <name>` — Remove a remote and its credentials.
|
||||
|
|
@ -147,9 +145,6 @@ twenty remote add
|
|||
# Provide values in flags (non-interactive, for CI)
|
||||
twenty remote add https://api.twenty.com --token $TWENTY_API_KEY
|
||||
|
||||
# Add a local development remote
|
||||
twenty remote add --local
|
||||
|
||||
# Name a remote explicitly
|
||||
twenty remote add https://api.twenty.com --as production
|
||||
|
||||
|
|
@ -339,14 +334,10 @@ Notes:
|
|||
|
||||
## How to use a local Twenty instance
|
||||
|
||||
If you're already running a local Twenty instance, you can connect to it instead of using Docker. Pass the port your local server is listening on (default: `3000`):
|
||||
If you're already running a local Twenty instance, you can connect to it instead of using Docker:
|
||||
|
||||
```bash
|
||||
# During scaffolding
|
||||
npx create-twenty-app@latest my-app --port 3000
|
||||
|
||||
# Or after scaffolding
|
||||
twenty remote add --local --port 3000
|
||||
twenty remote add http://localhost:3000 --as local
|
||||
```
|
||||
|
||||
## Troubleshooting
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
{
|
||||
"name": "twenty-sdk",
|
||||
"version": "0.8.0-canary.2",
|
||||
"version": "0.8.0-canary.3",
|
||||
"main": "dist/index.cjs",
|
||||
"module": "dist/index.mjs",
|
||||
"types": "dist/sdk/index.d.ts",
|
||||
|
|
@ -75,6 +75,7 @@
|
|||
"react": "^19.0.0",
|
||||
"react-dom": "^19.0.0",
|
||||
"tinyglobby": "^0.2.15",
|
||||
"twenty-client-sdk": "workspace:*",
|
||||
"typescript": "^5.9.2",
|
||||
"uuid": "^13.0.0",
|
||||
"vite": "^7.0.0",
|
||||
|
|
@ -96,7 +97,6 @@
|
|||
"storybook": "^10.2.13",
|
||||
"ts-morph": "^25.0.0",
|
||||
"tsx": "^4.7.0",
|
||||
"twenty-client-sdk": "workspace:*",
|
||||
"twenty-shared": "workspace:*",
|
||||
"twenty-ui": "workspace:*",
|
||||
"vite-plugin-dts": "^4.5.4",
|
||||
|
|
|
|||
|
|
@ -69,8 +69,6 @@ export const registerRemoteCommands = (program: Command): void => {
|
|||
.command('add [nameOrUrl]')
|
||||
.description('Add a new remote or re-authenticate an existing one')
|
||||
.option('--as <name>', 'Name for this remote')
|
||||
.option('--local', 'Connect to local development server')
|
||||
.option('--port <port>', 'Port for local server (use with --local)')
|
||||
.option('--token <token>', 'API key for non-interactive auth')
|
||||
.option('--url <url>', 'Server URL (alternative to positional arg)')
|
||||
.action(
|
||||
|
|
@ -78,8 +76,6 @@ export const registerRemoteCommands = (program: Command): void => {
|
|||
nameOrUrl: string | undefined,
|
||||
options: {
|
||||
as?: string;
|
||||
local?: boolean;
|
||||
port?: string;
|
||||
token?: string;
|
||||
url?: string;
|
||||
},
|
||||
|
|
@ -87,32 +83,6 @@ export const registerRemoteCommands = (program: Command): void => {
|
|||
const configService = new ConfigService();
|
||||
const existingRemotes = await configService.getRemotes();
|
||||
|
||||
if (options.local) {
|
||||
const remoteName = options.as ?? 'local';
|
||||
const preferredPort = options.port
|
||||
? parseInt(options.port, 10)
|
||||
: undefined;
|
||||
const localUrl = preferredPort
|
||||
? `http://localhost:${preferredPort}`
|
||||
: await detectLocalServer();
|
||||
|
||||
if (!localUrl) {
|
||||
console.error(
|
||||
chalk.red(
|
||||
'No local Twenty server found on ports 2020 or 3000.\n' +
|
||||
'Start one with: yarn twenty server start',
|
||||
),
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
console.log(chalk.gray(`Found server at ${localUrl}`));
|
||||
ConfigService.setActiveRemote(remoteName);
|
||||
await authenticate(localUrl, options.token);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Re-authenticate an existing remote by name
|
||||
const isExistingRemote =
|
||||
nameOrUrl !== undefined && existingRemotes.includes(nameOrUrl);
|
||||
|
|
|
|||
|
|
@ -1,76 +1,16 @@
|
|||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { serverStart } from '@/cli/operations/server-start';
|
||||
import {
|
||||
CONTAINER_NAME,
|
||||
containerExists,
|
||||
DEFAULT_PORT,
|
||||
getContainerPort,
|
||||
isContainerRunning,
|
||||
} 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 CONTAINER_NAME = 'twenty-app-dev';
|
||||
const IMAGE = 'twentycrm/twenty-app-dev:latest';
|
||||
const DEFAULT_PORT = 2020;
|
||||
|
||||
const isContainerRunning = (): boolean => {
|
||||
try {
|
||||
const result = execSync(
|
||||
`docker inspect -f '{{.State.Running}}' ${CONTAINER_NAME}`,
|
||||
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
|
||||
).trim();
|
||||
|
||||
return result === 'true';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const getContainerPort = (): number => {
|
||||
try {
|
||||
const result = execSync(
|
||||
`docker inspect -f '{{(index (index .NetworkSettings.Ports "3000/tcp") 0).HostPort}}' ${CONTAINER_NAME}`,
|
||||
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
|
||||
).trim();
|
||||
|
||||
return parseInt(result, 10) || DEFAULT_PORT;
|
||||
} catch {
|
||||
return DEFAULT_PORT;
|
||||
}
|
||||
};
|
||||
|
||||
const containerExists = (): boolean => {
|
||||
try {
|
||||
execSync(`docker inspect ${CONTAINER_NAME}`, {
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const checkDockerRunning = (): boolean => {
|
||||
try {
|
||||
execSync('docker info', { stdio: 'ignore' });
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
console.error(
|
||||
chalk.red('Docker is not running. Please start Docker and try again.'),
|
||||
);
|
||||
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
const validatePort = (value: string): number => {
|
||||
const port = parseInt(value, 10);
|
||||
|
||||
if (isNaN(port) || port < 1 || port > 65535) {
|
||||
console.error(chalk.red('Invalid port number.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
return port;
|
||||
};
|
||||
|
||||
export const registerServerCommands = (program: Command): void => {
|
||||
const server = program
|
||||
.command('server')
|
||||
|
|
@ -81,81 +21,26 @@ export const registerServerCommands = (program: Command): void => {
|
|||
.description('Start a local Twenty server')
|
||||
.option('-p, --port <port>', 'HTTP port', String(DEFAULT_PORT))
|
||||
.action(async (options: { port: string }) => {
|
||||
let port = validatePort(options.port);
|
||||
const port = parseInt(options.port, 10);
|
||||
|
||||
if (await checkServerHealth(port)) {
|
||||
const localUrl = `http://localhost:${port}`;
|
||||
const configService = new ConfigService();
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: localUrl });
|
||||
|
||||
console.log(
|
||||
chalk.green(`Twenty server is already running on localhost:${port}.`),
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!checkDockerRunning()) {
|
||||
if (isNaN(port) || port < 1 || port > 65535) {
|
||||
console.error(chalk.red('Invalid port number.'));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (isContainerRunning()) {
|
||||
console.log(chalk.gray('Container is running but not healthy yet.'));
|
||||
const result = await serverStart({
|
||||
port,
|
||||
onProgress: (message) => console.log(chalk.gray(message)),
|
||||
});
|
||||
|
||||
return;
|
||||
if (!result.success) {
|
||||
console.error(chalk.red(result.error.message));
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
if (containerExists()) {
|
||||
const existingPort = getContainerPort();
|
||||
|
||||
if (existingPort !== port) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`Existing container uses port ${existingPort}. Run 'yarn twenty server reset' first to change ports.`,
|
||||
),
|
||||
chalk.green(`\nLocal remote configured → ${result.data.url}`),
|
||||
);
|
||||
}
|
||||
|
||||
port = existingPort;
|
||||
|
||||
console.log(chalk.gray('Starting existing container...'));
|
||||
execSync(`docker start ${CONTAINER_NAME}`, { stdio: 'ignore' });
|
||||
} else {
|
||||
console.log(chalk.gray('Starting Twenty container...'));
|
||||
|
||||
const runResult = spawnSync(
|
||||
'docker',
|
||||
[
|
||||
'run',
|
||||
'-d',
|
||||
'--name',
|
||||
CONTAINER_NAME,
|
||||
'-p',
|
||||
`${port}:3000`,
|
||||
'-v',
|
||||
'twenty-app-dev-data:/data/postgres',
|
||||
'-v',
|
||||
'twenty-app-dev-storage:/app/.local-storage',
|
||||
IMAGE,
|
||||
],
|
||||
{ stdio: 'inherit' },
|
||||
);
|
||||
|
||||
if (runResult.status !== 0) {
|
||||
console.error(chalk.red('\nFailed to start Twenty container.'));
|
||||
process.exit(runResult.status ?? 1);
|
||||
}
|
||||
}
|
||||
|
||||
const localUrl = `http://localhost:${port}`;
|
||||
const configService = new ConfigService();
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: localUrl });
|
||||
|
||||
console.log(chalk.green(`\nLocal remote configured → ${localUrl}`));
|
||||
});
|
||||
|
||||
server
|
||||
|
|
|
|||
|
|
@ -22,11 +22,16 @@ export type { AppUninstallOptions } from './uninstall';
|
|||
export { functionExecute } from './execute';
|
||||
export type { FunctionExecuteOptions } from './execute';
|
||||
|
||||
// Server
|
||||
export { serverStart } from './server-start';
|
||||
export type { ServerStartOptions, ServerStartResult } from './server-start';
|
||||
|
||||
// Shared types and error codes
|
||||
export {
|
||||
APP_ERROR_CODES,
|
||||
AUTH_ERROR_CODES,
|
||||
FUNCTION_ERROR_CODES,
|
||||
SERVER_ERROR_CODES,
|
||||
} from '@/cli/types';
|
||||
export type {
|
||||
AuthListRemote,
|
||||
|
|
|
|||
191
packages/twenty-sdk/src/cli/operations/server-start.ts
Normal file
191
packages/twenty-sdk/src/cli/operations/server-start.ts
Normal file
|
|
@ -0,0 +1,191 @@
|
|||
import { SERVER_ERROR_CODES, type CommandResult } from '@/cli/types';
|
||||
import { ConfigService } from '@/cli/utilities/config/config-service';
|
||||
import { runSafe } from '@/cli/utilities/run-safe';
|
||||
import {
|
||||
checkDockerRunning,
|
||||
CONTAINER_NAME,
|
||||
containerExists,
|
||||
DEFAULT_PORT,
|
||||
getContainerPort,
|
||||
IMAGE,
|
||||
isContainerRunning,
|
||||
} from '@/cli/utilities/server/docker-container';
|
||||
import {
|
||||
checkServerHealth,
|
||||
detectLocalServer,
|
||||
} from '@/cli/utilities/server/detect-local-server';
|
||||
import { execSync, spawnSync } from 'node:child_process';
|
||||
|
||||
const HEALTH_POLL_INTERVAL_MS = 2000;
|
||||
const HEALTH_TIMEOUT_MS = 180 * 1000;
|
||||
|
||||
const waitForHealthy = async (port: number): Promise<boolean> => {
|
||||
const startTime = Date.now();
|
||||
|
||||
while (Date.now() - startTime < HEALTH_TIMEOUT_MS) {
|
||||
if (await checkServerHealth(port)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
await new Promise((resolve) =>
|
||||
setTimeout(resolve, HEALTH_POLL_INTERVAL_MS),
|
||||
);
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
export type ServerStartOptions = {
|
||||
port?: number;
|
||||
onProgress?: (message: string) => void;
|
||||
};
|
||||
|
||||
export type ServerStartResult = {
|
||||
port: number;
|
||||
url: string;
|
||||
};
|
||||
|
||||
const innerServerStart = async (
|
||||
options: ServerStartOptions = {},
|
||||
): Promise<CommandResult<ServerStartResult>> => {
|
||||
const { onProgress } = options;
|
||||
|
||||
const existingUrl = await detectLocalServer(options.port);
|
||||
|
||||
if (existingUrl) {
|
||||
const configService = new ConfigService();
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: existingUrl });
|
||||
|
||||
const port = new URL(existingUrl).port;
|
||||
|
||||
onProgress?.(`Twenty server detected on ${existingUrl}`);
|
||||
|
||||
return {
|
||||
success: true,
|
||||
data: { port: parseInt(port, 10), url: existingUrl },
|
||||
};
|
||||
}
|
||||
|
||||
if (!checkDockerRunning()) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: SERVER_ERROR_CODES.DOCKER_NOT_RUNNING,
|
||||
message: 'Docker is not running. Please start Docker and try again.',
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
if (isContainerRunning()) {
|
||||
const port = getContainerPort();
|
||||
|
||||
onProgress?.('Container is running, waiting for it to become healthy...');
|
||||
|
||||
const healthy = await waitForHealthy(port);
|
||||
|
||||
if (!healthy) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: SERVER_ERROR_CODES.HEALTH_TIMEOUT,
|
||||
message:
|
||||
'Twenty server did not become healthy in time.\n' +
|
||||
"Check: 'yarn twenty server logs'",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const url = `http://localhost:${port}`;
|
||||
const configService = new ConfigService();
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: url });
|
||||
|
||||
onProgress?.(`Server running on ${url}`);
|
||||
|
||||
return { success: true, data: { port, url } };
|
||||
}
|
||||
|
||||
let port = options.port ?? DEFAULT_PORT;
|
||||
|
||||
if (containerExists()) {
|
||||
const existingPort = getContainerPort();
|
||||
|
||||
if (existingPort !== port) {
|
||||
onProgress?.(
|
||||
`Existing container uses port ${existingPort}. Run 'yarn twenty server reset' first to change ports.`,
|
||||
);
|
||||
}
|
||||
|
||||
port = existingPort;
|
||||
|
||||
onProgress?.('Starting existing container...');
|
||||
execSync(`docker start ${CONTAINER_NAME}`, { stdio: 'ignore' });
|
||||
} else {
|
||||
onProgress?.('Starting Twenty container...');
|
||||
|
||||
const runResult = spawnSync(
|
||||
'docker',
|
||||
[
|
||||
'run',
|
||||
'-d',
|
||||
'--name',
|
||||
CONTAINER_NAME,
|
||||
'-p',
|
||||
`${port}:3000`,
|
||||
'-v',
|
||||
'twenty-app-dev-data:/data/postgres',
|
||||
'-v',
|
||||
'twenty-app-dev-storage:/app/.local-storage',
|
||||
IMAGE,
|
||||
],
|
||||
{ stdio: 'inherit' },
|
||||
);
|
||||
|
||||
if (runResult.status !== 0) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: SERVER_ERROR_CODES.CONTAINER_START_FAILED,
|
||||
message: 'Failed to start Twenty container.',
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
onProgress?.('Waiting for Twenty to be ready...');
|
||||
|
||||
const healthy = await waitForHealthy(port);
|
||||
|
||||
if (!healthy) {
|
||||
return {
|
||||
success: false,
|
||||
error: {
|
||||
code: SERVER_ERROR_CODES.HEALTH_TIMEOUT,
|
||||
message:
|
||||
'Twenty server did not become healthy in time.\n' +
|
||||
"Check: 'yarn twenty server logs'",
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
const url = `http://localhost:${port}`;
|
||||
const configService = new ConfigService();
|
||||
|
||||
ConfigService.setActiveRemote('local');
|
||||
await configService.setConfig({ apiUrl: url });
|
||||
|
||||
onProgress?.(`Server running on ${url}`);
|
||||
|
||||
return { success: true, data: { port, url } };
|
||||
};
|
||||
|
||||
export const serverStart = (
|
||||
options?: ServerStartOptions,
|
||||
): Promise<CommandResult<ServerStartResult>> =>
|
||||
runSafe(
|
||||
() => innerServerStart(options),
|
||||
SERVER_ERROR_CODES.CONTAINER_START_FAILED,
|
||||
);
|
||||
|
|
@ -27,6 +27,12 @@ export const APP_ERROR_CODES = {
|
|||
DEPLOY_FAILED: 'DEPLOY_FAILED',
|
||||
} as const;
|
||||
|
||||
export const SERVER_ERROR_CODES = {
|
||||
DOCKER_NOT_RUNNING: 'DOCKER_NOT_RUNNING',
|
||||
CONTAINER_START_FAILED: 'CONTAINER_START_FAILED',
|
||||
HEALTH_TIMEOUT: 'HEALTH_TIMEOUT',
|
||||
} as const;
|
||||
|
||||
export const FUNCTION_ERROR_CODES = {
|
||||
FETCH_FUNCTIONS_FAILED: 'FETCH_FUNCTIONS_FAILED',
|
||||
FUNCTION_NOT_FOUND: 'FUNCTION_NOT_FOUND',
|
||||
|
|
|
|||
|
|
@ -58,7 +58,7 @@ export class CheckServerOrchestratorStep {
|
|||
this.state.applyStepEvents([
|
||||
{
|
||||
message:
|
||||
'Authentication failed. Run `twenty remote add --local` to authenticate.',
|
||||
'Authentication failed. Run `twenty remote add <url>` to authenticate.',
|
||||
status: 'error',
|
||||
},
|
||||
]);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,53 @@
|
|||
import { execSync } from 'node:child_process';
|
||||
|
||||
export const CONTAINER_NAME = 'twenty-app-dev';
|
||||
export const IMAGE = 'twentycrm/twenty-app-dev:latest';
|
||||
export const DEFAULT_PORT = 2020;
|
||||
|
||||
export const isContainerRunning = (): boolean => {
|
||||
try {
|
||||
const result = execSync(
|
||||
`docker inspect -f '{{.State.Running}}' ${CONTAINER_NAME}`,
|
||||
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
|
||||
).trim();
|
||||
|
||||
return result === 'true';
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const getContainerPort = (): number => {
|
||||
try {
|
||||
const result = execSync(
|
||||
`docker inspect -f '{{(index (index .NetworkSettings.Ports "3000/tcp") 0).HostPort}}' ${CONTAINER_NAME}`,
|
||||
{ encoding: 'utf-8', stdio: ['pipe', 'pipe', 'ignore'] },
|
||||
).trim();
|
||||
|
||||
return parseInt(result, 10) || DEFAULT_PORT;
|
||||
} catch {
|
||||
return DEFAULT_PORT;
|
||||
}
|
||||
};
|
||||
|
||||
export const containerExists = (): boolean => {
|
||||
try {
|
||||
execSync(`docker inspect ${CONTAINER_NAME}`, {
|
||||
stdio: ['pipe', 'pipe', 'ignore'],
|
||||
});
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const checkDockerRunning = (): boolean => {
|
||||
try {
|
||||
execSync('docker info', { stdio: 'ignore' });
|
||||
|
||||
return true;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
@ -1,74 +0,0 @@
|
|||
import {
|
||||
checkServerHealth,
|
||||
detectLocalServer,
|
||||
} from '@/cli/utilities/server/detect-local-server';
|
||||
import chalk from 'chalk';
|
||||
import { execSync } from 'node:child_process';
|
||||
|
||||
const LOCAL_PORTS = [2020, 3000];
|
||||
|
||||
export type LocalInstanceResult = {
|
||||
running: boolean;
|
||||
serverUrl?: string;
|
||||
};
|
||||
|
||||
export const setupLocalInstance = async (
|
||||
appDirectory: string,
|
||||
preferredPort?: number,
|
||||
): Promise<LocalInstanceResult> => {
|
||||
const serverUrl = await detectLocalServer(preferredPort);
|
||||
|
||||
if (serverUrl) {
|
||||
console.log(chalk.green(`Twenty server detected on ${serverUrl}.\n`));
|
||||
|
||||
return { running: true, serverUrl };
|
||||
}
|
||||
|
||||
if (preferredPort) {
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
`No Twenty server found on port ${preferredPort}.\n` +
|
||||
'Start your server and run `yarn twenty remote add --local` manually.\n',
|
||||
),
|
||||
);
|
||||
|
||||
return { running: false };
|
||||
}
|
||||
|
||||
console.log(chalk.blue('Setting up local Twenty instance...\n'));
|
||||
|
||||
try {
|
||||
execSync('yarn twenty server start', {
|
||||
cwd: appDirectory,
|
||||
stdio: 'inherit',
|
||||
});
|
||||
} catch {
|
||||
return { running: false };
|
||||
}
|
||||
|
||||
console.log(chalk.gray('Waiting for Twenty to be ready...\n'));
|
||||
|
||||
const startTime = Date.now();
|
||||
const timeoutMs = 180 * 1000;
|
||||
|
||||
while (Date.now() - startTime < timeoutMs) {
|
||||
if (await checkServerHealth(LOCAL_PORTS[0])) {
|
||||
const serverUrl = `http://localhost:${LOCAL_PORTS[0]}`;
|
||||
|
||||
console.log(chalk.green(`Server running on '${serverUrl}'\n`));
|
||||
|
||||
return { running: true, serverUrl };
|
||||
}
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, 2000));
|
||||
}
|
||||
|
||||
console.log(
|
||||
chalk.yellow(
|
||||
'Twenty server did not become healthy in time.\n',
|
||||
"Check: 'yarn twenty server logs'\n",
|
||||
),
|
||||
);
|
||||
|
||||
return { running: false };
|
||||
};
|
||||
|
|
@ -1,28 +0,0 @@
|
|||
import { readFileSync, existsSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const verifyBuildPackagesBundleTwentyDependencies = () => {
|
||||
const distPath = resolve('dist/cli.cjs');
|
||||
|
||||
if (!existsSync(distPath)) {
|
||||
console.error(`Build check failed: ${distPath} does not exist`);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const content = readFileSync(distPath, 'utf8');
|
||||
|
||||
const filePath = resolve('package.json');
|
||||
|
||||
const pkg = JSON.parse(readFileSync(filePath, 'utf8'));
|
||||
|
||||
if (/require\("twenty/.test(content)) {
|
||||
console.error(
|
||||
`Build check failed: ${pkg.name}/dist/cli.cjs contains a require("twenty...) import. Workspace packages should be bundled, not externalized.`,
|
||||
);
|
||||
process.exit(1);
|
||||
} else {
|
||||
console.log(`${pkg.name}/dist/cli.cjs: OK`);
|
||||
}
|
||||
};
|
||||
|
||||
verifyBuildPackagesBundleTwentyDependencies();
|
||||
|
|
@ -1,27 +0,0 @@
|
|||
import { readFileSync } from 'fs';
|
||||
import { resolve } from 'path';
|
||||
|
||||
const packages = ['twenty-sdk', 'create-twenty-app', 'twenty-client-sdk'];
|
||||
|
||||
const verifyUniquePackageVersion = () => {
|
||||
const packageVersions = packages.map((pkg) => {
|
||||
const packageJsonPath = resolve('packages', pkg, 'package.json');
|
||||
|
||||
const packageJsonFile = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
|
||||
|
||||
return packageJsonFile.version as string;
|
||||
});
|
||||
|
||||
if (new Set(packageVersions).size !== 1) {
|
||||
console.error(
|
||||
`Build check failed: "${packages.join('", "')}" should have the same package.json version. Got ${packageVersions.join(', ')}`,
|
||||
);
|
||||
process.exit(1);
|
||||
return;
|
||||
}
|
||||
console.log(
|
||||
`"${packages.join('", "')}" share the same version ${packageVersions[0]}: OK`,
|
||||
);
|
||||
};
|
||||
|
||||
verifyUniquePackageVersion();
|
||||
Loading…
Reference in a new issue