본문 바로가기
개발/Github

Github : 오픈소스에 기여를 해보자. (Github PR 날리는 법)

by hsloth 2023. 6. 12.

 

오픈소스 기여 / PR 날리는 법만 궁금하다면 아래쪽을 보시면 됩니다.

 

https://suloth.tistory.com/107

 

Digimon project : 프로젝트 시작

내가 평소 즐겨하는 게임인 디지몬 알피지(Digimon RPG, 이하 디알)라는 게임이 있다. 이 게임을 하면서 항상 느끼는 점이 뉴비에게 불친절하다는 점과 어떤 정보를 알려고하면 게임을 오래 즐긴 유

suloth.tistory.com

위의 프로젝트를 진행하던 도중, typia 라는 패키지에서 문제가 발생했다. 

typia.random<T>();

위의 함수는 T에 해당하는 값들을 임의로 생성해주는 함수인데, 리턴 값은 오직 원시타입만 리턴하도록 되어있다.

그래서 다음과 같이 Date가 제네릭 인자로 들어가면 string을 리턴해야한다.

typia의 validate 기능을 이용하여 "2020-01-01" 과 같은 값을 리턴하려고 string으로 리턴을 강제한다.

// a의 타입은 string
const a = typia.random<Date>();

// b의 타입은 string | null 로 추론되어야 하는데 잘못 추론됨.
const b = typia.random<Date | null>();

하지만, 여기서 typia.random<Date | null>() 구문의 리턴 타입이 Date | null로 추론이 되는 상황이 발생했다.

 

그러면 typia.random함수에 문제가 있다는 뜻이니, typia.random 함수를 한 번 봐보자.

export declare function random<T>(generator?: Partial<IRandomGenerator>): Primitive<T>;

typia.random 함수는 제네릭 T를 받아서, Primitive<T> 타입을 리턴하는 함수이다.

우리는 리턴 타입을 추론하는데 문제가 있으니, Primitive타입을 뜯어보면 해결방법이 나올 것이다.

 

export type Primitive<T> = Equal<T, PrimitiveMain<T>> extends true 
	? T : PrimitiveMain<T>;
    
type Equal<X, Y> = X extends Y ? (Y extends X ? true : false) : false;

type PrimitiveMain<Instance> = ValueOf<Instance> extends boolean | number | bigint | string 
	? ValueOf<Instance> 
    : ValueOf<Instance> extends object 
    	? Instance extends object 
        	? Instance extends NativeClass 
            	? {} 
                : Instance extends IJsonable<infer Raw> 
                	? ValueOf<Raw> extends object 
                    	? Raw extends object 
                        	? PrimitiveObject<Raw> 
                            : never 
                        : ValueOf<Raw> 
                    : PrimitiveObject<Instance> 
            : never 
        : ValueOf<Instance>;

type PrimitiveObject<Instance extends object> = Instance extends Array<infer T> ? IsTuple<Instance> extends true ? PrimitiveTuple<Instance> : PrimitiveMain<T>[] : {
    [P in keyof Instance]: Instance[P] extends Function ? never : PrimitiveMain<Instance[P]>;
};

type PrimitiveTuple<T extends readonly any[]> = T extends [] ? [] : T extends [infer F] ? [PrimitiveMain<F>] : T extends [infer F, ...infer Rest extends readonly any[]] ? [PrimitiveMain<F>, ...PrimitiveTuple<Rest>] : T extends [(infer F)?] ? [PrimitiveMain<F>?] : T extends [(infer F)?, ...infer Rest extends readonly any[]] ? [PrimitiveMain<F>?, ...PrimitiveTuple<Rest>] : [];

type ValueOf<Instance> = IsValueOf<Instance, Boolean> extends true ? boolean : IsValueOf<Instance, Number> extends true ? number : IsValueOf<Instance, String> extends true ? string : Instance;

type NativeClass = Set<any> | Map<any, any> | WeakSet<any> | WeakMap<any, any> | Uint8Array | Uint8ClampedArray | Uint16Array | Uint32Array | BigUint64Array | Int8Array | Int16Array | Int32Array | BigInt64Array | Float32Array | Float64Array | ArrayBuffer | SharedArrayBuffer | DataView;

type IsTuple<T extends readonly any[] | {
    length: number;
}> = [T] extends [
    never
] ? false : T extends readonly any[] ? number extends T["length"] ? false : true : false;

type IsValueOf<Instance, Object extends IValueOf<any>> = Instance extends Object ? Object extends IValueOf<infer Primitive> ? Instance extends Primitive ? false : true : false : false;
interface IValueOf<T> {
    valueOf(): T;
}

interface IJsonable<T> {
    toJSON(): T;
}

export {};

여기서 나는 PrimitiveMain에 문제가 있는 것을 발견하고 코드를 다음과 같이 수정했다.

(평소에 타입챌린지를 어느정도 풀어봤다면 간단히 해결할 수 있는 문제였다)

type PrimitiveMain<Instance> = Instance extends [never] 
	? never 
    : ValueOf<Instance> extends boolean | number | bigint | string 
    	? ValueOf<Instance> 
        : ValueOf<Instance> extends object 
        	? Instance extends object 
            	? Instance extends NativeClass 
                	? {} 
                    : Instance extends IJsonable<infer Raw> 
                    	? ValueOf<Raw> extends object 
                        	? Raw extends object 
                            	? PrimitiveObject<Raw> 
                                : never : ValueOf<Raw> 
                            : PrimitiveObject<Instance> 
                        : never 
                : ValueOf<Instance>;

 

 

이렇게 패키지의 코드를 수정하고 나니 타입 추론이 정상적으로 되었다.

 


오픈소스에 기여하기 / Pull Request 날리기

 


먼저, 오픈소스의 깃허브 링크를 알아내서 해당 깃허브 사이트에 들어가보자.

보통 구글링을 통해 해당 패키지명을 입력하면, 해당 깃허브 사이트가 뜬다.

나의 경우는 https://github.com/samchon/typia

 

GitHub - samchon/typia: Super-fast Runtime validator (type checker) with only one line

Super-fast Runtime validator (type checker) with only one line - GitHub - samchon/typia: Super-fast Runtime validator (type checker) with only one line

github.com

해당 주소로 들어가면 되었다.

 

해당 주소에서 Issue탭에 들어가서 먼저 이슈를 작성해주자. 이슈란 해당 레포에서 발생한 문제들을 적어 놓는 곳이다. (다른 용도로 사용되기도 한다)

나의 경우는 이런식으로 작성을 했다. https://github.com/samchon/typia/issues/655

 

Error : `typia.random<{a : Date | null }>()` return type is inferred `Date | null` type. · Issue #655 · samchon/typia

Summary Write a short summary of the bug in here. SDK Version: 4.0.5-dev.20230609 Expected behavior: Actual behavior: Write detailed description in here. when I use typia.random<T>() function, If T...

github.com

이슈를 작성할 때, 이슈 템플릿이라는게 자동으로 적용되어 해당 형식에 맞게 이슈를 작성하면 된다.

보편적으로는 어떤 문제가 발생했고, 무슨 작업을 하다 발생했고, 문제가 되는 코드는 무엇인지 등등을 적으면 된다.

 

그리고 나서 먼저 해당 레포를 fork해오자.

 

위의 사진에서 fork 버튼을 누르면 자신의 레포지토리로 해당 레포를 fork해올 수 있다. fork 버튼을 누르고, create fork 버튼을 눌러서 레포를 생성하자. 따로 건드릴 필요는 없다.

 

그러면 나의 레포에 해당 레포가 생성된다.

 

그 다음, 나의 레포를 git clone을 이용해서 내려받으면 된다. (내려받는 위치는 각자 알아서 하자)

git clone https://github.com/8471919/typia

 

그리고 내려 받은 레포에서 문제가 있는 파일을 찾아간다. 나의 경우는 typia/src/Primitive.ts 였다.

 

 

해당 파일에서 문제가 되었던 부분을 수정해주고 commit하면 되는데, 여기서 내가 수정한 코드가 다른 코드에 영향을 미치는지 검증을 해볼 필요가 있다.

이 부분은 아마 각 패키지마다 Contribute에 관한 설명이 적혀있는 md(마크다운) 파일이 있을 것이다. 자세히 읽어보자.

나의 경우는 이거였다. https://github.com/samchon/typia/blob/master/CONTRIBUTING.md

 

GitHub - samchon/typia: Super-fast Runtime validator (type checker) with only one line

Super-fast Runtime validator (type checker) with only one line - GitHub - samchon/typia: Super-fast Runtime validator (type checker) with only one line

github.com

 

나의 경우는 코드를 수정한 후, 터미널에 test관련 명령어를 입력해서 다른 코드에 영향을 미치는지 테스트 해본 후, PR을 날리면 되는 것이었다. 물론, PR도 형식이 정해져 있으니 잘 읽어보고 날리자.

 

테스트까지 다 마쳤다면, 이제 커밋을 하고 내 레포에 변경사항을 push한 후, 내 레포에서 Pull Request에 들어가서 New Pull Request -> Create Pull Request를 눌러주자.

 

 

위의 사진에서 나는 이미 PR을 날려버렸기 떄문에 Create Pull Request가 활성화 되진 않았지만, 여러분들은 활성화가 되어 있어야 한다.

base는 원본 레포지토리의 브랜치이고, compare는 내가 수정한 레포의 브랜치를 말한다. 즉, base는 원작자 레포, compare는 내가 코드 수정한 레포를 말하고, PR이 승인되면 compare의 수정사항이 base에 적용된다는 뜻이다.

 

그리고 PR 형식에 맞게 PR제목과 내용을 작성하면 된다.

나의 경우 https://github.com/samchon/typia/pull/656

 

Fix #655 : `typia.random<T>()` problem by 8471919 · Pull Request #656 · samchon/typia

When user uses typia.random such as typia.random<{a: Date | null}>(), the type Date | null is not distributed as Date and null (naked type) but from now, It is distributed on PrimitiveMain type. so...

github.com

위와 같이 작성하였다.

Pull Request에는 내가 아까 작성한 이슈번호를 #이슈번호 를 붙여서 날렸다.

나도 오픈소스에는 처음 PR을 날려봐서 좀 실수한 부분이 있지만, 참고가 되길 바라며 올려본다!

 

그리고 PR을 날렸다면 끝이다! 이제 오픈소스 개발자분이 해당 PR을 읽고 Merge해도 되겠다고 생각되면 Merge를 해줄 것이다. 만약 아니라면 PR은 close될 것이다.

그리고 만약 PR이 Merge된다면 내가 open한(작성한) issue도 함께 close된다.

 

이로써 당신은 오픈소스의 컨트리뷰터가 되었다.

해당 오픈소스 레포에 들어가서 Contributors항목을 보면 자신의 사진이 있는 것을 확인할 수 있다!

 

 

 

제 첫 오픈소스 PR을 날리는데 도움을 주신 kakasoo님

제 바보짓을 용인해 주신 samchon님 

감사합니다... 열심히 할게요!