Skip to content

Commit

Permalink
Latest release housekeeping v3 (#3229)
Browse files Browse the repository at this point in the history
* More CI tweaks, and additional CLI restructuring
* Add 'Camelize'  from '@gitbeaker/core' export (#3231)

---------

Co-authored-by: Rafael Mello <[email protected]>
  • Loading branch information
jdalrymple and merorafael authored Apr 29, 2023
1 parent 8bb3ef8 commit 3808fbb
Show file tree
Hide file tree
Showing 11 changed files with 377 additions and 235 deletions.
12 changes: 5 additions & 7 deletions .gitlab-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -124,10 +124,10 @@ format:
policy: pull-push
before_script:
- apk add --no-cache libc6-compat
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/

.test:unit:base:
extends: .test:base
coverage: /All files[^|]*\|[^|]*\s+([\d\.]+)/
artifacts:
when: always
reports:
Expand Down Expand Up @@ -177,6 +177,10 @@ test:unit:rest:
variables:
PKG_PATH: 'packages/rest'

test:unit:cli:
extends: .test:unit:base
script: yarn test:unit --projects=@gitbeaker/cli

## Types Tests
test:types:utils:
extends: .test:types:base
Expand Down Expand Up @@ -249,7 +253,6 @@ test:live:teardown:
stage: test
image: gcr.io/google.com/cloudsdktool/cloud-sdk:alpine
needs:
- test:integration:cli
- test:integration:rest
- test:e2e:core
- test:e2e:cli
Expand All @@ -268,10 +271,6 @@ test:live:teardown:
- gcloud compute instances delete gitlab-${CI_PIPELINE_ID} --zone=northamerica-northeast1-b

## Integration Tests
test:integration:cli:
extends: .test:integration:base
script: yarn test:integration --projects=@gitbeaker/cli

test:integration:rest:
extends: .test:integration:base
script: yarn test:integration --projects=@gitbeaker/rest
Expand Down Expand Up @@ -299,7 +298,6 @@ test:e2e:cli:
image: node:18-alpine
needs:
- build
- test:integration:cli
- test:integration:rest
- test:e2e:core
- test:e2e:cli
Expand Down
1 change: 0 additions & 1 deletion jest.config.base.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ export default {
coverageReporters: ['html', 'text', 'text-summary', 'cobertura'],
reporters: ['default', ['jest-junit', { outputDirectory: 'reports', outputName: 'nodejs_junit.xml' }]],
moduleNameMapper: {
"@gitbeaker/core/map.json": '<rootDir>/../core/dist/map.json',
'^@gitbeaker/(.*)$': '<rootDir>/../$1/src'
},
transform: {
Expand Down
5 changes: 0 additions & 5 deletions nx.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,6 @@
"build"
]
},
"test:integration": {
"dependsOn": [
"build"
]
}
},
"defaultBase": "main"
}
2 changes: 1 addition & 1 deletion packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
<br>
<p align="center">
<a href="https://gitlab.com/jdalrymple/gitbeaker/-/commits/main"><img alt="pipeline status" src="https://gitlab.com/jdalrymple/gitbeaker/badges/main/pipeline.svg?ignore_skipped=true" /></a>
<a href="https://gitlab.com/jdalrymple/gitbeaker/-/commits/main"><img alt="coverage report" src="https://gitlab.com/jdalrymple/gitbeaker/badges/main/coverage.svg?job=test:integration:cli" /></a>
<a href="https://gitlab.com/jdalrymple/gitbeaker/-/commits/main"><img alt="coverage report" src="https://gitlab.com/jdalrymple/gitbeaker/badges/main/coverage.svg?job=test:unit:cli" /></a>
<a href="https://codeclimate.com/github/jdalrymple/gitbeaker">
<img src="https://codeclimate.com/github/jdalrymple/gitbeaker/badges/gpa.svg" alt="Code Climate maintainability">
</a>
Expand Down
4 changes: 4 additions & 0 deletions packages/cli/jest.config.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,8 @@ import base from '../../jest.config.base.mjs';
export default {
...base,
displayName: 'Gitbeaker CLI',
moduleNameMapper: {
"@gitbeaker/core/map.json": '<rootDir>/test/__mocks__/map.json',
...base.moduleNameMapper,
}
};
2 changes: 1 addition & 1 deletion packages/cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
],
"scripts": {
"build": "tsup src/index.ts --format esm --treeshake --target node18",
"test:integration": "jest --maxWorkers=50% test/integration",
"test:unit": "jest --maxWorkers=50% test/unit",
"test:e2e": "jest --maxWorkers=50% test/e2e",
"format:docs": "prettier '**/(*.json|.yml|.js|.md)' --ignore-path ../../.prettierignore",
"format:docs:fix": "yarn format:docs --write",
Expand Down
205 changes: 32 additions & 173 deletions packages/cli/src/cli.ts
Original file line number Diff line number Diff line change
@@ -1,148 +1,18 @@
import Chalk from 'chalk';
import Sywac from 'sywac';
import { camelize, decamelize, depascalize } from 'xcase';
import * as Gitbeaker from '@gitbeaker/rest';
import API_MAP from '@gitbeaker/core/map.json' assert { type: 'json' }; // eslint-disable-line import/no-unresolved

// Styling settings
const commandStyle = Chalk.hex('#e34329').bold;
const groupStyle = Chalk.hex('#fca325').bold;
const usageStyle = Chalk.hex('#fc6e26').bold;
const optionStyle = Chalk.white.bold;
const descriptionStyle = Chalk.hex('#848484');
const hintStyle = Chalk.hex('#6a5f88');

interface MethodTemplate {
name: string;
args: string[];
}

// Globally configurable arguments
function normalizeEnviromentVariables(env): Record<string, string> {
const normalized = { ...env };
const suffixes = [
'TOKEN',
'OAUTH_TOKEN',
'JOB_TOKEN',
'HOST',
'SUDO',
'CAMELIZE',
'REQUEST_TIMEOUT',
'PROFILE_TOKEN',
'PROFILE_MODE',
];

suffixes.forEach((s) => {
if (normalized[`GITLAB_${s}`] && !normalized[`GITBEAKER_${s}`]) {
normalized[`GITBEAKER_${s}`] = normalized[`GITLAB_${s}`];
}
});

return normalized;
}

function globalConfig(env = process.env): { [name: string]: Sywac.Options } {
const normalEnv = normalizeEnviromentVariables(env);

return {
'gb-token': {
alias: 'gl-token',
desc: 'Your Gitlab Personal Token',
type: 'string',
defaultValue: normalEnv.GITBEAKER_TOKEN,
},
'gb-oauth-token': {
alias: 'gl-oauth-token',
desc: 'Your Gitlab OAuth Token',
type: 'string',
defaultValue: normalEnv.GITBEAKER_OAUTH_TOKEN,
},
'gb-job-token': {
alias: 'gl-job-token',
desc: 'Your Gitlab Job Token',
type: 'string',
defaultValue: normalEnv.GITBEAKER_JOB_TOKEN,
},
'gb-host': {
alias: 'gl-host',
desc: 'Your Gitlab API host (Defaults to https://www.gitlab.com)',
type: 'string',
defaultValue: normalEnv.GITBEAKER_HOST,
},
'gb-sudo': {
alias: 'gl-sudo',
desc: '[Sudo](https://docs.gitlab.com/ee/api/#sudo) query parameter',
type: 'string',
defaultValue: normalEnv.GITBEAKER_SUDO,
},
'gb-camelize': {
alias: 'gl-camelize',
desc: 'Camelizes all response body keys',
type: 'boolean',
defaultValue: normalEnv.GITBEAKER_CAMELIZE,
},
'gb-request-timeout': {
alias: 'gl-request-timeout',
desc: 'Timeout for API requests. Measured in ms',
type: 'number',
defaultValue:
normalEnv.GITBEAKER_REQUEST_TIMEOUT && parseInt(normalEnv.GITBEAKER_REQUEST_TIMEOUT, 10),
},
'gb-profile-token': {
alias: 'gl-profile-token',
desc: '[Requests Profiles Token](https://docs.gitlab.com/ee/administration/monitoring/performance/request_profiling.html)',
type: 'string',
defaultValue: normalEnv.GITBEAKER_PROFILE_TOKEN,
},
'gb-profile-mode': {
alias: 'gl-profile-mode',
desc: '[Requests Profiles Token](https://docs.gitlab.com/ee/administration/monitoring/performance/request_profiling.html)',
type: 'string',
defaultValue: normalEnv.GITBEAKER_PROFILE_MODE,
},
};
}

// Options to be ignored when making an API call
const ignoreOptions = ['_', '$0', 'v', 'version', 'h', 'help', 'g', 'global-args'];

// Helper function to param case strings
function param(value: string): string {
let cleaned = value;

// Handle exceptions
const exceptions = [
'GitLabCI',
'YML',
'GPG',
'SSH',
'IId',
'NPM',
'NuGet',
'DORA4',
'LDAP',
'CICD',
'SAML',
'SCIM',
'PyPI',
];

exceptions
.filter((e) => value.includes(e))
.forEach((ex) => {
cleaned = cleaned.replace(ex, ex.charAt(0).toUpperCase() + ex.slice(1).toLowerCase());
});

// Decamelize
const decamelized = decamelize(cleaned, '-');

return decamelized !== cleaned ? decamelized : depascalize(cleaned, '-');
}
import {
buildArgumentObjects,
getDisplayConfig,
getExposedAPIs,
getGlobalConfig,
param,
} from './utils';
import type { MethodTemplate } from './utils';

function setupAPIMethods(setupArgs, methodArgs: string[]) {
methodArgs.forEach((name) => {
if (typeof name !== 'string') return;

setupArgs.positional(`[--${param(name)}] <${param(name)}>`, {
group: 'Required Options',
type: 'string',
Expand All @@ -153,20 +23,9 @@ function setupAPIMethods(setupArgs, methodArgs: string[]) {
}

function runAPIMethod(ctx, args: Record<string, string>, apiName: string, method: MethodTemplate) {
const coreArgs = {};
const optionalArgs = {};
const initArgs = {};
const globalConfig = getGlobalConfig();

Object.entries(args).forEach(([argName, value]) => {
if (ignoreOptions.includes(argName) || value == null) return;

const camelCased: string = camelize(argName.replace('gb-', '').replace('gl-', ''), '-');

if (globalConfig()[argName.replace('gl-', 'gb-')]) {
initArgs[camelCased] = value;
} else if (method.args.includes(camelCased)) coreArgs[camelCased] = value;
else optionalArgs[camelCased] = value;
});
const { initArgs, coreArgs, optionalArgs } = buildArgumentObjects(globalConfig, method, args);

// Create service
const s = new Gitbeaker[apiName](initArgs);
Expand All @@ -182,7 +41,9 @@ function runAPIMethod(ctx, args: Record<string, string>, apiName: string, method
}

function setupAPIs(setupArgs, apiName: string, methods: MethodTemplate[]) {
Object.entries(globalConfig()).forEach(([k, v]) => {
const globalConfig = getGlobalConfig();

Object.entries(globalConfig).forEach(([k, v]) => {
setupArgs.option(`${k} <value>`, {
group: 'Base Options',
...v,
Expand All @@ -202,18 +63,26 @@ function setupAPIs(setupArgs, apiName: string, methods: MethodTemplate[]) {
}

// Add default settings
// Styling settings
const commandStyle = Chalk.hex('#e34329').bold;
const groupStyle = Chalk.hex('#fca325').bold;
const usageStyle = Chalk.hex('#fc6e26').bold;
const optionStyle = Chalk.white.bold;
const descriptionStyle = Chalk.hex('#848484');
const hintStyle = Chalk.hex('#6a5f88');

const cli = Sywac.version('-v, --version')
.help('-h, --help')
.showHelpByDefault()
.epilogue('Copyright 2023')
.style({
usagePrefix: (s) => usageStyle(s),
group: (s) => groupStyle(s),
flags: (s) => optionStyle(s),
usageCommandPlaceholder: (s) => commandStyle(s),
usageOptionsPlaceholder: (s) => optionStyle(s),
desc: (s) => descriptionStyle(s),
hints: (s) => hintStyle(s),
usagePrefix: usageStyle,
group: groupStyle,
flags: optionStyle,
usageCommandPlaceholder: commandStyle,
usageOptionsPlaceholder: optionStyle,
desc: descriptionStyle,
hints: hintStyle,
});

// Add Global commands
Expand All @@ -224,17 +93,8 @@ cli.boolean('-g --global-args', {
cli.command('*', (argv, ctx) => {
if (!argv.g) return;

const display = {};

Object.entries(globalConfig()).forEach(([k, v]) => {
if (v.defaultValue == null) return;

display[k] = {
alias: v.alias,
description: v.desc,
value: v.defaultValue,
};
});
const globalConfig = getGlobalConfig();
const display = getDisplayConfig(globalConfig);

ctx.output =
Object.keys(display).length === 0
Expand All @@ -243,10 +103,9 @@ cli.command('*', (argv, ctx) => {
});

// Add all supported API's
Object.entries(API_MAP as Record<string, MethodTemplate[]>).forEach(([apiName, methods]) => {
// Skip Gitlab export
if (apiName === 'Gitlab') return;
const exposedAPIs = getExposedAPIs(API_MAP as Record<string, MethodTemplate[]>);

Object.entries(exposedAPIs).forEach(([apiName, methods]) => {
cli.command(param(apiName), {
desc: `The ${apiName} API`,
setup: (setupArgs) => setupAPIs(setupArgs, apiName, methods),
Expand Down
Loading

0 comments on commit 3808fbb

Please sign in to comment.