From e656ee4b60f62e6bf428ad1b1b6082167680bc7f Mon Sep 17 00:00:00 2001 From: Maor Barazani Date: Mon, 5 Feb 2024 00:17:36 +0200 Subject: [PATCH 1/4] create app command basic skeleton --- src/commands/app/create.ts | 39 ++++++++++++++++++++ src/consts/urls.ts | 4 ++ src/services/apps-service.ts | 30 +++++++++++++-- src/services/schemas/apps-service-schemas.ts | 6 +++ src/types/services/apps-service.ts | 3 +- 5 files changed, 78 insertions(+), 4 deletions(-) create mode 100644 src/commands/app/create.ts diff --git a/src/commands/app/create.ts b/src/commands/app/create.ts new file mode 100644 index 0000000..c3e99bf --- /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({ + port: Flags.integer({ + 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(); + 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..a0c2df1 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 (name?: string): Promise => { + try { + const path = createAppUrl(); + const url = appsUrlBuilder(path); + const response = await execute( + { + url, + headers: { Accept: 'application/json' }, + method: HttpMethodTypes.POST, + body: name ? { name } : undefined, + }, + 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; From 30531eac520b2d5adf54a7c732f61b5a463f316d Mon Sep 17 00:00:00 2001 From: Maor Barazani Date: Tue, 6 Feb 2024 16:42:51 +0200 Subject: [PATCH 2/4] fixes test --- src/commands/app/__tests__/create.test.ts | 21 +++++++++++++++++++++ src/commands/app/__tests__/list.test.ts | 6 +++--- src/commands/app/create.ts | 2 +- 3 files changed, 25 insertions(+), 4 deletions(-) create mode 100644 src/commands/app/__tests__/create.test.ts 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 index c3e99bf..cab19bc 100644 --- a/src/commands/app/create.ts +++ b/src/commands/app/create.ts @@ -12,7 +12,7 @@ export default class AppCreate extends AuthenticatedCommand { static examples = ['<%= config.bin %> <%= command.id %>', '<%= config.bin %> <%= command.id %> -n NEW_APP_NAME']; static flags = AppCreate.serializeFlags({ - port: Flags.integer({ + name: Flags.string({ char: 'n', description: 'Name your new app.', required: false, From 5109a492d6dca6a960c1c3c2f7aa48418ceac43d Mon Sep 17 00:00:00 2001 From: Maor Barazani Date: Wed, 7 Feb 2024 17:19:21 +0200 Subject: [PATCH 3/4] version bump --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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", From 8da1e7ce75a3c52644e8f5fa55e91d1ed6f911b8 Mon Sep 17 00:00:00 2001 From: Maor Barazani Date: Wed, 7 Feb 2024 22:35:23 +0200 Subject: [PATCH 4/4] minor fix --- src/commands/app/create.ts | 2 +- src/services/apps-service.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/commands/app/create.ts b/src/commands/app/create.ts index cab19bc..1d1b299 100644 --- a/src/commands/app/create.ts +++ b/src/commands/app/create.ts @@ -27,7 +27,7 @@ export default class AppCreate extends AuthenticatedCommand { const { name } = flags; logger.debug(`invoking create app: name=${name}`, this.DEBUG_TAG); - const app = await createApp(); + const app = await createApp({ name }); logger.success(`App created successfully: ${app.name} (id: ${app.id})`); } catch (error: any) { logger.debug(error, this.DEBUG_TAG); diff --git a/src/services/apps-service.ts b/src/services/apps-service.ts index a0c2df1..8298011 100644 --- a/src/services/apps-service.ts +++ b/src/services/apps-service.ts @@ -29,7 +29,7 @@ export const listApps = async (): Promise> => { } }; -export const createApp = async (name?: string): Promise => { +export const createApp = async (body?: { name?: string }): Promise => { try { const path = createAppUrl(); const url = appsUrlBuilder(path); @@ -38,7 +38,7 @@ export const createApp = async (name?: string): Promise => { url, headers: { Accept: 'application/json' }, method: HttpMethodTypes.POST, - body: name ? { name } : undefined, + body, }, createAppSchema, );