Skip to content

Commit

Permalink
feat(cli): Unsupported properties (#35)
Browse files Browse the repository at this point in the history
  • Loading branch information
dawidk92 authored Jul 25, 2024
1 parent 3c24c6e commit 1c755a8
Show file tree
Hide file tree
Showing 21 changed files with 238 additions and 252 deletions.
52 changes: 22 additions & 30 deletions packages/cli/src/commands/easBuildOnComplete.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,9 @@ import path from 'path';
import process from 'process';
import { DOCS_LINK } from '../constants';
import { printHeader } from '../helpers';
import { getErrorMessage, logLink } from '../utils';
import { getTokenParts, handleClientError } from './utils';
import { getLogLink, throwError } from '../utils';
import { asyncUploadMode } from './main/modes';
import { getTokenParts, handleClientError } from './utils';

/**
* Build lifecycle hooks: https://docs.expo.dev/build-reference/npm-hooks/
Expand Down Expand Up @@ -51,13 +51,11 @@ async function easBuildOnComplete() {

// Sherlo build profile is not defined
if (!sherloBuildProfile) {
throw new Error(
getErrorMessage({
message:
'in `eas-build-on-complete` script you must define the same EAS `--profile` that you use for your Sherlo test builds',
learnMoreLink: DOCS_LINK.remoteExpoBuilds,
})
);
throwError({
message:
'in `eas-build-on-complete` script you must define the same EAS `--profile` that you use for your Sherlo test builds',
learnMoreLink: DOCS_LINK.remoteExpoBuilds,
});
}

const { buildIndex, token } = getSherloData();
Expand All @@ -76,7 +74,7 @@ async function easBuildOnComplete() {
})
.catch(handleClientError);

throw new Error(getErrorMessage({ message: 'canceled due to error on Expo servers' }));
throwError({ message: 'canceled due to error on Expo servers' });
}

// Upload the platform build
Expand All @@ -97,7 +95,7 @@ function getInfoMessage({
return (
[
chalk.blue(`Info: ${message}`),
learnMoreLink ? `↳ Learn more: ${logLink(learnMoreLink)}` : null,
learnMoreLink ? `↳ Learn more: ${getLogLink(learnMoreLink)}` : null,
]
.filter((v) => v !== null)
.join('\n') + '\n'
Expand Down Expand Up @@ -135,33 +133,27 @@ function getSherloData(): { buildIndex: number; token: string } {
const SHERLO_TEMP_FILE_PATH = './.sherlo/data.json';

if (!fs.existsSync(SHERLO_TEMP_FILE_PATH)) {
throw new Error(
getErrorMessage({
message: `temporary file "${SHERLO_TEMP_FILE_PATH}" not found - ensure it isn't filtered out by \`.gitignore\`, or use the \`--projectRoot\` flag when working with a monorepo`,
learnMoreLink: DOCS_LINK.sherloScriptFlags,
})
);
throwError({
message: `temporary file "${SHERLO_TEMP_FILE_PATH}" not found - ensure it isn't filtered out by \`.gitignore\`, or use the \`--projectRoot\` flag when working with a monorepo`,
learnMoreLink: DOCS_LINK.sherloScriptFlags,
});
}

const { buildIndex, token } = JSON.parse(fs.readFileSync(SHERLO_TEMP_FILE_PATH, 'utf8'));

if (typeof buildIndex !== 'number') {
throw new Error(
getErrorMessage({
type: 'unexpected',
message: `field \`buildIndex\` in temporary file "${SHERLO_TEMP_FILE_PATH}" is not valid`,
})
);
throwError({
type: 'unexpected',
message: `field \`buildIndex\` in temporary file "${SHERLO_TEMP_FILE_PATH}" is not valid`,
});
}

const tokenRegex = /^[A-Za-z0-9_-]{40}[0-9]{1,4}$/;
if (!tokenRegex.test(token)) {
throw new Error(
getErrorMessage({
message: `passed \`token\` ("${token}") is not valid`,
learnMoreLink: DOCS_LINK.configToken,
})
);
throwError({
message: `passed \`token\` ("${token}") is not valid`,
learnMoreLink: DOCS_LINK.configToken,
});
}

return { buildIndex, token };
Expand Down Expand Up @@ -201,7 +193,7 @@ async function asyncUploadPlatformBuild({
token,
});

console.log(`Tests start after all builds are uploaded ➜ ${logLink(url)}\n`);
console.log(`Tests start after all builds are uploaded ➜ ${getLogLink(url)}\n`);
}

function getPlatformPathFromEasJson({
Expand Down
45 changes: 19 additions & 26 deletions packages/cli/src/commands/main/helpers/getArguments.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,17 @@
import { Build, Platform } from '@sherlo/api-types';
import { defaultDeviceOsLocale, defaultDeviceOsTheme } from '@sherlo/shared';
import chalk from 'chalk';
import { Command } from 'commander';
import nodePath from 'path';
import { DEFAULT_CONFIG_PATH, DEFAULT_PROJECT_ROOT } from '../../../constants';
import { getErrorMessage } from '../../../utils';
import { logWarning, throwError } from '../../../utils';
import { Config, InvalidatedConfig, Mode } from '../types';
import {
getGitInfo,
parseConfigFile,
validateConfigDevices,
validateConfigPlatformPath,
validateConfigPlatforms,
validateConfigProperties,
validateConfigToken,
} from './utils';

Expand Down Expand Up @@ -70,6 +70,7 @@ function getArguments(githubActionParameters?: Parameters): Arguments {
mode = 'asyncUpload';
}

validateConfigProperties(config);
validateConfigToken(config);
const { token } = config;

Expand Down Expand Up @@ -108,12 +109,10 @@ function getArguments(githubActionParameters?: Parameters): Arguments {
const { asyncBuildIndex } = parameters;

if (!asyncBuildIndex) {
throw new Error(
getErrorMessage({
type: 'unexpected',
message: 'asyncBuildIndex is undefined',
})
);
throwError({
type: 'unexpected',
message: 'asyncBuildIndex is undefined',
});
}

return {
Expand Down Expand Up @@ -225,7 +224,7 @@ function removeDuplicateDevices(
.replace(/}$/, ' }');

if (uniqueDevices.has(key)) {
console.log(chalk.yellow('Config Warning: duplicated device', key, '\n'));
logWarning({ type: 'config', message: `duplicated device ${key}` });

return false;
}
Expand All @@ -238,20 +237,16 @@ function removeDuplicateDevices(

function getAsyncUploadArguments(parameters: Parameters): { path: string; platform: Platform } {
if (parameters.android && parameters.ios) {
throw new Error(
getErrorMessage({
message:
'If you are providing both Android and iOS at the same time, use Sherlo in regular mode (without the `--async` flag)',
})
);
throwError({
message:
'If you are providing both Android and iOS at the same time, use Sherlo in regular mode (without the `--async` flag)',
});
}

if (!parameters.android && !parameters.ios) {
throw new Error(
getErrorMessage({
message: 'When using "asyncBuildIndex" you need to provide one build path, ios or android',
})
);
throwError({
message: 'When using "asyncBuildIndex" you need to provide one build path, ios or android',
});
}

if (parameters.android) {
Expand All @@ -266,10 +261,8 @@ function getAsyncUploadArguments(parameters: Parameters): { path: string; platfo
return { path: parameters.ios, platform: 'ios' };
}

throw new Error(
getErrorMessage({
type: 'unexpected',
message: 'Unexpected error in getAsyncUploadArguments function',
})
);
throwError({
type: 'unexpected',
message: 'Unexpected error in getAsyncUploadArguments function',
});
}
1 change: 1 addition & 0 deletions packages/cli/src/commands/main/helpers/utils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,5 @@ export { default as parseConfigFile } from './parseConfigFile';
export { default as validateConfigDevices } from './validateConfigDevices';
export { default as validateConfigPlatformPath } from './validateConfigPlatformPath';
export { default as validateConfigPlatforms } from './validateConfigPlatforms';
export { default as validateConfigProperties } from './validateConfigProperties';
export { default as validateConfigToken } from './validateConfigToken';
44 changes: 22 additions & 22 deletions packages/cli/src/commands/main/helpers/utils/parseConfigFile.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import fs from 'fs';
import { getErrorMessage } from '../../../../utils';
import { DOCS_LINK } from '../../../../constants';
import { throwError } from '../../../../utils';
import { InvalidatedConfig } from '../../types';
import { getConfigErrorMessage } from '../../utils';
import { throwConfigError } from '../../utils';

/*
* 1. Both `include` and `exclude` can be defined as a string or an array of
Expand All @@ -13,12 +13,10 @@ function parseConfigFile(path: string): InvalidatedConfig {
const config = JSON.parse(fs.readFileSync(path, 'utf8'));

if (!config) {
throw new Error(
getErrorMessage({
type: 'unexpected',
message: `parsed config file "${path}" is undefined`,
})
);
throwError({
type: 'unexpected',
message: `parsed config file "${path}" is undefined`,
});
}

/* 1 */
Expand All @@ -32,26 +30,28 @@ function parseConfigFile(path: string): InvalidatedConfig {

switch (nodeError.code) {
case 'ENOENT':
throw new Error(
getConfigErrorMessage(
`config file "${path}" not found - verify the path or use the \`--projectRoot\` flag`,
DOCS_LINK.sherloScriptFlags
)
throwConfigError(
`config file "${path}" not found - verify the path or use the \`--projectRoot\` flag`,
DOCS_LINK.sherloScriptFlags
);
break;

case 'EACCES':
throw new Error(getConfigErrorMessage(`config file "${path}" cannot be accessed`));
throwConfigError(`config file "${path}" cannot be accessed`);
break;

case 'EISDIR':
throw new Error(getConfigErrorMessage(`"${path}" is a directory, not a config file`));
throwConfigError(`"${path}" is a directory, not a config file`);
break;

default:
if (error instanceof SyntaxError) {
throw new Error(getConfigErrorMessage(`config file "${path}" is not valid JSON`));
throwConfigError(`config file "${path}" is not valid JSON`);
} else {
throw new Error(
getErrorMessage({
type: 'unexpected',
message: `issue reading config file "${path}"`,
})
);
throwError({
type: 'unexpected',
message: `issue reading config file "${path}"`,
});
}
}
}
Expand Down
Original file line number Diff line number Diff line change
@@ -1,49 +1,42 @@
import { DeviceTheme } from '@sherlo/api-types';
import { devices as sherloDevices } from '@sherlo/shared';
import { DOCS_LINK } from '../../../../constants';
import { logWarning } from '../../../../utils';
import { InvalidatedConfig } from '../../types';
import { getConfigErrorMessage } from '../../utils';
import { throwConfigError } from '../../utils';

function validateConfigDevices(config: InvalidatedConfig): void {
const { devices } = config;

if (!devices || !Array.isArray(devices) || devices.length === 0) {
throw new Error(
getConfigErrorMessage('`devices` must be a non-empty array', DOCS_LINK.devices)
);
throwConfigError('`devices` must be a non-empty array', DOCS_LINK.devices);
}

for (let i = 0; i < devices.length; i++) {
const { id, osLocale, osVersion, osTheme, ...rest } = devices[i] ?? {};
const { id, osLocale, osVersion, osTheme, ...unsupportedProperties } = devices[i] ?? {};

if (!id || typeof id !== 'string' || !osVersion || typeof osVersion !== 'string') {
throw new Error(
getConfigErrorMessage(
'each device must have defined `id` and `osVersion` as strings',
DOCS_LINK.devices
)
throwConfigError(
'each device must have defined `id` and `osVersion` as strings',
DOCS_LINK.devices
);
}

const sherloDevice = sherloDevices[id];

if (!sherloDevice) {
throw new Error(getConfigErrorMessage(`device "${id}" is invalid`, DOCS_LINK.devices));
throwConfigError(`device "${id}" is invalid`, DOCS_LINK.devices);
}

if (!sherloDevice.osVersions.some(({ version }) => version === osVersion)) {
throw new Error(
getConfigErrorMessage(
`the osVersion "${osVersion}" is not supported by the device "${id}"`,
DOCS_LINK.devices
)
throwConfigError(
`the osVersion "${osVersion}" is not supported by the device "${id}"`,
DOCS_LINK.devices
);
}

if (!osLocale) {
throw new Error(
getConfigErrorMessage('device `osLocale` must be defined', DOCS_LINK.configDevices)
);
throwConfigError('device `osLocale` must be defined', DOCS_LINK.configDevices);
}

const osLocaleRegex = /^[a-z]{2}(_[A-Z]{2})?$/;
Expand All @@ -56,32 +49,25 @@ function validateConfigDevices(config: InvalidatedConfig): void {
// $ - end of the string

if (!osLocaleRegex.test(osLocale)) {
throw new Error(
getConfigErrorMessage(`device osLocale "${osLocale}" is invalid`, DOCS_LINK.configDevices)
);
throwConfigError(`device osLocale "${osLocale}" is invalid`, DOCS_LINK.configDevices);
}

if (!osTheme) {
throw new Error(
getConfigErrorMessage('device `osTheme` must be defined', DOCS_LINK.configDevices)
);
throwConfigError('device `osTheme` must be defined', DOCS_LINK.configDevices);
}

const deviceThemes: [DeviceTheme, DeviceTheme] = ['light', 'dark'];
if (!deviceThemes.includes(osTheme)) {
throw new Error(
getConfigErrorMessage(`device osTheme "${osTheme}" is invalid`, DOCS_LINK.configDevices)
);
throwConfigError(`device osTheme "${osTheme}" is invalid`, DOCS_LINK.configDevices);
}

if (Object.keys(rest).length > 0) {
throw new Error(
getConfigErrorMessage(
`device property "${Object.keys(rest)[0]}" is not supported`,
DOCS_LINK.configDevices
)
);
}
Object.keys(unsupportedProperties).forEach((property) => {
logWarning({
type: 'config',
message: `device property "${property}" is not supported`,
learnMoreLink: DOCS_LINK.configDevices,
});
});
}
}

Expand Down
Loading

0 comments on commit 1c755a8

Please sign in to comment.