Skip to content

Commit

Permalink
fix: packages validation + builds upload (#49)
Browse files Browse the repository at this point in the history
Co-authored-by: Sherlo Bot <admin@sherlo.io>
  • Loading branch information
dawidk92 and Sherlo Bot authored Jan 21, 2025
1 parent aba1a71 commit 245ef1d
Show file tree
Hide file tree
Showing 19 changed files with 199 additions and 49 deletions.
1 change: 1 addition & 0 deletions .github/workflows/pr_check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ jobs:
chore
fix
feat
test
scopes: |
action
cli
Expand Down
2 changes: 1 addition & 1 deletion lerna.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"npmClient": "yarn",
"version": "1.1.2",
"version": "1.1.3-alpha.3",
"$schema": "node_modules/lerna/schemas/lerna-schema.json",
"command": {
"publish": {
Expand Down
4 changes: 2 additions & 2 deletions packages/action/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sherlo/action",
"version": "1.1.2",
"version": "1.1.3-alpha.3",
"description": "A github action that uploads provided iOS & Android builds to Sherlo and starts a test run",
"license": "MIT",
"author": "Sherlo",
Expand Down Expand Up @@ -28,7 +28,7 @@
"dependencies": {
"@actions/core": "^1.10.1",
"@actions/github": "^6.0.0",
"sherlo": "^1.1.2"
"sherlo": "^1.1.3-alpha.3"
},
"devDependencies": {
"@types/jest": "^26.0.15",
Expand Down
2 changes: 1 addition & 1 deletion packages/action/release/index.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "sherlo",
"version": "1.1.2",
"version": "1.1.3-alpha.3",
"description": "Sherlo CLI for React Native Storybook visual testing",
"keywords": [
"visual testing",
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/commands/expoCloudBuilds/expoCloudBuilds.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
logSherloIntro,
getPlatformsToTest,
getValidatedCommandParams,
validatePackages,
} from '../../helpers';
import { Options } from '../../types';
import { THIS_COMMAND } from './constants';
Expand All @@ -26,7 +27,7 @@ import {
async function expoCloudBuilds(passedOptions: Options<THIS_COMMAND>) {
logSherloIntro();

// validatePackages(EXPO_CLOUD_BUILDS_COMMAND);
validatePackages(EXPO_CLOUD_BUILDS_COMMAND);

const commandParams = getValidatedCommandParams(
{ command: EXPO_CLOUD_BUILDS_COMMAND, passedOptions },
Expand Down
3 changes: 2 additions & 1 deletion packages/cli/src/commands/expoUpdate/expoUpdate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import {
logSherloIntro,
uploadOrReuseBuildsAndRunTests,
getValidatedCommandParams,
validatePackages,
} from '../../helpers';
import { Options } from '../../types';
import { THIS_COMMAND } from './constants';
Expand All @@ -10,7 +11,7 @@ import { getValidatedExpoUpdateData } from './helpers';
async function expoUpdate(passedOptions: Options<THIS_COMMAND>): Promise<{ url: string }> {
logSherloIntro();

// validatePackages(THIS_COMMAND);
validatePackages(THIS_COMMAND);

const commandParams = getValidatedCommandParams(
{ command: THIS_COMMAND, passedOptions },
Expand Down
4 changes: 2 additions & 2 deletions packages/cli/src/commands/localBuilds/localBuilds.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import { logSherloIntro, uploadOrReuseBuildsAndRunTests } from '../../helpers';
import { logSherloIntro, uploadOrReuseBuildsAndRunTests, validatePackages } from '../../helpers';
import { Options } from '../../types';
import { getValidatedCommandParams } from '../../helpers';
import { THIS_COMMAND } from './constants';

async function localBuilds(passedOptions: Options<THIS_COMMAND>): Promise<{ url: string }> {
logSherloIntro();

// validatePackages(THIS_COMMAND);
validatePackages(THIS_COMMAND);

const commandParams = getValidatedCommandParams(
{ command: THIS_COMMAND, passedOptions },
Expand Down
23 changes: 19 additions & 4 deletions packages/cli/src/helpers/shared/isPackageVersionCompatible.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,33 @@ function isPackageVersionCompatible({
version: string;
minVersion: string;
}): boolean {
const versionParts = version.split('.').map(Number);
const minParts = minVersion.split('.').map(Number);
// Handle various semver formats:
// - Standard: "1.2.3"
// - Pre-release: "1.2.3-alpha.1", "1.2.3-beta.2", "1.2.3-rc.1"
// - Build metadata: "1.2.3+build.123"
// - Combined: "1.2.3-beta.1+build.123"
// We only compare the core version (1.2.3), ignoring pre-release and build metadata
const separatorRegex = /-|\+/; // Matches either "-" or "+"
const cleanVersion = version.split(separatorRegex)[0];
const cleanMinVersion = minVersion.split(separatorRegex)[0];

const versionParts = cleanVersion.split('.').map(Number);
const minParts = cleanMinVersion.split('.').map(Number);

if (
versionParts.length !== 3 ||
minParts.length !== 3 ||
versionParts.some(isNaN) ||
minParts.length !== 3 ||
minParts.some(isNaN)
) {
const invalidVersion =
versionParts.length !== 3 || versionParts.some(isNaN) ? version : minVersion;

throwError({
type: 'unexpected',
error: new Error('Package versions must contain exactly 3 numbers'),
error: new Error(
`Invalid version format: "${invalidVersion}". Core version numbers (major.minor.patch) must contain exactly 3 numbers`
),
});
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { Platform } from '@sherlo/api-types';
import fs from 'fs';
import logBuildMessage from '../../../logBuildMessage';
import throwError from '../../../throwError';
import getSizeInMB from '../getSizeInMB';
import compressDirectoryToTarGzip from './compressDirectoryToTarGzip';
import compressFileToGzip from './compressFileToGzip';

Expand All @@ -26,16 +28,25 @@ function getBuildData({
* .tar.gz file
*/
return fs.readFileSync(buildPath);
} else if (buildPath.endsWith('.tar')) {
/**
* .tar file
*/
return compressFileToGzip(buildPath);
} else {
/**
* .app directory
*/
return compressDirectoryToTarGzip({ directoryPath: buildPath, projectRoot });
const buildSizeMB = getSizeInMB({ path: buildPath, projectRoot });

logBuildMessage({
message: `compressing build... (${buildSizeMB} MB)`,
type: 'info',
});

if (buildPath.endsWith('.tar')) {
/**
* .tar file
*/
return compressFileToGzip(buildPath);
} else {
/**
* .app directory
*/
return compressDirectoryToTarGzip({ directoryPath: buildPath, projectRoot });
}
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import runShellCommand from '../../runShellCommand';

function getSizeInMB({
path,
buffer,
projectRoot,
}: {
path?: string;
buffer?: Buffer;
projectRoot: string;
}): string {
if (buffer) {
return (buffer.length / 1024 / 1024).toFixed(2); // bytes to MB
}

if (path) {
const duOutput = runShellCommand({
command: `du -sk "${path}"`,
projectRoot,
});

const [sizeInKB] = duOutput.split('\t');
return (parseInt(sizeInKB, 10) / 1024).toFixed(2); // KB to MB
}

throw new Error('Either path or buffer must be provided');
}

export default getSizeInMB;
Original file line number Diff line number Diff line change
@@ -1,9 +1,14 @@
import { Platform } from '@sherlo/api-types';
import https from 'https';
import fetch from 'node-fetch';
import { PLATFORM_LABEL } from '../../../constants';
import logBuildMessage from '../../logBuildMessage';
import throwError from '../../throwError';
import getBuildData from './getBuildData';
import getSizeInMB from './getSizeInMB';

const MAX_RETRIES = 3;
const TIMEOUT = 5 * 60 * 1000; // 5 minutes

async function uploadBuild({
buildPath,
Expand All @@ -17,22 +22,72 @@ async function uploadBuild({
uploadUrl: string;
}): Promise<void> {
const buildData = getBuildData({ buildPath, platform, projectRoot });
const buildSizeMB = getSizeInMB({ buffer: buildData, projectRoot });

logBuildMessage({ message: 'uploading build...', type: 'info' });
logBuildMessage({ message: `uploading build... (${buildSizeMB} MB)`, type: 'info' });

const response = await fetch(uploadUrl, {
method: 'PUT',
body: buildData,
/**
* Configure HTTPS agent for optimized uploads:
* - keepAlive: true - Reuses TCP connection between requests, reducing latency (default in Node.js 19+)
* - timeout: 60s - Prevents hanging requests for large uploads
*
* Note: We set keepAlive explicitly for compatibility with Node.js < 19
*/
const agent = new https.Agent({
keepAlive: true,
timeout: TIMEOUT,
});

if (!response || !response.ok) {
throwError({
type: 'unexpected',
error: new Error(`Failed to upload ${PLATFORM_LABEL[platform]} build`),
});
}
let attempt = 0;
while (attempt < MAX_RETRIES) {
try {
const response = await fetch(uploadUrl, {
method: 'PUT',
body: buildData,
headers: {
'Content-Length': buildData.length.toString(),
'Content-Type': 'application/octet-stream',
},
timeout: TIMEOUT,
agent,
});

if (!response.ok) {
const responseText = await response.text();
throw new Error(`Server responded with ${response.status}: ${responseText}`);
}

logBuildMessage({ message: 'upload complete', type: 'success', endsWithNewLine: true });
return;
} catch (error) {
attempt++;

logBuildMessage({ message: 'upload complete', type: 'success', endsWithNewLine: true });
// Log detailed error info
console.error('Upload error details:', {
code: error.code,
message: error.message,
stack: error.stack,
attempt,
buildSize: buildData.length,
url: uploadUrl,
});

if (attempt === MAX_RETRIES) {
throwError({
type: 'unexpected',
error: new Error(
`Failed to upload ${PLATFORM_LABEL[platform]} build after ${MAX_RETRIES} attempts. ` +
`Last error (${error.code}): ${error.message}`
),
});
}

logBuildMessage({
message: `Upload failed (attempt ${attempt}/${MAX_RETRIES}), retrying...`,
type: 'info',
});
}
}
}

export default uploadBuild;
45 changes: 41 additions & 4 deletions packages/cli/src/helpers/validatePackages/getPackageVersion.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,48 @@
import { throwError } from '../../helpers';
import { readFileSync } from 'fs';
import { join, dirname } from 'path';

const PACKAGE_JSON = 'package.json';

function getPackageVersion(packageName: string): string | null {
let packagePath;
let packageJson;

try {
const pkgPath = require.resolve(`${packageName}/package.json`);
packagePath = require.resolve(packageName);
} catch (error) {
if (error.code === 'MODULE_NOT_FOUND') {
return null;
}

throwError({ type: 'unexpected', error });
}

return require(pkgPath).version;
} catch {
return null;
let currentDir = dirname(packagePath);
while (!currentDir.endsWith('node_modules') && currentDir !== '/') {
try {
const currentPackageJsonPath = join(currentDir, PACKAGE_JSON);
const currentPackageJson = JSON.parse(readFileSync(currentPackageJsonPath, 'utf8'));

if (currentPackageJson.name === packageName) {
packageJson = currentPackageJson;
break;
}
} catch {}

currentDir = dirname(currentDir);
}

if (!packageJson) {
throwError({
type: 'unexpected',
error: new Error(
`Package ${packageName} was found at ${packagePath} but its ${PACKAGE_JSON} is invalid or inaccessible`
),
});
}

return packageJson.version;
}

export default getPackageVersion;
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version": "1.1.2"}
{"version": "1.1.3-alpha.3"}
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"version": "1.1.2"}
{"version": "1.1.3-alpha.3"}
4 changes: 2 additions & 2 deletions packages/react-native-storybook/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@sherlo/react-native-storybook",
"version": "1.1.2",
"version": "1.1.3-alpha.3",
"description": "Sherlo is a Visual Testing tool for React Native Storybook",
"keywords": [
"visual testing",
Expand Down Expand Up @@ -48,7 +48,7 @@
"base-64": "^0.1.0",
"jsencrypt": "^3.3.2",
"node-forge": "^1.3.1",
"sherlo": "^1.1.2",
"sherlo": "^1.1.3-alpha.3",
"utf8": "^3.0.0"
},
"devDependencies": {
Expand Down
Loading

0 comments on commit 245ef1d

Please sign in to comment.