Skip to content

Commit

Permalink
feat: add basic bh push command implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
roman-sainchuk committed Nov 8, 2023
1 parent 828290e commit f6f1bf0
Show file tree
Hide file tree
Showing 11 changed files with 410 additions and 17 deletions.
22 changes: 6 additions & 16 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

146 changes: 146 additions & 0 deletions packages/cli/src/commands/push-bh.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
import * as fs from 'fs';
import * as path from 'path';
import { BlueHarvest, Config } from '@redocly/openapi-core';
import * as pluralize from 'pluralize';
import { exitWithError, printExecutionTime } from '../utils';
import { green } from 'colorette';

export type PushBhOptions = {
organization?: string;
project: string;
mountPath: string;

branch: string;
author: string;
message: string;
files: string[];

domain?: string;
config?: string;
};

type FileToUpload = { path: string };

export async function handleBhPush(argv: PushBhOptions, config: Config) {
const startedAt = performance.now();
const { organization, project: projectId, mountPath } = argv;

const orgId = organization || config.organization;

if (!orgId) {
return exitWithError(
`No organization provided, please use --organization option or specify the 'organization' field in the config file.`
);
}

const domain = argv.domain || BlueHarvest.getDomain();

if (!domain) {
return exitWithError(
`No domain provided, please use --domain option or environment variable REDOCLY_DOMAIN.`
);
}

try {
const author = parseCommitAuthor(argv.author);
const apiKey = BlueHarvest.getApiKeys(domain);
const filesToUpload = collectFilesToPush(argv.files);

if (!filesToUpload.length) {
return printExecutionTime('push-bh', startedAt, `No files to upload`);
}

const client = new BlueHarvest.ApiClient(domain, apiKey);
const projectDefaultBranch = await client.remotes.getDefaultBranch(orgId, projectId);
const remote = await client.remotes.upsert(orgId, projectId, {
mountBranchName: projectDefaultBranch,
mountPath,
});

process.stdout.write(
`Uploading ${filesToUpload.length} ${pluralize('file', filesToUpload.length)}:\n\n`
);

const { branchName: filesBranch } = await client.remotes.push(
orgId,
projectId,
remote.id,
{
commit: {
message: argv.message,
author,
branchName: argv.branch,
},
},
filesToUpload.map((f) => ({ path: f.path, stream: fs.createReadStream(f.path) }))
);

filesToUpload.forEach((f) => {
process.stdout.write(green(`✓ ${path.relative(process.cwd(), f.path)}\n`));
});

printExecutionTime(
'push-bh',
startedAt,
`${pluralize(
'file',
filesToUpload.length
)} uploaded to organization ${orgId}, project ${projectId}, branch ${filesBranch}`
);
} catch (err) {
exitWithError(`✗ File upload failed. Reason: ${err.message}\n`);
}
}

function parseCommitAuthor(author: string): { name: string; email: string } {
// Author Name <[email protected]>
const reg = /^.+\s<[^<>]+>$/;

if (!reg.test(author)) {
throw new Error('Invalid author format. Use "Author Name <[email protected]>"');
}

const [name, email] = author.split('<');

return {
name: name.trim(),
email: email.replace('>', '').trim(),
};
}

function collectFilesToPush(files: string[]): FileToUpload[] {
const collectedFiles = new Set<string>();

for (const file of files) {
if (fs.statSync(file).isDirectory()) {
const fileList = getFilesList(file, []);
fileList.forEach((f) => collectedFiles.add(f));
} else {
collectedFiles.add(file);
}
}

return Array.from(collectedFiles).map((f) => getFileEntry(f));
}

function getFileEntry(filename: string): FileToUpload {
return {
path: path.resolve(filename),
};
}

function getFilesList(dir: string, files: string[]): string[] {
const filesAndDirs = fs.readdirSync(dir);

for (const name of filesAndDirs) {
const currentPath = path.join(dir, name);

if (fs.statSync(currentPath).isDirectory()) {
files = getFilesList(currentPath, files);
} else {
files.push(currentPath);
}
}

return files;
}
56 changes: 56 additions & 0 deletions packages/cli/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { handleStats } from './commands/stats';
import { handleSplit } from './commands/split';
import { handleJoin } from './commands/join';
import { handlePush, transformPush } from './commands/push';
import { handleBhPush } from './commands/push-bh';
import { handleLint } from './commands/lint';
import { handleBundle } from './commands/bundle';
import { handleLogin } from './commands/login';
Expand Down Expand Up @@ -226,6 +227,61 @@ yargs
commandWrapper(transformPush(handlePush))(argv);
}
)
.command(
'push-bh <files..>',
'Push files to the Redocly BlueHarvest.',
(yargs) =>
yargs
.positional('files', {
type: 'string',
array: true,
required: true,
default: [],
description: 'List of files and folders (or glob) to upload',
})
.option({
organization: {
description: 'Name of the organization to push to.',
type: 'string',
alias: 'o',
},
project: {
description: 'Name of the project to push to.',
type: 'string',
required: true,
alias: 'p',
},
mountPath: {
description: 'The path files should be pushed to.',
type: 'string',
required: true,
alias: 'mp',
},
branch: {
description: 'Branch name files are pushed from.',
type: 'string',
required: true,
alias: 'b',
},
author: {
description: 'Author of the commit.',
type: 'string',
required: true,
alias: 'a',
},
message: {
description: 'Commit messsage.',
type: 'string',
required: true,
alias: 'm',
},
domain: { description: 'Specify a domain.', alias: 'd', type: 'string' },
}),
(argv) => {
process.env.REDOCLY_CLI_COMMAND = 'push-bh';
commandWrapper(handleBhPush)(argv);
}
)
.command(
'lint [apis...]',
'Lint definition.',
Expand Down
2 changes: 2 additions & 0 deletions packages/cli/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import type { StatsOptions } from './commands/stats';
import type { SplitOptions } from './commands/split';
import type { PreviewDocsOptions } from './commands/preview-docs';
import type { BuildDocsArgv } from './commands/build-docs/types';
import type { PushBhOptions } from './commands/push-bh';

export type Totals = {
errors: number;
Expand All @@ -26,6 +27,7 @@ export type CommandOptions =
| SplitOptions
| JoinOptions
| PushOptions
| PushBhOptions
| LintOptions
| BundleOptions
| LoginOptions
Expand Down
1 change: 1 addition & 0 deletions packages/core/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@
"lodash.isequal": "^4.5.0",
"minimatch": "^5.0.1",
"node-fetch": "^2.6.1",
"form-data": "^3.0.1",
"pluralize": "^8.0.0",
"yaml-ast-parser": "0.0.43"
},
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export {
ResolvedApi,
} from './config';

export { RedoclyClient, isRedoclyRegistryURL } from './redocly';
export { RedoclyClient, isRedoclyRegistryURL, BlueHarvest } from './redocly';

export {
Source,
Expand Down
Loading

0 comments on commit f6f1bf0

Please sign in to comment.