From 2dad9dd0147bfb2cafa6aab168055c1b02967e0a Mon Sep 17 00:00:00 2001 From: Daniel Rochetti Date: Mon, 25 Sep 2023 15:00:26 -0700 Subject: [PATCH] feat: use branch name on permalinks --- docs/_includes/home.md | 24 ++++++++++------ docs/reference/index.md | 40 +++++++++++++++++++------- docs/reference/interfaces/GitRepo.md | 10 +++---- docs/reference/interfaces/LocalPath.md | 4 +-- docs/reference/interfaces/Snippet.md | 18 ++++++------ package.json | 2 +- src/__tests__/createSnippet.spec.ts | 2 +- src/__tests__/extractSnippets.spec.ts | 11 ++++--- src/__tests__/git.spec.ts | 14 ++++----- src/cli/generate.ts | 7 +++++ src/createSnippet.ts | 8 +++--- src/extractSnippets.ts | 29 +++++++++++++++---- src/git.ts | 7 +++-- src/index.ts | 1 + src/types.ts | 16 +++++++++-- 15 files changed, 130 insertions(+), 63 deletions(-) diff --git a/docs/_includes/home.md b/docs/_includes/home.md index 05cca30..d25383d 100644 --- a/docs/_includes/home.md +++ b/docs/_includes/home.md @@ -15,23 +15,23 @@ This project was motivated by past experiences dealing with outdated or faulty c Coldsnip can be used as a library, as a CLI or through direct integrations with other platforms. Check the [getting started guide](https://roxlabs.github.io/coldsnip/getting-started/) in order to determine the best option for your needs. -### CLI Commands - - - ### Library - + + ```ts const snippets = await extractSnippets([ { path: "src/__tests__", pattern: "snippets/twoSnippets.js" }, ]); ``` + + The return type is an map between the key and the snippet information, as detailed bellow: - + + ```ts /** * Represents a code snippet extracted from a source file. The field @@ -42,10 +42,14 @@ export interface Snippet { language: string; /** The file path relative to the working directory. */ sourcePath: string; + /** The name of the file, derived from `sourcePath`. */ + filename: string; /** The start line of the snippet. */ startLine: number; /** The end line of the snippet. */ endLine: number; + /** The lines to be highlighted, if any. */ + highlightedLines: number[]; /** The snippet content. Leading spaces are trimmed. */ content: string; /** The link to the file on the remote Git repo when available. */ @@ -66,6 +70,8 @@ export interface Snippet { export type Snippets = { [key: string]: Snippet[] }; ``` + + ## Roadmap See the [open feature requests](https://github.com/roxlabs/coldsnip/labels/enhancement) for a list of proposed features and join the discussion. @@ -77,9 +83,9 @@ Contributions are what make the open source community such an amazing place to b 1. Make sure you read our [Code of Conduct](https://github.com/roxlabs/coldsnip/blob/main/CODE_OF_CONDUCT.md) 1. Fork the project and clone your fork 1. Setup the local environment with `npm install` -1. Create a feature branch (`git checkout -b feature/AmazingFeature`) or a bugfix branch (`git checkout -b fix/BoringBug`) -1. Commit the changes (`git commit -m 'Some meaningful message'`) -1. Push to the branch (`git push origin feature/AmazingFeature`) +1. Create a feature branch (`git checkout -b feature/cool-thing`) or a bugfix branch (`git checkout -b fix/bad-bug`) +1. Commit the changes (`git commit -m 'feat: some meaningful message'`) +1. Push to the branch (`git push origin feature/cool-thing`) 1. Open a Pull Request diff --git a/docs/reference/index.md b/docs/reference/index.md index 95c2b8f..0565680 100644 --- a/docs/reference/index.md +++ b/docs/reference/index.md @@ -1,4 +1,4 @@ -# coldsnip - v0.9.0-alpha.3 +# coldsnip - v0.9.1 ## Table of contents @@ -10,6 +10,7 @@ ### Type Aliases +- [ExtractSnippetOptions](index.md#extractsnippetoptions) - [LookupOptions](index.md#lookupoptions) - [Snippets](index.md#snippets) - [SourcePath](index.md#sourcepath) @@ -21,6 +22,24 @@ ## Type Aliases +### ExtractSnippetOptions + +Ƭ **ExtractSnippetOptions**: `Object` + +Options to customize the snippet extraction process. + +#### Type declaration + +| Name | Type | Description | +| :------ | :------ | :------ | +| `branch` | `string` | The branch to use when extracting snippets from remote Git repositories. **`Default`** ```ts "main" ``` | + +#### Defined in + +[types.ts:115](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L115) + +___ + ### LookupOptions Ƭ **LookupOptions**: `Object` @@ -38,7 +57,7 @@ are useful for matching multi-language snippets indexed by the same key. #### Defined in -[lookupSnippet.ts:7](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/lookupSnippet.ts#L7) +[lookupSnippet.ts:7](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/lookupSnippet.ts#L7) ___ @@ -57,7 +76,7 @@ each supported language. #### Defined in -[types.ts:36](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L36) +[types.ts:36](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L36) ___ @@ -70,13 +89,13 @@ entry point of the library. #### Defined in -[types.ts:80](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L80) +[types.ts:80](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L80) ## Functions ### extractSnippets -▸ **extractSnippets**(`sources`): `Promise`<[`Snippets`](index.md#snippets)\> +▸ **extractSnippets**(`sources`, `options?`): `Promise`<[`Snippets`](index.md#snippets)\> The main function of the library. It takes a list of [SourcePath](index.md#sourcepath) that represents local directories or Git repositories where source files containing tagged code snippets @@ -91,9 +110,10 @@ const snippets = await extractSnippets([ #### Parameters -| Name | Type | Description | -| :------ | :------ | :------ | -| `sources` | [`SourcePath`](index.md#sourcepath)[] | a collection of local or remote (_i.e._ Git) sources. | +| Name | Type | Default value | Description | +| :------ | :------ | :------ | :------ | +| `sources` | [`SourcePath`](index.md#sourcepath)[] | `undefined` | a collection of local or remote (_i.e._ Git) sources. | +| `options` | [`ExtractSnippetOptions`](index.md#extractsnippetoptions) | `DEFAULT_EXTRACT_OPTIONS` | a set of optional parameters to customize the snippet extraction process. | #### Returns @@ -108,7 +128,7 @@ extractSnippets #### Defined in -[extractSnippets.ts:217](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/extractSnippets.ts#L217) +[extractSnippets.ts:223](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/extractSnippets.ts#L223) ___ @@ -136,4 +156,4 @@ the matching snippet or `undefined` in case it couldn't be found. #### Defined in -[lookupSnippet.ts:26](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/lookupSnippet.ts#L26) +[lookupSnippet.ts:26](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/lookupSnippet.ts#L26) diff --git a/docs/reference/interfaces/GitRepo.md b/docs/reference/interfaces/GitRepo.md index 7e77d04..28e6d86 100644 --- a/docs/reference/interfaces/GitRepo.md +++ b/docs/reference/interfaces/GitRepo.md @@ -32,7 +32,7 @@ An optional branch name. #### Defined in -[types.ts:68](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L68) +[types.ts:68](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L68) ___ @@ -44,7 +44,7 @@ The file pattern / glob to match. #### Defined in -[types.ts:58](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L58) +[types.ts:58](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L58) ___ @@ -62,7 +62,7 @@ false #### Defined in -[types.ts:63](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L63) +[types.ts:63](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L63) ___ @@ -74,7 +74,7 @@ The remote Git repository URL. #### Defined in -[types.ts:56](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L56) +[types.ts:56](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L56) ___ @@ -92,4 +92,4 @@ The directory where the repo should be cloned to. #### Defined in -[types.ts:73](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L73) +[types.ts:73](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L73) diff --git a/docs/reference/interfaces/LocalPath.md b/docs/reference/interfaces/LocalPath.md index ad14df6..9320ef1 100644 --- a/docs/reference/interfaces/LocalPath.md +++ b/docs/reference/interfaces/LocalPath.md @@ -23,7 +23,7 @@ The relative or absolute path. #### Defined in -[types.ts:45](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L45) +[types.ts:45](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L45) ___ @@ -35,4 +35,4 @@ The file pattern / glob to match. #### Defined in -[types.ts:47](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L47) +[types.ts:47](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L47) diff --git a/docs/reference/interfaces/Snippet.md b/docs/reference/interfaces/Snippet.md index db67dbc..13b2c82 100644 --- a/docs/reference/interfaces/Snippet.md +++ b/docs/reference/interfaces/Snippet.md @@ -27,7 +27,7 @@ The snippet content. Leading spaces are trimmed. #### Defined in -[types.ts:20](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L20) +[types.ts:20](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L20) ___ @@ -39,7 +39,7 @@ The end line of the snippet. #### Defined in -[types.ts:16](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L16) +[types.ts:16](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L16) ___ @@ -51,7 +51,7 @@ The name of the file, derived from `sourcePath`. #### Defined in -[types.ts:12](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L12) +[types.ts:12](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L12) ___ @@ -63,7 +63,7 @@ The lines to be highlighted, if any. #### Defined in -[types.ts:18](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L18) +[types.ts:18](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L18) ___ @@ -75,7 +75,7 @@ The source language. It matches the file extension. #### Defined in -[types.ts:8](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L8) +[types.ts:8](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L8) ___ @@ -87,7 +87,7 @@ The link to the file on the remote Git repo when available. #### Defined in -[types.ts:22](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L22) +[types.ts:22](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L22) ___ @@ -100,7 +100,7 @@ that might come from the same file extension. #### Defined in -[types.ts:27](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L27) +[types.ts:27](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L27) ___ @@ -112,7 +112,7 @@ The file path relative to the working directory. #### Defined in -[types.ts:10](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L10) +[types.ts:10](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L10) ___ @@ -124,4 +124,4 @@ The start line of the snippet. #### Defined in -[types.ts:14](https://github.com/roxlabs/coldsnip/blob/f9e1b95/src/types.ts#L14) +[types.ts:14](https://github.com/roxlabs/coldsnip/blob/3c9352e/src/types.ts#L14) diff --git a/package.json b/package.json index 1c36051..14ef9d3 100644 --- a/package.json +++ b/package.json @@ -31,7 +31,7 @@ "docs:readme": "cp README.md ./docs/_includes/home.md", "docs:coldsnip": "./bin/run generate --source=./src --pattern=**/*.ts --format=include", "docs:typedoc": "typedoc --tsconfig tsconfig.lib.json", - "docs": "npm run docs:readme & npm run docs:typedoc", + "docs": "npm run docs:readme && npm run docs:typedoc", "format:imports": "organize-imports-cli tsconfig.json", "format:prettier": "prettier --write \"./**/*.{js,json,ts}\"", "format:check": "prettier --check \"./**/*.{js,json,ts}\"", diff --git a/src/__tests__/createSnippet.spec.ts b/src/__tests__/createSnippet.spec.ts index 88851f7..5615c32 100644 --- a/src/__tests__/createSnippet.spec.ts +++ b/src/__tests__/createSnippet.spec.ts @@ -16,7 +16,7 @@ describe("the createSnippet test suite", () => { const snippet = createSnippet({ filePath: "src/index.ts", repoUrl: "https://github.com/roxlabs/coldsnip", - commit: "d16f69f4eb4f46855c66420c02c586138fb38fcf", + branch: "main", directory: "./", startLine: 1, endLine: 2, diff --git a/src/__tests__/extractSnippets.spec.ts b/src/__tests__/extractSnippets.spec.ts index acc60fc..38775ae 100644 --- a/src/__tests__/extractSnippets.spec.ts +++ b/src/__tests__/extractSnippets.spec.ts @@ -3,10 +3,13 @@ import extractSnippets, { findFiles } from "../extractSnippets"; describe("the extractSnippets public API test suite", () => { it("should find the files from all sources", async () => { - const result = await findFiles([ - { path: "./", pattern: "src/__tests__/snippets/*.js" }, - { url: "https://github.com/drochetti/drochetti", pattern: "*.md" }, - ]); + const result = await findFiles( + [ + { path: "./", pattern: "src/__tests__/snippets/*.js" }, + { url: "https://github.com/drochetti/drochetti", pattern: "*.md" }, + ], + { branch: "main" }, + ); const paths: string[] = []; result.forEach(([files, source]) => { paths.push(...files); diff --git a/src/__tests__/git.spec.ts b/src/__tests__/git.spec.ts index a03815e..da7b283 100644 --- a/src/__tests__/git.spec.ts +++ b/src/__tests__/git.spec.ts @@ -38,39 +38,39 @@ describe("the Git repo test suite", () => { it("should resolve the permalink for GitHub repos", () => { const permalink = getPermalink({ repoUrl: "https://github.com/roxlabs/coldsnip", - commit: "d16f69f4eb4f46855c66420c02c586138fb38fcf", + branch: "main", path: "src/index.ts", startLine: 1, endLine: 3, }); expect(permalink).toBe( - "https://github.com/roxlabs/coldsnip/blob/d16f69f4eb4f46855c66420c02c586138fb38fcf/src/index.ts#L1-L3", + "https://github.com/roxlabs/coldsnip/blob/main/src/index.ts#L1-L3", ); }); it("should resolve the permalink for GitLab repos", () => { const permalink = getPermalink({ repoUrl: "https://gitlab.com/gitlab-org/gitlab", - commit: "12ecbd8580e13bd385729772102f86bcecfda04f", + branch: "develop", path: "package.json", startLine: 4, endLine: 9, }); expect(permalink).toBe( - "https://gitlab.com/gitlab-org/gitlab/-/blob/12ecbd8580e13bd385729772102f86bcecfda04f/package.json#L4-9", + "https://gitlab.com/gitlab-org/gitlab/-/blob/develop/package.json#L4-9", ); }); it("should resolve the permalink for BitBucket repos", () => { const permalink = getPermalink({ repoUrl: "https://bitbucket.org/atlassian/npm-publish", - commit: "81c90f7e6b1c0859652bf6bd0dc478d99c37d6ac", + branch: "staging", path: "test/test.py", startLine: 22, endLine: 27, }); expect(permalink).toBe( - "https://bitbucket.org/atlassian/npm-publish/src/81c90f7e6b1c0859652bf6bd0dc478d99c37d6ac/test/test.py#lines-22:27", + "https://bitbucket.org/atlassian/npm-publish/src/staging/test/test.py#lines-22:27", ); }); @@ -78,7 +78,7 @@ describe("the Git repo test suite", () => { expect(() => { getPermalink({ repoUrl: "https://unsupported.provider.com/cool/repo", - commit: "main", + branch: "main", path: "README.md", startLine: 1, endLine: 2, diff --git a/src/cli/generate.ts b/src/cli/generate.ts index 37bec66..934c1cd 100644 --- a/src/cli/generate.ts +++ b/src/cli/generate.ts @@ -51,6 +51,12 @@ class GenerateCommand extends Command { dependsOn: ["source"], description: "the file match pattern, e.g. src/**/*.js", }), + branch: Flags.string({ + char: "b", + required: false, + description: "the target git branch name for permalinks", + default: "main", + }), }; async run() { @@ -60,6 +66,7 @@ class GenerateCommand extends Command { source, pattern = DEFAULT_SOURCE_FILES_PATTERN, format, + branch, out: outputPath, } = flags; let parsedConfig: Config | undefined; diff --git a/src/createSnippet.ts b/src/createSnippet.ts index d12459c..1d82580 100644 --- a/src/createSnippet.ts +++ b/src/createSnippet.ts @@ -10,7 +10,7 @@ type SnippetInput = { startLine: number; endLine: number; qualifier?: string; - commit?: string; + branch?: string; repoUrl?: string; highlightedLines?: number[]; }; @@ -22,16 +22,16 @@ export default function createSnippet(input: SnippetInput): Snippet { startLine, endLine, repoUrl, - commit, + branch, qualifier, highlightedLines = [], } = input; const sourcePath = getRelativePath(directory, filePath); const permalink = - repoUrl !== undefined && commit !== undefined + repoUrl !== undefined && branch !== undefined ? getPermalink({ repoUrl, - commit, + branch, startLine: startLine as number, endLine, path: sourcePath, diff --git a/src/extractSnippets.ts b/src/extractSnippets.ts index 8f426d3..a81c943 100644 --- a/src/extractSnippets.ts +++ b/src/extractSnippets.ts @@ -10,7 +10,12 @@ import { matchesEndTag, matchesStartTag, } from "./patterns"; -import type { Snippets, SourcePath, SourceRef } from "./types"; +import type { + ExtractSnippetOptions, + Snippets, + SourcePath, + SourceRef, +} from "./types"; type SourceFilesTuple = [string[], SourceRef]; type SourceFileRecord = { filePath: string; ref: SourceRef }; @@ -23,8 +28,10 @@ type SourceFileRecord = { filePath: string; ref: SourceRef }; */ export async function findFiles( sources: SourcePath[], + options: ExtractSnippetOptions, ): Promise { const promises: Array> = []; + const { branch } = options; for (const source of sources) { if ("path" in source) { // when in local paths, try to get git info if possible @@ -32,7 +39,7 @@ export async function findFiles( promises.push( Promise.all([ glob(source.pattern, { cwd: source.path, absolute: true }), - Promise.resolve({ directory: source.path, repoUrl, commit }), + Promise.resolve({ directory: source.path, repoUrl, commit, branch }), ]), ); } else if ("url" in source) { @@ -46,6 +53,7 @@ export async function findFiles( directory: workingDir, repoUrl: source.url, commit, + branch, }), ]), ); @@ -134,7 +142,7 @@ async function extractSnippetFromFile( } else if (matchesEndTag(lineContent)?.name === "snippet") { state = "OUTSIDE_SNIPPET"; const endLine = lineNumber - 1; - const { repoUrl, commit, directory } = ref; + const { repoUrl, branch, directory } = ref; snippets[key as string] = [ createSnippet({ @@ -144,7 +152,7 @@ async function extractSnippetFromFile( startLine: startLine as number, endLine, repoUrl, - commit, + branch, qualifier, highlightedLines, }), @@ -189,6 +197,10 @@ async function extractSnippetFromFile( return snippets; } +const DEFAULT_EXTRACT_OPTIONS: ExtractSnippetOptions = { + branch: "main", +}; + /** * The main function of the library. It takes a list of {@link SourcePath} that represents * local directories or Git repositories where source files containing tagged code snippets @@ -204,12 +216,17 @@ async function extractSnippetFromFile( * @name extractSnippets * @public * @param sources a collection of local or remote (_i.e._ Git) sources. + * @param options a set of optional parameters to customize the snippet extraction process. * @returns an object indexed by a key, representing the snippet identifier and an array of one * or more code snippets. */ -async function extractSnippets(sources: SourcePath[]): Promise { +async function extractSnippets( + sources: SourcePath[], + options: ExtractSnippetOptions = DEFAULT_EXTRACT_OPTIONS, +): Promise { // Find all files, either from local path or a git repo - const files = await findFiles(sources); + const computedOptions = { ...DEFAULT_EXTRACT_OPTIONS, ...options }; + const files = await findFiles(sources, computedOptions); if (files.length === 0) { return {}; } diff --git a/src/git.ts b/src/git.ts index 66c58db..8ac5c6c 100644 --- a/src/git.ts +++ b/src/git.ts @@ -26,6 +26,7 @@ export async function ensureRepoIsCurrent( return { commit, workingDir, + branch, }; } @@ -33,11 +34,11 @@ export function getPermalink(ref: GitFileRef): string { const url = new URL(ref.repoUrl); switch (url.hostname) { case "github.com": - return `${ref.repoUrl}/blob/${ref.commit}/${ref.path}#L${ref.startLine}-L${ref.endLine}`; + return `${ref.repoUrl}/blob/${ref.branch}/${ref.path}#L${ref.startLine}-L${ref.endLine}`; case "gitlab.com": - return `${ref.repoUrl}/-/blob/${ref.commit}/${ref.path}#L${ref.startLine}-${ref.endLine}`; + return `${ref.repoUrl}/-/blob/${ref.branch}/${ref.path}#L${ref.startLine}-${ref.endLine}`; case "bitbucket.org": - return `${ref.repoUrl}/src/${ref.commit}/${ref.path}#lines-${ref.startLine}:${ref.endLine}`; + return `${ref.repoUrl}/src/${ref.branch}/${ref.path}#lines-${ref.startLine}:${ref.endLine}`; default: throw new Error(`Git provider not yet supported: ${ref.repoUrl}`); } diff --git a/src/index.ts b/src/index.ts index 1c7be48..c047169 100644 --- a/src/index.ts +++ b/src/index.ts @@ -3,6 +3,7 @@ import lookupSnippets from "./lookupSnippet"; export type { LookupOptions } from "./lookupSnippet"; export type { + ExtractSnippetOptions, GitRepo, LocalPath, Snippet, diff --git a/src/types.ts b/src/types.ts index e7e0650..2bedad8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -84,13 +84,13 @@ export type GitRepoInfo = Omit; export interface SourceRef { directory: string; repoUrl?: string; - commit?: string; + branch?: string; } export interface GitFileRef { repoUrl: string; path: string; - commit: string; + branch: string; startLine: number; endLine: number; } @@ -98,6 +98,7 @@ export interface GitFileRef { export interface GitRepoRef { workingDir: string; commit: string; + branch: string; } export type OutputFormat = "json" | "markdown" | "include"; @@ -107,3 +108,14 @@ export type SnippetTransformer = (snippets: Snippets) => Promise; export interface Config { paths: SourcePath | SourcePath[]; } + +/** + * Options to customize the snippet extraction process. + */ +export type ExtractSnippetOptions = { + /** + * The branch to use when extracting snippets from remote Git repositories. + * @default "main" + */ + branch: string; +};