## 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
98 lines
2.7 KiB
JavaScript
98 lines
2.7 KiB
JavaScript
#!/usr/bin/env node
|
|
import chalk from 'chalk';
|
|
import { Command, CommanderError } from 'commander';
|
|
import { CreateAppCommand } from '@/create-app.command';
|
|
import { type ScaffoldingMode } from '@/types/scaffolding-options';
|
|
import packageJson from '../package.json';
|
|
|
|
const program = new Command(packageJson.name)
|
|
.description('CLI tool to initialize a new Twenty application')
|
|
.version(
|
|
packageJson.version,
|
|
'-v, --version',
|
|
'Output the current version of create-twenty-app.',
|
|
)
|
|
.argument('[directory]')
|
|
.option('-e, --exhaustive', 'Create all example entities (default)')
|
|
.option(
|
|
'-m, --minimal',
|
|
'Create only core entities (application-config and default-role)',
|
|
)
|
|
.option('-n, --name <name>', 'Application name (skips prompt)')
|
|
.option(
|
|
'-d, --display-name <displayName>',
|
|
'Application display name (skips prompt)',
|
|
)
|
|
.option(
|
|
'--description <description>',
|
|
'Application description (skips prompt)',
|
|
)
|
|
.option(
|
|
'--skip-local-instance',
|
|
'Skip the local Twenty instance setup prompt',
|
|
)
|
|
.helpOption('-h, --help', 'Display this help message.')
|
|
.action(
|
|
async (
|
|
directory?: string,
|
|
options?: {
|
|
exhaustive?: boolean;
|
|
minimal?: boolean;
|
|
name?: string;
|
|
displayName?: string;
|
|
description?: string;
|
|
skipLocalInstance?: boolean;
|
|
},
|
|
) => {
|
|
const modeFlags = [options?.exhaustive, options?.minimal].filter(Boolean);
|
|
|
|
if (modeFlags.length > 1) {
|
|
console.error(
|
|
chalk.red(
|
|
'Error: --exhaustive and --minimal are mutually exclusive.',
|
|
),
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (directory && !/^[a-z0-9-]+$/.test(directory)) {
|
|
console.error(
|
|
chalk.red(
|
|
`Invalid directory "${directory}". Must contain only lowercase letters, numbers, and hyphens`,
|
|
),
|
|
);
|
|
process.exit(1);
|
|
}
|
|
|
|
if (options?.name !== undefined && options.name.trim().length === 0) {
|
|
console.error(chalk.red('Error: --name cannot be empty.'));
|
|
process.exit(1);
|
|
}
|
|
|
|
const mode: ScaffoldingMode = options?.minimal ? 'minimal' : 'exhaustive';
|
|
|
|
await new CreateAppCommand().execute({
|
|
directory,
|
|
mode,
|
|
name: options?.name,
|
|
displayName: options?.displayName,
|
|
description: options?.description,
|
|
skipLocalInstance: options?.skipLocalInstance,
|
|
});
|
|
},
|
|
);
|
|
|
|
program.exitOverride();
|
|
|
|
try {
|
|
program.parse();
|
|
} catch (error) {
|
|
if (error instanceof CommanderError) {
|
|
process.exit(error.exitCode);
|
|
}
|
|
if (error instanceof Error) {
|
|
console.error(chalk.red('Error:'), error.message);
|
|
process.exit(1);
|
|
}
|
|
}
|