Skip to content

Commit

Permalink
feat: add get action (#16)
Browse files Browse the repository at this point in the history
  • Loading branch information
oljekechoro authored Jan 18, 2021
1 parent edf6647 commit dc28262
Show file tree
Hide file tree
Showing 18 changed files with 518 additions and 71 deletions.
4 changes: 2 additions & 2 deletions packages/cli-api/src/main/ts/executors/deprecate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,10 +22,10 @@ export const processResults = (results: THandledValue[], config: TDeprecationCon
const failedResults = getFailedResults(enrichedResults)

if (config.batch?.jsonOutput) {
printResultsJson(
printResultsJson({
successfulResults,
failedResults
)
})
return
}

Expand Down
68 changes: 68 additions & 0 deletions packages/cli-api/src/main/ts/executors/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
import { INpmRegClientWrapper, Packument, RegClient, TBatchResult } from '@qiwi/npm-batch-client'

import { TGetConfig } from '../interfaces'
import { npmRegClientWrapperFactory, printResults, printResultsJson, writeToFile } from '../utils'

export const performGet = (
config: TGetConfig,
customBatchClient?: INpmRegClientWrapper
): Promise<void> => {
const batchClient = customBatchClient || npmRegClientWrapperFactory(config, ['get'], new RegClient())

return batchClient.getBatch(config.data, config.batch?.skipErrors)
.then(results => processGetResults(results, config))
}

export const isResultSuccessful = (
item: PromiseSettledResult<Packument>
): item is PromiseFulfilledResult<Packument> & { name: string } =>
item.status === 'fulfilled' &&
typeof item.value === 'object' &&
item.value !== null &&
!(item.value as any).error

export const isResultFailed = (item: PromiseSettledResult<Packument>): boolean =>
item.status === 'rejected' ||
typeof item.value !== 'object' ||
!item.value ||
(item.value as any).error

export const getErrorMessage = (item: PromiseSettledResult<Packument>): string =>
item.status === 'rejected'
? item.reason?.message ?? item.reason
: `got ${typeof item.value === 'object' ? JSON.stringify(item.value) : item.value} instead of Packument`


export const processGetResults = (
results: TBatchResult<Packument>[],
config: TGetConfig
): void => {
const enrichedResults = results.map((item, i) => ({ ...item, name: config.data[i] }))
const packuments = enrichedResults
.filter(isResultSuccessful)
.map(({ name, value }) => ({ name, value }))
const successfulPackages = packuments.map(({ name }) => ({ name }))
const failedPackages = enrichedResults
.filter(isResultFailed)
.map(item => ({ name: item.name, error: getErrorMessage(item) }))

if (config.batch?.jsonOutput) {
printResultsJson({
successfulPackages,
failedPackages,
packuments
})
return
}

writeToFile(config.batch?.path as string, packuments)

printResults(
successfulPackages,
failedPackages,
['name'],
['name', 'error'],
`Packuments of following packages have been written to ${config.batch?.path}:`,
'Packuments of following packages have not been downloaded because of errors:'
)
}
2 changes: 2 additions & 0 deletions packages/cli-api/src/main/ts/executors/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,3 @@
export * from './deprecate'
export * from './publish'
export * from './get'
2 changes: 1 addition & 1 deletion packages/cli-api/src/main/ts/executors/publish.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ export const processPublishResults = (results: TBatchResult<TPublishResult>[], c
}))

if (config.batch?.jsonOutput) {
printResultsJson(successfulPackages, failedPackages)
printResultsJson({ successfulPackages, failedPackages })
return
}

Expand Down
7 changes: 5 additions & 2 deletions packages/cli-api/src/main/ts/interfaces.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { IDeprecatePackageParams, TNpmRegClientAuth, TTarballOpts } from '@qiwi/npm-batch-client'
import { IComplexDelay } from 'push-it-to-the-limit'

export type TNpmAction = 'deprecate' | 'un-deprecate' | 'publish'
export type TNpmAction = 'deprecate' | 'un-deprecate' | 'publish' | 'get'

export type TRateLimit = IComplexDelay | IComplexDelay[]

Expand All @@ -12,7 +12,8 @@ export interface IBaseConfig<T = any> {
batch?: {
ratelimit?: TRateLimit,
skipErrors?: boolean,
jsonOutput?: boolean
jsonOutput?: boolean,
path?: string
},
data: T,
}
Expand All @@ -21,6 +22,8 @@ export type TPublishConfig = IBaseConfig<Array<TTarballOpts>>

export type TDeprecationConfig = IBaseConfig<Array<IDeprecatePackageParams>>

export type TGetConfig = IBaseConfig<string[]>

export interface ICliArgs {
config: string
}
15 changes: 12 additions & 3 deletions packages/cli-api/src/main/ts/runner.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
import { performDeprecation } from './executors'
import { performPublish } from './executors/publish'
import { performDeprecation, performGet,performPublish } from './executors'
import { ICliArgs } from './interfaces'
import { readFileToString, validateBaseConfig, validateDeprecationConfig, validatePublishConfig } from './utils'
import {
readFileToString,
validateBaseConfig,
validateDeprecationConfig,
validateGetConfig,
validatePublishConfig
} from './utils'

export const run = (configString: string): Promise<void> => {
const rawConfig = JSON.parse(configString)
Expand All @@ -16,6 +21,10 @@ export const run = (configString: string): Promise<void> => {
const publishConfig = validatePublishConfig(validatedConfig)
return performPublish(publishConfig)
}
case 'get': {
const getConfig = validateGetConfig(validatedConfig)
return performGet(getConfig)
}
default:
throw new Error(`Action ${validatedConfig.action} is not supported`)
}
Expand Down
18 changes: 11 additions & 7 deletions packages/cli-api/src/main/ts/utils/misc.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NpmRegClientWrapper, RegClient } from '@qiwi/npm-batch-client'
import assert from 'assert'
import { readFileSync } from 'fs'
import { readFileSync, writeFileSync } from 'fs'

import { defaultRateLimit } from '../default'
import { IBaseConfig } from '../interfaces'
Expand All @@ -15,16 +15,12 @@ export const assertString = (value: any, name: string): void => {
}

export const printResultsJson = (
successfulPackages: Array<any>,
failedPackages: Array<any>,
results: Record<string, any>,
logger = console
): void => {
logger.log(
JSON.stringify(
{
successfulPackages,
failedPackages
},
results,
null, // eslint-disable-line unicorn/no-null
'\t'
)
Expand Down Expand Up @@ -60,3 +56,11 @@ export const npmRegClientWrapperFactory = (
config.auth,
withRateLimit<RegClient>(regClient, config.batch?.ratelimit || defaultRateLimit, limitedMethods)
)

export const writeToFile = (
path: string,
body: Record<string, any>
): void => writeFileSync(
path,
JSON.stringify(body, null, '\t') // eslint-disable-line unicorn/no-null
)
15 changes: 12 additions & 3 deletions packages/cli-api/src/main/ts/utils/validators.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from 'assert'

import { IBaseConfig, TDeprecationConfig, TPublishConfig } from '../interfaces'
import { IBaseConfig, TDeprecationConfig, TGetConfig, TPublishConfig } from '../interfaces'
import { assertString } from './misc'

const isObject = (data: any) => typeof data === 'object' && data !== null
Expand All @@ -23,7 +23,7 @@ export const validateBaseConfig = (config: any): IBaseConfig => { // eslint-disa
}

export const validateDeprecationConfig = (config: IBaseConfig): TDeprecationConfig => {
assert.ok(Array.isArray(config.data), 'Data in config file should be an array')
assert.ok(Array.isArray(config.data), 'Data in config file should be an array') // eslint-disable-line sonarjs/no-duplicate-string
config.data.forEach(({ packageName, version, message }) => {
assertString(packageName, 'packageName')
assertString(version, 'version')
Expand All @@ -33,7 +33,7 @@ export const validateDeprecationConfig = (config: IBaseConfig): TDeprecationConf
}

export const validatePublishConfig = (config: IBaseConfig): TPublishConfig => {
assert.ok(Array.isArray(config.data), 'Data in config file should be an array')
assert.ok(Array.isArray(config.data), 'Data in config file should be an array') // eslint-disable-line sonarjs/no-duplicate-string
config.data.forEach(({ name, version, filePath, access }) => {
assertString(name, 'name')
assertString(version, 'version')
Expand All @@ -42,3 +42,12 @@ export const validatePublishConfig = (config: IBaseConfig): TPublishConfig => {
})
return config
}

export const validateGetConfig = (config: IBaseConfig): TGetConfig => {
assert.ok(Array.isArray(config.data), 'Data in config file should be an array') // eslint-disable-line sonarjs/no-duplicate-string
if (!config.batch?.jsonOutput) {
assertString(config.batch?.path, 'batch.path')
}
config.data.forEach(name => assertString(name, 'name'))
return config
}
5 changes: 4 additions & 1 deletion packages/cli-api/src/test/ts/executors/deprecate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,7 +97,10 @@ describe('processResults', () => {

await performDeprecation({ ...config, batch: { jsonOutput: true }}, npmClientMock as any)

expect(printResultsJsonSpy).toHaveBeenCalledWith([], [])
expect(printResultsJsonSpy).toHaveBeenCalledWith({
successfulResults: [],
failedResults: []
})
})
})

Expand Down
130 changes: 130 additions & 0 deletions packages/cli-api/src/test/ts/executors/get.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
import { Packument, TBatchResult } from '@qiwi/npm-batch-client'

import { TGetConfig } from '../../../main/ts'
import { performGet, processGetResults } from '../../../main/ts/executors/get'
import * as misc from '../../../main/ts/utils/misc'

const registryUrl = 'http://localhost'

const config: TGetConfig = {
registryUrl,
auth: {
username: 'username',
password: 'password',
email: '[email protected]'
},
batch: {
jsonOutput: true,
skipErrors: true
},
action: 'get',
data: []
}

beforeEach(jest.restoreAllMocks)

describe('performGet', () => {
it('calls getBatch', async () => {
const npmClientMock = {
getBatch: jest.fn(() => Promise.resolve([]))
}
const printResultsJsonSpy = jest.spyOn(misc, 'printResultsJson')
.mockImplementation(() => { /* noop */
})

await performGet(config, npmClientMock as any)

expect(npmClientMock.getBatch).toHaveBeenCalledWith(config.data, true)
expect(printResultsJsonSpy).toHaveBeenCalledWith({
failedPackages: [],
successfulPackages: [],
packuments: []
})
})
})

describe('processGetResults', () => {
const results: TBatchResult<Packument>[] = [
{
status: 'fulfilled',
value: {} as Packument
},
{
status: 'fulfilled',
value: undefined as any
},
{
status: 'rejected',
reason: 'error'
},
{
status: 'rejected',
reason: new Error('error')
},
{
status: 'rejected',
reason: undefined
}
]

it('handles results and writes them to a file', () => {
const customConfig = {
...config,
batch: { path: 'foo' },
data: ['foo', 'bar', 'baz', 'bat', 'qux']
}
const printResults = jest.spyOn(misc, 'printResults')
.mockImplementation(() => { /* noop */
})
const writeFileSyncSpy = jest.spyOn(misc, 'writeToFile')
.mockImplementation(() => { /* noop */
})
const printResultsJsonSpy = jest.spyOn(misc, 'printResultsJson')
.mockImplementation(() => { /* noop */
})

processGetResults(results, customConfig)

expect(printResultsJsonSpy).not.toHaveBeenCalled()
expect(writeFileSyncSpy).toHaveBeenCalledWith(
'foo',
[{ name: 'foo', value: {} }],
)
expect(printResults).toHaveBeenCalled()
})

it('prints results in json', () => {
const customConfig = {
...config,
batch: { jsonOutput: true },
data: ['foo', 'bar', 'baz', 'bat', 'qux']
}

const printResults = jest.spyOn(misc, 'printResults')
.mockImplementation(() => { /* noop */
})
const writeFileSyncSpy = jest.spyOn(misc, 'writeToFile')
.mockImplementation(() => { /* noop */
})
const printResultsJsonSpy = jest.spyOn(misc, 'printResultsJson')
.mockImplementation(() => { /* noop */
})

processGetResults(results, customConfig)

expect(printResults).not.toHaveBeenCalled()
expect(writeFileSyncSpy).not.toHaveBeenCalled()
expect(printResultsJsonSpy).toHaveBeenCalledWith({
successfulPackages: [
{ name: 'foo' },
],
failedPackages: [
{ name: 'bar', error: 'got undefined instead of Packument' },
{ name: 'baz', error: 'error' },
{ name: 'bat', error: 'error' },
{ name: 'qux', error: undefined },
],
packuments: [{ name: 'foo', value: {} }]
})
})
})
Loading

0 comments on commit dc28262

Please sign in to comment.