1
0
Fork 0

feat: create remote via DI container

This commit is contained in:
ssube 2020-08-22 10:24:50 -05:00
parent ceafdbab8a
commit a38c130e73
Signed by: ssube
GPG Key ID: 3EED7B957D362AF1
15 changed files with 131 additions and 11 deletions

View File

@ -7,9 +7,9 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
connect(): Promise<void>; connect(): Promise<boolean>;
``` ```
<b>Returns:</b> <b>Returns:</b>
Promise&lt;void&gt; Promise&lt;boolean&gt;

View File

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [cautious-journey](./cautious-journey.md) &gt; [GitlabRemote](./cautious-journey.gitlabremote.md) &gt; [connect](./cautious-journey.gitlabremote.connect.md)
## GitlabRemote.connect() method
<b>Signature:</b>
```typescript
connect(): Promise<boolean>;
```
<b>Returns:</b>
Promise&lt;boolean&gt;

View File

@ -23,6 +23,7 @@ export declare class GitlabRemote implements Remote
| Method | Modifiers | Description | | Method | Modifiers | Description |
| --- | --- | --- | | --- | --- | --- |
| [connect()](./cautious-journey.gitlabremote.connect.md) | | |
| [createComment()](./cautious-journey.gitlabremote.createcomment.md) | | | | [createComment()](./cautious-journey.gitlabremote.createcomment.md) | | |
| [createLabel()](./cautious-journey.gitlabremote.createlabel.md) | | | | [createLabel()](./cautious-journey.gitlabremote.createlabel.md) | | |
| [deleteLabel()](./cautious-journey.gitlabremote.deletelabel.md) | | | | [deleteLabel()](./cautious-journey.gitlabremote.deletelabel.md) | | |

View File

@ -0,0 +1,15 @@
<!-- Do not edit this file. It is automatically generated by API Documenter. -->
[Home](./index.md) &gt; [cautious-journey](./cautious-journey.md) &gt; [Remote](./cautious-journey.remote.md) &gt; [connect](./cautious-journey.remote.connect.md)
## Remote.connect() method
<b>Signature:</b>
```typescript
connect(): Promise<boolean>;
```
<b>Returns:</b>
Promise&lt;boolean&gt;

View File

@ -16,6 +16,7 @@ export interface Remote
| Method | Description | | Method | Description |
| --- | --- | | --- | --- |
| [connect()](./cautious-journey.remote.connect.md) | |
| [createComment(options)](./cautious-journey.remote.createcomment.md) | Add a comment to an issue (for attribution and auditing). | | [createComment(options)](./cautious-journey.remote.createcomment.md) | Add a comment to an issue (for attribution and auditing). |
| [createLabel(options)](./cautious-journey.remote.createlabel.md) | Create a new label. | | [createLabel(options)](./cautious-journey.remote.createlabel.md) | Create a new label. |
| [deleteLabel(options)](./cautious-journey.remote.deletelabel.md) | Delete an existing label. | | [deleteLabel(options)](./cautious-journey.remote.deletelabel.md) | Delete an existing label. |

View File

@ -7,8 +7,9 @@
<b>Signature:</b> <b>Signature:</b>
```typescript ```typescript
export interface RemoteOptions export interface RemoteOptions extends BaseOptions
``` ```
<b>Extends:</b> BaseOptions
## Properties ## Properties

View File

@ -1,10 +1,13 @@
import { doesExist, InvalidArgumentError } from '@apextoaster/js-utils'; import { doesExist, InvalidArgumentError } from '@apextoaster/js-utils';
import { Container } from 'noicejs';
import { alea } from 'seedrandom'; import { alea } from 'seedrandom';
import { initConfig } from './config'; import { initConfig } from './config';
import { Commands, createParser } from './config/args'; import { Commands, createParser } from './config/args';
import { dotGraph, graphLabels } from './graph'; import { dotGraph, graphLabels } from './graph';
import { BunyanLogger } from './logger/bunyan'; import { BunyanLogger } from './logger/bunyan';
import { RemoteModule } from './module/RemoteModule';
import { Remote, RemoteOptions } from './remote';
import { GithubRemote } from './remote/github'; import { GithubRemote } from './remote/github';
import { syncIssueLabels, SyncOptions, syncProjectLabels } from './sync'; import { syncIssueLabels, SyncOptions, syncProjectLabels } from './sync';
import { VERSION_INFO } from './version'; import { VERSION_INFO } from './version';
@ -34,6 +37,9 @@ export async function main(argv: Array<string>): Promise<number> {
config, config,
}, 'runtime data'); }, 'runtime data');
const container = Container.from(new RemoteModule());
await container.configure();
for (const project of config.projects) { for (const project of config.projects) {
const { name } = project; const { name } = project;
@ -43,13 +49,18 @@ export async function main(argv: Array<string>): Promise<number> {
} }
const random = alea(name); const random = alea(name);
const remote = new GithubRemote({ const remote = await container.create<Remote, RemoteOptions>(project.remote.type, {
data: project.remote.data, data: project.remote.data,
dryrun: args.dryrun || project.remote.dryrun || false, dryrun: args.dryrun || project.remote.dryrun || false,
logger, logger,
type: project.remote.type, type: project.remote.type,
}); });
await remote.connect();
const connected = await remote.connect();
if (!connected) {
logger.error({ type: project.remote.type }, 'unable to connect to remote');
return 1;
}
// mode switch // mode switch
const options: SyncOptions = { const options: SyncOptions = {

View File

@ -0,0 +1,15 @@
import { Module, ModuleOptions } from 'noicejs';
import { Remote, RemoteOptions } from '../remote';
import { GithubRemote } from '../remote/github';
import { GitlabRemote } from '../remote/gitlab';
import { kebabCase } from '../utils';
export class RemoteModule extends Module {
public async configure(options: ModuleOptions) {
await super.configure(options);
this.bind<Remote, GithubRemote, RemoteOptions>(kebabCase(GithubRemote.name)).toConstructor(GithubRemote);
this.bind<Remote, GitlabRemote, RemoteOptions>(kebabCase(GitlabRemote.name)).toConstructor(GitlabRemote);
}
}

View File

@ -22,7 +22,7 @@ export class GithubRemote implements Remote {
this.options.logger.debug(options, 'created github remote'); this.options.logger.debug(options, 'created github remote');
} }
public async connect() { public async connect(): Promise<boolean> {
this.options.logger.info('connecting to github'); this.options.logger.info('connecting to github');
const type = mustExist(this.options.data.type); const type = mustExist(this.options.data.type);
@ -48,6 +48,8 @@ export class GithubRemote implements Remote {
default: default:
throw new InvalidArgumentError('unknown authentication type'); throw new InvalidArgumentError('unknown authentication type');
} }
return true;
} }
public async splitProject(project: string): Promise<{ public async splitProject(project: string): Promise<{

View File

@ -11,7 +11,11 @@ export class GitlabRemote implements Remote {
// TODO: set up gitlab API // TODO: set up gitlab API
} }
public async createComment() { public async connect(): Promise<boolean> {
throw new NotImplementedError();
}
public async createComment(): Promise<void> {
throw new NotImplementedError(); throw new NotImplementedError();
} }

View File

@ -1,4 +1,5 @@
import { Logger } from 'noicejs'; import { BaseOptions, Logger } from 'noicejs';
import { ChangeRecord, ErrorRecord } from '../resolve'; import { ChangeRecord, ErrorRecord } from '../resolve';
export interface ProjectQuery { export interface ProjectQuery {
@ -32,7 +33,7 @@ export interface LabelUpdate extends LabelQuery {
desc: string; desc: string;
} }
export interface RemoteOptions { export interface RemoteOptions extends BaseOptions {
/** /**
* Arbitrary key-value data for this remote, usually credentials and base URLs. * Arbitrary key-value data for this remote, usually credentials and base URLs.
*/ */
@ -55,6 +56,8 @@ export interface RemoteOptions {
* Basic functions which every remote API must provide. * Basic functions which every remote API must provide.
*/ */
export interface Remote { export interface Remote {
connect(): Promise<boolean>;
/** /**
* Add a comment to an issue (for attribution and auditing). * Add a comment to an issue (for attribution and auditing).
*/ */

View File

@ -35,3 +35,11 @@ export function compareItems<T>(a: Array<T>, b: Array<T>): boolean {
return true; return true;
} }
export function kebabCase(name: string): string {
return name
.replace(/([A-Z])/g, (m: string, p1: string) => `-${p1.toLocaleLowerCase()}`) // capitals
.replace(/[^-a-z0-9]/g, '-') // non-alnum
.replace(/--+/g, '-') // duplicates
.replace(/(^-|-$)/g, ''); // leading/trailing
}

View File

@ -1,6 +1,6 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { defaultTo, defaultUntil, compareItems } from '../src/utils'; import { defaultTo, defaultUntil, compareItems, kebabCase } from '../src/utils';
const TEST_TRUE = 'foo'; const TEST_TRUE = 'foo';
const TEST_FALSE = 'bar'; const TEST_FALSE = 'bar';
@ -64,4 +64,31 @@ describe('utils', () => {
)).to.equal(false); )).to.equal(false);
}); });
}); });
describe('kebab case helper', () => {
it('should replace non-alnum characters with dashes', () => {
expect(kebabCase('1_2,3+4')).to.equal('1-2-3-4');
});
it('should lowercase the value', () => {
expect(kebabCase('ABC')).to.equal('a-b-c');
expect(kebabCase('A-B-C')).to.equal('a-b-c');
});
it('should remove leading dashes', () => {
expect(kebabCase('--1')).to.equal('1');
expect(kebabCase('++1')).to.equal('1');
expect(kebabCase('-g-g')).to.equal('g-g');
});
it('should remove trailing dashes', () => {
expect(kebabCase('1--')).to.equal('1');
expect(kebabCase('1++')).to.equal('1');
});
it('should remove duplicate dashes', () => {
expect(kebabCase('foo...bar')).to.equal('foo-bar');
expect(kebabCase('foo-.-bar')).to.equal('foo-bar');
});
});
}); });

View File

@ -1,5 +1,5 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { NullLogger } from 'noicejs'; import { Container, NullLogger } from 'noicejs';
import { alea } from 'seedrandom'; import { alea } from 'seedrandom';
import { stub } from 'sinon'; import { stub } from 'sinon';
@ -8,8 +8,12 @@ import { syncIssueLabels } from '../../src/sync';
describe('issue sync', () => { describe('issue sync', () => {
it('should resolve each issue', async () => { it('should resolve each issue', async () => {
const container = Container.from();
await container.configure();
const logger = NullLogger.global; const logger = NullLogger.global;
const remoteData = { const remoteData = {
container,
data: {}, data: {},
dryrun: true, dryrun: true,
logger, logger,

View File

@ -1,4 +1,5 @@
import { expect } from 'chai'; import { expect } from 'chai';
import { Container } from 'noicejs';
import { alea } from 'seedrandom'; import { alea } from 'seedrandom';
import { match, spy, stub } from 'sinon'; import { match, spy, stub } from 'sinon';
@ -10,10 +11,14 @@ describe('project sync', () => {
describe('all labels', () => { describe('all labels', () => {
it('should sync each label'); it('should sync each label');
it('should pick a stable random color for each label', async () => { it('should pick a stable random color for each label', async () => {
const container = Container.from();
await container.configure();
const logger = BunyanLogger.create({ const logger = BunyanLogger.create({
name: 'test', name: 'test',
}); });
const remoteConfig = { const remoteConfig = {
container,
data: {}, data: {},
dryrun: true, dryrun: true,
logger, logger,
@ -59,10 +64,14 @@ describe('project sync', () => {
}); });
it('should create missing labels', async () => { it('should create missing labels', async () => {
const container = Container.from();
await container.configure();
const logger = BunyanLogger.create({ const logger = BunyanLogger.create({
name: 'test', name: 'test',
}); });
const remoteConfig = { const remoteConfig = {
container,
data: {}, data: {},
dryrun: true, dryrun: true,
logger, logger,
@ -101,10 +110,14 @@ describe('project sync', () => {
}); });
it('should delete extra labels', async () => { it('should delete extra labels', async () => {
const container = Container.from();
await container.configure();
const logger = BunyanLogger.create({ const logger = BunyanLogger.create({
name: 'test', name: 'test',
}); });
const remoteConfig = { const remoteConfig = {
container,
data: {}, data: {},
dryrun: true, dryrun: true,
logger, logger,