├── .gitattributes
├── .github
└── workflows
│ ├── build.yml
│ ├── release-git-urls.yml
│ └── release-vscode-extension.yml
├── .gitignore
├── README.md
├── gitlink.code-workspace
├── images
└── how_to_use_it.gif
├── package-lock.json
├── package.json
└── packages
├── eslint-config
├── index.js
└── package.json
├── git-urls
├── .eslintrc.js
├── .gitignore
├── .npmignore
├── .prettierrc.js
├── LICENSE
├── README.md
├── package.json
├── preprocessor.js
├── src
│ ├── error.ts
│ ├── helper.ts
│ ├── host
│ │ ├── basicHost.ts
│ │ ├── bitbucket.ts
│ │ ├── devops.ts
│ │ ├── github.ts
│ │ ├── gitlab.ts
│ │ ├── host.ts
│ │ ├── hostBuilder.ts
│ │ └── vsts.ts
│ ├── index.ts
│ ├── info.ts
│ └── result.ts
├── test
│ ├── bitbucket.test.ts
│ ├── devops.test.ts
│ ├── github.test.ts
│ ├── gitlab.test.ts
│ ├── helper.test.ts
│ ├── host.test.ts
│ └── vsts.test.ts
└── tsconfig.json
├── prettier-config
├── index.js
└── package.json
└── vscode-gitlink
├── .eslintrc.js
├── .prettierrc.js
├── .vscode
└── launch.json
├── .vscodeignore
├── LICENSE
├── README.md
├── images
└── logo.png
├── package.json
├── src
├── extension.ts
└── logger.ts
└── tsconfig.json
/.gitattributes:
--------------------------------------------------------------------------------
1 | * text=auto
2 | *.js eol=lf
3 | *.json eol=lf
4 | *.md eol=lf
5 | *.ts eol=lf
6 |
--------------------------------------------------------------------------------
/.github/workflows/build.yml:
--------------------------------------------------------------------------------
1 | name: Build Project
2 |
3 | on:
4 | workflow_dispatch:
5 | push:
6 | branches:
7 | - main
8 | pull_request:
9 | branches:
10 | - main
11 |
12 | jobs:
13 | build:
14 | strategy:
15 | matrix:
16 | os: [ubuntu-latest, windows-latest, macos-latest]
17 | node-version: [">=18"]
18 |
19 | runs-on: ${{ matrix.os }}
20 |
21 | steps:
22 | - name: Setup node environment
23 | uses: actions/setup-node@v2.1.2
24 | with:
25 | node-version: ${{ matrix.node-version }}
26 |
27 | - name: Checkout code
28 | uses: actions/checkout@v2
29 |
30 | - name: Install
31 | run: npm install
32 |
33 | - name: Build
34 | run: npm run build
35 |
36 | - name: Lint
37 | run: npm run lint
38 |
39 | - name: Test
40 | run: npm run test
--------------------------------------------------------------------------------
/.github/workflows/release-git-urls.yml:
--------------------------------------------------------------------------------
1 | name: Publish Git Urls to npmjs.org
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | environment: production
10 | steps:
11 | - name: Setup node environment
12 | uses: actions/setup-node@v2.1.2
13 | with:
14 | node-version: ">=18"
15 | registry-url: https://registry.npmjs.org/
16 |
17 | - name: Checkout code
18 | uses: actions/checkout@v2
19 |
20 | - name: Install
21 | run: |
22 | npm install
23 | working-directory: ./packages/git-urls
24 |
25 | - name: Build
26 | run: npm run build
27 | working-directory: ./packages/git-urls
28 |
29 | - name: Test
30 | run: npm run test
31 | working-directory: ./packages/git-urls
32 |
33 | - name: Publish
34 | run: npm publish --access public
35 | working-directory: ./packages/git-urls
36 | env:
37 | NODE_AUTH_TOKEN: ${{secrets.NPM_TOKEN}}
38 |
--------------------------------------------------------------------------------
/.github/workflows/release-vscode-extension.yml:
--------------------------------------------------------------------------------
1 | name: Publish VS Code Extension to marketplace
2 |
3 | on:
4 | workflow_dispatch:
5 |
6 | jobs:
7 | build:
8 | runs-on: ubuntu-latest
9 | environment: production
10 | steps:
11 | - name: Setup node environment
12 | uses: actions/setup-node@v2.1.2
13 | with:
14 | node-version: ">=18"
15 | registry-url: https://registry.npmjs.org/
16 |
17 | - name: Checkout code
18 | uses: actions/checkout@v2
19 |
20 | - name: Install
21 | run: |
22 | npm install
23 | npm install -g vsce
24 | working-directory: ./packages/vscode-gitlink
25 |
26 | - name: Build
27 | run: npm run build
28 | working-directory: ./packages/vscode-gitlink
29 |
30 | - name: Test
31 | run: npm run test
32 | working-directory: ./packages/vscode-gitlink
33 |
34 | - name: Publish
35 | run: vsce publish -p ${{secrets.VSCODE_TOKEN}}
36 | working-directory: ./packages/vscode-gitlink
37 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | out/
2 | node_modules/
3 | .vscode-test/
4 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # GitLink
2 |
3 | [](https://marketplace.visualstudio.com/items?itemName=qezhu.gitlink)
4 | [](https://marketplace.visualstudio.com/items?itemName=qezhu.gitlink)
5 | [](https://marketplace.visualstudio.com/items?itemName=qezhu.gitlink)
6 | [](https://ci.appveyor.com/project/qinezh/vscode-gitlink)
7 |
8 | Inspired by GitHub extension for Visual Studio, this extension provide features that **Go To** current file's online link in browser and **Copy** the link in clipboard.
9 |
10 | ## Features
11 |
12 | 1. Go to the online link of current file.
13 | 2. Copy the online link of current file.
14 | 3. Supported git repo platforms:
15 | 1. GitHub
16 | 2. GitLab
17 | 3. Azure DevOps
18 | 4. BitBucket
19 | 5. VSTS
20 |
21 | ## Usage
22 |
23 |
24 |
25 | ### Set default remote source
26 |
27 | When your project has multiple git remotes, you need to choose the git remote before the git link is generated. If you don't want to choose it every time, you could set the default remote source:
28 |
29 | **Workspace Level**: add `gitlink.defaultRemote: ""` in `.vscode/settings.json` under the root of your workspace.
30 |
31 | **Global Level**: toggle the preference of vscode, and add `gitlink.defaultRemote: ""` in User Settings.
32 |
33 | Please note, you could get the name of your remote sources by the command: `git remote -v`:
34 |
35 | ```bash
36 | # example
37 | $ git remote -v
38 | origin git@github.com:qinezh/vscode-gitlink (fetch)
39 | origin git@github.com:qinezh/vscode-gitlink (push)
40 | upstream git@github.com:upstream/vscode-gitlink.git (fetch)
41 | upstream git@github.com:upstream/vscode-gitlink.git (push)
42 | ```
43 |
44 | And the sample `settings.json` could be like:
45 | ```json
46 | {
47 | "gitlink.defaultRemote": "upsteam"
48 | }
49 | ```
50 |
51 | **Enjoy!**
52 |
--------------------------------------------------------------------------------
/gitlink.code-workspace:
--------------------------------------------------------------------------------
1 | {
2 | "folders": [
3 | {
4 | "path": ".github"
5 | },
6 | {
7 | "path": "packages/vscode-gitlink"
8 | },
9 | {
10 | "path": "packages/git-urls"
11 | },
12 | {
13 | "path": "packages/eslint-config"
14 | },
15 | {
16 | "path": "packages/prettier-config"
17 | }
18 | ]
19 | }
--------------------------------------------------------------------------------
/images/how_to_use_it.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/qinezh/vscode-gitlink/c4490f5636969b2a264a7d3d6d64ab2739c6b98a/images/how_to_use_it.gif
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "gitlink",
3 | "private": true,
4 | "workspaces": [
5 | "packages/git-urls",
6 | "packages/vscode-gitlink",
7 | "packages/eslint-config"
8 | ],
9 | "scripts": {
10 | "build": "npm run build --workspaces",
11 | "test": "npm run test --workspaces",
12 | "lint": "npm run lint --workspaces"
13 | }
14 | }
--------------------------------------------------------------------------------
/packages/eslint-config/index.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | env: {
3 | browser: true,
4 | es6: true,
5 | node: true,
6 | },
7 | parserOptions: {
8 | ecmaVersion: 2018,
9 | sourceType: "module",
10 | },
11 | extends: [
12 | "plugin:@typescript-eslint/recommended",
13 | "prettier",
14 | "plugin:import/errors",
15 | "plugin:import/warnings",
16 | "plugin:import/typescript"
17 | ],
18 | plugins: [
19 | "@typescript-eslint/eslint-plugin",
20 | "prettier"
21 | ],
22 | rules: {
23 | 'prettier/prettier': 'error',
24 | quotes: ["error", "double", { "allowTemplateLiterals": true, "avoidEscape": true }],
25 | semi: ["error", "always"],
26 | '@typescript-eslint/no-var-requires': 0,
27 | '@typescript-eslint/no-empty-function': 0,
28 | "import/no-cycle": [
29 | "error",
30 | {
31 | "maxDepth": Infinity,
32 | "ignoreExternal": true
33 | }
34 | ],
35 | "no-unused-vars": "off",
36 | "import/no-unresolved": "off",
37 | "@typescript-eslint/no-unused-vars": ["error", { "vars": "local", "args": "none", "ignoreRestSiblings": true, "varsIgnorePattern": "^_" }]
38 | },
39 | };
40 |
--------------------------------------------------------------------------------
/packages/eslint-config/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@shared/eslint-config",
3 | "private": true,
4 | "version": "1.0.0",
5 | "description": "Shared ESLint config",
6 | "main": "index.js",
7 | "devDependencies": {
8 | "@typescript-eslint/eslint-plugin": "^5.0.0",
9 | "@typescript-eslint/parser": "^5.0.0",
10 | "eslint-config-prettier": "^8.5.0"
11 | },
12 | "scripts": {
13 | "build": "",
14 | "test": "",
15 | "lint": ""
16 | }
17 | }
--------------------------------------------------------------------------------
/packages/git-urls/.eslintrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | extends: ["../eslint-config"],
3 | };
4 |
--------------------------------------------------------------------------------
/packages/git-urls/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules/
2 | shrinkwrap.yaml
3 | .vscode/
4 | dist/
--------------------------------------------------------------------------------
/packages/git-urls/.npmignore:
--------------------------------------------------------------------------------
1 | src/
2 | test/
3 | dist/test/
4 | .github
--------------------------------------------------------------------------------
/packages/git-urls/.prettierrc.js:
--------------------------------------------------------------------------------
1 | module.exports = {
2 | ...require("../prettier-config"),
3 | };
4 |
--------------------------------------------------------------------------------
/packages/git-urls/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2017 Qinen Zhu
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/packages/git-urls/README.md:
--------------------------------------------------------------------------------
1 | [](https://www.npmjs.com/package/git-urls)
2 | [](https://ci.appveyor.com/project/qinezh/git-urls)
3 |
4 | # git-urls
5 |
6 | Get online link of file with git remote URL(support Github, GitLab, Bitbucket, VSTS, DevOps)
7 |
8 | ## Install
9 |
10 | ```bash
11 | npm install git-urls
12 | ```
13 |
14 | ## Usage example
15 |
16 | ```javascript
17 | import GitUrls from "git-urls";
18 |
19 | const f = async () => {
20 | return await GitUrls.getUrlsAsync(__filename);
21 | };
22 |
23 | f().then(linkMap => {
24 | for (const [remoteName, link] of linkMap) {
25 | console.log(`${remoteName}: ${link}`);
26 | }
27 | });
28 | ```
29 |
--------------------------------------------------------------------------------
/packages/git-urls/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "git-urls",
3 | "version": "2.0.2",
4 | "description": "Get online URL of file (Github, GitLab, Bitbucket...)",
5 | "main": "out/src/index.js",
6 | "types": "out/src/index.d.ts",
7 | "repository": {
8 | "type": "git",
9 | "url": "git+https://github.com/qinezh/git-urls.git"
10 | },
11 | "keywords": [
12 | "git"
13 | ],
14 | "author": "qinezh",
15 | "license": "MIT",
16 | "bugs": {
17 | "url": "https://github.com/qinezh/git-urls/issues"
18 | },
19 | "homepage": "https://github.com/qinezh/git-urls#readme",
20 | "contributors": [
21 | "Junle Li",
22 | "Scott Bonebrake",
23 | "Tyler Kindy"
24 | ],
25 | "dependencies": {
26 | "fs-extra": "^7.0.1"
27 | },
28 | "devDependencies": {
29 | "@types/fs-extra": "^5.1.0",
30 | "@types/jest": "^27.4.1",
31 | "@types/node": "^7.10.9",
32 | "@typescript-eslint/eslint-plugin": "^5.21.0",
33 | "@typescript-eslint/parser": "^5.21.0",
34 | "eslint": "^8.14.0",
35 | "eslint-config-prettier": "^8.5.0",
36 | "eslint-plugin-import": "^2.25.2",
37 | "eslint-plugin-prettier": "^4.0.0",
38 | "jest": "^27.5.1",
39 | "jest-cli": "^27.5.1",
40 | "prettier": "^2.6.2",
41 | "ts-jest": "^27.1.4",
42 | "typescript": "^4.6.4"
43 | },
44 | "scripts": {
45 | "build": "tsc",
46 | "test": "jest",
47 | "lint": "npx eslint \"src/**/*.ts\" \"test/**/*.ts\"",
48 | "lint:fix": "npx eslint --fix \"src/**/*.ts\" \"test/**/*.ts\""
49 | },
50 | "jest": {
51 | "moduleFileExtensions": [
52 | "ts",
53 | "js"
54 | ],
55 | "transform": {
56 | "^.+\\.(ts|tsx)$": "/preprocessor.js"
57 | },
58 | "testMatch": [
59 | "/test/*.test.ts"
60 | ]
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/packages/git-urls/preprocessor.js:
--------------------------------------------------------------------------------
1 | const tsc = require('typescript');
2 | const tsConfig = require('./tsconfig.json');
3 |
4 | module.exports = {
5 | process(src, path) {
6 | if (path.endsWith('.ts') || path.endsWith('.tsx')) {
7 | return tsc.transpile(src, tsConfig.compilerOptions, path, []);
8 | }
9 | return src;
10 | },
11 | };
--------------------------------------------------------------------------------
/packages/git-urls/src/error.ts:
--------------------------------------------------------------------------------
1 | export class GitUrlError extends Error {
2 | constructor(public message: string) {
3 | super(message);
4 | }
5 | }
6 |
--------------------------------------------------------------------------------
/packages/git-urls/src/helper.ts:
--------------------------------------------------------------------------------
1 | import * as fs from "fs-extra";
2 | import * as path from "path";
3 |
4 | export default class Helper {
5 | public static async getRepoRoot(filePath: string): Promise {
6 | let currentFolder = this.normalize(path.dirname(filePath));
7 | while (true) {
8 | const configFolder = path.join(currentFolder, ".git");
9 | if (await fs.pathExists(configFolder)) {
10 | return currentFolder;
11 | }
12 |
13 | const index = currentFolder.lastIndexOf("/");
14 | if (index < 0) {
15 | break;
16 | }
17 |
18 | currentFolder = currentFolder.substring(0, index);
19 | }
20 | return null;
21 | }
22 |
23 | public static normalize(filePath: string): string {
24 | return filePath.replace(/\\/g, "/");
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/packages/git-urls/src/host/basicHost.ts:
--------------------------------------------------------------------------------
1 | import Host from "./host";
2 | import { GitConfigInfo, GitUrlInfo } from "../info";
3 | import { GitUrlError } from "../error";
4 |
5 | export default abstract class BasicHost implements Host {
6 | private readonly httpRegex = /(https?:\/\/)(?:[^:@]+:[^:@]+@)?([^\/:]+)(?:\/)([^\/:]+)(?:\/)([^\/:\n]+)/;
7 | private readonly gitRegex = /(git@)([^\/:]+)(?::)(?:\d+\/)?([^\/:]+)(?:\/)([^\/:\n]+)/;
8 | protected abstract get separateFolder(): string | undefined;
9 |
10 | public abstract get name(): string;
11 | public abstract match(url: string): boolean;
12 |
13 | public parse(configInfo: GitConfigInfo): GitUrlInfo {
14 | const matches = this.httpRegex.exec(configInfo.remoteUrl) ?? this.gitRegex.exec(configInfo.remoteUrl);
15 | if (!matches) {
16 | throw new GitUrlError(`Can't parse ${configInfo.remoteUrl} with the rule of ${this.constructor.name}.`);
17 | }
18 |
19 | const schema = matches[1];
20 | let isHttp = false;
21 | if (schema.startsWith("http://")) {
22 | isHttp = true;
23 | }
24 |
25 | let repoName = matches[4];
26 | if (repoName.endsWith(".git")) {
27 | repoName = repoName.substring(0, repoName.lastIndexOf(".git"));
28 | }
29 |
30 | let hostName = matches[2];
31 | const index = hostName.indexOf("@");
32 | if (index !== -1) {
33 | hostName = hostName.substring(index + 1);
34 | }
35 |
36 | const gitInfo: GitUrlInfo = {
37 | hostName: hostName,
38 | repoName: repoName,
39 | ref: { ...configInfo.ref, value: encodeURIComponent(configInfo.ref.value) },
40 | userName: matches[3],
41 | section: configInfo.section,
42 | metadata: { isHttp: isHttp },
43 | };
44 |
45 | if (configInfo.relativeFilePath) {
46 | let parts = configInfo.relativeFilePath.split("/");
47 | parts = parts.map(p => encodeURIComponent(p));
48 | gitInfo.relativeFilePath = parts.join("/");
49 | }
50 |
51 | return gitInfo;
52 | }
53 |
54 | public assemble(info: GitUrlInfo): string {
55 | return this.assembleLink(info);
56 | }
57 |
58 | protected assembleLink(info: GitUrlInfo): string {
59 | let prefix = "https://";
60 | if (info.metadata && info.metadata["isHttp"]) {
61 | prefix = "http://";
62 | }
63 |
64 | return `${prefix}${info.hostName}/${info.userName}/${info.repoName}/${this.separateFolder}/${info.ref.value}/${info.relativeFilePath}`;
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/packages/git-urls/src/host/bitbucket.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 |
3 | import BasicHost from "./basicHost";
4 | import { GitUrlInfo } from "../info";
5 |
6 | export default class BitBucket extends BasicHost {
7 | protected override get separateFolder(): string | undefined {
8 | return "src";
9 | }
10 |
11 | public override get name(): string {
12 | return "BitBucket";
13 | }
14 |
15 | public override match(url: string): boolean {
16 | return url.indexOf("bitbucket") >= 0;
17 | }
18 |
19 | public assemble(info: GitUrlInfo): string {
20 | const link = this.assembleLink(info);
21 |
22 | let fileName: string | null = null;
23 | if (info.relativeFilePath) {
24 | fileName = path.basename(info.relativeFilePath);
25 | }
26 |
27 | if (
28 | info.section &&
29 | info.section.startLine &&
30 | info.section.endLine &&
31 | info.section.startLine !== info.section.endLine &&
32 | fileName
33 | ) {
34 | return `${link}#${fileName}-${info.section.startLine}:${info.section.endLine}`;
35 | } else if (info.section && info.section.startLine && fileName) {
36 | return `${link}#${fileName}-${info.section.startLine}`;
37 | } else {
38 | return link;
39 | }
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/packages/git-urls/src/host/devops.ts:
--------------------------------------------------------------------------------
1 | import { GitConfigInfo, GitUrlInfo } from "../info";
2 | import BasicHost from "./basicHost";
3 |
4 | export default class DevOps extends BasicHost {
5 | /**
6 | * The regular expression to match the DevOps Git URL.
7 | * @example https://my-tenant@dev.azure.com/my-org/my-project/_git/my-repo
8 | * @example https://dev.azure.com/my-org/my-project/_git/my-repo
9 | * @example my-tenant@ssh.dev.azure.com:22/my-org/my-project/my-repo
10 | */
11 | private static urlRegex =
12 | /(?:https:\/\/)?(?:[\w-]+@)?(?:ssh.)?dev\.azure\.com(?::[v\d]+)?\/([^\/]+\/[^\/]+)\/(?:_git\/)?([^/]+)/i;
13 |
14 | protected override get separateFolder(): string | undefined {
15 | return undefined;
16 | }
17 |
18 | public override get name(): string {
19 | return "DevOps";
20 | }
21 |
22 | public override match(url: string): boolean {
23 | return DevOps.urlRegex.test(url);
24 | }
25 |
26 | public override parse(configInfo: GitConfigInfo): GitUrlInfo {
27 | const gitInfo: GitUrlInfo = {
28 | repoName: configInfo.remoteUrl,
29 | ref: configInfo.ref,
30 | userName: "",
31 | section: configInfo.section,
32 | };
33 |
34 | if (configInfo.relativeFilePath) {
35 | let parts = configInfo.relativeFilePath.split("/");
36 | parts = parts.map(p => encodeURIComponent(p));
37 | gitInfo.relativeFilePath = parts.join("/");
38 | }
39 |
40 | return gitInfo;
41 | }
42 |
43 | public override assemble(info: GitUrlInfo): string {
44 | const baseUrl = info.repoName.replace(DevOps.urlRegex, "https://dev.azure.com/$1/_git/$2");
45 | const path: string = encodeURIComponent(`/${info.relativeFilePath}`);
46 |
47 | let version: string;
48 | switch (info.ref.type) {
49 | case "branch":
50 | version = `GB${info.ref.value}`;
51 | break;
52 | case "commit":
53 | version = `GC${info.ref.value}`;
54 | break;
55 | }
56 |
57 | let url = `${baseUrl}?path=${path}&version=${version}&_a=contents`;
58 |
59 | if (info.section && info.section.startLine && info.section.endLine) {
60 | url += `&lineStyle=plain&line=${info.section.startLine}&lineEnd=${info.section.endLine}`;
61 |
62 | if (info.section.startColumn && info.section.endColumn) {
63 | url += `&lineStartColumn=${info.section.startColumn}&lineEndColumn=${info.section.endColumn}`;
64 | } else {
65 | url += "&lineStartColumn=1&lineEndColumn=1";
66 | }
67 | }
68 |
69 | return url;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/packages/git-urls/src/host/github.ts:
--------------------------------------------------------------------------------
1 | import BasicHost from "./basicHost";
2 | import { GitUrlInfo } from "../info";
3 |
4 | export default class GitHub extends BasicHost {
5 | protected override get separateFolder(): string | undefined {
6 | return "blob";
7 | }
8 |
9 | public override get name(): string {
10 | return "GitHub";
11 | }
12 |
13 | public override match(url: string): boolean {
14 | return url.indexOf("github") >= 0;
15 | }
16 |
17 | public override assemble(info: GitUrlInfo): string {
18 | const link = this.assembleLink(info);
19 |
20 | if (
21 | info.section &&
22 | info.section.startLine &&
23 | info.section.endLine &&
24 | info.section.startLine !== info.section.endLine
25 | ) {
26 | return `${link}#L${info.section.startLine}-L${info.section.endLine}`;
27 | } else if (info.section && info.section.startLine) {
28 | return `${link}#L${info.section.startLine}`;
29 | } else {
30 | return link;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/git-urls/src/host/gitlab.ts:
--------------------------------------------------------------------------------
1 | import BasicHost from "./basicHost";
2 | import { GitUrlInfo } from "../info";
3 |
4 | export default class GitLab extends BasicHost {
5 | public override get name(): string {
6 | return "GitLab";
7 | }
8 |
9 | public override match(url: string): boolean {
10 | return url.indexOf("gitlab") >= 0;
11 | }
12 |
13 | protected override get separateFolder(): string | undefined {
14 | return "blob";
15 | }
16 |
17 | assemble(info: GitUrlInfo): string {
18 | const link = this.assembleLink(info);
19 |
20 | if (
21 | info.section &&
22 | info.section.startLine &&
23 | info.section.endLine &&
24 | info.section.startLine !== info.section.endLine
25 | ) {
26 | return `${link}#L${info.section.startLine}-${info.section.endLine}`;
27 | } else if (info.section && info.section.startLine) {
28 | return `${link}#L${info.section.startLine}`;
29 | } else {
30 | return link;
31 | }
32 | }
33 | }
34 |
--------------------------------------------------------------------------------
/packages/git-urls/src/host/host.ts:
--------------------------------------------------------------------------------
1 | import { GitConfigInfo, GitUrlInfo } from "../info";
2 |
3 | interface Host {
4 | get name(): string;
5 | match(url: string): boolean;
6 | parse(info: GitConfigInfo): GitUrlInfo;
7 | assemble(info: GitUrlInfo): string;
8 | }
9 |
10 | export default Host;
11 |
--------------------------------------------------------------------------------
/packages/git-urls/src/host/hostBuilder.ts:
--------------------------------------------------------------------------------
1 | import Host from "./host";
2 | import GitHub from "./github";
3 | import GitLab from "./gitlab";
4 | import BitBucket from "./bitbucket";
5 | import Vsts from "./vsts";
6 | import DevOps from "./devops";
7 | import { GitConfigInfo } from "../info";
8 |
9 | class HostBuilder {
10 | constructor(readonly hosts: Host[]) {
11 | this.hosts = hosts.length > 0 ? hosts : [new GitHub(), new GitLab(), new DevOps(), new Vsts(), new BitBucket()];
12 | }
13 |
14 | create(info: GitConfigInfo, hostType?: string): Host {
15 | const url = info.remoteUrl;
16 | for (const host of this.hosts) {
17 | if (hostType?.toLowerCase() === host.name.toLowerCase()) {
18 | return host;
19 | }
20 | }
21 |
22 | for (const host of this.hosts) {
23 | if (host.match(url)) {
24 | return host;
25 | }
26 | }
27 |
28 | // if no host matched, use GitHub as default
29 | return new GitHub();
30 | }
31 | }
32 |
33 | export const hostBuilder = new HostBuilder([]);
34 |
--------------------------------------------------------------------------------
/packages/git-urls/src/host/vsts.ts:
--------------------------------------------------------------------------------
1 | import { GitConfigInfo, GitUrlInfo } from "../info";
2 | import BasicHost from "./basicHost";
3 |
4 | export default class Vsts extends BasicHost {
5 | /**
6 | * The regular expression to match the VSTS Git URL.
7 | * @example https://my-tenant.visualstudio.com/DefaultCollection/MyCollection/_git/my-repo
8 | * @example ssh://my-tenant@my-tenant.visualstudio.com:22/DefaultCollection/MyCollection/_ssh/my-repo
9 | */
10 | private static urlRegex =
11 | /(?:https:\/\/|ssh:\/\/)([\w-]+)@?.*\.visualstudio\.com(?:\:\d+)?\/(.+)\/(?:_git|_ssh)\/([^/]+)/i;
12 | protected override get separateFolder(): string | undefined {
13 | return undefined;
14 | }
15 |
16 | public override get name(): string {
17 | return "VSTS";
18 | }
19 |
20 | public override match(url: string): boolean {
21 | return Vsts.urlRegex.test(url);
22 | }
23 |
24 | public override parse(configInfo: GitConfigInfo): GitUrlInfo {
25 | const gitInfo: GitUrlInfo = {
26 | repoName: configInfo.remoteUrl,
27 | ref: configInfo.ref,
28 | userName: "",
29 | section: configInfo.section,
30 | };
31 |
32 | if (configInfo.relativeFilePath) {
33 | let parts = configInfo.relativeFilePath.split("/");
34 | parts = parts.map(p => encodeURIComponent(p));
35 | gitInfo.relativeFilePath = parts.join("/");
36 | }
37 |
38 | return gitInfo;
39 | }
40 |
41 | public override assemble(info: GitUrlInfo): string {
42 | const baseUrl = info.repoName.replace(Vsts.urlRegex, "https://$1.visualstudio.com/$2/_git/$3");
43 | const path: string = encodeURIComponent(`/${info.relativeFilePath}`);
44 |
45 | let version: string;
46 | switch (info.ref.type) {
47 | case "branch":
48 | version = `GB${info.ref.value}`;
49 | break;
50 | case "commit":
51 | version = `GC${info.ref.value}`;
52 | break;
53 | }
54 |
55 | let url = `${baseUrl}?path=${path}&version=${version}&_a=contents`;
56 |
57 | if (info.section && info.section.startLine && info.section.endLine) {
58 | url += `&lineStyle=plain&line=${info.section.startLine}&lineEnd=${info.section.endLine}`;
59 |
60 | if (info.section.startColumn && info.section.endColumn) {
61 | url += `&lineStartColumn=${info.section.startColumn}&lineEndColumn=${info.section.endColumn}`;
62 | } else {
63 | url += "&lineStartColumn=1&lineEndColumn=1";
64 | }
65 | }
66 |
67 | return url;
68 | }
69 | }
70 |
--------------------------------------------------------------------------------
/packages/git-urls/src/index.ts:
--------------------------------------------------------------------------------
1 | import * as path from "path";
2 | import * as fs from "fs-extra";
3 |
4 | import { GitConfigInfo, Section, GitReference } from "./info";
5 | import { hostBuilder } from "./host/hostBuilder";
6 | import Helper from "./helper";
7 | import { GitUrlError } from "./error";
8 | import { err, GitRemote, GitUrlResult, ok } from "./result";
9 |
10 | export { GitUrlResult } from "./result";
11 |
12 | export default class GitUrls {
13 | public static async getUrls(
14 | filePath: string,
15 | section?: Section,
16 | hostType?: string
17 | ): Promise