Skip to content

Commit

Permalink
Feature/shayel/add support for multi region param interactive (#96)
Browse files Browse the repository at this point in the history
* feature: add support for multi region param

* feature: add support for multi region param

* feature: update version

* feature: [beta] added interactive

* feature: [beta] added interactive

* feature: [beta] added interactive

* feature: [beta] added interactive

* feature: [beta] added interactive

* feature: fix name of type

* feature: fix version

* feature: fix version
  • Loading branch information
ShayElkana authored Jun 3, 2024
1 parent a8e737e commit ac5e8ce
Show file tree
Hide file tree
Showing 15 changed files with 150 additions and 22 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@mondaycom/apps-cli",
"version": "2.6.2-beta.1",
"version": "2.6.1",
"description": "A cli tool to manage apps (and monday-code projects) in monday.com",
"author": "monday.com Apps Team",
"type": "module",
Expand Down
11 changes: 8 additions & 3 deletions src/commands/app/deploy.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { getManifestAssetPath, readManifestFile } from 'services/manifest-servic
import { getTasksForClientSide, getTasksForServerSide } from 'services/share/deploy';
import { ManifestHostingType } from 'types/services/manifest-service';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const MESSAGES = {
directory: 'Directory path of you project in your machine. If not included will use the current working directory.',
Expand Down Expand Up @@ -68,6 +68,11 @@ export default class AppDeploy extends AuthenticatedCommand {
appId = appId || manifestFileData.app.id;

appVersionId = await this.getAppVersionId(appVersionId, appId, force);

const selectedRegion = await chooseRegionIfNeeded(region, {
appVersionId: Number(appVersionId),
});

this.preparePrintCommand(this, { appVersionId: appVersionId, directoryPath: manifestFileData });

const { cdn, server } = manifestFileData.app?.hosting || {};
Expand All @@ -76,7 +81,7 @@ export default class AppDeploy extends AuthenticatedCommand {
await getTasksForClientSide(
Number(appVersionId),
getManifestAssetPath(manifestFileDir, cdn.path),
region,
selectedRegion,
).run();
}

Expand All @@ -85,7 +90,7 @@ export default class AppDeploy extends AuthenticatedCommand {
await getTasksForServerSide(
Number(appVersionId),
getManifestAssetPath(manifestFileDir, server.path),
region,
selectedRegion,
).run();
}
} catch (error) {
Expand Down
5 changes: 4 additions & 1 deletion src/commands/app/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ import { App } from 'types/services/apps-service';
import logger from 'utils/logger';

const printApps = (apps: Array<App>) => {
logger.table(apps);
const cleanedApps = apps.map(app => {
return { id: app.id, name: app.name };
});
logger.table(cleanedApps);
};

export default class AppList extends AuthenticatedCommand {
Expand Down
8 changes: 5 additions & 3 deletions src/commands/code/env.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { PromptService } from 'services/prompt-service';
import { ManageAppEnvFlags } from 'types/commands/manage-app-env';
import { AppId } from 'types/general';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const MODES_WITH_KEYS: Array<APP_ENV_MANAGEMENT_MODES> = [
APP_ENV_MANAGEMENT_MODES.SET,
Expand Down Expand Up @@ -107,12 +107,14 @@ export default class Env extends AuthenticatedCommand {
appId = Number(await DynamicChoicesService.chooseApp());
}

const selectedRegion = await chooseRegionIfNeeded(region, { appId });

mode = await promptForModeIfNotProvided(mode);
key = await promptForKeyIfNotProvided(mode, appId, key);
value = await promptForValueIfNotProvided(mode, value);
this.preparePrintCommand(this, { appId, mode, key, value });
this.preparePrintCommand(this, { appId, mode, key, value, region: selectedRegion });

await handleEnvironmentRequest(appId, mode, key, value, region);
await handleEnvironmentRequest(appId, mode, key, value, selectedRegion);
} catch (error: any) {
logger.debug(error, this.DEBUG_TAG);

Expand Down
6 changes: 3 additions & 3 deletions src/commands/code/logs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { PromptService } from 'services/prompt-service';
import { EventSource, LogType, LogsCommandArguments, LogsFilterCriteriaArguments } from 'types/commands/logs';
import { isDefined } from 'utils/guards';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';
import { TIME_IN_MILLISECONDS } from 'utils/time-enum';
import { getDayDiff, isDate } from 'utils/validations';

Expand Down Expand Up @@ -89,7 +89,7 @@ export default class Logs extends AuthenticatedCommand {
const { logsStartDate, logsEndDate, logSearchFromText, region: strRegion } = flags;
const region = getRegionFromString(strRegion);
const appVersionId = await this.getAppVersionId(flags.appVersionId);

const selectedRegion = await chooseRegionIfNeeded(region, { appVersionId });
const eventSource = (flags.eventSource || (await eventSourcePrompt())) as EventSource;
const logsType = await this.getLogType(eventSource, flags.logsType);
const logsFilterCriteria = await this.getLogsFilterCriteria(
Expand All @@ -114,7 +114,7 @@ export default class Logs extends AuthenticatedCommand {
logSearchFromText: logsFilterCriteria?.text,
});

const clientChannel = await logsStream(args.appVersionId, args.logsType, logsFilterCriteria, region);
const clientChannel = await logsStream(args.appVersionId, args.logsType, logsFilterCriteria, selectedRegion);
await streamMessages(clientChannel);
} catch (error: any) {
logger.debug(error, this.DEBUG_TAG);
Expand Down
6 changes: 4 additions & 2 deletions src/commands/code/push.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ import { APP_ID_TO_ENTER, APP_VERSION_ID_TO_ENTER } from 'consts/messages';
import { DynamicChoicesService } from 'services/dynamic-choices-service';
import { getTasksForServerSide } from 'services/share/deploy';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const MESSAGES = {
directory: 'Directory path of you project in your machine. If not included will use the current working directory.',
Expand Down Expand Up @@ -65,10 +65,12 @@ export default class Push extends AuthenticatedCommand {
appVersionId = appAndAppVersion.appVersionId;
}

const selectedRegion = await chooseRegionIfNeeded(region, { appVersionId });

logger.debug(`push code to appVersionId: ${appVersionId}`, this.DEBUG_TAG);
this.preparePrintCommand(this, { appVersionId, directoryPath: directoryPath });

const tasks = getTasksForServerSide(appVersionId, directoryPath, region);
const tasks = getTasksForServerSide(appVersionId, directoryPath, selectedRegion);

await tasks.run();
} catch (error: any) {
Expand Down
10 changes: 7 additions & 3 deletions src/commands/code/status.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { getMondayCodeBuild } from 'src/services/app-builds-service';
import { HttpError } from 'types/errors';
import { AppVersionDeploymentStatus } from 'types/services/push-service';
import logger from 'utils/logger';
import { addRegionToFlags, getRegionFromString } from 'utils/region';
import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region';

const DEBUG_TAG = 'code_status';

Expand Down Expand Up @@ -58,9 +58,11 @@ export default class Status extends AuthenticatedCommand {
appVersionId = appAndAppVersion.appVersionId;
}

const selectedRegion = await chooseRegionIfNeeded(region, { appVersionId });

this.preparePrintCommand(this, { appVersionId });
const deploymentStatus = await getAppVersionDeploymentStatus(appVersionId, region);
const mondayCodeRelease = await getMondayCodeBuild(appVersionId, region);
const deploymentStatus = await getAppVersionDeploymentStatus(appVersionId, selectedRegion);
const mondayCodeRelease = await getMondayCodeBuild(appVersionId, selectedRegion);

if (deploymentStatus.deployment) {
deploymentStatus.deployment.liveUrl = mondayCodeRelease?.data?.liveUrl;
Expand All @@ -71,6 +73,8 @@ export default class Status extends AuthenticatedCommand {
logger.debug({ res: error }, DEBUG_TAG);
if (error instanceof HttpError && error.code === StatusCodes.NOT_FOUND) {
logger.error(`No deployment found for provided app version id - "${appVersionId || VAR_UNKNOWN}"`);
} else if (error instanceof HttpError && error.code === 400) {
logger.error(error.message);
} else {
logger.error(
`An unknown error happened while fetching deployment status for app version id - "${
Expand Down
7 changes: 6 additions & 1 deletion src/consts/urls.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { AppFeatureType } from 'src/types/services/app-features-service';
import { LogType, LogsFilterCriteriaArguments } from 'types/commands/logs';
import { AppId } from 'types/general';
import { AppId, AppVersionId } from 'types/general';
import { Region } from 'types/general/region';

const BASE_APPS_URL = '/api/apps';
const BASE_VERSIONS_URL = '/api/app-versions';
const BASE_APP_VERSIONS_URL = '/api/app-versions';
const BASE_MONDAY_CODE_URL = '/api/code';

Expand Down Expand Up @@ -73,6 +74,10 @@ export const listAppVersionsByAppIdUrl = (appId: AppId): string => {
return `${BASE_APPS_URL}/${appId}/versions`;
};

export const getAppVersionsByAppIdUrl = (appVersionId: AppVersionId): string => {
return `${BASE_VERSIONS_URL}/${appVersionId}`;
};

export const appEnvironmentUrl = (appId: AppId, key: string): string => {
return `/api/code/${appId}/env/${key}`;
};
Expand Down
33 changes: 29 additions & 4 deletions src/services/app-versions-service.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { APP_VERSION_STATUS } from 'consts/app-versions';
import { listAppVersionsByAppIdUrl } from 'consts/urls';
import { getAppVersionsByAppIdUrl, listAppVersionsByAppIdUrl } from 'consts/urls';
import { execute } from 'services/api-service';
import { listAppVersionsSchema } from 'services/schemas/app-versions-schemas';
import { getAppVersionSchema, listAppVersionsSchema } from 'services/schemas/app-versions-schemas';
import logger from 'src/utils/logger';
import { HttpError } from 'types/errors';
import { AppId } from 'types/general';
import { AppId, AppVersionId } from 'types/general';
import { HttpMethodTypes } from 'types/services/api-service';
import { AppVersion, ListAppVersionsResponse } from 'types/services/app-versions-service';
import { AppVersion, GetAppVersionResponse, ListAppVersionsResponse } from 'types/services/app-versions-service';
import { appsUrlBuilder } from 'utils/urls-builder';

export const listAppVersionsByAppId = async (appId: AppId): Promise<Array<AppVersion>> => {
Expand Down Expand Up @@ -51,3 +51,28 @@ export const defaultVersionByAppId = async (appId: AppId, useLiveVersion = false

return validVersion;
};

export const getAppVersionById = async (appVersionId: AppVersionId): Promise<AppVersion> => {
try {
const path = getAppVersionsByAppIdUrl(appVersionId);
const url = appsUrlBuilder(path);
logger.debug(`fetching logs url: ${url}`);
const response = await execute<GetAppVersionResponse>(
{
url,
headers: { Accept: 'application/json' },
method: HttpMethodTypes.GET,
},
getAppVersionSchema,
);

return response.appVersion;
} catch (error: any) {
if (error instanceof HttpError) {
logger.error(error.message);
throw error;
}

throw new Error('Failed to list app versions.');
}
};
7 changes: 7 additions & 0 deletions src/services/apps-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,10 @@ export const createFeatures = async (ctx: AppCreateCommandTasksContext) => {

await Promise.all(createFeaturesPromises);
};

export const checkIfAppSupportMultiRegion = async (appId: number): Promise<boolean> => {
const apps = await listApps();
const app = apps.find(app => app.id === appId);
if (!app) throw new Error(`App with id ${appId} not found.`);
return Boolean(app.mondayCodeConfig?.isMultiRegion);
};
8 changes: 8 additions & 0 deletions src/services/schemas/app-versions-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { z } from 'zod';

import { APP_VERSION_STATUS } from 'consts/app-versions';
import { baseResponseHttpMetaDataSchema } from 'services/schemas/api-service-schemas';
import { mondayCodeConfigSchema } from 'services/schemas/apps-service-schemas';
import { appIdSchema, appVersionIdSchema } from 'services/schemas/general-schemas';

export const appVersionSchema = z.object({
Expand All @@ -10,10 +11,17 @@ export const appVersionSchema = z.object({
versionNumber: z.string(),
appId: appIdSchema,
status: z.nativeEnum(APP_VERSION_STATUS),
mondayCodeConfig: mondayCodeConfigSchema,
});

export const listAppVersionsSchema = z
.object({
appVersions: z.array(appVersionSchema),
})
.merge(baseResponseHttpMetaDataSchema);

export const getAppVersionSchema = z
.object({
appVersion: appVersionSchema,
})
.merge(baseResponseHttpMetaDataSchema);
6 changes: 6 additions & 0 deletions src/services/schemas/apps-service-schemas.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,15 @@ import { z } from 'zod';
import { baseResponseHttpMetaDataSchema } from 'services/schemas/api-service-schemas';
import { appIdSchema } from 'services/schemas/general-schemas';

export const mondayCodeConfigSchema = z
.object({
isMultiRegion: z.boolean().optional(),
})
.optional();
export const appSchema = z.object({
id: appIdSchema,
name: z.string(),
mondayCodeConfig: mondayCodeConfigSchema,
});

export const listAppSchema = z
Expand Down
3 changes: 2 additions & 1 deletion src/types/services/app-versions-service.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import { z } from 'zod';

import { appVersionSchema, listAppVersionsSchema } from 'services/schemas/app-versions-schemas';
import { appVersionSchema, getAppVersionSchema, listAppVersionsSchema } from 'services/schemas/app-versions-schemas';

export type AppVersion = z.infer<typeof appVersionSchema>;
export type GetAppVersionResponse = z.infer<typeof getAppVersionSchema>;
export type ListAppVersionsResponse = z.infer<typeof listAppVersionsSchema>;
44 changes: 44 additions & 0 deletions src/utils/region.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
import { Flags } from '@oclif/core';

import { getAppVersionById } from 'services/app-versions-service';
import { checkIfAppSupportMultiRegion } from 'services/apps-service';
import { PromptService } from 'services/prompt-service';
import { Region } from 'types/general/region';
import { Permissions } from 'types/utils/permissions';
import { isPermitted } from 'utils/permissions';
import { isANumber } from 'utils/validations';

export const addRegionToQuery = (query: object | undefined, region?: Region) => {
if (region) {
Expand Down Expand Up @@ -34,3 +38,43 @@ export function addRegionToFlags<T>(flags: T): T {

return flags;
}

const regionsPrompt = async () =>
PromptService.promptList('Choose region', [Region.US, Region.EU, Region.AU], Region.US);

export async function chooseRegionIfNeeded(
region?: Region,
options?: { appId?: number; appVersionId?: number },
): Promise<Region | undefined> {
if (region || !isPermitted(Permissions.MCODE_MULTI_REGION)) {
return region;
}

const { appId, appVersionId } = options || {};

let isMultiRegionApp = false;

let _appId = appId;
if (appVersionId && isANumber(appVersionId)) {
const appVersion = await getAppVersionById(appVersionId);
if (!appVersion) throw new Error(`AppVersion with id ${appVersionId} not found.`);
_appId = appVersion.appId;
if (appVersion?.mondayCodeConfig?.isMultiRegion) {
isMultiRegionApp = true;
}
}

if (!isMultiRegionApp && _appId && isANumber(_appId)) {
const isAppSupportMultiRegion = await checkIfAppSupportMultiRegion(_appId);
if (isAppSupportMultiRegion) {
isMultiRegionApp = true;
}
}

if (!isMultiRegionApp) {
return region;
}

const returnedRegion = await regionsPrompt();
return getRegionFromString(returnedRegion);
}
16 changes: 16 additions & 0 deletions src/utils/validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,19 @@ export const getDayDiff = (fromDate: Date, toDate: Date): number | null => {
const diffInMS = toDate.getTime() - fromDate.getTime();
return diffInMS / TIME_IN_MILLISECONDS.DAY;
};

export function isDefined<T>(input: T | null | undefined): input is T {
return input !== null && input !== undefined;
}

/***
* This function receives an input (string or number) and returns true if it is a number
* @param input - The input to check if it is a number
* @returns true if the input is a number
*/
export function isANumber(input: unknown): input is number {
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
// @ts-ignore
// eslint-disable-next-line @typescript-eslint/restrict-template-expressions
return isDefined(input) && `${Number(input)}` === `${input}` && Number.isFinite(input);
}

0 comments on commit ac5e8ce

Please sign in to comment.