diff --git a/package.json b/package.json index 46d9f54..09f18cb 100755 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@mondaycom/apps-cli", - "version": "4.1.1", + "version": "4.2.0", "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/code/push.ts b/src/commands/code/push.ts index ad53356..b72a035 100644 --- a/src/commands/code/push.ts +++ b/src/commands/code/push.ts @@ -3,6 +3,8 @@ import { Flags } from '@oclif/core'; import { AuthenticatedCommand } from 'commands-base/authenticated-command'; import { APP_ID_TO_ENTER, APP_VERSION_ID_TO_ENTER } from 'consts/messages'; import { DynamicChoicesService } from 'services/dynamic-choices-service'; +import { getCurrentWorkingDirectory } from 'services/env-service'; +import { validateIfCanBuild } from 'services/files-service'; import { getTasksForServerSide } from 'services/share/deploy'; import logger from 'utils/logger'; import { addRegionToFlags, chooseRegionIfNeeded, getRegionFromString } from 'utils/region'; @@ -52,6 +54,7 @@ export default class Push extends AuthenticatedCommand { const { directoryPath, region: strRegion } = flags; const region = getRegionFromString(strRegion); let appVersionId = flags.appVersionId; + validateIfCanBuild(directoryPath || getCurrentWorkingDirectory()); try { if (!appVersionId) { diff --git a/src/services/__tests__/mondaycoderc-schema.test.ts b/src/services/__tests__/mondaycoderc-schema.test.ts new file mode 100644 index 0000000..cb83c88 --- /dev/null +++ b/src/services/__tests__/mondaycoderc-schema.test.ts @@ -0,0 +1,38 @@ +import { mondaycodercSchema } from 'services/schemas/mondaycoderc-schema'; + +describe('mondaycodercSchema Validation', () => { + it('should validate a correct Python runtime and version', () => { + const data = { RUNTIME: 'Python', RUNTIME_VERSION: '3.10.1' }; + expect(() => mondaycodercSchema.parse(data)).not.toThrow(); + }); + + it('should invalidate an incorrect Python runtime version', () => { + const data = { RUNTIME: 'Python', RUNTIME_VERSION: '2.7.0' }; + expect(() => mondaycodercSchema.parse(data)).toThrow('Invalid RUNTIME_VERSION'); + }); + + it('should validate a correct Java runtime and version', () => { + const data = { RUNTIME: 'Java', RUNTIME_VERSION: '17' }; + expect(() => mondaycodercSchema.parse(data)).not.toThrow(); + }); + + it('should validate a missing runtime version when runtime is specified', () => { + const data = { RUNTIME: 'Java' }; + expect(() => mondaycodercSchema.parse(data)).not.toThrow(); + }); + + it('should validate when runtime is not specified', () => { + const data = {}; + expect(() => mondaycodercSchema.parse(data)).not.toThrow(); + }); + + it('should invalidate an incorrect Go runtime version', () => { + const data = { RUNTIME: 'Go', RUNTIME_VERSION: '2.0.0' }; + expect(() => mondaycodercSchema.parse(data)).toThrow('Invalid RUNTIME_VERSION'); + }); + + it('should invalidate an Unsupported Runtime', () => { + const data = { RUNTIME: 'Invalid', RUNTIME_VERSION: '1.0.0' }; + expect(() => mondaycodercSchema.parse(data)).toThrow('Invalid Runtime'); + }); +}); diff --git a/src/services/files-service.ts b/src/services/files-service.ts index 0ac65cb..6d4bba0 100644 --- a/src/services/files-service.ts +++ b/src/services/files-service.ts @@ -5,8 +5,10 @@ import path from 'node:path'; import archiver from 'archiver'; import glob from 'glob'; import parseGitIgnore from 'parse-gitignore'; +import { ZodError } from 'zod'; import { CONFIG_NAME } from 'services/config-service'; +import { mondaycodercSchema } from 'services/schemas/mondaycoderc-schema'; import logger from '../utils/logger.js'; @@ -113,18 +115,33 @@ export const createGitignoreAndAppendConfigFileIfNeeded = (directoryPath: string **/ export const validateIfCanBuild = (directoryPath: string): void => { const filePath = path.join(directoryPath, 'yarn.lock'); - if (!checkIfFileExists(filePath)) { - return; + if (checkIfFileExists(filePath)) { + const packageJsonPath = path.join(directoryPath, 'package.json'); + const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8'); + const packageJson = JSON.parse(packageJsonContent) as { scripts?: { build?: string } }; + const hasBuildCommand = packageJson?.scripts?.build; + if (hasBuildCommand) { + throw new Error( + 'monday-code does not support yarn projects with a build command. If you need a build step, use npm instead', + ); + } } - const packageJsonPath = path.join(directoryPath, 'package.json'); - const packageJsonContent = fs.readFileSync(packageJsonPath, 'utf8'); - const packageJson = JSON.parse(packageJsonContent) as { scripts?: { build?: string } }; - const hasBuildCommand = packageJson?.scripts?.build; - if (hasBuildCommand) { - throw new Error( - 'monday-code does not support yarn projects with a build command. If you need a build step, use npm instead', - ); + const rcFilePath = path.join(directoryPath, '.mondaycoderc'); + if (checkIfFileExists(rcFilePath)) { + const rcFileContent = JSON.parse(fs.readFileSync(rcFilePath, 'utf8')) as { + RUNTIME: string; + RUNTIME_VERSION: string; + }; + try { + mondaycodercSchema.parse(rcFileContent); + } catch (error) { + if (error instanceof ZodError) { + throw new TypeError(error.errors[0].message); + } + + throw error; + } } }; diff --git a/src/services/push-service.ts b/src/services/push-service.ts index d97e4cd..de11703 100644 --- a/src/services/push-service.ts +++ b/src/services/push-service.ts @@ -11,7 +11,6 @@ import { createTarGzArchive, readFileData, readZipFileAsBuffer, - validateIfCanBuild, verifyClientDirectory, } from 'services/files-service'; import { pollPromise } from 'services/polling-service'; @@ -192,7 +191,6 @@ export const buildAssetToDeployTask = async ( } task.output = `Building asset to deploy from "${ctx.directoryPath}" directory`; - validateIfCanBuild(ctx.directoryPath); const archivePath = await createTarGzArchive(ctx.directoryPath, 'code'); ctx.archivePath = archivePath; ctx.showPrepareEnvironmentTask = true; diff --git a/src/services/schemas/mondaycoderc-schema.ts b/src/services/schemas/mondaycoderc-schema.ts new file mode 100644 index 0000000..49de3b0 --- /dev/null +++ b/src/services/schemas/mondaycoderc-schema.ts @@ -0,0 +1,80 @@ +import { z } from 'zod'; + +export const mondaycodercSchema = z + .object({ + RUNTIME: z + .enum(['Python', 'Java', 'Go', 'PHP', 'Ruby', 'Node.js', 'NETCore'], { + errorMap: () => ({ + message: + 'Invalid Runtime in .mondaycoderc. Supported runtimes are Python, Java, Go, PHP, Ruby, Node.js, NETCore', + }), + }) + .optional(), + RUNTIME_VERSION: z.string().optional(), + }) + .strict() + .refine(data => { + if (data.RUNTIME_VERSION) { + if (data.RUNTIME === 'Python') { + if (!/^3\.(10|11|12)\.\d+$/.test(data.RUNTIME_VERSION || '')) { + throw new Error( + 'Invalid RUNTIME_VERSION for Python in .mondaycoderc. Allowed versions are 3.10.x, 3.11.x, 3.12.x', + ); + } + + return true; + } + + if (data.RUNTIME === 'Java') { + if (!['11', '17', '18'].includes(data.RUNTIME_VERSION || '')) { + throw new Error('Invalid RUNTIME_VERSION for Java in .mondaycoderc. Allowed versions are 11, 17, 18'); + } + + return true; + } + + if (data.RUNTIME === 'Go') { + if (!/^1\.\d+\.\d+$/.test(data.RUNTIME_VERSION || '')) { + throw new Error('Invalid RUNTIME_VERSION for Go in .mondaycoderc. Allowed versions are 1.x.x'); + } + + return true; + } + + if (data.RUNTIME === 'PHP') { + if (!/^8\.(1|2)\.\d+$/.test(data.RUNTIME_VERSION || '')) { + throw new Error('Invalid RUNTIME_VERSION for PHP in .mondaycoderc. Allowed versions are 8.1.x, 8.2.x'); + } + + return true; + } + + if (data.RUNTIME === 'Ruby') { + if (!/^3\.(1|2)\.\d+$/.test(data.RUNTIME_VERSION || '')) { + throw new Error('Invalid RUNTIME_VERSION for Ruby in .mondaycoderc. Allowed versions are 3.1.x, 3.2.x'); + } + + return true; + } + + if (data.RUNTIME === 'Node.js') { + if (!/^(12|14|16|18|20)\.\d+\.\d+$/.test(data.RUNTIME_VERSION || '')) { + throw new Error( + 'Invalid RUNTIME_VERSION for Node.js in .mondaycoderc. Allowed versions are 12.x.x, 14.x.x, 16.x.x, 18.x.x, 20.x.x', + ); + } + + return true; + } + + if (data.RUNTIME === 'NETCore') { + if (!/^(6|7)\.\d+$/.test(data.RUNTIME_VERSION || '')) { + throw new Error('Invalid RUNTIME_VERSION for NETCore in .mondaycoderc. Allowed versions are 6.x, 7.x'); + } + + return true; + } + } + + return true; + }); diff --git a/tsconfig.json b/tsconfig.json index ae35453..c114a9b 100755 --- a/tsconfig.json +++ b/tsconfig.json @@ -50,12 +50,12 @@ } }, "include": [ - "src/**/*", + "src/**/*" ], "exclude": [ "test", "node_modules", "bin", - "dist", + "dist" ] } diff --git a/yarn.lock b/yarn.lock index 94dd6aa..f1a55cf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -8884,7 +8884,16 @@ string-length@^4.0.1: char-regex "^1.0.2" strip-ansi "^6.0.0" -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": + version "4.2.3" + resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.0.0, string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: version "4.2.3" resolved "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -8943,7 +8952,14 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": + version "6.0.1" + resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -9669,7 +9685,7 @@ wordwrap@^1.0.0: resolved "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz" integrity sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -9687,6 +9703,15 @@ wrap-ansi@^6.0.1: string-width "^4.1.0" strip-ansi "^6.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.0.1, wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-8.1.0.tgz"