Simplify create-twenty-app command (#20512)
## Simplify `create-twenty-app` for zero-interaction use Makes `npx create-twenty-app@latest my-app` a fully non-interactive, single-command experience suitable for automated environments (Codex, Claude plugins). ### Changes - **Remove all interactive prompts** — app name, display name, description, and scaffold confirmation are now derived from CLI args with sensible defaults. `inquirer` dependency removed entirely. - **Replace OAuth with API key auth** — use the seeded dev API key (`DEV_API_KEY`) to authenticate against the Docker instance as `tim@apple.dev`, eliminating the browser-based OAuth flow. - **Docker-first with early validation** — check Docker is installed before scaffolding; if missing, print the install URL and exit. Detect alternative runtimes (Podman, nerdctl). - **Parallel image pull** — `docker pull` runs in the background during scaffold + dependency install, saving 10-30s on typical runs. - **Always pull latest image** — ensures the dev server is up-to-date on every run. - **Stop detecting port 3000** — only check port 2020 (Docker instance). - **Update CLI flags** — remove `--skip-local-instance` and `--yes`; add `--skip-docker`. - **Update CI workflows and docs** — align e2e workflows, package README, and template README/cd.yml with the new flow.
This commit is contained in:
+2
@@ -7,6 +7,7 @@ import { ApplicationDevelopmentResolver } from 'src/engine/core-modules/applicat
|
||||
import { TokenModule } from 'src/engine/core-modules/auth/token/token.module';
|
||||
import { FeatureFlagModule } from 'src/engine/core-modules/feature-flag/feature-flag.module';
|
||||
import { FileStorageModule } from 'src/engine/core-modules/file-storage/file-storage.module';
|
||||
import { ThrottlerModule } from 'src/engine/core-modules/throttler/throttler.module';
|
||||
import { SdkClientModule } from 'src/engine/core-modules/sdk-client/sdk-client.module';
|
||||
import { PermissionsModule } from 'src/engine/metadata-modules/permissions/permissions.module';
|
||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||
@@ -21,6 +22,7 @@ import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/wor
|
||||
TokenModule,
|
||||
FileStorageModule,
|
||||
PermissionsModule,
|
||||
ThrottlerModule,
|
||||
],
|
||||
providers: [
|
||||
ApplicationDevelopmentResolver,
|
||||
|
||||
+31
-2
@@ -42,19 +42,21 @@ import { type WorkspaceEntity } from 'src/engine/core-modules/workspace/workspac
|
||||
import { AuthUser } from 'src/engine/decorators/auth/auth-user.decorator';
|
||||
import { AuthUserWorkspaceId } from 'src/engine/decorators/auth/auth-user-workspace-id.decorator';
|
||||
import { AuthWorkspace } from 'src/engine/decorators/auth/auth-workspace.decorator';
|
||||
import { DevelopmentGuard } from 'src/engine/guards/development.guard';
|
||||
import { SettingsPermissionGuard } from 'src/engine/guards/settings-permission.guard';
|
||||
import { WorkspaceAuthGuard } from 'src/engine/guards/workspace-auth.guard';
|
||||
import { ThrottlerService } from 'src/engine/core-modules/throttler/throttler.service';
|
||||
import { WorkspaceMigrationGraphqlApiExceptionInterceptor } from 'src/engine/workspace-manager/workspace-migration/interceptors/workspace-migration-graphql-api-exception.interceptor';
|
||||
import { streamToBuffer } from 'src/utils/stream-to-buffer';
|
||||
|
||||
const APP_DEV_RATE_LIMIT_MAX = 30;
|
||||
const APP_DEV_RATE_LIMIT_WINDOW_MS = 30_000;
|
||||
|
||||
@UsePipes(ResolverValidationPipe)
|
||||
@MetadataResolver()
|
||||
@UseInterceptors(WorkspaceMigrationGraphqlApiExceptionInterceptor)
|
||||
@UseFilters(ApplicationExceptionFilter)
|
||||
@UseGuards(
|
||||
WorkspaceAuthGuard,
|
||||
DevelopmentGuard,
|
||||
SettingsPermissionGuard(PermissionFlagType.APPLICATIONS),
|
||||
)
|
||||
export class ApplicationDevelopmentResolver {
|
||||
@@ -67,6 +69,7 @@ export class ApplicationDevelopmentResolver {
|
||||
private readonly fileStorageService: FileStorageService,
|
||||
private readonly sdkClientGenerationService: SdkClientGenerationService,
|
||||
private readonly twentyConfigService: TwentyConfigService,
|
||||
private readonly throttlerService: ThrottlerService,
|
||||
) {}
|
||||
|
||||
@Mutation(() => DevelopmentApplicationDTO)
|
||||
@@ -74,6 +77,8 @@ export class ApplicationDevelopmentResolver {
|
||||
@Args() { universalIdentifier, name }: CreateDevelopmentApplicationInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<DevelopmentApplicationDTO> {
|
||||
await this.throttlePerApplication(universalIdentifier, workspaceId);
|
||||
|
||||
const applicationRegistrationId =
|
||||
await this.findApplicationRegistrationId(universalIdentifier);
|
||||
|
||||
@@ -111,6 +116,8 @@ export class ApplicationDevelopmentResolver {
|
||||
@AuthUser({ allowUndefined: true }) user?: { id: string },
|
||||
@AuthUserWorkspaceId({ allowUndefined: true }) userWorkspaceId?: string,
|
||||
): Promise<ApplicationTokenPairDTO> {
|
||||
await this.throttlePerApplication(applicationId, workspaceId);
|
||||
|
||||
return this.applicationTokenService.generateApplicationTokenPair({
|
||||
workspaceId,
|
||||
applicationId,
|
||||
@@ -124,6 +131,11 @@ export class ApplicationDevelopmentResolver {
|
||||
@Args() { manifest }: ApplicationInput,
|
||||
@AuthWorkspace() { id: workspaceId }: WorkspaceEntity,
|
||||
): Promise<WorkspaceMigrationDTO> {
|
||||
await this.throttlePerApplication(
|
||||
manifest.application.universalIdentifier,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const applicationRegistrationId = await this.findApplicationRegistrationId(
|
||||
manifest.application.universalIdentifier,
|
||||
);
|
||||
@@ -187,6 +199,11 @@ export class ApplicationDevelopmentResolver {
|
||||
filePath,
|
||||
}: UploadApplicationFileInput,
|
||||
): Promise<FileDTO> {
|
||||
await this.throttlePerApplication(
|
||||
applicationUniversalIdentifier,
|
||||
workspaceId,
|
||||
);
|
||||
|
||||
const allowedApplicationFileFolders: FileFolder[] = [
|
||||
FileFolder.BuiltLogicFunction,
|
||||
FileFolder.BuiltFrontComponent,
|
||||
@@ -215,6 +232,18 @@ export class ApplicationDevelopmentResolver {
|
||||
});
|
||||
}
|
||||
|
||||
private async throttlePerApplication(
|
||||
applicationIdentifier: string,
|
||||
workspaceId: string,
|
||||
): Promise<void> {
|
||||
await this.throttlerService.tokenBucketThrottleOrThrow(
|
||||
`app-dev:${workspaceId}:${applicationIdentifier}`,
|
||||
1,
|
||||
APP_DEV_RATE_LIMIT_MAX,
|
||||
APP_DEV_RATE_LIMIT_WINDOW_MS,
|
||||
);
|
||||
}
|
||||
|
||||
private async findApplicationRegistrationId(
|
||||
universalIdentifier: string,
|
||||
): Promise<string> {
|
||||
|
||||
Reference in New Issue
Block a user