diff --git a/package.json b/package.json index d36ff81..0b2a9dc 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mondaycom/apps-cli", - "version": "2.3.0", + "version": "2.3.1", "description": "A cli tool to manage apps (and monday-code projects) in monday.com", "author": "monday.com Apps Team", "type": "module", diff --git a/src/commands/app/__tests__/create.test.ts b/src/commands/app/__tests__/create.test.ts new file mode 100644 index 0000000..d4b6e09 --- /dev/null +++ b/src/commands/app/__tests__/create.test.ts @@ -0,0 +1,21 @@ +import AppCreate from 'commands/app/create'; +import { buildMockFlags, getStdout, mockRequestResolvedValueOnce } from 'test/cli-test-utils'; + +describe('app:create', () => { + const mockAppCreateResponse = { + app: { + id: 1_000_000_001, + name: 'New App by CLI', + state: 'active', + kind: 'private', + }, + }; + + it('should create app successfully', async () => { + const mockPushFlags = buildMockFlags(AppCreate, { name: 'New App by CLI' }); + mockRequestResolvedValueOnce(mockAppCreateResponse); + await AppCreate.run(mockPushFlags); + const stdout = getStdout(); + expect(stdout).toContain('App created successfully: New App by CLI (id: 1000000001)'); + }); +}); diff --git a/src/commands/app/__tests__/list.test.ts b/src/commands/app/__tests__/list.test.ts index d41f224..739acbd 100644 --- a/src/commands/app/__tests__/list.test.ts +++ b/src/commands/app/__tests__/list.test.ts @@ -1,5 +1,5 @@ import AppList from 'commands/app/list'; -import { getStderr, getStdout, mockRequestResolvedValueOnce } from 'test/cli-test-utils'; +import { getStdout, mockRequestResolvedValueOnce } from 'test/cli-test-utils'; describe('app:list', () => { const mockAppListResponse = { @@ -26,7 +26,7 @@ describe('app:list', () => { it('should print message if no apps', async () => { mockRequestResolvedValueOnce({ apps: [] }); await AppList.run(); - const stderr = getStderr(); - expect(stderr).toContain('No apps found'); + const stdout = getStdout(); + expect(stdout).toContain('No apps found'); }); }); diff --git a/src/commands/app/create.ts b/src/commands/app/create.ts new file mode 100644 index 0000000..1d1b299 --- /dev/null +++ b/src/commands/app/create.ts @@ -0,0 +1,39 @@ +import { Flags } from '@oclif/core'; + +import { AuthenticatedCommand } from 'commands-base/authenticated-command'; +import { createApp } from 'services/apps-service'; +import logger from 'utils/logger'; + +export default class AppCreate extends AuthenticatedCommand { + static description = 'Create an app.'; + + static withPrintCommand = false; + + static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> -n NEW_APP_NAME']; + + static flags = AppCreate.serializeFlags({ + name: Flags.string({ + char: 'n', + description: 'Name your new app.', + required: false, + }), + }); + + DEBUG_TAG = 'app_create'; + + public async run(): Promise { + try { + const { flags } = await this.parse(AppCreate); + const { name } = flags; + + logger.debug(`invoking create app: name=${name}`, this.DEBUG_TAG); + const app = await createApp({ name }); + logger.success(`App created successfully: ${app.name} (id: ${app.id})`); + } catch (error: any) { + logger.debug(error, this.DEBUG_TAG); + + // need to signal to the parent process that the command failed + process.exit(1); + } + } +} diff --git a/src/consts/urls.ts b/src/consts/urls.ts index 4ce0bc5..2b0d76d 100644 --- a/src/consts/urls.ts +++ b/src/consts/urls.ts @@ -61,6 +61,10 @@ export const listAppsUrl = (): string => { return BASE_APPS_URL; }; +export const createAppUrl = (): string => { + return BASE_APPS_URL; +}; + export const listAppVersionsByAppIdUrl = (appId: AppId): string => { return `${BASE_APPS_URL}/${appId}/versions`; }; diff --git a/src/services/apps-service.ts b/src/services/apps-service.ts index d2f6a62..8298011 100644 --- a/src/services/apps-service.ts +++ b/src/services/apps-service.ts @@ -1,9 +1,9 @@ -import { listAppsUrl } from 'consts/urls'; +import { createAppUrl, listAppsUrl } from 'consts/urls'; import { execute } from 'services/api-service'; -import { listAppSchema } from 'services/schemas/apps-service-schemas'; +import { createAppSchema, listAppSchema } from 'services/schemas/apps-service-schemas'; import { HttpError } from 'types/errors'; import { HttpMethodTypes } from 'types/services/api-service'; -import { App, ListAppResponse } from 'types/services/apps-service'; +import { App, CreateAppResponse, ListAppResponse } from 'types/services/apps-service'; import { appsUrlBuilder } from 'utils/urls-builder'; export const listApps = async (): Promise> => { @@ -28,3 +28,27 @@ export const listApps = async (): Promise> => { throw new Error('Failed to list apps.'); } }; + +export const createApp = async (body?: { name?: string }): Promise => { + try { + const path = createAppUrl(); + const url = appsUrlBuilder(path); + const response = await execute( + { + url, + headers: { Accept: 'application/json' }, + method: HttpMethodTypes.POST, + body, + }, + createAppSchema, + ); + + return response.app; + } catch (error: any) { + if (error instanceof HttpError) { + throw error; + } + + throw new Error('Failed to create app.'); + } +}; diff --git a/src/services/schemas/apps-service-schemas.ts b/src/services/schemas/apps-service-schemas.ts index cef84bb..f974c66 100644 --- a/src/services/schemas/apps-service-schemas.ts +++ b/src/services/schemas/apps-service-schemas.ts @@ -13,3 +13,9 @@ export const listAppSchema = z apps: z.array(appSchema), }) .merge(baseResponseHttpMetaDataSchema); + +export const createAppSchema = z + .object({ + app: appSchema, + }) + .merge(baseResponseHttpMetaDataSchema); diff --git a/src/types/services/apps-service.ts b/src/types/services/apps-service.ts index 5741228..acd7b2c 100644 --- a/src/types/services/apps-service.ts +++ b/src/types/services/apps-service.ts @@ -1,6 +1,7 @@ import { z } from 'zod'; -import { appSchema, listAppSchema } from 'services/schemas/apps-service-schemas'; +import { appSchema, createAppSchema, listAppSchema } from 'services/schemas/apps-service-schemas'; export type App = z.infer; export type ListAppResponse = z.infer; +export type CreateAppResponse = z.infer;