├── .commitlintrc.js
├── .eslintrc.js
├── .github
├── CODEOWNERS
├── ISSUE_TEMPLATE
│ ├── bug.yml
│ └── config.yml
├── actions
│ ├── create-check
│ │ └── action.yml
│ └── install-latest-npm
│ │ └── action.yml
├── dependabot.yml
├── matchers
│ └── tap.json
├── settings.yml
└── workflows
│ ├── audit.yml
│ ├── ci-release.yml
│ ├── ci.yml
│ ├── codeql-analysis.yml
│ ├── post-dependabot.yml
│ ├── pull-request.yml
│ ├── release-integration.yml
│ └── release.yml
├── .gitignore
├── .npmrc
├── .release-please-manifest.json
├── CHANGELOG.md
├── CODE_OF_CONDUCT.md
├── CONTRIBUTING.md
├── LICENSE
├── README.md
├── SECURITY.md
├── lib
├── from-url.js
├── hosts.js
├── index.js
└── parse-url.js
├── package.json
├── release-please-config.json
└── test
├── bitbucket.js
├── file.js
├── gist.js
├── github.js
├── gitlab.js
├── invalid.js
├── localhost.js
├── parse-url.js
└── sourcehut.js
/.commitlintrc.js:
--------------------------------------------------------------------------------
1 | /* This file is automatically added by @npmcli/template-oss. Do not edit. */
2 |
3 | module.exports = {
4 | extends: ['@commitlint/config-conventional'],
5 | rules: {
6 | 'type-enum': [2, 'always', ['feat', 'fix', 'docs', 'deps', 'chore']],
7 | 'header-max-length': [2, 'always', 80],
8 | 'subject-case': [0],
9 | 'body-max-line-length': [0],
10 | 'footer-max-line-length': [0],
11 | },
12 | }
13 |
--------------------------------------------------------------------------------
/.eslintrc.js:
--------------------------------------------------------------------------------
1 | /* This file is automatically added by @npmcli/template-oss. Do not edit. */
2 |
3 | 'use strict'
4 |
5 | const { readdirSync: readdir } = require('fs')
6 |
7 | const localConfigs = readdir(__dirname)
8 | .filter((file) => file.startsWith('.eslintrc.local.'))
9 | .map((file) => `./${file}`)
10 |
11 | module.exports = {
12 | root: true,
13 | ignorePatterns: [
14 | 'tap-testdir*/',
15 | ],
16 | extends: [
17 | '@npmcli',
18 | ...localConfigs,
19 | ],
20 | }
21 |
--------------------------------------------------------------------------------
/.github/CODEOWNERS:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | * @npm/cli-team
4 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/bug.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Bug
4 | description: File a bug/issue
5 | title: "[BUG]
"
6 | labels: [ Bug, Needs Triage ]
7 |
8 | body:
9 | - type: checkboxes
10 | attributes:
11 | label: Is there an existing issue for this?
12 | description: Please [search here](./issues) to see if an issue already exists for your problem.
13 | options:
14 | - label: I have searched the existing issues
15 | required: true
16 | - type: textarea
17 | attributes:
18 | label: Current Behavior
19 | description: A clear & concise description of what you're experiencing.
20 | validations:
21 | required: false
22 | - type: textarea
23 | attributes:
24 | label: Expected Behavior
25 | description: A clear & concise description of what you expected to happen.
26 | validations:
27 | required: false
28 | - type: textarea
29 | attributes:
30 | label: Steps To Reproduce
31 | description: Steps to reproduce the behavior.
32 | value: |
33 | 1. In this environment...
34 | 2. With this config...
35 | 3. Run '...'
36 | 4. See error...
37 | validations:
38 | required: false
39 | - type: textarea
40 | attributes:
41 | label: Environment
42 | description: |
43 | examples:
44 | - **npm**: 7.6.3
45 | - **Node**: 13.14.0
46 | - **OS**: Ubuntu 20.04
47 | - **platform**: Macbook Pro
48 | value: |
49 | - npm:
50 | - Node:
51 | - OS:
52 | - platform:
53 | validations:
54 | required: false
55 |
--------------------------------------------------------------------------------
/.github/ISSUE_TEMPLATE/config.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | blank_issues_enabled: true
4 |
--------------------------------------------------------------------------------
/.github/actions/create-check/action.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: 'Create Check'
4 | inputs:
5 | name:
6 | required: true
7 | token:
8 | required: true
9 | sha:
10 | required: true
11 | check-name:
12 | default: ''
13 | outputs:
14 | check-id:
15 | value: ${{ steps.create-check.outputs.check_id }}
16 | runs:
17 | using: "composite"
18 | steps:
19 | - name: Get Workflow Job
20 | uses: actions/github-script@v7
21 | id: workflow
22 | env:
23 | JOB_NAME: "${{ inputs.name }}"
24 | SHA: "${{ inputs.sha }}"
25 | with:
26 | result-encoding: string
27 | script: |
28 | const { repo: { owner, repo}, runId, serverUrl } = context
29 | const { JOB_NAME, SHA } = process.env
30 |
31 | const job = await github.rest.actions.listJobsForWorkflowRun({
32 | owner,
33 | repo,
34 | run_id: runId,
35 | per_page: 100
36 | }).then(r => r.data.jobs.find(j => j.name.endsWith(JOB_NAME)))
37 |
38 | return [
39 | `This check is assosciated with ${serverUrl}/${owner}/${repo}/commit/${SHA}.`,
40 | 'Run logs:',
41 | job?.html_url || `could not be found for a job ending with: "${JOB_NAME}"`,
42 | ].join(' ')
43 | - name: Create Check
44 | uses: LouisBrunner/checks-action@v1.6.0
45 | id: create-check
46 | with:
47 | token: ${{ inputs.token }}
48 | sha: ${{ inputs.sha }}
49 | status: in_progress
50 | name: ${{ inputs.check-name || inputs.name }}
51 | output: |
52 | {"summary":"${{ steps.workflow.outputs.result }}"}
53 |
--------------------------------------------------------------------------------
/.github/actions/install-latest-npm/action.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: 'Install Latest npm'
4 | description: 'Install the latest version of npm compatible with the Node version'
5 | inputs:
6 | node:
7 | description: 'Current Node version'
8 | required: true
9 | runs:
10 | using: "composite"
11 | steps:
12 | # node 10/12/14 ship with npm@6, which is known to fail when updating itself in windows
13 | - name: Update Windows npm
14 | if: |
15 | runner.os == 'Windows' && (
16 | startsWith(inputs.node, 'v10.') ||
17 | startsWith(inputs.node, 'v12.') ||
18 | startsWith(inputs.node, 'v14.')
19 | )
20 | shell: cmd
21 | run: |
22 | curl -sO https://registry.npmjs.org/npm/-/npm-7.5.4.tgz
23 | tar xf npm-7.5.4.tgz
24 | cd package
25 | node lib/npm.js install --no-fund --no-audit -g ..\npm-7.5.4.tgz
26 | cd ..
27 | rmdir /s /q package
28 | - name: Install Latest npm
29 | shell: bash
30 | env:
31 | NODE_VERSION: ${{ inputs.node }}
32 | working-directory: ${{ runner.temp }}
33 | run: |
34 | MATCH=""
35 | SPECS=("latest" "next-10" "next-9" "next-8" "next-7" "next-6")
36 |
37 | echo "node@$NODE_VERSION"
38 |
39 | for SPEC in ${SPECS[@]}; do
40 | ENGINES=$(npm view npm@$SPEC --json | jq -r '.engines.node')
41 | echo "Checking if node@$NODE_VERSION satisfies npm@$SPEC ($ENGINES)"
42 |
43 | if npx semver -r "$ENGINES" "$NODE_VERSION" > /dev/null; then
44 | MATCH=$SPEC
45 | echo "Found compatible version: npm@$MATCH"
46 | break
47 | fi
48 | done
49 |
50 | if [ -z $MATCH ]; then
51 | echo "Could not find a compatible version of npm for node@$NODE_VERSION"
52 | exit 1
53 | fi
54 |
55 | npm i --prefer-online --no-fund --no-audit -g npm@$MATCH
56 | - name: npm Version
57 | shell: bash
58 | run: npm -v
59 |
--------------------------------------------------------------------------------
/.github/dependabot.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | version: 2
4 |
5 | updates:
6 | - package-ecosystem: npm
7 | directory: /
8 | schedule:
9 | interval: daily
10 | target-branch: "main"
11 | allow:
12 | - dependency-type: direct
13 | versioning-strategy: increase-if-necessary
14 | commit-message:
15 | prefix: deps
16 | prefix-development: chore
17 | labels:
18 | - "Dependencies"
19 | open-pull-requests-limit: 10
20 | - package-ecosystem: npm
21 | directory: /
22 | schedule:
23 | interval: daily
24 | target-branch: "release/v5"
25 | allow:
26 | - dependency-type: direct
27 | dependency-name: "@npmcli/template-oss"
28 | versioning-strategy: increase-if-necessary
29 | commit-message:
30 | prefix: deps
31 | prefix-development: chore
32 | labels:
33 | - "Dependencies"
34 | - "Backport"
35 | - "release/v5"
36 | open-pull-requests-limit: 10
37 | - package-ecosystem: npm
38 | directory: /
39 | schedule:
40 | interval: daily
41 | target-branch: "release/v6"
42 | allow:
43 | - dependency-type: direct
44 | dependency-name: "@npmcli/template-oss"
45 | versioning-strategy: increase-if-necessary
46 | commit-message:
47 | prefix: deps
48 | prefix-development: chore
49 | labels:
50 | - "Dependencies"
51 | - "Backport"
52 | - "release/v6"
53 | open-pull-requests-limit: 10
54 |
--------------------------------------------------------------------------------
/.github/matchers/tap.json:
--------------------------------------------------------------------------------
1 | {
2 | "//@npmcli/template-oss": "This file is automatically added by @npmcli/template-oss. Do not edit.",
3 | "problemMatcher": [
4 | {
5 | "owner": "tap",
6 | "pattern": [
7 | {
8 | "regexp": "^\\s*not ok \\d+ - (.*)",
9 | "message": 1
10 | },
11 | {
12 | "regexp": "^\\s*---"
13 | },
14 | {
15 | "regexp": "^\\s*at:"
16 | },
17 | {
18 | "regexp": "^\\s*line:\\s*(\\d+)",
19 | "line": 1
20 | },
21 | {
22 | "regexp": "^\\s*column:\\s*(\\d+)",
23 | "column": 1
24 | },
25 | {
26 | "regexp": "^\\s*file:\\s*(.*)",
27 | "file": 1
28 | }
29 | ]
30 | }
31 | ]
32 | }
33 |
--------------------------------------------------------------------------------
/.github/settings.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | repository:
4 | allow_merge_commit: false
5 | allow_rebase_merge: true
6 | allow_squash_merge: true
7 | squash_merge_commit_title: PR_TITLE
8 | squash_merge_commit_message: PR_BODY
9 | delete_branch_on_merge: true
10 | enable_automated_security_fixes: true
11 | enable_vulnerability_alerts: true
12 |
13 | branches:
14 | - name: main
15 | protection:
16 | required_status_checks: null
17 | enforce_admins: true
18 | block_creations: true
19 | required_pull_request_reviews:
20 | required_approving_review_count: 1
21 | require_code_owner_reviews: true
22 | require_last_push_approval: true
23 | dismiss_stale_reviews: true
24 | restrictions:
25 | apps: []
26 | users: []
27 | teams: [ "cli-team" ]
28 | - name: release/v5
29 | protection:
30 | required_status_checks: null
31 | enforce_admins: true
32 | block_creations: true
33 | required_pull_request_reviews:
34 | required_approving_review_count: 1
35 | require_code_owner_reviews: true
36 | require_last_push_approval: true
37 | dismiss_stale_reviews: true
38 | restrictions:
39 | apps: []
40 | users: []
41 | teams: [ "cli-team" ]
42 | - name: release/v6
43 | protection:
44 | required_status_checks: null
45 | enforce_admins: true
46 | block_creations: true
47 | required_pull_request_reviews:
48 | required_approving_review_count: 1
49 | require_code_owner_reviews: true
50 | require_last_push_approval: true
51 | dismiss_stale_reviews: true
52 | restrictions:
53 | apps: []
54 | users: []
55 | teams: [ "cli-team" ]
56 |
--------------------------------------------------------------------------------
/.github/workflows/audit.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Audit
4 |
5 | on:
6 | workflow_dispatch:
7 | schedule:
8 | # "At 08:00 UTC (01:00 PT) on Monday" https://crontab.guru/#0_8_*_*_1
9 | - cron: "0 8 * * 1"
10 |
11 | jobs:
12 | audit:
13 | name: Audit Dependencies
14 | if: github.repository_owner == 'npm'
15 | runs-on: ubuntu-latest
16 | defaults:
17 | run:
18 | shell: bash
19 | steps:
20 | - name: Checkout
21 | uses: actions/checkout@v4
22 | - name: Setup Git User
23 | run: |
24 | git config --global user.email "npm-cli+bot@github.com"
25 | git config --global user.name "npm CLI robot"
26 | - name: Setup Node
27 | uses: actions/setup-node@v4
28 | id: node
29 | with:
30 | node-version: 22.x
31 | check-latest: contains('22.x', '.x')
32 | - name: Install Latest npm
33 | uses: ./.github/actions/install-latest-npm
34 | with:
35 | node: ${{ steps.node.outputs.node-version }}
36 | - name: Install Dependencies
37 | run: npm i --ignore-scripts --no-audit --no-fund --package-lock
38 | - name: Run Production Audit
39 | run: npm audit --omit=dev
40 | - name: Run Full Audit
41 | run: npm audit --audit-level=none
42 |
--------------------------------------------------------------------------------
/.github/workflows/ci-release.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: CI - Release
4 |
5 | on:
6 | workflow_dispatch:
7 | inputs:
8 | ref:
9 | required: true
10 | type: string
11 | default: main
12 | workflow_call:
13 | inputs:
14 | ref:
15 | required: true
16 | type: string
17 | check-sha:
18 | required: true
19 | type: string
20 |
21 | jobs:
22 | lint-all:
23 | name: Lint All
24 | if: github.repository_owner == 'npm'
25 | runs-on: ubuntu-latest
26 | defaults:
27 | run:
28 | shell: bash
29 | steps:
30 | - name: Checkout
31 | uses: actions/checkout@v4
32 | with:
33 | ref: ${{ inputs.ref }}
34 | - name: Setup Git User
35 | run: |
36 | git config --global user.email "npm-cli+bot@github.com"
37 | git config --global user.name "npm CLI robot"
38 | - name: Create Check
39 | id: create-check
40 | if: ${{ inputs.check-sha }}
41 | uses: ./.github/actions/create-check
42 | with:
43 | name: "Lint All"
44 | token: ${{ secrets.GITHUB_TOKEN }}
45 | sha: ${{ inputs.check-sha }}
46 | - name: Setup Node
47 | uses: actions/setup-node@v4
48 | id: node
49 | with:
50 | node-version: 22.x
51 | check-latest: contains('22.x', '.x')
52 | - name: Install Latest npm
53 | uses: ./.github/actions/install-latest-npm
54 | with:
55 | node: ${{ steps.node.outputs.node-version }}
56 | - name: Install Dependencies
57 | run: npm i --ignore-scripts --no-audit --no-fund
58 | - name: Lint
59 | run: npm run lint --ignore-scripts
60 | - name: Post Lint
61 | run: npm run postlint --ignore-scripts
62 | - name: Conclude Check
63 | uses: LouisBrunner/checks-action@v1.6.0
64 | if: steps.create-check.outputs.check-id && always()
65 | with:
66 | token: ${{ secrets.GITHUB_TOKEN }}
67 | conclusion: ${{ job.status }}
68 | check_id: ${{ steps.create-check.outputs.check-id }}
69 |
70 | test-all:
71 | name: Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }}
72 | if: github.repository_owner == 'npm'
73 | strategy:
74 | fail-fast: false
75 | matrix:
76 | platform:
77 | - name: Linux
78 | os: ubuntu-latest
79 | shell: bash
80 | - name: macOS
81 | os: macos-latest
82 | shell: bash
83 | - name: macOS
84 | os: macos-13
85 | shell: bash
86 | - name: Windows
87 | os: windows-latest
88 | shell: cmd
89 | node-version:
90 | - 18.17.0
91 | - 18.x
92 | - 20.5.0
93 | - 20.x
94 | - 22.x
95 | exclude:
96 | - platform: { name: macOS, os: macos-13, shell: bash }
97 | node-version: 18.17.0
98 | - platform: { name: macOS, os: macos-13, shell: bash }
99 | node-version: 18.x
100 | - platform: { name: macOS, os: macos-13, shell: bash }
101 | node-version: 20.5.0
102 | - platform: { name: macOS, os: macos-13, shell: bash }
103 | node-version: 20.x
104 | - platform: { name: macOS, os: macos-13, shell: bash }
105 | node-version: 22.x
106 | runs-on: ${{ matrix.platform.os }}
107 | defaults:
108 | run:
109 | shell: ${{ matrix.platform.shell }}
110 | steps:
111 | - name: Checkout
112 | uses: actions/checkout@v4
113 | with:
114 | ref: ${{ inputs.ref }}
115 | - name: Setup Git User
116 | run: |
117 | git config --global user.email "npm-cli+bot@github.com"
118 | git config --global user.name "npm CLI robot"
119 | - name: Create Check
120 | id: create-check
121 | if: ${{ inputs.check-sha }}
122 | uses: ./.github/actions/create-check
123 | with:
124 | name: "Test All - ${{ matrix.platform.name }} - ${{ matrix.node-version }}"
125 | token: ${{ secrets.GITHUB_TOKEN }}
126 | sha: ${{ inputs.check-sha }}
127 | - name: Setup Node
128 | uses: actions/setup-node@v4
129 | id: node
130 | with:
131 | node-version: ${{ matrix.node-version }}
132 | check-latest: contains(matrix.node-version, '.x')
133 | - name: Install Latest npm
134 | uses: ./.github/actions/install-latest-npm
135 | with:
136 | node: ${{ steps.node.outputs.node-version }}
137 | - name: Install Dependencies
138 | run: npm i --ignore-scripts --no-audit --no-fund
139 | - name: Add Problem Matcher
140 | run: echo "::add-matcher::.github/matchers/tap.json"
141 | - name: Test
142 | run: npm test --ignore-scripts
143 | - name: Conclude Check
144 | uses: LouisBrunner/checks-action@v1.6.0
145 | if: steps.create-check.outputs.check-id && always()
146 | with:
147 | token: ${{ secrets.GITHUB_TOKEN }}
148 | conclusion: ${{ job.status }}
149 | check_id: ${{ steps.create-check.outputs.check-id }}
150 |
--------------------------------------------------------------------------------
/.github/workflows/ci.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: CI
4 |
5 | on:
6 | workflow_dispatch:
7 | pull_request:
8 | push:
9 | branches:
10 | - main
11 | - release/v*
12 | schedule:
13 | # "At 09:00 UTC (02:00 PT) on Monday" https://crontab.guru/#0_9_*_*_1
14 | - cron: "0 9 * * 1"
15 |
16 | jobs:
17 | lint:
18 | name: Lint
19 | if: github.repository_owner == 'npm'
20 | runs-on: ubuntu-latest
21 | defaults:
22 | run:
23 | shell: bash
24 | steps:
25 | - name: Checkout
26 | uses: actions/checkout@v4
27 | - name: Setup Git User
28 | run: |
29 | git config --global user.email "npm-cli+bot@github.com"
30 | git config --global user.name "npm CLI robot"
31 | - name: Setup Node
32 | uses: actions/setup-node@v4
33 | id: node
34 | with:
35 | node-version: 22.x
36 | check-latest: contains('22.x', '.x')
37 | - name: Install Latest npm
38 | uses: ./.github/actions/install-latest-npm
39 | with:
40 | node: ${{ steps.node.outputs.node-version }}
41 | - name: Install Dependencies
42 | run: npm i --ignore-scripts --no-audit --no-fund
43 | - name: Lint
44 | run: npm run lint --ignore-scripts
45 | - name: Post Lint
46 | run: npm run postlint --ignore-scripts
47 |
48 | test:
49 | name: Test - ${{ matrix.platform.name }} - ${{ matrix.node-version }}
50 | if: github.repository_owner == 'npm'
51 | strategy:
52 | fail-fast: false
53 | matrix:
54 | platform:
55 | - name: Linux
56 | os: ubuntu-latest
57 | shell: bash
58 | - name: macOS
59 | os: macos-latest
60 | shell: bash
61 | - name: macOS
62 | os: macos-13
63 | shell: bash
64 | - name: Windows
65 | os: windows-latest
66 | shell: cmd
67 | node-version:
68 | - 18.17.0
69 | - 18.x
70 | - 20.5.0
71 | - 20.x
72 | - 22.x
73 | exclude:
74 | - platform: { name: macOS, os: macos-13, shell: bash }
75 | node-version: 18.17.0
76 | - platform: { name: macOS, os: macos-13, shell: bash }
77 | node-version: 18.x
78 | - platform: { name: macOS, os: macos-13, shell: bash }
79 | node-version: 20.5.0
80 | - platform: { name: macOS, os: macos-13, shell: bash }
81 | node-version: 20.x
82 | - platform: { name: macOS, os: macos-13, shell: bash }
83 | node-version: 22.x
84 | runs-on: ${{ matrix.platform.os }}
85 | defaults:
86 | run:
87 | shell: ${{ matrix.platform.shell }}
88 | steps:
89 | - name: Checkout
90 | uses: actions/checkout@v4
91 | - name: Setup Git User
92 | run: |
93 | git config --global user.email "npm-cli+bot@github.com"
94 | git config --global user.name "npm CLI robot"
95 | - name: Setup Node
96 | uses: actions/setup-node@v4
97 | id: node
98 | with:
99 | node-version: ${{ matrix.node-version }}
100 | check-latest: contains(matrix.node-version, '.x')
101 | - name: Install Latest npm
102 | uses: ./.github/actions/install-latest-npm
103 | with:
104 | node: ${{ steps.node.outputs.node-version }}
105 | - name: Install Dependencies
106 | run: npm i --ignore-scripts --no-audit --no-fund
107 | - name: Add Problem Matcher
108 | run: echo "::add-matcher::.github/matchers/tap.json"
109 | - name: Test
110 | run: npm test --ignore-scripts
111 |
--------------------------------------------------------------------------------
/.github/workflows/codeql-analysis.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: CodeQL
4 |
5 | on:
6 | push:
7 | branches:
8 | - main
9 | - release/v*
10 | pull_request:
11 | branches:
12 | - main
13 | - release/v*
14 | schedule:
15 | # "At 10:00 UTC (03:00 PT) on Monday" https://crontab.guru/#0_10_*_*_1
16 | - cron: "0 10 * * 1"
17 |
18 | jobs:
19 | analyze:
20 | name: Analyze
21 | runs-on: ubuntu-latest
22 | permissions:
23 | actions: read
24 | contents: read
25 | security-events: write
26 | steps:
27 | - name: Checkout
28 | uses: actions/checkout@v4
29 | - name: Setup Git User
30 | run: |
31 | git config --global user.email "npm-cli+bot@github.com"
32 | git config --global user.name "npm CLI robot"
33 | - name: Initialize CodeQL
34 | uses: github/codeql-action/init@v3
35 | with:
36 | languages: javascript
37 | - name: Perform CodeQL Analysis
38 | uses: github/codeql-action/analyze@v3
39 |
--------------------------------------------------------------------------------
/.github/workflows/post-dependabot.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Post Dependabot
4 |
5 | on: pull_request
6 |
7 | permissions:
8 | contents: write
9 |
10 | jobs:
11 | template-oss:
12 | name: template-oss
13 | if: github.repository_owner == 'npm' && github.actor == 'dependabot[bot]'
14 | runs-on: ubuntu-latest
15 | defaults:
16 | run:
17 | shell: bash
18 | steps:
19 | - name: Checkout
20 | uses: actions/checkout@v4
21 | with:
22 | ref: ${{ github.event.pull_request.head.ref }}
23 | - name: Setup Git User
24 | run: |
25 | git config --global user.email "npm-cli+bot@github.com"
26 | git config --global user.name "npm CLI robot"
27 | - name: Setup Node
28 | uses: actions/setup-node@v4
29 | id: node
30 | with:
31 | node-version: 22.x
32 | check-latest: contains('22.x', '.x')
33 | - name: Install Latest npm
34 | uses: ./.github/actions/install-latest-npm
35 | with:
36 | node: ${{ steps.node.outputs.node-version }}
37 | - name: Install Dependencies
38 | run: npm i --ignore-scripts --no-audit --no-fund
39 | - name: Fetch Dependabot Metadata
40 | id: metadata
41 | uses: dependabot/fetch-metadata@v1
42 | with:
43 | github-token: ${{ secrets.GITHUB_TOKEN }}
44 |
45 | # Dependabot can update multiple directories so we output which directory
46 | # it is acting on so we can run the command for the correct root or workspace
47 | - name: Get Dependabot Directory
48 | if: contains(steps.metadata.outputs.dependency-names, '@npmcli/template-oss')
49 | id: flags
50 | run: |
51 | dependabot_dir="${{ steps.metadata.outputs.directory }}"
52 | if [[ "$dependabot_dir" == "/" || "$dependabot_dir" == "/main" ]]; then
53 | echo "workspace=-iwr" >> $GITHUB_OUTPUT
54 | else
55 | # strip leading slash from directory so it works as a
56 | # a path to the workspace flag
57 | echo "workspace=-w ${dependabot_dir#/}" >> $GITHUB_OUTPUT
58 | fi
59 |
60 | - name: Apply Changes
61 | if: steps.flags.outputs.workspace
62 | id: apply
63 | run: |
64 | npm run template-oss-apply ${{ steps.flags.outputs.workspace }}
65 | if [[ `git status --porcelain` ]]; then
66 | echo "changes=true" >> $GITHUB_OUTPUT
67 | fi
68 | # This only sets the conventional commit prefix. This workflow can't reliably determine
69 | # what the breaking change is though. If a BREAKING CHANGE message is required then
70 | # this PR check will fail and the commit will be amended with stafftools
71 | if [[ "${{ steps.metadata.outputs.update-type }}" == "version-update:semver-major" ]]; then
72 | prefix='feat!'
73 | else
74 | prefix='chore'
75 | fi
76 | echo "message=$prefix: postinstall for dependabot template-oss PR" >> $GITHUB_OUTPUT
77 |
78 | # This step will fail if template-oss has made any workflow updates. It is impossible
79 | # for a workflow to update other workflows. In the case it does fail, we continue
80 | # and then try to apply only a portion of the changes in the next step
81 | - name: Push All Changes
82 | if: steps.apply.outputs.changes
83 | id: push
84 | continue-on-error: true
85 | env:
86 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
87 | run: |
88 | git commit -am "${{ steps.apply.outputs.message }}"
89 | git push
90 |
91 | # If the previous step failed, then reset the commit and remove any workflow changes
92 | # and attempt to commit and push again. This is helpful because we will have a commit
93 | # with the correct prefix that we can then --amend with @npmcli/stafftools later.
94 | - name: Push All Changes Except Workflows
95 | if: steps.apply.outputs.changes && steps.push.outcome == 'failure'
96 | env:
97 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
98 | run: |
99 | git reset HEAD~
100 | git checkout HEAD -- .github/workflows/
101 | git clean -fd .github/workflows/
102 | git commit -am "${{ steps.apply.outputs.message }}"
103 | git push
104 |
105 | # Check if all the necessary template-oss changes were applied. Since we continued
106 | # on errors in one of the previous steps, this check will fail if our follow up
107 | # only applied a portion of the changes and we need to followup manually.
108 | #
109 | # Note that this used to run `lint` and `postlint` but that will fail this action
110 | # if we've also shipped any linting changes separate from template-oss. We do
111 | # linting in another action, so we want to fail this one only if there are
112 | # template-oss changes that could not be applied.
113 | - name: Check Changes
114 | if: steps.apply.outputs.changes
115 | run: |
116 | npm exec --offline ${{ steps.flags.outputs.workspace }} -- template-oss-check
117 |
118 | - name: Fail on Breaking Change
119 | if: steps.apply.outputs.changes && startsWith(steps.apply.outputs.message, 'feat!')
120 | run: |
121 | echo "This PR has a breaking change. Run 'npx -p @npmcli/stafftools gh template-oss-fix'"
122 | echo "for more information on how to fix this with a BREAKING CHANGE footer."
123 | exit 1
124 |
--------------------------------------------------------------------------------
/.github/workflows/pull-request.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Pull Request
4 |
5 | on:
6 | pull_request:
7 | types:
8 | - opened
9 | - reopened
10 | - edited
11 | - synchronize
12 |
13 | jobs:
14 | commitlint:
15 | name: Lint Commits
16 | if: github.repository_owner == 'npm'
17 | runs-on: ubuntu-latest
18 | defaults:
19 | run:
20 | shell: bash
21 | steps:
22 | - name: Checkout
23 | uses: actions/checkout@v4
24 | with:
25 | fetch-depth: 0
26 | - name: Setup Git User
27 | run: |
28 | git config --global user.email "npm-cli+bot@github.com"
29 | git config --global user.name "npm CLI robot"
30 | - name: Setup Node
31 | uses: actions/setup-node@v4
32 | id: node
33 | with:
34 | node-version: 22.x
35 | check-latest: contains('22.x', '.x')
36 | - name: Install Latest npm
37 | uses: ./.github/actions/install-latest-npm
38 | with:
39 | node: ${{ steps.node.outputs.node-version }}
40 | - name: Install Dependencies
41 | run: npm i --ignore-scripts --no-audit --no-fund
42 | - name: Run Commitlint on Commits
43 | id: commit
44 | continue-on-error: true
45 | run: npx --offline commitlint -V --from 'origin/${{ github.base_ref }}' --to ${{ github.event.pull_request.head.sha }}
46 | - name: Run Commitlint on PR Title
47 | if: steps.commit.outcome == 'failure'
48 | env:
49 | PR_TITLE: ${{ github.event.pull_request.title }}
50 | run: echo "$PR_TITLE" | npx --offline commitlint -V
51 |
--------------------------------------------------------------------------------
/.github/workflows/release-integration.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Release Integration
4 |
5 | on:
6 | workflow_dispatch:
7 | inputs:
8 | releases:
9 | required: true
10 | type: string
11 | description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version'
12 | workflow_call:
13 | inputs:
14 | releases:
15 | required: true
16 | type: string
17 | description: 'A json array of releases. Required fields: publish: tagName, publishTag. publish check: pkgName, version'
18 | secrets:
19 | PUBLISH_TOKEN:
20 | required: true
21 |
22 | jobs:
23 | publish:
24 | name: Publish
25 | runs-on: ubuntu-latest
26 | defaults:
27 | run:
28 | shell: bash
29 | permissions:
30 | id-token: write
31 | steps:
32 | - name: Checkout
33 | uses: actions/checkout@v4
34 | with:
35 | ref: ${{ fromJSON(inputs.releases)[0].tagName }}
36 | - name: Setup Git User
37 | run: |
38 | git config --global user.email "npm-cli+bot@github.com"
39 | git config --global user.name "npm CLI robot"
40 | - name: Setup Node
41 | uses: actions/setup-node@v4
42 | id: node
43 | with:
44 | node-version: 22.x
45 | check-latest: contains('22.x', '.x')
46 | - name: Install Latest npm
47 | uses: ./.github/actions/install-latest-npm
48 | with:
49 | node: ${{ steps.node.outputs.node-version }}
50 | - name: Install Dependencies
51 | run: npm i --ignore-scripts --no-audit --no-fund
52 | - name: Set npm authToken
53 | run: npm config set '//registry.npmjs.org/:_authToken'=\${PUBLISH_TOKEN}
54 | - name: Publish
55 | env:
56 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
57 | RELEASES: ${{ inputs.releases }}
58 | run: |
59 | EXIT_CODE=0
60 |
61 | for release in $(echo $RELEASES | jq -r '.[] | @base64'); do
62 | PUBLISH_TAG=$(echo "$release" | base64 --decode | jq -r .publishTag)
63 | npm publish --provenance --tag="$PUBLISH_TAG"
64 | STATUS=$?
65 | if [[ "$STATUS" -eq 1 ]]; then
66 | EXIT_CODE=$STATUS
67 | fi
68 | done
69 |
70 | exit $EXIT_CODE
71 |
--------------------------------------------------------------------------------
/.github/workflows/release.yml:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | name: Release
4 |
5 | on:
6 | push:
7 | branches:
8 | - main
9 | - release/v*
10 |
11 | permissions:
12 | contents: write
13 | pull-requests: write
14 | checks: write
15 |
16 | jobs:
17 | release:
18 | outputs:
19 | pr: ${{ steps.release.outputs.pr }}
20 | pr-branch: ${{ steps.release.outputs.pr-branch }}
21 | pr-number: ${{ steps.release.outputs.pr-number }}
22 | pr-sha: ${{ steps.release.outputs.pr-sha }}
23 | releases: ${{ steps.release.outputs.releases }}
24 | comment-id: ${{ steps.create-comment.outputs.comment-id || steps.update-comment.outputs.comment-id }}
25 | check-id: ${{ steps.create-check.outputs.check-id }}
26 | name: Release
27 | if: github.repository_owner == 'npm'
28 | runs-on: ubuntu-latest
29 | defaults:
30 | run:
31 | shell: bash
32 | steps:
33 | - name: Checkout
34 | uses: actions/checkout@v4
35 | - name: Setup Git User
36 | run: |
37 | git config --global user.email "npm-cli+bot@github.com"
38 | git config --global user.name "npm CLI robot"
39 | - name: Setup Node
40 | uses: actions/setup-node@v4
41 | id: node
42 | with:
43 | node-version: 22.x
44 | check-latest: contains('22.x', '.x')
45 | - name: Install Latest npm
46 | uses: ./.github/actions/install-latest-npm
47 | with:
48 | node: ${{ steps.node.outputs.node-version }}
49 | - name: Install Dependencies
50 | run: npm i --ignore-scripts --no-audit --no-fund
51 | - name: Release Please
52 | id: release
53 | env:
54 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
55 | run: npx --offline template-oss-release-please --branch="${{ github.ref_name }}" --backport="" --defaultTag="latest"
56 | - name: Create Release Manager Comment Text
57 | if: steps.release.outputs.pr-number
58 | uses: actions/github-script@v7
59 | id: comment-text
60 | with:
61 | result-encoding: string
62 | script: |
63 | const { runId, repo: { owner, repo } } = context
64 | const { data: workflow } = await github.rest.actions.getWorkflowRun({ owner, repo, run_id: runId })
65 | return['## Release Manager', `Release workflow run: ${workflow.html_url}`].join('\n\n')
66 | - name: Find Release Manager Comment
67 | uses: peter-evans/find-comment@v2
68 | if: steps.release.outputs.pr-number
69 | id: found-comment
70 | with:
71 | issue-number: ${{ steps.release.outputs.pr-number }}
72 | comment-author: 'github-actions[bot]'
73 | body-includes: '## Release Manager'
74 | - name: Create Release Manager Comment
75 | id: create-comment
76 | if: steps.release.outputs.pr-number && !steps.found-comment.outputs.comment-id
77 | uses: peter-evans/create-or-update-comment@v3
78 | with:
79 | issue-number: ${{ steps.release.outputs.pr-number }}
80 | body: ${{ steps.comment-text.outputs.result }}
81 | - name: Update Release Manager Comment
82 | id: update-comment
83 | if: steps.release.outputs.pr-number && steps.found-comment.outputs.comment-id
84 | uses: peter-evans/create-or-update-comment@v3
85 | with:
86 | comment-id: ${{ steps.found-comment.outputs.comment-id }}
87 | body: ${{ steps.comment-text.outputs.result }}
88 | edit-mode: 'replace'
89 | - name: Create Check
90 | id: create-check
91 | uses: ./.github/actions/create-check
92 | if: steps.release.outputs.pr-sha
93 | with:
94 | name: "Release"
95 | token: ${{ secrets.GITHUB_TOKEN }}
96 | sha: ${{ steps.release.outputs.pr-sha }}
97 |
98 | update:
99 | needs: release
100 | outputs:
101 | sha: ${{ steps.commit.outputs.sha }}
102 | check-id: ${{ steps.create-check.outputs.check-id }}
103 | name: Update - Release
104 | if: github.repository_owner == 'npm' && needs.release.outputs.pr
105 | runs-on: ubuntu-latest
106 | defaults:
107 | run:
108 | shell: bash
109 | steps:
110 | - name: Checkout
111 | uses: actions/checkout@v4
112 | with:
113 | fetch-depth: 0
114 | ref: ${{ needs.release.outputs.pr-branch }}
115 | - name: Setup Git User
116 | run: |
117 | git config --global user.email "npm-cli+bot@github.com"
118 | git config --global user.name "npm CLI robot"
119 | - name: Setup Node
120 | uses: actions/setup-node@v4
121 | id: node
122 | with:
123 | node-version: 22.x
124 | check-latest: contains('22.x', '.x')
125 | - name: Install Latest npm
126 | uses: ./.github/actions/install-latest-npm
127 | with:
128 | node: ${{ steps.node.outputs.node-version }}
129 | - name: Install Dependencies
130 | run: npm i --ignore-scripts --no-audit --no-fund
131 | - name: Create Release Manager Checklist Text
132 | id: comment-text
133 | env:
134 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
135 | run: npm exec --offline -- template-oss-release-manager --pr="${{ needs.release.outputs.pr-number }}" --backport="" --defaultTag="latest" --publish
136 | - name: Append Release Manager Comment
137 | uses: peter-evans/create-or-update-comment@v3
138 | with:
139 | comment-id: ${{ needs.release.outputs.comment-id }}
140 | body: ${{ steps.comment-text.outputs.result }}
141 | edit-mode: 'append'
142 | - name: Run Post Pull Request Actions
143 | env:
144 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
145 | run: npm run rp-pull-request --ignore-scripts --if-present -- --pr="${{ needs.release.outputs.pr-number }}" --commentId="${{ needs.release.outputs.comment-id }}"
146 | - name: Commit
147 | id: commit
148 | env:
149 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
150 | run: |
151 | git commit --all --amend --no-edit || true
152 | git push --force-with-lease
153 | echo "sha=$(git rev-parse HEAD)" >> $GITHUB_OUTPUT
154 | - name: Create Check
155 | id: create-check
156 | uses: ./.github/actions/create-check
157 | with:
158 | name: "Update - Release"
159 | check-name: "Release"
160 | token: ${{ secrets.GITHUB_TOKEN }}
161 | sha: ${{ steps.commit.outputs.sha }}
162 | - name: Conclude Check
163 | uses: LouisBrunner/checks-action@v1.6.0
164 | with:
165 | token: ${{ secrets.GITHUB_TOKEN }}
166 | conclusion: ${{ job.status }}
167 | check_id: ${{ needs.release.outputs.check-id }}
168 |
169 | ci:
170 | name: CI - Release
171 | needs: [ release, update ]
172 | if: needs.release.outputs.pr
173 | uses: ./.github/workflows/ci-release.yml
174 | with:
175 | ref: ${{ needs.release.outputs.pr-branch }}
176 | check-sha: ${{ needs.update.outputs.sha }}
177 |
178 | post-ci:
179 | needs: [ release, update, ci ]
180 | name: Post CI - Release
181 | if: github.repository_owner == 'npm' && needs.release.outputs.pr && always()
182 | runs-on: ubuntu-latest
183 | defaults:
184 | run:
185 | shell: bash
186 | steps:
187 | - name: Get CI Conclusion
188 | id: conclusion
189 | run: |
190 | result=""
191 | if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
192 | result="failure"
193 | elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
194 | result="cancelled"
195 | else
196 | result="success"
197 | fi
198 | echo "result=$result" >> $GITHUB_OUTPUT
199 | - name: Conclude Check
200 | uses: LouisBrunner/checks-action@v1.6.0
201 | with:
202 | token: ${{ secrets.GITHUB_TOKEN }}
203 | conclusion: ${{ steps.conclusion.outputs.result }}
204 | check_id: ${{ needs.update.outputs.check-id }}
205 |
206 | post-release:
207 | needs: release
208 | outputs:
209 | comment-id: ${{ steps.create-comment.outputs.comment-id }}
210 | name: Post Release - Release
211 | if: github.repository_owner == 'npm' && needs.release.outputs.releases
212 | runs-on: ubuntu-latest
213 | defaults:
214 | run:
215 | shell: bash
216 | steps:
217 | - name: Create Release PR Comment Text
218 | id: comment-text
219 | uses: actions/github-script@v7
220 | env:
221 | RELEASES: ${{ needs.release.outputs.releases }}
222 | with:
223 | result-encoding: string
224 | script: |
225 | const releases = JSON.parse(process.env.RELEASES)
226 | const { runId, repo: { owner, repo } } = context
227 | const issue_number = releases[0].prNumber
228 | const runUrl = `https://github.com/${owner}/${repo}/actions/runs/${runId}`
229 |
230 | return [
231 | '## Release Workflow\n',
232 | ...releases.map(r => `- \`${r.pkgName}@${r.version}\` ${r.url}`),
233 | `- Workflow run: :arrows_counterclockwise: ${runUrl}`,
234 | ].join('\n')
235 | - name: Create Release PR Comment
236 | id: create-comment
237 | uses: peter-evans/create-or-update-comment@v3
238 | with:
239 | issue-number: ${{ fromJSON(needs.release.outputs.releases)[0].prNumber }}
240 | body: ${{ steps.comment-text.outputs.result }}
241 |
242 | release-integration:
243 | needs: release
244 | name: Release Integration
245 | if: needs.release.outputs.releases
246 | uses: ./.github/workflows/release-integration.yml
247 | permissions:
248 | id-token: write
249 | secrets:
250 | PUBLISH_TOKEN: ${{ secrets.PUBLISH_TOKEN }}
251 | with:
252 | releases: ${{ needs.release.outputs.releases }}
253 |
254 | post-release-integration:
255 | needs: [ release, release-integration, post-release ]
256 | name: Post Release Integration - Release
257 | if: github.repository_owner == 'npm' && needs.release.outputs.releases && always()
258 | runs-on: ubuntu-latest
259 | defaults:
260 | run:
261 | shell: bash
262 | steps:
263 | - name: Get Post Release Conclusion
264 | id: conclusion
265 | run: |
266 | if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" ]]; then
267 | result="x"
268 | elif [[ "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
269 | result="heavy_multiplication_x"
270 | else
271 | result="white_check_mark"
272 | fi
273 | echo "result=$result" >> $GITHUB_OUTPUT
274 | - name: Find Release PR Comment
275 | uses: peter-evans/find-comment@v2
276 | id: found-comment
277 | with:
278 | issue-number: ${{ fromJSON(needs.release.outputs.releases)[0].prNumber }}
279 | comment-author: 'github-actions[bot]'
280 | body-includes: '## Release Workflow'
281 | - name: Create Release PR Comment Text
282 | id: comment-text
283 | if: steps.found-comment.outputs.comment-id
284 | uses: actions/github-script@v7
285 | env:
286 | RESULT: ${{ steps.conclusion.outputs.result }}
287 | BODY: ${{ steps.found-comment.outputs.comment-body }}
288 | with:
289 | result-encoding: string
290 | script: |
291 | const { RESULT, BODY } = process.env
292 | const body = [BODY.replace(/(Workflow run: :)[a-z_]+(:)/, `$1${RESULT}$2`)]
293 | if (RESULT !== 'white_check_mark') {
294 | body.push(':rotating_light::rotating_light::rotating_light:')
295 | body.push([
296 | '@npm/cli-team: The post-release workflow failed for this release.',
297 | 'Manual steps may need to be taken after examining the workflow output.'
298 | ].join(' '))
299 | body.push(':rotating_light::rotating_light::rotating_light:')
300 | }
301 | return body.join('\n\n').trim()
302 | - name: Update Release PR Comment
303 | if: steps.comment-text.outputs.result
304 | uses: peter-evans/create-or-update-comment@v3
305 | with:
306 | comment-id: ${{ steps.found-comment.outputs.comment-id }}
307 | body: ${{ steps.comment-text.outputs.result }}
308 | edit-mode: 'replace'
309 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | # This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | # ignore everything in the root
4 | /*
5 |
6 | !**/.gitignore
7 | !/.commitlintrc.js
8 | !/.eslint.config.js
9 | !/.eslintrc.js
10 | !/.eslintrc.local.*
11 | !/.git-blame-ignore-revs
12 | !/.github/
13 | !/.gitignore
14 | !/.npmrc
15 | !/.prettierignore
16 | !/.prettierrc.js
17 | !/.release-please-manifest.json
18 | !/bin/
19 | !/CHANGELOG*
20 | !/CODE_OF_CONDUCT.md
21 | !/CONTRIBUTING.md
22 | !/docs/
23 | !/lib/
24 | !/LICENSE*
25 | !/map.js
26 | !/package.json
27 | !/README*
28 | !/release-please-config.json
29 | !/scripts/
30 | !/SECURITY.md
31 | !/tap-snapshots/
32 | !/test/
33 | !/tsconfig.json
34 | tap-testdir*/
35 |
--------------------------------------------------------------------------------
/.npmrc:
--------------------------------------------------------------------------------
1 | ; This file is automatically added by @npmcli/template-oss. Do not edit.
2 |
3 | package-lock=false
4 |
--------------------------------------------------------------------------------
/.release-please-manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | ".": "8.1.0"
3 | }
4 |
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # Changelog
2 |
3 | ## [8.1.0](https://github.com/npm/hosted-git-info/compare/v8.0.2...v8.1.0) (2025-04-14)
4 | ### Features
5 | * [`ef0865c`](https://github.com/npm/hosted-git-info/commit/ef0865cc5c28700f990bf25d919e2520c944cf55) [#288](https://github.com/npm/hosted-git-info/pull/288) add `HostedGitInfo.fromManifest` (#288) (@ljharb)
6 | ### Chores
7 | * [`ac08fe8`](https://github.com/npm/hosted-git-info/commit/ac08fe89153d19d1fecbd1e5ce5014fad833134c) [#296](https://github.com/npm/hosted-git-info/pull/296) bump @npmcli/template-oss from 4.23.6 to 4.24.3 (#296) (@dependabot[bot], @npm-cli-bot)
8 |
9 | ## [8.0.2](https://github.com/npm/hosted-git-info/compare/v8.0.1...v8.0.2) (2024-11-21)
10 | ### Bug Fixes
11 | * [`cc004ba`](https://github.com/npm/hosted-git-info/commit/cc004bae62d17b90c2fc889fcde5afbcac2fc508) [#280](https://github.com/npm/hosted-git-info/pull/280) even better regex for host fragment (#280) (@wraithgar)
12 |
13 | ## [8.0.1](https://github.com/npm/hosted-git-info/compare/v8.0.0...v8.0.1) (2024-11-20)
14 | ### Bug Fixes
15 | * [`e47b7e4`](https://github.com/npm/hosted-git-info/commit/e47b7e476199820446483aefa0525d4726e49450) [#274](https://github.com/npm/hosted-git-info/pull/274) break up greedy host fragment parsing regex (#274) (@wraithgar)
16 | ### Chores
17 | * [`3d55d13`](https://github.com/npm/hosted-git-info/commit/3d55d1316d1b323b1402ad2c642c6d1f37249058) [#277](https://github.com/npm/hosted-git-info/pull/277) fix workflows for new backport branch (#277) (@wraithgar)
18 | * [`b3e455f`](https://github.com/npm/hosted-git-info/commit/b3e455fd7d66c2c967dba0cc624db8ed142bb86f) [#273](https://github.com/npm/hosted-git-info/pull/273) bump @npmcli/template-oss from 4.23.3 to 4.23.4 (#273) (@dependabot[bot], @npm-cli-bot)
19 |
20 | ## [8.0.0](https://github.com/npm/hosted-git-info/compare/v7.0.2...v8.0.0) (2024-09-03)
21 | ### ⚠️ BREAKING CHANGES
22 | * `hosted-git-info` now supports node `^18.17.0 || >=20.5.0`
23 | ### Bug Fixes
24 | * [`967d930`](https://github.com/npm/hosted-git-info/commit/967d930a3a2adb8b0b55c9d8ddfa1eeb9470f3e1) [#268](https://github.com/npm/hosted-git-info/pull/268) align to npm 10 node engine range (@hashtagchris)
25 | ### Chores
26 | * [`20551b0`](https://github.com/npm/hosted-git-info/commit/20551b02dffa5fdb56d9b89b3521c016c4924ace) [#268](https://github.com/npm/hosted-git-info/pull/268) run template-oss-apply (@hashtagchris)
27 | * [`9a3c062`](https://github.com/npm/hosted-git-info/commit/9a3c062a74dba37c6958a00ee22eb9207d45aefc) [#265](https://github.com/npm/hosted-git-info/pull/265) bump @npmcli/eslint-config from 4.0.5 to 5.0.0 (@dependabot[bot])
28 | * [`8f0fa04`](https://github.com/npm/hosted-git-info/commit/8f0fa04d0fba8d6a2467acc648a2f568f3baa7ed) [#266](https://github.com/npm/hosted-git-info/pull/266) postinstall for dependabot template-oss PR (@hashtagchris)
29 | * [`e0fe523`](https://github.com/npm/hosted-git-info/commit/e0fe523d96dc023b8e6750aa458b0d816673cb49) [#266](https://github.com/npm/hosted-git-info/pull/266) bump @npmcli/template-oss from 4.23.1 to 4.23.3 (@dependabot[bot])
30 |
31 | ## [7.0.2](https://github.com/npm/hosted-git-info/compare/v7.0.1...v7.0.2) (2024-05-04)
32 |
33 | ### Bug Fixes
34 |
35 | * [`682fa35`](https://github.com/npm/hosted-git-info/commit/682fa356278e342b93361bb61cfb0e598011b61f) [#249](https://github.com/npm/hosted-git-info/pull/249) linting: no-unused-vars (@lukekarrys)
36 |
37 | ### Chores
38 |
39 | * [`f33287c`](https://github.com/npm/hosted-git-info/commit/f33287c39772f714b41c2d32a5cb9e98b0d00c6f) [#249](https://github.com/npm/hosted-git-info/pull/249) bump @npmcli/template-oss to 4.22.0 (@lukekarrys)
40 | * [`7bbdfd8`](https://github.com/npm/hosted-git-info/commit/7bbdfd8a564ddd5952fd245c38193af17e6a8d2c) [#248](https://github.com/npm/hosted-git-info/pull/248) chore: postinstall for dependabot template-oss PR (@lukekarrys)
41 | * [`0d4310e`](https://github.com/npm/hosted-git-info/commit/0d4310e90809efa2c7f5be586709c821d432a551) [#249](https://github.com/npm/hosted-git-info/pull/249) postinstall for dependabot template-oss PR (@lukekarrys)
42 | * [`2efc69b`](https://github.com/npm/hosted-git-info/commit/2efc69beca342455f1113625c66157f3f5c53af4) [#248](https://github.com/npm/hosted-git-info/pull/248) bump @npmcli/template-oss from 4.21.3 to 4.21.4 (@dependabot[bot])
43 |
44 | ## [7.0.1](https://github.com/npm/hosted-git-info/compare/v7.0.0...v7.0.1) (2023-09-13)
45 |
46 | ### Bug Fixes
47 |
48 | * [`d7bac33`](https://github.com/npm/hosted-git-info/commit/d7bac33726d6a65788d16e3314f52449f0da58c4) [#213](https://github.com/npm/hosted-git-info/pull/213) remove sourcehut bugstemplate (#213) (@vladh)
49 |
50 | ## [7.0.0](https://github.com/npm/hosted-git-info/compare/v6.1.1...v7.0.0) (2023-08-14)
51 |
52 | ### ⚠️ BREAKING CHANGES
53 |
54 | * support for node 14 has been removed
55 |
56 | ### Bug Fixes
57 |
58 | * [`f9f7fde`](https://github.com/npm/hosted-git-info/commit/f9f7fde1385d3f99ed7a52b9d4b079d8074fc99f) [#209](https://github.com/npm/hosted-git-info/pull/209) use lru-cache named export (@lukekarrys)
59 | * [`c98e908`](https://github.com/npm/hosted-git-info/commit/c98e90807775bf5c306a30426d7f6c6ebe9842d5) [#209](https://github.com/npm/hosted-git-info/pull/209) drop node14 support (@lukekarrys)
60 |
61 | ### Dependencies
62 |
63 | * [`ecdd7de`](https://github.com/npm/hosted-git-info/commit/ecdd7decf24f66297ca5f459b4f1f36d41352e23) [#209](https://github.com/npm/hosted-git-info/pull/209) bump lru-cache from 7.18.3 to 10.0.1
64 |
65 | ## [6.1.1](https://github.com/npm/hosted-git-info/compare/v6.1.0...v6.1.1) (2022-10-27)
66 |
67 | ### Bug Fixes
68 |
69 | * [`f03bfbd`](https://github.com/npm/hosted-git-info/commit/f03bfbd3022c8f6283a991ff879ed97704ac35fa) [#176](https://github.com/npm/hosted-git-info/pull/176) only correct protocols when called from githost (@lukekarrys)
70 |
71 | ## [6.1.0](https://github.com/npm/hosted-git-info/compare/v6.0.0...v6.1.0) (2022-10-26)
72 |
73 | ### Features
74 |
75 | * [`a44bd35`](https://github.com/npm/hosted-git-info/commit/a44bd35820eaa6878f13ee12eba5dca6425ea2bd) [#172](https://github.com/npm/hosted-git-info/pull/172) add separate static method for just parsing urls (@lukekarrys)
76 |
77 | ## [6.0.0](https://github.com/npm/hosted-git-info/compare/v5.1.0...v6.0.0) (2022-10-12)
78 |
79 | ### ⚠️ BREAKING CHANGES
80 |
81 | * `GitHost` now has a static `addHost` method to use instead of manually editing the object from `lib/git-host-info.js`.
82 | * set default git ref to HEAD
83 | * `hosted-git-info` is now compatible with the following semver range for node: `^14.17.0 || ^16.13.0 || >=18.0.0`
84 |
85 | ### Features
86 |
87 | * [`9e0ce62`](https://github.com/npm/hosted-git-info/commit/9e0ce62b9aadb2a9cfe8999e96b004a5de4edfdf) [#142](https://github.com/npm/hosted-git-info/pull/142) refactor (@lukekarrys)
88 | * [`89155e8`](https://github.com/npm/hosted-git-info/commit/89155e8799369f20ae71713f64e3d0f664192a58) set default git ref to HEAD (@darcyclarke)
89 | * [`9ed9c38`](https://github.com/npm/hosted-git-info/commit/9ed9c38002f899ad2628f96b27b2ec9fecb4662f) [#162](https://github.com/npm/hosted-git-info/pull/162) postinstall for dependabot template-oss PR (@lukekarrys)
90 |
91 | ### Bug Fixes
92 |
93 | * [`61ca7fb`](https://github.com/npm/hosted-git-info/commit/61ca7fb8f003299693e23f351eea589c38a3602c) [#152](https://github.com/npm/hosted-git-info/pull/152) parse branch names containing @ (@lukekarrys)
94 | * [`3cd4a98`](https://github.com/npm/hosted-git-info/commit/3cd4a9881e20d3a59bf3bb470661a29208824dd6) ignore colons after hash when correcting scp urls (@lukekarrys)
95 |
96 | ## [5.1.0](https://github.com/npm/hosted-git-info/compare/v5.0.0...v5.1.0) (2022-08-09)
97 |
98 |
99 | ### Features
100 |
101 | * add method to get an edit link to a file ([ad02952](https://github.com/npm/hosted-git-info/commit/ad02952f89fbdc99e67ae0d5308029395bde3331))
102 |
103 |
104 | ### Bug Fixes
105 |
106 | * add comments to empty catch blocks for linting ([70a770d](https://github.com/npm/hosted-git-info/commit/70a770d1202128e15887d69dfd5c930e4ff29a00))
107 |
108 | ## [5.0.0](https://www.github.com/npm/hosted-git-info/compare/v4.1.0...v5.0.0) (2022-03-14)
109 |
110 |
111 | ### ⚠ BREAKING CHANGES
112 |
113 | * this drops support for node 10 and non-LTS versions of node 12 and node 14
114 |
115 | ### Bug Fixes
116 |
117 | * move files to lib ([a3f4836](https://www.github.com/npm/hosted-git-info/commit/a3f4836ba0a75b355c004e1991e8dd1e6321a983))
118 |
119 |
120 | * @npmcli/template-oss@2.9.2 ([c42e1f2](https://www.github.com/npm/hosted-git-info/commit/c42e1f216542ead9d0d328704c5db02204f15ce8))
121 |
122 |
123 | ### Dependencies
124 |
125 | * bump lru-cache from 6.0.0 to 7.5.1 ([#128](https://www.github.com/npm/hosted-git-info/issues/128)) ([5b0b3b5](https://www.github.com/npm/hosted-git-info/commit/5b0b3b50bd36f659037e3b82a7ff47b0eff3b9f9))
126 |
127 | ## [4.0.0](https://github.com/npm/hosted-git-info/compare/v3.0.7...v4.0.0) (2021-03-09)
128 |
129 |
130 | ### Features
131 |
132 | * rewrite the entire module: all internals have been rewritten to maintain a similar contract but to remove excessive use of regular expressions, unnecessary loops, the custom string templating engine, and various other bits of complexity ([c218b9](https://github.com/npm/hosted-git-info/commit/c218b9ec90cf6a818341cd0f7b03ea65793b185b))
133 |
134 |
135 | ### BREAKING CHANGES
136 |
137 | * extending with custom providers has changed ([c218b9](https://github.com/npm/hosted-git-info/commit/c218b9ec90cf6a818341cd0f7b03ea65793b185b))
138 |
139 |
140 |
141 |
142 | ## [3.0.8](https://github.com/npm/hosted-git-info/compare/v3.0.7...v3.0.8) (2021-01-28)
143 |
144 |
145 | ### Bug Fixes
146 |
147 | * simplify the regular expression for shortcut matching ([bede0dc](https://github.com/npm/hosted-git-info/commit/bede0dc)), closes [#76](https://github.com/npm/hosted-git-info/issues/76)
148 |
149 |
150 |
151 |
152 | ## [3.0.7](https://github.com/npm/hosted-git-info/compare/v3.0.6...v3.0.7) (2020-10-15)
153 |
154 |
155 | ### Bug Fixes
156 |
157 | * correctly filter out urls for tarballs in gitlab ([eb5bd5a](https://github.com/npm/hosted-git-info/commit/eb5bd5a)), closes [#69](https://github.com/npm/hosted-git-info/issues/69)
158 |
159 |
160 |
161 |
162 | ## [3.0.6](https://github.com/npm/hosted-git-info/compare/v3.0.5...v3.0.6) (2020-10-12)
163 |
164 |
165 | ### Bug Fixes
166 |
167 | * support to github gist legacy hash length ([c067102](https://github.com/npm/hosted-git-info/commit/c067102)), closes [#68](https://github.com/npm/hosted-git-info/issues/68)
168 |
169 |
170 |
171 |
172 | ## [3.0.5](https://github.com/npm/hosted-git-info/compare/v3.0.4...v3.0.5) (2020-07-11)
173 |
174 |
175 |
176 |
177 | ## [3.0.4](https://github.com/npm/hosted-git-info/compare/v3.0.3...v3.0.4) (2020-02-26)
178 |
179 |
180 | ### Bug Fixes
181 |
182 | * Do not pass scp-style URLs to the WhatWG url.URL ([0835306](https://github.com/npm/hosted-git-info/commit/0835306)), closes [#60](https://github.com/npm/hosted-git-info/issues/60) [#63](https://github.com/npm/hosted-git-info/issues/63)
183 |
184 |
185 |
186 |
187 | ## [3.0.3](https://github.com/npm/hosted-git-info/compare/v3.0.2...v3.0.3) (2020-02-25)
188 |
189 |
190 |
191 |
192 | ## [3.0.2](https://github.com/npm/hosted-git-info/compare/v3.0.1...v3.0.2) (2019-10-08)
193 |
194 |
195 | ### Bug Fixes
196 |
197 | * do not encodeURIComponent the domain ([3e5fbec](https://github.com/npm/hosted-git-info/commit/3e5fbec)), closes [#53](https://github.com/npm/hosted-git-info/issues/53)
198 |
199 |
200 |
201 |
202 | ## [3.0.1](https://github.com/npm/hosted-git-info/compare/v3.0.0...v3.0.1) (2019-10-07)
203 |
204 |
205 | ### Bug Fixes
206 |
207 | * update pathmatch for gitlab ([e3e3054](https://github.com/npm/hosted-git-info/commit/e3e3054)), closes [#52](https://github.com/npm/hosted-git-info/issues/52)
208 | * updated pathmatch for gitlab ([fa87af7](https://github.com/npm/hosted-git-info/commit/fa87af7))
209 |
210 |
211 |
212 |
213 | # [3.0.0](https://github.com/npm/hosted-git-info/compare/v2.8.3...v3.0.0) (2019-08-12)
214 |
215 |
216 | ### Bug Fixes
217 |
218 | * **cache:** Switch to lru-cache to save ourselves from unlimited memory consumption ([37c2891](https://github.com/npm/hosted-git-info/commit/37c2891)), closes [#38](https://github.com/npm/hosted-git-info/issues/38)
219 |
220 |
221 | ### BREAKING CHANGES
222 |
223 | * **cache:** Drop support for node 0.x
224 |
225 |
226 |
227 |
228 | ## [2.8.3](https://github.com/npm/hosted-git-info/compare/v2.8.2...v2.8.3) (2019-08-12)
229 |
230 |
231 |
232 |
233 | ## [2.8.2](https://github.com/npm/hosted-git-info/compare/v2.8.1...v2.8.2) (2019-08-05)
234 |
235 |
236 | ### Bug Fixes
237 |
238 | * http protocol use sshurl by default ([3b1d629](https://github.com/npm/hosted-git-info/commit/3b1d629)), closes [#48](https://github.com/npm/hosted-git-info/issues/48)
239 |
240 |
241 |
242 |
243 | ## [2.8.1](https://github.com/npm/hosted-git-info/compare/v2.8.0...v2.8.1) (2019-08-05)
244 |
245 |
246 | ### Bug Fixes
247 |
248 | * ignore noCommittish on tarball url generation ([5d4a8d7](https://github.com/npm/hosted-git-info/commit/5d4a8d7))
249 | * use gist tarball url that works for anonymous gists ([1692435](https://github.com/npm/hosted-git-info/commit/1692435))
250 |
251 |
252 |
253 |
254 | # [2.8.0](https://github.com/npm/hosted-git-info/compare/v2.7.1...v2.8.0) (2019-08-05)
255 |
256 |
257 | ### Bug Fixes
258 |
259 | * Allow slashes in gitlab project section ([bbcf7b2](https://github.com/npm/hosted-git-info/commit/bbcf7b2)), closes [#46](https://github.com/npm/hosted-git-info/issues/46) [#43](https://github.com/npm/hosted-git-info/issues/43)
260 | * **git-host:** disallow URI-encoded slash (%2F) in `path` ([3776fa5](https://github.com/npm/hosted-git-info/commit/3776fa5)), closes [#44](https://github.com/npm/hosted-git-info/issues/44)
261 | * **gitlab:** Do not URL encode slashes in project name for GitLab https URL ([cbf04f9](https://github.com/npm/hosted-git-info/commit/cbf04f9)), closes [#47](https://github.com/npm/hosted-git-info/issues/47)
262 | * do not allow invalid gist urls ([d5cf830](https://github.com/npm/hosted-git-info/commit/d5cf830))
263 | * **cache:** Switch to lru-cache to save ourselves from unlimited memory consumption ([e518222](https://github.com/npm/hosted-git-info/commit/e518222)), closes [#38](https://github.com/npm/hosted-git-info/issues/38)
264 |
265 |
266 | ### Features
267 |
268 | * give these objects a name ([60abaea](https://github.com/npm/hosted-git-info/commit/60abaea))
269 |
270 |
271 |
272 |
273 | ## [2.7.1](https://github.com/npm/hosted-git-info/compare/v2.7.0...v2.7.1) (2018-07-07)
274 |
275 |
276 | ### Bug Fixes
277 |
278 | * **index:** Guard against non-string types ([5bc580d](https://github.com/npm/hosted-git-info/commit/5bc580d))
279 | * **parse:** Crash on strings that parse to having no host ([c931482](https://github.com/npm/hosted-git-info/commit/c931482)), closes [#35](https://github.com/npm/hosted-git-info/issues/35)
280 |
281 |
282 |
283 |
284 | # [2.7.0](https://github.com/npm/hosted-git-info/compare/v2.6.1...v2.7.0) (2018-07-06)
285 |
286 |
287 | ### Bug Fixes
288 |
289 | * **github tarball:** update github tarballtemplate ([6efd582](https://github.com/npm/hosted-git-info/commit/6efd582)), closes [#34](https://github.com/npm/hosted-git-info/issues/34)
290 | * **gitlab docs:** switched to lowercase anchors for readmes ([701bcd1](https://github.com/npm/hosted-git-info/commit/701bcd1))
291 |
292 |
293 | ### Features
294 |
295 | * **all:** Support www. prefixes on hostnames ([3349575](https://github.com/npm/hosted-git-info/commit/3349575)), closes [#32](https://github.com/npm/hosted-git-info/issues/32)
296 |
297 |
298 |
299 |
300 | ## [2.6.1](https://github.com/npm/hosted-git-info/compare/v2.6.0...v2.6.1) (2018-06-25)
301 |
302 | ### Bug Fixes
303 |
304 | * **Revert:** "compat: remove Object.assign fallback ([#25](https://github.com/npm/hosted-git-info/issues/25))" ([cce5a62](https://github.com/npm/hosted-git-info/commit/cce5a62))
305 | * **Revert:** "git-host: fix forgotten extend()" ([a815ec9](https://github.com/npm/hosted-git-info/commit/a815ec9))
306 |
307 |
308 |
309 |
310 | # [2.6.0](https://github.com/npm/hosted-git-info/compare/v2.5.0...v2.6.0) (2018-03-07)
311 |
312 |
313 | ### Bug Fixes
314 |
315 | * **compat:** remove Object.assign fallback ([#25](https://github.com/npm/hosted-git-info/issues/25)) ([627ab55](https://github.com/npm/hosted-git-info/commit/627ab55))
316 | * **git-host:** fix forgotten extend() ([eba1f7b](https://github.com/npm/hosted-git-info/commit/eba1f7b))
317 |
318 |
319 | ### Features
320 |
321 | * **browse:** fragment support for browse() ([#28](https://github.com/npm/hosted-git-info/issues/28)) ([cd5e5bb](https://github.com/npm/hosted-git-info/commit/cd5e5bb))
322 |
--------------------------------------------------------------------------------
/CODE_OF_CONDUCT.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | All interactions in this repo are covered by the [npm Code of
4 | Conduct](https://docs.npmjs.com/policies/conduct)
5 |
6 | The npm cli team may, at its own discretion, moderate, remove, or edit
7 | any interactions such as pull requests, issues, and comments.
8 |
--------------------------------------------------------------------------------
/CONTRIBUTING.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | # Contributing
4 |
5 | ## Code of Conduct
6 |
7 | All interactions in the **npm** organization on GitHub are considered to be covered by our standard [Code of Conduct](https://docs.npmjs.com/policies/conduct).
8 |
9 | ## Reporting Bugs
10 |
11 | Before submitting a new bug report please search for an existing or similar report.
12 |
13 | Use one of our existing issue templates if you believe you've come across a unique problem.
14 |
15 | Duplicate issues, or issues that don't use one of our templates may get closed without a response.
16 |
17 | ## Pull Request Conventions
18 |
19 | ### Commits
20 |
21 | We use [Conventional Commits](https://www.conventionalcommits.org/en/v1.0.0/).
22 |
23 | When opening a pull request please be sure that either the pull request title, or each commit in the pull request, has one of the following prefixes:
24 |
25 | - `feat`: For when introducing a new feature. The result will be a new semver minor version of the package when it is next published.
26 | - `fix`: For bug fixes. The result will be a new semver patch version of the package when it is next published.
27 | - `docs`: For documentation updates. The result will be a new semver patch version of the package when it is next published.
28 | - `chore`: For changes that do not affect the published module. Often these are changes to tests. The result will be *no* change to the version of the package when it is next published (as the commit does not affect the published version).
29 |
30 | ### Test Coverage
31 |
32 | Pull requests made against this repo will run `npm test` automatically. Please make sure tests pass locally before submitting a PR.
33 |
34 | Every new feature or bug fix should come with a corresponding test or tests that validate the solutions. Testing also reports on code coverage and will fail if code coverage drops.
35 |
36 | ### Linting
37 |
38 | Linting is also done automatically once tests pass. `npm run lintfix` will fix most linting errors automatically.
39 |
40 | Please make sure linting passes before submitting a PR.
41 |
42 | ## What _not_ to contribute?
43 |
44 | ### Dependencies
45 |
46 | It should be noted that our team does not accept third-party dependency updates/PRs. If you submit a PR trying to update our dependencies we will close it with or without a reference to these contribution guidelines.
47 |
48 | ### Tools/Automation
49 |
50 | Our core team is responsible for the maintenance of the tooling/automation in this project and we ask contributors to not make changes to these when contributing (e.g. `.github/*`, `.eslintrc.json`, `.licensee.json`). Most of those files also have a header at the top to remind folks they are automatically generated. Pull requests that alter these will not be accepted.
51 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Copyright (c) 2015, Rebecca Turner
2 |
3 | Permission to use, copy, modify, and/or distribute this software for any
4 | purpose with or without fee is hereby granted, provided that the above
5 | copyright notice and this permission notice appear in all copies.
6 |
7 | THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
8 | REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND
9 | FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
10 | INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
11 | LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
12 | OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
13 | PERFORMANCE OF THIS SOFTWARE.
14 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # hosted-git-info
2 |
3 | This will let you identify and transform various git hosts URLs between
4 | protocols. It also can tell you what the URL is for the raw path for
5 | particular file for direct access without git.
6 |
7 | ## Example
8 |
9 | ```javascript
10 | const hostedGitInfo = require("hosted-git-info")
11 | const info = hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git", opts)
12 | /* info looks like:
13 | {
14 | type: "github",
15 | domain: "github.com",
16 | user: "npm",
17 | project: "hosted-git-info"
18 | }
19 | */
20 | ```
21 |
22 | If the URL can't be matched with a git host, `null` will be returned. We
23 | can match git, ssh and https urls. Additionally, we can match ssh connect
24 | strings (`git@github.com:npm/hosted-git-info`) and shortcuts (eg,
25 | `github:npm/hosted-git-info`). GitHub specifically, is detected in the case
26 | of a third, unprefixed, form: `npm/hosted-git-info`.
27 |
28 | If it does match, the returned object has properties of:
29 |
30 | * info.type -- The short name of the service
31 | * info.domain -- The domain for git protocol use
32 | * info.user -- The name of the user/org on the git host
33 | * info.project -- The name of the project on the git host
34 |
35 | ## Version Contract
36 |
37 | The major version will be bumped any time…
38 |
39 | * The constructor stops accepting URLs that it previously accepted.
40 | * A method is removed.
41 | * A method can no longer accept the number and type of arguments it previously accepted.
42 | * A method can return a different type than it currently returns.
43 |
44 | Implications:
45 |
46 | * I do not consider the specific format of the urls returned from, say
47 | `.https()` to be a part of the contract. The contract is that it will
48 | return a string that can be used to fetch the repo via HTTPS. But what
49 | that string looks like, specifically, can change.
50 | * Dropping support for a hosted git provider would constitute a breaking
51 | change.
52 |
53 | ## Usage
54 |
55 | ### const info = hostedGitInfo.fromUrl(gitSpecifier[, options])
56 |
57 | * *gitSpecifer* is a URL of a git repository or a SCP-style specifier of one.
58 | * *options* is an optional object. It can have the following properties:
59 | * *noCommittish* — If true then committishes won't be included in generated URLs.
60 | * *noGitPlus* — If true then `git+` won't be prefixed on URLs.
61 |
62 | ### const infoOrURL = hostedGitInfo.fromManifest(manifest[, options])
63 |
64 | * *manifest* is a package manifest, such as that returned by [`pacote.manifest()`](https://npmjs.com/pacote)
65 | * *options* is an optional object. It can have the same properties as `fromUrl` above.
66 |
67 | ## Methods
68 |
69 | All of the methods take the same options as the `fromUrl` factory. Options
70 | provided to a method override those provided to the constructor.
71 |
72 | * info.file(path, opts)
73 |
74 | Given the path of a file relative to the repository, returns a URL for
75 | directly fetching it from the githost. If no committish was set then
76 | `HEAD` will be used as the default.
77 |
78 | For example `hostedGitInfo.fromUrl("git@github.com:npm/hosted-git-info.git#v1.0.0").file("package.json")`
79 | would return `https://raw.githubusercontent.com/npm/hosted-git-info/v1.0.0/package.json`
80 |
81 | * info.shortcut(opts)
82 |
83 | eg, `github:npm/hosted-git-info`
84 |
85 | * info.browse(path, fragment, opts)
86 |
87 | eg, `https://github.com/npm/hosted-git-info/tree/v1.2.0`,
88 | `https://github.com/npm/hosted-git-info/tree/v1.2.0/package.json`,
89 | `https://github.com/npm/hosted-git-info/tree/v1.2.0/README.md#supported-hosts`
90 |
91 | * info.bugs(opts)
92 |
93 | eg, `https://github.com/npm/hosted-git-info/issues`
94 |
95 | * info.docs(opts)
96 |
97 | eg, `https://github.com/npm/hosted-git-info/tree/v1.2.0#readme`
98 |
99 | * info.https(opts)
100 |
101 | eg, `git+https://github.com/npm/hosted-git-info.git`
102 |
103 | * info.sshurl(opts)
104 |
105 | eg, `git+ssh://git@github.com/npm/hosted-git-info.git`
106 |
107 | * info.ssh(opts)
108 |
109 | eg, `git@github.com:npm/hosted-git-info.git`
110 |
111 | * info.path(opts)
112 |
113 | eg, `npm/hosted-git-info`
114 |
115 | * info.tarball(opts)
116 |
117 | eg, `https://github.com/npm/hosted-git-info/archive/v1.2.0.tar.gz`
118 |
119 | * info.getDefaultRepresentation()
120 |
121 | Returns the default output type. The default output type is based on the
122 | string you passed in to be parsed
123 |
124 | * info.toString(opts)
125 |
126 | Uses the getDefaultRepresentation to call one of the other methods to get a URL for
127 | this resource. As such `hostedGitInfo.fromUrl(url).toString()` will give
128 | you a normalized version of the URL that still uses the same protocol.
129 |
130 | Shortcuts will still be returned as shortcuts, but the special case github
131 | form of `org/project` will be normalized to `github:org/project`.
132 |
133 | SSH connect strings will be normalized into `git+ssh` URLs.
134 |
135 | ## Supported hosts
136 |
137 | Currently this supports GitHub (including Gists), Bitbucket, GitLab and Sourcehut.
138 | Pull requests for additional hosts welcome.
139 |
--------------------------------------------------------------------------------
/SECURITY.md:
--------------------------------------------------------------------------------
1 |
2 |
3 | GitHub takes the security of our software products and services seriously, including the open source code repositories managed through our GitHub organizations, such as [GitHub](https://github.com/GitHub).
4 |
5 | If you believe you have found a security vulnerability in this GitHub-owned open source repository, you can report it to us in one of two ways.
6 |
7 | If the vulnerability you have found is *not* [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) or if you do not wish to be considered for a bounty reward, please report the issue to us directly through [opensource-security@github.com](mailto:opensource-security@github.com).
8 |
9 | If the vulnerability you have found is [in scope for the GitHub Bug Bounty Program](https://bounty.github.com/#scope) and you would like for your finding to be considered for a bounty reward, please submit the vulnerability to us through [HackerOne](https://hackerone.com/github) in order to be eligible to receive a bounty award.
10 |
11 | **Please do not report security vulnerabilities through public GitHub issues, discussions, or pull requests.**
12 |
13 | Thanks for helping make GitHub safe for everyone.
14 |
--------------------------------------------------------------------------------
/lib/from-url.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const parseUrl = require('./parse-url')
4 |
5 | // look for github shorthand inputs, such as npm/cli
6 | const isGitHubShorthand = (arg) => {
7 | // it cannot contain whitespace before the first #
8 | // it cannot start with a / because that's probably an absolute file path
9 | // but it must include a slash since repos are username/repository
10 | // it cannot start with a . because that's probably a relative file path
11 | // it cannot start with an @ because that's a scoped package if it passes the other tests
12 | // it cannot contain a : before a # because that tells us that there's a protocol
13 | // a second / may not exist before a #
14 | const firstHash = arg.indexOf('#')
15 | const firstSlash = arg.indexOf('/')
16 | const secondSlash = arg.indexOf('/', firstSlash + 1)
17 | const firstColon = arg.indexOf(':')
18 | const firstSpace = /\s/.exec(arg)
19 | const firstAt = arg.indexOf('@')
20 |
21 | const spaceOnlyAfterHash = !firstSpace || (firstHash > -1 && firstSpace.index > firstHash)
22 | const atOnlyAfterHash = firstAt === -1 || (firstHash > -1 && firstAt > firstHash)
23 | const colonOnlyAfterHash = firstColon === -1 || (firstHash > -1 && firstColon > firstHash)
24 | const secondSlashOnlyAfterHash = secondSlash === -1 || (firstHash > -1 && secondSlash > firstHash)
25 | const hasSlash = firstSlash > 0
26 | // if a # is found, what we really want to know is that the character
27 | // immediately before # is not a /
28 | const doesNotEndWithSlash = firstHash > -1 ? arg[firstHash - 1] !== '/' : !arg.endsWith('/')
29 | const doesNotStartWithDot = !arg.startsWith('.')
30 |
31 | return spaceOnlyAfterHash && hasSlash && doesNotEndWithSlash &&
32 | doesNotStartWithDot && atOnlyAfterHash && colonOnlyAfterHash &&
33 | secondSlashOnlyAfterHash
34 | }
35 |
36 | module.exports = (giturl, opts, { gitHosts, protocols }) => {
37 | if (!giturl) {
38 | return
39 | }
40 |
41 | const correctedUrl = isGitHubShorthand(giturl) ? `github:${giturl}` : giturl
42 | const parsed = parseUrl(correctedUrl, protocols)
43 | if (!parsed) {
44 | return
45 | }
46 |
47 | const gitHostShortcut = gitHosts.byShortcut[parsed.protocol]
48 | const gitHostDomain = gitHosts.byDomain[parsed.hostname.startsWith('www.')
49 | ? parsed.hostname.slice(4)
50 | : parsed.hostname]
51 | const gitHostName = gitHostShortcut || gitHostDomain
52 | if (!gitHostName) {
53 | return
54 | }
55 |
56 | const gitHostInfo = gitHosts[gitHostShortcut || gitHostDomain]
57 | let auth = null
58 | if (protocols[parsed.protocol]?.auth && (parsed.username || parsed.password)) {
59 | auth = `${parsed.username}${parsed.password ? ':' + parsed.password : ''}`
60 | }
61 |
62 | let committish = null
63 | let user = null
64 | let project = null
65 | let defaultRepresentation = null
66 |
67 | try {
68 | if (gitHostShortcut) {
69 | let pathname = parsed.pathname.startsWith('/') ? parsed.pathname.slice(1) : parsed.pathname
70 | const firstAt = pathname.indexOf('@')
71 | // we ignore auth for shortcuts, so just trim it out
72 | if (firstAt > -1) {
73 | pathname = pathname.slice(firstAt + 1)
74 | }
75 |
76 | const lastSlash = pathname.lastIndexOf('/')
77 | if (lastSlash > -1) {
78 | user = decodeURIComponent(pathname.slice(0, lastSlash))
79 | // we want nulls only, never empty strings
80 | if (!user) {
81 | user = null
82 | }
83 | project = decodeURIComponent(pathname.slice(lastSlash + 1))
84 | } else {
85 | project = decodeURIComponent(pathname)
86 | }
87 |
88 | if (project.endsWith('.git')) {
89 | project = project.slice(0, -4)
90 | }
91 |
92 | if (parsed.hash) {
93 | committish = decodeURIComponent(parsed.hash.slice(1))
94 | }
95 |
96 | defaultRepresentation = 'shortcut'
97 | } else {
98 | if (!gitHostInfo.protocols.includes(parsed.protocol)) {
99 | return
100 | }
101 |
102 | const segments = gitHostInfo.extract(parsed)
103 | if (!segments) {
104 | return
105 | }
106 |
107 | user = segments.user && decodeURIComponent(segments.user)
108 | project = decodeURIComponent(segments.project)
109 | committish = decodeURIComponent(segments.committish)
110 | defaultRepresentation = protocols[parsed.protocol]?.name || parsed.protocol.slice(0, -1)
111 | }
112 | } catch (err) {
113 | /* istanbul ignore else */
114 | if (err instanceof URIError) {
115 | return
116 | } else {
117 | throw err
118 | }
119 | }
120 |
121 | return [gitHostName, user, auth, project, committish, defaultRepresentation, opts]
122 | }
123 |
--------------------------------------------------------------------------------
/lib/hosts.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 |
3 | 'use strict'
4 |
5 | const maybeJoin = (...args) => args.every(arg => arg) ? args.join('') : ''
6 | const maybeEncode = (arg) => arg ? encodeURIComponent(arg) : ''
7 | const formatHashFragment = (f) => f.toLowerCase()
8 | .replace(/^\W+/g, '') // strip leading non-characters
9 | .replace(/(?
15 | `git@${domain}:${user}/${project}.git${maybeJoin('#', committish)}`,
16 | sshurltemplate: ({ domain, user, project, committish }) =>
17 | `git+ssh://git@${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
18 | edittemplate: ({ domain, user, project, committish, editpath, path }) =>
19 | `https://${domain}/${user}/${project}${maybeJoin('/', editpath, '/', maybeEncode(committish || 'HEAD'), '/', path)}`,
20 | browsetemplate: ({ domain, user, project, committish, treepath }) =>
21 | `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}`,
22 | browsetreetemplate: ({ domain, user, project, committish, treepath, path, fragment, hashformat }) =>
23 | `https://${domain}/${user}/${project}/${treepath}/${maybeEncode(committish || 'HEAD')}/${path}${maybeJoin('#', hashformat(fragment || ''))}`,
24 | browseblobtemplate: ({ domain, user, project, committish, blobpath, path, fragment, hashformat }) =>
25 | `https://${domain}/${user}/${project}/${blobpath}/${maybeEncode(committish || 'HEAD')}/${path}${maybeJoin('#', hashformat(fragment || ''))}`,
26 | docstemplate: ({ domain, user, project, treepath, committish }) =>
27 | `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish))}#readme`,
28 | httpstemplate: ({ auth, domain, user, project, committish }) =>
29 | `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
30 | filetemplate: ({ domain, user, project, committish, path }) =>
31 | `https://${domain}/${user}/${project}/raw/${maybeEncode(committish || 'HEAD')}/${path}`,
32 | shortcuttemplate: ({ type, user, project, committish }) =>
33 | `${type}:${user}/${project}${maybeJoin('#', committish)}`,
34 | pathtemplate: ({ user, project, committish }) =>
35 | `${user}/${project}${maybeJoin('#', committish)}`,
36 | bugstemplate: ({ domain, user, project }) =>
37 | `https://${domain}/${user}/${project}/issues`,
38 | hashformat: formatHashFragment,
39 | }
40 |
41 | const hosts = {}
42 | hosts.github = {
43 | // First two are insecure and generally shouldn't be used any more, but
44 | // they are still supported.
45 | protocols: ['git:', 'http:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'],
46 | domain: 'github.com',
47 | treepath: 'tree',
48 | blobpath: 'blob',
49 | editpath: 'edit',
50 | filetemplate: ({ auth, user, project, committish, path }) =>
51 | `https://${maybeJoin(auth, '@')}raw.githubusercontent.com/${user}/${project}/${maybeEncode(committish || 'HEAD')}/${path}`,
52 | gittemplate: ({ auth, domain, user, project, committish }) =>
53 | `git://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
54 | tarballtemplate: ({ domain, user, project, committish }) =>
55 | `https://codeload.${domain}/${user}/${project}/tar.gz/${maybeEncode(committish || 'HEAD')}`,
56 | extract: (url) => {
57 | let [, user, project, type, committish] = url.pathname.split('/', 5)
58 | if (type && type !== 'tree') {
59 | return
60 | }
61 |
62 | if (!type) {
63 | committish = url.hash.slice(1)
64 | }
65 |
66 | if (project && project.endsWith('.git')) {
67 | project = project.slice(0, -4)
68 | }
69 |
70 | if (!user || !project) {
71 | return
72 | }
73 |
74 | return { user, project, committish }
75 | },
76 | }
77 |
78 | hosts.bitbucket = {
79 | protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'],
80 | domain: 'bitbucket.org',
81 | treepath: 'src',
82 | blobpath: 'src',
83 | editpath: '?mode=edit',
84 | edittemplate: ({ domain, user, project, committish, treepath, path, editpath }) =>
85 | `https://${domain}/${user}/${project}${maybeJoin('/', treepath, '/', maybeEncode(committish || 'HEAD'), '/', path, editpath)}`,
86 | tarballtemplate: ({ domain, user, project, committish }) =>
87 | `https://${domain}/${user}/${project}/get/${maybeEncode(committish || 'HEAD')}.tar.gz`,
88 | extract: (url) => {
89 | let [, user, project, aux] = url.pathname.split('/', 4)
90 | if (['get'].includes(aux)) {
91 | return
92 | }
93 |
94 | if (project && project.endsWith('.git')) {
95 | project = project.slice(0, -4)
96 | }
97 |
98 | if (!user || !project) {
99 | return
100 | }
101 |
102 | return { user, project, committish: url.hash.slice(1) }
103 | },
104 | }
105 |
106 | hosts.gitlab = {
107 | protocols: ['git+ssh:', 'git+https:', 'ssh:', 'https:'],
108 | domain: 'gitlab.com',
109 | treepath: 'tree',
110 | blobpath: 'tree',
111 | editpath: '-/edit',
112 | httpstemplate: ({ auth, domain, user, project, committish }) =>
113 | `git+https://${maybeJoin(auth, '@')}${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
114 | tarballtemplate: ({ domain, user, project, committish }) =>
115 | `https://${domain}/${user}/${project}/repository/archive.tar.gz?ref=${maybeEncode(committish || 'HEAD')}`,
116 | extract: (url) => {
117 | const path = url.pathname.slice(1)
118 | if (path.includes('/-/') || path.includes('/archive.tar.gz')) {
119 | return
120 | }
121 |
122 | const segments = path.split('/')
123 | let project = segments.pop()
124 | if (project.endsWith('.git')) {
125 | project = project.slice(0, -4)
126 | }
127 |
128 | const user = segments.join('/')
129 | if (!user || !project) {
130 | return
131 | }
132 |
133 | return { user, project, committish: url.hash.slice(1) }
134 | },
135 | }
136 |
137 | hosts.gist = {
138 | protocols: ['git:', 'git+ssh:', 'git+https:', 'ssh:', 'https:'],
139 | domain: 'gist.github.com',
140 | editpath: 'edit',
141 | sshtemplate: ({ domain, project, committish }) =>
142 | `git@${domain}:${project}.git${maybeJoin('#', committish)}`,
143 | sshurltemplate: ({ domain, project, committish }) =>
144 | `git+ssh://git@${domain}/${project}.git${maybeJoin('#', committish)}`,
145 | edittemplate: ({ domain, user, project, committish, editpath }) =>
146 | `https://${domain}/${user}/${project}${maybeJoin('/', maybeEncode(committish))}/${editpath}`,
147 | browsetemplate: ({ domain, project, committish }) =>
148 | `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`,
149 | browsetreetemplate: ({ domain, project, committish, path, hashformat }) =>
150 | `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}${maybeJoin('#', hashformat(path))}`,
151 | browseblobtemplate: ({ domain, project, committish, path, hashformat }) =>
152 | `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}${maybeJoin('#', hashformat(path))}`,
153 | docstemplate: ({ domain, project, committish }) =>
154 | `https://${domain}/${project}${maybeJoin('/', maybeEncode(committish))}`,
155 | httpstemplate: ({ domain, project, committish }) =>
156 | `git+https://${domain}/${project}.git${maybeJoin('#', committish)}`,
157 | filetemplate: ({ user, project, committish, path }) =>
158 | `https://gist.githubusercontent.com/${user}/${project}/raw${maybeJoin('/', maybeEncode(committish))}/${path}`,
159 | shortcuttemplate: ({ type, project, committish }) =>
160 | `${type}:${project}${maybeJoin('#', committish)}`,
161 | pathtemplate: ({ project, committish }) =>
162 | `${project}${maybeJoin('#', committish)}`,
163 | bugstemplate: ({ domain, project }) =>
164 | `https://${domain}/${project}`,
165 | gittemplate: ({ domain, project, committish }) =>
166 | `git://${domain}/${project}.git${maybeJoin('#', committish)}`,
167 | tarballtemplate: ({ project, committish }) =>
168 | `https://codeload.github.com/gist/${project}/tar.gz/${maybeEncode(committish || 'HEAD')}`,
169 | extract: (url) => {
170 | let [, user, project, aux] = url.pathname.split('/', 4)
171 | if (aux === 'raw') {
172 | return
173 | }
174 |
175 | if (!project) {
176 | if (!user) {
177 | return
178 | }
179 |
180 | project = user
181 | user = null
182 | }
183 |
184 | if (project.endsWith('.git')) {
185 | project = project.slice(0, -4)
186 | }
187 |
188 | return { user, project, committish: url.hash.slice(1) }
189 | },
190 | hashformat: function (fragment) {
191 | return fragment && 'file-' + formatHashFragment(fragment)
192 | },
193 | }
194 |
195 | hosts.sourcehut = {
196 | protocols: ['git+ssh:', 'https:'],
197 | domain: 'git.sr.ht',
198 | treepath: 'tree',
199 | blobpath: 'tree',
200 | filetemplate: ({ domain, user, project, committish, path }) =>
201 | `https://${domain}/${user}/${project}/blob/${maybeEncode(committish) || 'HEAD'}/${path}`,
202 | httpstemplate: ({ domain, user, project, committish }) =>
203 | `https://${domain}/${user}/${project}.git${maybeJoin('#', committish)}`,
204 | tarballtemplate: ({ domain, user, project, committish }) =>
205 | `https://${domain}/${user}/${project}/archive/${maybeEncode(committish) || 'HEAD'}.tar.gz`,
206 | bugstemplate: () => null,
207 | extract: (url) => {
208 | let [, user, project, aux] = url.pathname.split('/', 4)
209 |
210 | // tarball url
211 | if (['archive'].includes(aux)) {
212 | return
213 | }
214 |
215 | if (project && project.endsWith('.git')) {
216 | project = project.slice(0, -4)
217 | }
218 |
219 | if (!user || !project) {
220 | return
221 | }
222 |
223 | return { user, project, committish: url.hash.slice(1) }
224 | },
225 | }
226 |
227 | for (const [name, host] of Object.entries(hosts)) {
228 | hosts[name] = Object.assign({}, defaults, host)
229 | }
230 |
231 | module.exports = hosts
232 |
--------------------------------------------------------------------------------
/lib/index.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 |
3 | const { LRUCache } = require('lru-cache')
4 | const hosts = require('./hosts.js')
5 | const fromUrl = require('./from-url.js')
6 | const parseUrl = require('./parse-url.js')
7 |
8 | const cache = new LRUCache({ max: 1000 })
9 |
10 | function unknownHostedUrl (url) {
11 | try {
12 | const {
13 | protocol,
14 | hostname,
15 | pathname,
16 | } = new URL(url)
17 |
18 | if (!hostname) {
19 | return null
20 | }
21 |
22 | const proto = /(?:git\+)http:$/.test(protocol) ? 'http:' : 'https:'
23 | const path = pathname.replace(/\.git$/, '')
24 | return `${proto}//${hostname}${path}`
25 | } catch {
26 | return null
27 | }
28 | }
29 |
30 | class GitHost {
31 | constructor (type, user, auth, project, committish, defaultRepresentation, opts = {}) {
32 | Object.assign(this, GitHost.#gitHosts[type], {
33 | type,
34 | user,
35 | auth,
36 | project,
37 | committish,
38 | default: defaultRepresentation,
39 | opts,
40 | })
41 | }
42 |
43 | static #gitHosts = { byShortcut: {}, byDomain: {} }
44 | static #protocols = {
45 | 'git+ssh:': { name: 'sshurl' },
46 | 'ssh:': { name: 'sshurl' },
47 | 'git+https:': { name: 'https', auth: true },
48 | 'git:': { auth: true },
49 | 'http:': { auth: true },
50 | 'https:': { auth: true },
51 | 'git+http:': { auth: true },
52 | }
53 |
54 | static addHost (name, host) {
55 | GitHost.#gitHosts[name] = host
56 | GitHost.#gitHosts.byDomain[host.domain] = name
57 | GitHost.#gitHosts.byShortcut[`${name}:`] = name
58 | GitHost.#protocols[`${name}:`] = { name }
59 | }
60 |
61 | static fromUrl (giturl, opts) {
62 | if (typeof giturl !== 'string') {
63 | return
64 | }
65 |
66 | const key = giturl + JSON.stringify(opts || {})
67 |
68 | if (!cache.has(key)) {
69 | const hostArgs = fromUrl(giturl, opts, {
70 | gitHosts: GitHost.#gitHosts,
71 | protocols: GitHost.#protocols,
72 | })
73 | cache.set(key, hostArgs ? new GitHost(...hostArgs) : undefined)
74 | }
75 |
76 | return cache.get(key)
77 | }
78 |
79 | static fromManifest (manifest, opts = {}) {
80 | if (!manifest || typeof manifest !== 'object') {
81 | return
82 | }
83 |
84 | const r = manifest.repository
85 | // TODO: look into also checking the `bugs`/`homepage` URLs
86 |
87 | const rurl = r && (
88 | typeof r === 'string'
89 | ? r
90 | : typeof r === 'object' && typeof r.url === 'string'
91 | ? r.url
92 | : null
93 | )
94 |
95 | if (!rurl) {
96 | throw new Error('no repository')
97 | }
98 |
99 | const info = (rurl && GitHost.fromUrl(rurl.replace(/^git\+/, ''), opts)) || null
100 | if (info) {
101 | return info
102 | }
103 | const unk = unknownHostedUrl(rurl)
104 | return GitHost.fromUrl(unk, opts) || unk
105 | }
106 |
107 | static parseUrl (url) {
108 | return parseUrl(url)
109 | }
110 |
111 | #fill (template, opts) {
112 | if (typeof template !== 'function') {
113 | return null
114 | }
115 |
116 | const options = { ...this, ...this.opts, ...opts }
117 |
118 | // the path should always be set so we don't end up with 'undefined' in urls
119 | if (!options.path) {
120 | options.path = ''
121 | }
122 |
123 | // template functions will insert the leading slash themselves
124 | if (options.path.startsWith('/')) {
125 | options.path = options.path.slice(1)
126 | }
127 |
128 | if (options.noCommittish) {
129 | options.committish = null
130 | }
131 |
132 | const result = template(options)
133 | return options.noGitPlus && result.startsWith('git+') ? result.slice(4) : result
134 | }
135 |
136 | hash () {
137 | return this.committish ? `#${this.committish}` : ''
138 | }
139 |
140 | ssh (opts) {
141 | return this.#fill(this.sshtemplate, opts)
142 | }
143 |
144 | sshurl (opts) {
145 | return this.#fill(this.sshurltemplate, opts)
146 | }
147 |
148 | browse (path, ...args) {
149 | // not a string, treat path as opts
150 | if (typeof path !== 'string') {
151 | return this.#fill(this.browsetemplate, path)
152 | }
153 |
154 | if (typeof args[0] !== 'string') {
155 | return this.#fill(this.browsetreetemplate, { ...args[0], path })
156 | }
157 |
158 | return this.#fill(this.browsetreetemplate, { ...args[1], fragment: args[0], path })
159 | }
160 |
161 | // If the path is known to be a file, then browseFile should be used. For some hosts
162 | // the url is the same as browse, but for others like GitHub a file can use both `/tree/`
163 | // and `/blob/` in the path. When using a default committish of `HEAD` then the `/tree/`
164 | // path will redirect to a specific commit. Using the `/blob/` path avoids this and
165 | // does not redirect to a different commit.
166 | browseFile (path, ...args) {
167 | if (typeof args[0] !== 'string') {
168 | return this.#fill(this.browseblobtemplate, { ...args[0], path })
169 | }
170 |
171 | return this.#fill(this.browseblobtemplate, { ...args[1], fragment: args[0], path })
172 | }
173 |
174 | docs (opts) {
175 | return this.#fill(this.docstemplate, opts)
176 | }
177 |
178 | bugs (opts) {
179 | return this.#fill(this.bugstemplate, opts)
180 | }
181 |
182 | https (opts) {
183 | return this.#fill(this.httpstemplate, opts)
184 | }
185 |
186 | git (opts) {
187 | return this.#fill(this.gittemplate, opts)
188 | }
189 |
190 | shortcut (opts) {
191 | return this.#fill(this.shortcuttemplate, opts)
192 | }
193 |
194 | path (opts) {
195 | return this.#fill(this.pathtemplate, opts)
196 | }
197 |
198 | tarball (opts) {
199 | return this.#fill(this.tarballtemplate, { ...opts, noCommittish: false })
200 | }
201 |
202 | file (path, opts) {
203 | return this.#fill(this.filetemplate, { ...opts, path })
204 | }
205 |
206 | edit (path, opts) {
207 | return this.#fill(this.edittemplate, { ...opts, path })
208 | }
209 |
210 | getDefaultRepresentation () {
211 | return this.default
212 | }
213 |
214 | toString (opts) {
215 | if (this.default && typeof this[this.default] === 'function') {
216 | return this[this.default](opts)
217 | }
218 |
219 | return this.sshurl(opts)
220 | }
221 | }
222 |
223 | for (const [name, host] of Object.entries(hosts)) {
224 | GitHost.addHost(name, host)
225 | }
226 |
227 | module.exports = GitHost
228 |
--------------------------------------------------------------------------------
/lib/parse-url.js:
--------------------------------------------------------------------------------
1 | const url = require('url')
2 |
3 | const lastIndexOfBefore = (str, char, beforeChar) => {
4 | const startPosition = str.indexOf(beforeChar)
5 | return str.lastIndexOf(char, startPosition > -1 ? startPosition : Infinity)
6 | }
7 |
8 | const safeUrl = (u) => {
9 | try {
10 | return new url.URL(u)
11 | } catch {
12 | // this fn should never throw
13 | }
14 | }
15 |
16 | // accepts input like git:github.com:user/repo and inserts the // after the first :
17 | const correctProtocol = (arg, protocols) => {
18 | const firstColon = arg.indexOf(':')
19 | const proto = arg.slice(0, firstColon + 1)
20 | if (Object.prototype.hasOwnProperty.call(protocols, proto)) {
21 | return arg
22 | }
23 |
24 | const firstAt = arg.indexOf('@')
25 | if (firstAt > -1) {
26 | if (firstAt > firstColon) {
27 | return `git+ssh://${arg}`
28 | } else {
29 | return arg
30 | }
31 | }
32 |
33 | const doubleSlash = arg.indexOf('//')
34 | if (doubleSlash === firstColon + 1) {
35 | return arg
36 | }
37 |
38 | return `${arg.slice(0, firstColon + 1)}//${arg.slice(firstColon + 1)}`
39 | }
40 |
41 | // attempt to correct an scp style url so that it will parse with `new URL()`
42 | const correctUrl = (giturl) => {
43 | // ignore @ that come after the first hash since the denotes the start
44 | // of a committish which can contain @ characters
45 | const firstAt = lastIndexOfBefore(giturl, '@', '#')
46 | // ignore colons that come after the hash since that could include colons such as:
47 | // git@github.com:user/package-2#semver:^1.0.0
48 | const lastColonBeforeHash = lastIndexOfBefore(giturl, ':', '#')
49 |
50 | if (lastColonBeforeHash > firstAt) {
51 | // the last : comes after the first @ (or there is no @)
52 | // like it would in:
53 | // proto://hostname.com:user/repo
54 | // username@hostname.com:user/repo
55 | // :password@hostname.com:user/repo
56 | // username:password@hostname.com:user/repo
57 | // proto://username@hostname.com:user/repo
58 | // proto://:password@hostname.com:user/repo
59 | // proto://username:password@hostname.com:user/repo
60 | // then we replace the last : with a / to create a valid path
61 | giturl = giturl.slice(0, lastColonBeforeHash) + '/' + giturl.slice(lastColonBeforeHash + 1)
62 | }
63 |
64 | if (lastIndexOfBefore(giturl, ':', '#') === -1 && giturl.indexOf('//') === -1) {
65 | // we have no : at all
66 | // as it would be in:
67 | // username@hostname.com/user/repo
68 | // then we prepend a protocol
69 | giturl = `git+ssh://${giturl}`
70 | }
71 |
72 | return giturl
73 | }
74 |
75 | module.exports = (giturl, protocols) => {
76 | const withProtocol = protocols ? correctProtocol(giturl, protocols) : giturl
77 | return safeUrl(withProtocol) || safeUrl(correctUrl(withProtocol))
78 | }
79 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "hosted-git-info",
3 | "version": "8.1.0",
4 | "description": "Provides metadata and conversions from repository urls for GitHub, Bitbucket and GitLab",
5 | "main": "./lib/index.js",
6 | "repository": {
7 | "type": "git",
8 | "url": "git+https://github.com/npm/hosted-git-info.git"
9 | },
10 | "keywords": [
11 | "git",
12 | "github",
13 | "bitbucket",
14 | "gitlab"
15 | ],
16 | "author": "GitHub Inc.",
17 | "license": "ISC",
18 | "bugs": {
19 | "url": "https://github.com/npm/hosted-git-info/issues"
20 | },
21 | "homepage": "https://github.com/npm/hosted-git-info",
22 | "scripts": {
23 | "posttest": "npm run lint",
24 | "snap": "tap",
25 | "test": "tap",
26 | "test:coverage": "tap --coverage-report=html",
27 | "lint": "npm run eslint",
28 | "postlint": "template-oss-check",
29 | "lintfix": "npm run eslint -- --fix",
30 | "template-oss-apply": "template-oss-apply --force",
31 | "eslint": "eslint \"**/*.{js,cjs,ts,mjs,jsx,tsx}\""
32 | },
33 | "dependencies": {
34 | "lru-cache": "^10.0.1"
35 | },
36 | "devDependencies": {
37 | "@npmcli/eslint-config": "^5.0.0",
38 | "@npmcli/template-oss": "4.24.3",
39 | "tap": "^16.0.1"
40 | },
41 | "files": [
42 | "bin/",
43 | "lib/"
44 | ],
45 | "engines": {
46 | "node": "^18.17.0 || >=20.5.0"
47 | },
48 | "tap": {
49 | "color": 1,
50 | "coverage": true,
51 | "nyc-arg": [
52 | "--exclude",
53 | "tap-snapshots/**"
54 | ]
55 | },
56 | "templateOSS": {
57 | "//@npmcli/template-oss": "This file is partially managed by @npmcli/template-oss. Edits may be overwritten.",
58 | "version": "4.24.3",
59 | "publish": "true"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/release-please-config.json:
--------------------------------------------------------------------------------
1 | {
2 | "group-pull-request-title-pattern": "chore: release ${version}",
3 | "pull-request-title-pattern": "chore: release${component} ${version}",
4 | "changelog-sections": [
5 | {
6 | "type": "feat",
7 | "section": "Features",
8 | "hidden": false
9 | },
10 | {
11 | "type": "fix",
12 | "section": "Bug Fixes",
13 | "hidden": false
14 | },
15 | {
16 | "type": "docs",
17 | "section": "Documentation",
18 | "hidden": false
19 | },
20 | {
21 | "type": "deps",
22 | "section": "Dependencies",
23 | "hidden": false
24 | },
25 | {
26 | "type": "chore",
27 | "section": "Chores",
28 | "hidden": true
29 | }
30 | ],
31 | "packages": {
32 | ".": {
33 | "package-name": ""
34 | }
35 | },
36 | "prerelease-type": "pre.0"
37 | }
38 |
--------------------------------------------------------------------------------
/test/bitbucket.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | 'use strict'
3 | const HostedGit = require('..')
4 | const t = require('tap')
5 |
6 | const invalid = [
7 | // invalid protocol
8 | 'git://bitbucket.org/foo/bar',
9 | // url to get a tarball
10 | 'https://bitbucket.org/foo/bar/get/archive.tar.gz',
11 | // missing project
12 | 'https://bitbucket.org/foo',
13 | ]
14 |
15 | const defaults = { type: 'bitbucket', user: 'foo', project: 'bar' }
16 |
17 | const valid = {
18 | // shortucts
19 | //
20 | // NOTE auth is accepted but ignored
21 | 'bitbucket:foo/bar': { ...defaults, default: 'shortcut' },
22 | 'bitbucket:foo/bar#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
23 | 'bitbucket:user@foo/bar': { ...defaults, default: 'shortcut', auth: null },
24 | 'bitbucket:user@foo/bar#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
25 | 'bitbucket:user:password@foo/bar': { ...defaults, default: 'shortcut', auth: null },
26 | 'bitbucket:user:password@foo/bar#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
27 | 'bitbucket::password@foo/bar': { ...defaults, default: 'shortcut', auth: null },
28 | 'bitbucket::password@foo/bar#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
29 |
30 | 'bitbucket:foo/bar.git': { ...defaults, default: 'shortcut' },
31 | 'bitbucket:foo/bar.git#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
32 | 'bitbucket:user@foo/bar.git': { ...defaults, default: 'shortcut', auth: null },
33 | 'bitbucket:user@foo/bar.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
34 | 'bitbucket:user:password@foo/bar.git': { ...defaults, default: 'shortcut', auth: null },
35 | 'bitbucket:user:password@foo/bar.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
36 | 'bitbucket::password@foo/bar.git': { ...defaults, default: 'shortcut', auth: null },
37 | 'bitbucket::password@foo/bar.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
38 |
39 | // no-protocol git+ssh
40 | //
41 | // NOTE auth is accepted but ignored
42 | 'git@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
43 | 'git@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
44 | 'user@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
45 | 'user@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
46 | 'user:password@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
47 | 'user:password@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
48 | ':password@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
49 | ':password@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
50 |
51 | 'git@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
52 | 'git@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
53 | 'user@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
54 | 'user@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
55 | 'user:password@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
56 | 'user:password@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
57 | ':password@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
58 | ':password@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
59 |
60 | // git+ssh urls
61 | //
62 | // NOTE auth is accepted but ignored
63 | 'git+ssh://bitbucket.org:foo/bar': { ...defaults, default: 'sshurl' },
64 | 'git+ssh://bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', committish: 'branch' },
65 | 'git+ssh://user@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
66 | 'git+ssh://user@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
67 | 'git+ssh://user:password@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
68 | 'git+ssh://user:password@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
69 | 'git+ssh://:password@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
70 | 'git+ssh://:password@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
71 |
72 | 'git+ssh://bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl' },
73 | 'git+ssh://bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', committish: 'branch' },
74 | 'git+ssh://user@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
75 | 'git+ssh://user@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
76 | 'git+ssh://user:password@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
77 | 'git+ssh://user:password@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
78 | 'git+ssh://:password@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
79 | 'git+ssh://:password@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
80 |
81 | // ssh urls
82 | //
83 | // NOTE auth is accepted but ignored
84 | 'ssh://bitbucket.org:foo/bar': { ...defaults, default: 'sshurl' },
85 | 'ssh://bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', committish: 'branch' },
86 | 'ssh://user@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
87 | 'ssh://user@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
88 | 'ssh://user:password@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
89 | 'ssh://user:password@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
90 | 'ssh://:password@bitbucket.org:foo/bar': { ...defaults, default: 'sshurl', auth: null },
91 | 'ssh://:password@bitbucket.org:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
92 |
93 | 'ssh://bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl' },
94 | 'ssh://bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', committish: 'branch' },
95 | 'ssh://user@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
96 | 'ssh://user@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
97 | 'ssh://user:password@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
98 | 'ssh://user:password@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
99 | 'ssh://:password@bitbucket.org:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
100 | 'ssh://:password@bitbucket.org:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
101 |
102 | // git+https urls
103 | //
104 | // NOTE auth is accepted and respected
105 | 'git+https://bitbucket.org/foo/bar': { ...defaults, default: 'https' },
106 | 'git+https://bitbucket.org/foo/bar#branch': { ...defaults, default: 'https', committish: 'branch' },
107 | 'git+https://user@bitbucket.org/foo/bar': { ...defaults, default: 'https', auth: 'user' },
108 | 'git+https://user@bitbucket.org/foo/bar#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
109 | 'git+https://user:password@bitbucket.org/foo/bar': { ...defaults, default: 'https', auth: 'user:password' },
110 | 'git+https://user:password@bitbucket.org/foo/bar#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
111 | 'git+https://:password@bitbucket.org/foo/bar': { ...defaults, default: 'https', auth: ':password' },
112 | 'git+https://:password@bitbucket.org/foo/bar#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
113 |
114 | 'git+https://bitbucket.org/foo/bar.git': { ...defaults, default: 'https' },
115 | 'git+https://bitbucket.org/foo/bar.git#branch': { ...defaults, default: 'https', committish: 'branch' },
116 | 'git+https://user@bitbucket.org/foo/bar.git': { ...defaults, default: 'https', auth: 'user' },
117 | 'git+https://user@bitbucket.org/foo/bar.git#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
118 | 'git+https://user:password@bitbucket.org/foo/bar.git': { ...defaults, default: 'https', auth: 'user:password' },
119 | 'git+https://user:password@bitbucket.org/foo/bar.git#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
120 | 'git+https://:password@bitbucket.org/foo/bar.git': { ...defaults, default: 'https', auth: ':password' },
121 | 'git+https://:password@bitbucket.org/foo/bar.git#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
122 |
123 | // https urls
124 | //
125 | // NOTE auth is accepted and respected
126 | 'https://bitbucket.org/foo/bar': { ...defaults, default: 'https' },
127 | 'https://bitbucket.org/foo/bar#branch': { ...defaults, default: 'https', committish: 'branch' },
128 | 'https://user@bitbucket.org/foo/bar': { ...defaults, default: 'https', auth: 'user' },
129 | 'https://user@bitbucket.org/foo/bar#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
130 | 'https://user:password@bitbucket.org/foo/bar': { ...defaults, default: 'https', auth: 'user:password' },
131 | 'https://user:password@bitbucket.org/foo/bar#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
132 | 'https://:password@bitbucket.org/foo/bar': { ...defaults, default: 'https', auth: ':password' },
133 | 'https://:password@bitbucket.org/foo/bar#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
134 |
135 | 'https://bitbucket.org/foo/bar.git': { ...defaults, default: 'https' },
136 | 'https://bitbucket.org/foo/bar.git#branch': { ...defaults, default: 'https', committish: 'branch' },
137 | 'https://user@bitbucket.org/foo/bar.git': { ...defaults, default: 'https', auth: 'user' },
138 | 'https://user@bitbucket.org/foo/bar.git#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
139 | 'https://user:password@bitbucket.org/foo/bar.git': { ...defaults, default: 'https', auth: 'user:password' },
140 | 'https://user:password@bitbucket.org/foo/bar.git#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
141 | 'https://:password@bitbucket.org/foo/bar.git': { ...defaults, default: 'https', auth: ':password' },
142 | 'https://:password@bitbucket.org/foo/bar.git#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
143 | }
144 |
145 | t.test('valid urls parse properly', t => {
146 | t.plan(Object.keys(valid).length)
147 | for (const [url, result] of Object.entries(valid)) {
148 | t.hasStrict(HostedGit.fromUrl(url), result, `${url} parses`)
149 | }
150 | })
151 |
152 | t.test('invalid urls return undefined', t => {
153 | t.plan(invalid.length)
154 | for (const url of invalid) {
155 | t.equal(HostedGit.fromUrl(url), undefined, `${url} returns undefined`)
156 | }
157 | })
158 |
159 | t.test('toString respects defaults', t => {
160 | const sshurl = HostedGit.fromUrl('git+ssh://bitbucket.org/foo/bar')
161 | t.equal(sshurl.default, 'sshurl', 'got the right default')
162 | t.equal(sshurl.toString(), sshurl.sshurl(), 'toString calls sshurl')
163 |
164 | const https = HostedGit.fromUrl('https://bitbucket.org/foo/bar')
165 | t.equal(https.default, 'https', 'got the right default')
166 | t.equal(https.toString(), https.https(), 'toString calls https')
167 |
168 | const shortcut = HostedGit.fromUrl('bitbucket:foo/bar')
169 | t.equal(shortcut.default, 'shortcut', 'got the right default')
170 | t.equal(shortcut.toString(), shortcut.shortcut(), 'toString calls shortcut')
171 |
172 | t.end()
173 | })
174 |
175 | t.test('string methods populate correctly', t => {
176 | const parsed = HostedGit.fromUrl('git+ssh://bitbucket.org/foo/bar')
177 | t.equal(parsed.getDefaultRepresentation(), parsed.default, 'getDefaultRepresentation()')
178 | t.equal(parsed.hash(), '', 'hash() returns empty string when committish is unset')
179 | t.equal(parsed.ssh(), 'git@bitbucket.org:foo/bar.git')
180 | t.equal(parsed.sshurl(), 'git+ssh://git@bitbucket.org/foo/bar.git')
181 | t.equal(parsed.edit(), 'https://bitbucket.org/foo/bar')
182 | t.equal(parsed.edit('/lib/index.js'), 'https://bitbucket.org/foo/bar/src/HEAD/lib/index.js?mode=edit')
183 | t.equal(parsed.browse(), 'https://bitbucket.org/foo/bar')
184 | t.equal(parsed.browse('/lib/index.js'), 'https://bitbucket.org/foo/bar/src/HEAD/lib/index.js')
185 | t.equal(parsed.browse('/lib/index.js', 'L100'), 'https://bitbucket.org/foo/bar/src/HEAD/lib/index.js#l100')
186 | t.equal(parsed.docs(), 'https://bitbucket.org/foo/bar#readme')
187 | t.equal(parsed.https(), 'git+https://bitbucket.org/foo/bar.git')
188 | t.equal(parsed.shortcut(), 'bitbucket:foo/bar')
189 | t.equal(parsed.path(), 'foo/bar')
190 | t.equal(parsed.tarball(), 'https://bitbucket.org/foo/bar/get/HEAD.tar.gz')
191 | t.equal(parsed.file(), 'https://bitbucket.org/foo/bar/raw/HEAD/')
192 | t.equal(parsed.file('/lib/index.js'), 'https://bitbucket.org/foo/bar/raw/HEAD/lib/index.js')
193 | t.equal(parsed.bugs(), 'https://bitbucket.org/foo/bar/issues')
194 |
195 | t.equal(parsed.docs({ committish: 'fix/bug' }), 'https://bitbucket.org/foo/bar/src/fix%2Fbug#readme', 'allows overriding options')
196 |
197 | t.same(parsed.git(), null, 'git() returns null')
198 |
199 | const extra = HostedGit.fromUrl('https://user@bitbucket.org/foo/bar#fix/bug')
200 | t.equal(extra.hash(), '#fix/bug')
201 | t.equal(extra.https(), 'git+https://user@bitbucket.org/foo/bar.git#fix/bug')
202 | t.equal(extra.shortcut(), 'bitbucket:foo/bar#fix/bug')
203 | t.equal(extra.ssh(), 'git@bitbucket.org:foo/bar.git#fix/bug')
204 | t.equal(extra.sshurl(), 'git+ssh://git@bitbucket.org/foo/bar.git#fix/bug')
205 | t.equal(extra.browse(), 'https://bitbucket.org/foo/bar/src/fix%2Fbug')
206 | t.equal(extra.browse('/lib/index.js'), 'https://bitbucket.org/foo/bar/src/fix%2Fbug/lib/index.js')
207 | t.equal(extra.browse('/lib/index.js', 'L200'), 'https://bitbucket.org/foo/bar/src/fix%2Fbug/lib/index.js#l200')
208 | t.equal(extra.docs(), 'https://bitbucket.org/foo/bar/src/fix%2Fbug#readme')
209 | t.equal(extra.file(), 'https://bitbucket.org/foo/bar/raw/fix%2Fbug/')
210 | t.equal(extra.file('/lib/index.js'), 'https://bitbucket.org/foo/bar/raw/fix%2Fbug/lib/index.js')
211 |
212 | t.equal(extra.sshurl({ noCommittish: true }), 'git+ssh://git@bitbucket.org/foo/bar.git', 'noCommittish drops committish from urls')
213 | t.equal(extra.sshurl({ noGitPlus: true }), 'ssh://git@bitbucket.org/foo/bar.git#fix/bug', 'noGitPlus drops git+ prefix from urls')
214 |
215 | t.end()
216 | })
217 |
--------------------------------------------------------------------------------
/test/file.js:
--------------------------------------------------------------------------------
1 | const HostedGit = require('..')
2 | const t = require('tap')
3 |
4 | t.test('file:// URLs', t => {
5 | const fileRepo = {
6 | name: 'foo',
7 | repository: {
8 | url: 'file:///path/dot.git',
9 | },
10 | }
11 | t.equal(HostedGit.fromManifest(fileRepo), null)
12 |
13 | t.end()
14 | })
15 |
--------------------------------------------------------------------------------
/test/gist.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | 'use strict'
3 | const HostedGit = require('..')
4 | const t = require('tap')
5 |
6 | const invalid = [
7 | // raw urls that are wrong anyway but for some reason are in the wild
8 | 'https://gist.github.com/foo/feedbeef/raw/fix%2Fbug/',
9 | // missing both user and project
10 | 'https://gist.github.com/',
11 | ]
12 |
13 | const defaults = { type: 'gist', user: null, project: 'feedbeef' }
14 | const valid = {
15 | // shortcuts
16 | //
17 | // NOTE auth is accepted but ignored
18 | 'gist:feedbeef': { ...defaults, default: 'shortcut' },
19 | 'gist:feedbeef#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
20 | 'gist:user@feedbeef': { ...defaults, default: 'shortcut', auth: null },
21 | 'gist:user@feedbeef#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
22 | 'gist:user:password@feedbeef': { ...defaults, default: 'shortcut', auth: null },
23 | 'gist:user:password@feedbeef#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
24 | 'gist::password@feedbeef': { ...defaults, default: 'shortcut', auth: null },
25 | 'gist::password@feedbeef#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
26 |
27 | 'gist:feedbeef.git': { ...defaults, default: 'shortcut' },
28 | 'gist:feedbeef.git#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
29 | 'gist:user@feedbeef.git': { ...defaults, default: 'shortcut', auth: null },
30 | 'gist:user@feedbeef.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
31 | 'gist:user:password@feedbeef.git': { ...defaults, default: 'shortcut', auth: null },
32 | 'gist:user:password@feedbeef.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
33 | 'gist::password@feedbeef.git': { ...defaults, default: 'shortcut', auth: null },
34 | 'gist::password@feedbeef.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
35 |
36 | 'gist:/feedbeef': { ...defaults, default: 'shortcut' },
37 | 'gist:/feedbeef#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
38 | 'gist:user@/feedbeef': { ...defaults, default: 'shortcut', auth: null },
39 | 'gist:user@/feedbeef#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
40 | 'gist:user:password@/feedbeef': { ...defaults, default: 'shortcut', auth: null },
41 | 'gist:user:password@/feedbeef#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
42 | 'gist::password@/feedbeef': { ...defaults, default: 'shortcut', auth: null },
43 | 'gist::password@/feedbeef#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
44 |
45 | 'gist:/feedbeef.git': { ...defaults, default: 'shortcut' },
46 | 'gist:/feedbeef.git#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
47 | 'gist:user@/feedbeef.git': { ...defaults, default: 'shortcut', auth: null },
48 | 'gist:user@/feedbeef.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
49 | 'gist:user:password@/feedbeef.git': { ...defaults, default: 'shortcut', auth: null },
50 | 'gist:user:password@/feedbeef.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
51 | 'gist::password@/feedbeef.git': { ...defaults, default: 'shortcut', auth: null },
52 | 'gist::password@/feedbeef.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
53 |
54 | 'gist:foo/feedbeef': { ...defaults, default: 'shortcut', user: 'foo' },
55 | 'gist:foo/feedbeef#branch': { ...defaults, default: 'shortcut', user: 'foo', committish: 'branch' },
56 | 'gist:user@foo/feedbeef': { ...defaults, default: 'shortcut', user: 'foo', auth: null },
57 | 'gist:user@foo/feedbeef#branch': { ...defaults, default: 'shortcut', user: 'foo', auth: null, committish: 'branch' },
58 | 'gist:user:password@foo/feedbeef': { ...defaults, default: 'shortcut', user: 'foo', auth: null },
59 | 'gist:user:password@foo/feedbeef#branch': { ...defaults, default: 'shortcut', user: 'foo', auth: null, committish: 'branch' },
60 | 'gist::password@foo/feedbeef': { ...defaults, default: 'shortcut', user: 'foo', auth: null },
61 | 'gist::password@foo/feedbeef#branch': { ...defaults, default: 'shortcut', user: 'foo', auth: null, committish: 'branch' },
62 |
63 | 'gist:foo/feedbeef.git': { ...defaults, default: 'shortcut', user: 'foo' },
64 | 'gist:foo/feedbeef.git#branch': { ...defaults, default: 'shortcut', user: 'foo', committish: 'branch' },
65 | 'gist:user@foo/feedbeef.git': { ...defaults, default: 'shortcut', user: 'foo', auth: null },
66 | 'gist:user@foo/feedbeef.git#branch': { ...defaults, default: 'shortcut', user: 'foo', auth: null, committish: 'branch' },
67 | 'gist:user:password@foo/feedbeef.git': { ...defaults, default: 'shortcut', user: 'foo', auth: null },
68 | 'gist:user:password@foo/feedbeef.git#branch': { ...defaults, default: 'shortcut', user: 'foo', auth: null, committish: 'branch' },
69 | 'gist::password@foo/feedbeef.git': { ...defaults, default: 'shortcut', user: 'foo', auth: null },
70 | 'gist::password@foo/feedbeef.git#branch': { ...defaults, default: 'shortcut', user: 'foo', auth: null, committish: 'branch' },
71 |
72 | // git urls
73 | //
74 | // NOTE auth is accepted and respected
75 | 'git://gist.github.com/feedbeef': { ...defaults, default: 'git' },
76 | 'git://gist.github.com/feedbeef#branch': { ...defaults, default: 'git', committish: 'branch' },
77 | 'git://user@gist.github.com/feedbeef': { ...defaults, default: 'git', auth: 'user' },
78 | 'git://user@gist.github.com/feedbeef#branch': { ...defaults, default: 'git', auth: 'user', committish: 'branch' },
79 | 'git://user:password@gist.github.com/feedbeef': { ...defaults, default: 'git', auth: 'user:password' },
80 | 'git://user:password@gist.github.com/feedbeef#branch': { ...defaults, default: 'git', auth: 'user:password', committish: 'branch' },
81 | 'git://:password@gist.github.com/feedbeef': { ...defaults, default: 'git', auth: ':password' },
82 | 'git://:password@gist.github.com/feedbeef#branch': { ...defaults, default: 'git', auth: ':password', committish: 'branch' },
83 |
84 | 'git://gist.github.com/feedbeef.git': { ...defaults, default: 'git' },
85 | 'git://gist.github.com/feedbeef.git#branch': { ...defaults, default: 'git', committish: 'branch' },
86 | 'git://user@gist.github.com/feedbeef.git': { ...defaults, default: 'git', auth: 'user' },
87 | 'git://user@gist.github.com/feedbeef.git#branch': { ...defaults, default: 'git', auth: 'user', committish: 'branch' },
88 | 'git://user:password@gist.github.com/feedbeef.git': { ...defaults, default: 'git', auth: 'user:password' },
89 | 'git://user:password@gist.github.com/feedbeef.git#branch': { ...defaults, default: 'git', auth: 'user:password', committish: 'branch' },
90 | 'git://:password@gist.github.com/feedbeef.git': { ...defaults, default: 'git', auth: ':password' },
91 | 'git://:password@gist.github.com/feedbeef.git#branch': { ...defaults, default: 'git', auth: ':password', committish: 'branch' },
92 |
93 | 'git://gist.github.com/foo/feedbeef': { ...defaults, default: 'git', user: 'foo' },
94 | 'git://gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'git', user: 'foo', committish: 'branch' },
95 | 'git://user@gist.github.com/foo/feedbeef': { ...defaults, default: 'git', user: 'foo', auth: 'user' },
96 | 'git://user@gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'git', user: 'foo', auth: 'user', committish: 'branch' },
97 | 'git://user:password@gist.github.com/foo/feedbeef': { ...defaults, default: 'git', user: 'foo', auth: 'user:password' },
98 | 'git://user:password@gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'git', user: 'foo', auth: 'user:password', committish: 'branch' },
99 | 'git://:password@gist.github.com/foo/feedbeef': { ...defaults, default: 'git', user: 'foo', auth: ':password' },
100 | 'git://:password@gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'git', user: 'foo', auth: ':password', committish: 'branch' },
101 |
102 | 'git://gist.github.com/foo/feedbeef.git': { ...defaults, default: 'git', user: 'foo' },
103 | 'git://gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'git', user: 'foo', committish: 'branch' },
104 | 'git://user@gist.github.com/foo/feedbeef.git': { ...defaults, default: 'git', user: 'foo', auth: 'user' },
105 | 'git://user@gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'git', user: 'foo', auth: 'user', committish: 'branch' },
106 | 'git://user:password@gist.github.com/foo/feedbeef.git': { ...defaults, default: 'git', user: 'foo', auth: 'user:password' },
107 | 'git://user:password@gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'git', user: 'foo', auth: 'user:password', committish: 'branch' },
108 | 'git://:password@gist.github.com/foo/feedbeef.git': { ...defaults, default: 'git', user: 'foo', auth: ':password' },
109 | 'git://:password@gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'git', user: 'foo', auth: ':password', committish: 'branch' },
110 |
111 | // no-protocol git+ssh
112 | //
113 | // NOTE auth is accepted and ignored
114 | 'git@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
115 | 'git@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
116 | 'user@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
117 | 'user@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
118 | 'user:password@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
119 | 'user:password@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
120 | ':password@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
121 | ':password@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
122 |
123 | 'git@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
124 | 'git@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', committish: 'branch', auth: null },
125 | 'user@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
126 | 'user@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
127 | 'user:password@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
128 | 'user:password@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
129 | ':password@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
130 | ':password@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
131 |
132 | 'git@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
133 | 'git@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
134 | 'user@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
135 | 'user@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
136 | 'user:password@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
137 | 'user:password@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
138 | ':password@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
139 | ':password@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
140 |
141 | 'git@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
142 | 'git@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
143 | 'user@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
144 | 'user@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
145 | 'user:password@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
146 | 'user:password@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
147 | ':password@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
148 | ':password@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
149 |
150 | // git+ssh urls
151 | //
152 | // NOTE auth is accepted but ignored
153 | // NOTE see TODO at list of invalids, some inputs fail and shouldn't
154 | 'git+ssh://gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
155 | 'git+ssh://gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
156 | 'git+ssh://user@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
157 | 'git+ssh://user@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
158 | 'git+ssh://user:password@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
159 | 'git+ssh://user:password@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
160 | 'git+ssh://:password@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
161 | 'git+ssh://:password@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
162 |
163 | 'git+ssh://gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
164 | 'git+ssh://gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
165 | 'git+ssh://user@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
166 | 'git+ssh://user@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
167 | 'git+ssh://user:password@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
168 | 'git+ssh://user:password@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
169 | 'git+ssh://:password@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
170 | 'git+ssh://:password@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
171 |
172 | 'git+ssh://gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', user: 'foo' },
173 | 'git+ssh://gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', user: 'foo', committish: 'branch' },
174 | 'git+ssh://user@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
175 | 'git+ssh://user@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
176 | 'git+ssh://user:password@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
177 | 'git+ssh://user:password@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
178 | 'git+ssh://:password@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
179 | 'git+ssh://:password@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
180 |
181 | 'git+ssh://gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', user: 'foo' },
182 | 'git+ssh://gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', user: 'foo', committish: 'branch' },
183 | 'git+ssh://user@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
184 | 'git+ssh://user@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
185 | 'git+ssh://user:password@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
186 | 'git+ssh://user:password@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
187 | 'git+ssh://:password@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
188 | 'git+ssh://:password@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
189 |
190 | // ssh urls
191 | //
192 | // NOTE auth is accepted but ignored
193 | 'ssh://gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
194 | 'ssh://gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
195 | 'ssh://user@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
196 | 'ssh://user@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
197 | 'ssh://user:password@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
198 | 'ssh://user:password@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
199 | 'ssh://:password@gist.github.com:feedbeef': { ...defaults, default: 'sshurl', auth: null },
200 | 'ssh://:password@gist.github.com:feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
201 |
202 | 'ssh://gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
203 | 'ssh://gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
204 | 'ssh://user@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
205 | 'ssh://user@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
206 | 'ssh://user:password@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
207 | 'ssh://user:password@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
208 | 'ssh://:password@gist.github.com:feedbeef.git': { ...defaults, default: 'sshurl', auth: null },
209 | 'ssh://:password@gist.github.com:feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
210 |
211 | 'ssh://gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', user: 'foo' },
212 | 'ssh://gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', user: 'foo', committish: 'branch' },
213 | 'ssh://user@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
214 | 'ssh://user@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
215 | 'ssh://user:password@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
216 | 'ssh://user:password@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
217 | 'ssh://:password@gist.github.com:foo/feedbeef': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
218 | 'ssh://:password@gist.github.com:foo/feedbeef#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
219 |
220 | 'ssh://gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', user: 'foo' },
221 | 'ssh://gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', user: 'foo', committish: 'branch' },
222 | 'ssh://user@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
223 | 'ssh://user@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
224 | 'ssh://user:password@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
225 | 'ssh://user:password@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
226 | 'ssh://:password@gist.github.com:foo/feedbeef.git': { ...defaults, default: 'sshurl', auth: null, user: 'foo' },
227 | 'ssh://:password@gist.github.com:foo/feedbeef.git#branch': { ...defaults, default: 'sshurl', auth: null, user: 'foo', committish: 'branch' },
228 |
229 | // git+https urls
230 | //
231 | // NOTE auth is accepted and respected
232 | 'git+https://gist.github.com/feedbeef': { ...defaults, default: 'https' },
233 | 'git+https://gist.github.com/feedbeef#branch': { ...defaults, default: 'https', committish: 'branch' },
234 | 'git+https://user@gist.github.com/feedbeef': { ...defaults, default: 'https', auth: 'user' },
235 | 'git+https://user@gist.github.com/feedbeef#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
236 | 'git+https://user:password@gist.github.com/feedbeef': { ...defaults, default: 'https', auth: 'user:password' },
237 | 'git+https://user:password@gist.github.com/feedbeef#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
238 | 'git+https://:password@gist.github.com/feedbeef': { ...defaults, default: 'https', auth: ':password' },
239 | 'git+https://:password@gist.github.com/feedbeef#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
240 |
241 | 'git+https://gist.github.com/feedbeef.git': { ...defaults, default: 'https' },
242 | 'git+https://gist.github.com/feedbeef.git#branch': { ...defaults, default: 'https', committish: 'branch' },
243 | 'git+https://user@gist.github.com/feedbeef.git': { ...defaults, default: 'https', auth: 'user' },
244 | 'git+https://user@gist.github.com/feedbeef.git#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
245 | 'git+https://user:password@gist.github.com/feedbeef.git': { ...defaults, default: 'https', auth: 'user:password' },
246 | 'git+https://user:password@gist.github.com/feedbeef.git#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
247 | 'git+https://:password@gist.github.com/feedbeef.git': { ...defaults, default: 'https', auth: ':password' },
248 | 'git+https://:password@gist.github.com/feedbeef.git#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
249 |
250 | 'git+https://gist.github.com/foo/feedbeef': { ...defaults, default: 'https', user: 'foo' },
251 | 'git+https://gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'https', user: 'foo', committish: 'branch' },
252 | 'git+https://user@gist.github.com/foo/feedbeef': { ...defaults, default: 'https', auth: 'user', user: 'foo' },
253 | 'git+https://user@gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'https', auth: 'user', user: 'foo', committish: 'branch' },
254 | 'git+https://user:password@gist.github.com/foo/feedbeef': { ...defaults, default: 'https', auth: 'user:password', user: 'foo' },
255 | 'git+https://user:password@gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'https', auth: 'user:password', user: 'foo', committish: 'branch' },
256 | 'git+https://:password@gist.github.com/foo/feedbeef': { ...defaults, default: 'https', auth: ':password', user: 'foo' },
257 | 'git+https://:password@gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'https', auth: ':password', user: 'foo', committish: 'branch' },
258 |
259 | 'git+https://gist.github.com/foo/feedbeef.git': { ...defaults, default: 'https', user: 'foo' },
260 | 'git+https://gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'https', user: 'foo', committish: 'branch' },
261 | 'git+https://user@gist.github.com/foo/feedbeef.git': { ...defaults, default: 'https', auth: 'user', user: 'foo' },
262 | 'git+https://user@gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'https', auth: 'user', user: 'foo', committish: 'branch' },
263 | 'git+https://user:password@gist.github.com/foo/feedbeef.git': { ...defaults, default: 'https', auth: 'user:password', user: 'foo' },
264 | 'git+https://user:password@gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'https', auth: 'user:password', user: 'foo', committish: 'branch' },
265 | 'git+https://:password@gist.github.com/foo/feedbeef.git': { ...defaults, default: 'https', auth: ':password', user: 'foo' },
266 | 'git+https://:password@gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'https', auth: ':password', user: 'foo', committish: 'branch' },
267 |
268 | // https urls
269 | //
270 | // NOTE auth is accepted and respected
271 | 'https://gist.github.com/feedbeef': { ...defaults, default: 'https' },
272 | 'https://gist.github.com/feedbeef#branch': { ...defaults, default: 'https', committish: 'branch' },
273 | 'https://user@gist.github.com/feedbeef': { ...defaults, default: 'https', auth: 'user' },
274 | 'https://user@gist.github.com/feedbeef#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
275 | 'https://user:password@gist.github.com/feedbeef': { ...defaults, default: 'https', auth: 'user:password' },
276 | 'https://user:password@gist.github.com/feedbeef#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
277 | 'https://:password@gist.github.com/feedbeef': { ...defaults, default: 'https', auth: ':password' },
278 | 'https://:password@gist.github.com/feedbeef#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
279 |
280 | 'https://gist.github.com/feedbeef.git': { ...defaults, default: 'https' },
281 | 'https://gist.github.com/feedbeef.git#branch': { ...defaults, default: 'https', committish: 'branch' },
282 | 'https://user@gist.github.com/feedbeef.git': { ...defaults, default: 'https', auth: 'user' },
283 | 'https://user@gist.github.com/feedbeef.git#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
284 | 'https://user:password@gist.github.com/feedbeef.git': { ...defaults, default: 'https', auth: 'user:password' },
285 | 'https://user:password@gist.github.com/feedbeef.git#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
286 | 'https://:password@gist.github.com/feedbeef.git': { ...defaults, default: 'https', auth: ':password' },
287 | 'https://:password@gist.github.com/feedbeef.git#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
288 |
289 | 'https://gist.github.com/foo/feedbeef': { ...defaults, default: 'https', user: 'foo' },
290 | 'https://gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'https', user: 'foo', committish: 'branch' },
291 | 'https://user@gist.github.com/foo/feedbeef': { ...defaults, default: 'https', auth: 'user', user: 'foo' },
292 | 'https://user@gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'https', auth: 'user', user: 'foo', committish: 'branch' },
293 | 'https://user:password@gist.github.com/foo/feedbeef': { ...defaults, default: 'https', auth: 'user:password', user: 'foo' },
294 | 'https://user:password@gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'https', auth: 'user:password', user: 'foo', committish: 'branch' },
295 | 'https://:password@gist.github.com/foo/feedbeef': { ...defaults, default: 'https', auth: ':password', user: 'foo' },
296 | 'https://:password@gist.github.com/foo/feedbeef#branch': { ...defaults, default: 'https', auth: ':password', user: 'foo', committish: 'branch' },
297 |
298 | 'https://gist.github.com/foo/feedbeef.git': { ...defaults, default: 'https', user: 'foo' },
299 | 'https://gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'https', user: 'foo', committish: 'branch' },
300 | 'https://user@gist.github.com/foo/feedbeef.git': { ...defaults, default: 'https', auth: 'user', user: 'foo' },
301 | 'https://user@gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'https', auth: 'user', user: 'foo', committish: 'branch' },
302 | 'https://user:password@gist.github.com/foo/feedbeef.git': { ...defaults, default: 'https', auth: 'user:password', user: 'foo' },
303 | 'https://user:password@gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'https', auth: 'user:password', user: 'foo', committish: 'branch' },
304 | 'https://:password@gist.github.com/foo/feedbeef.git': { ...defaults, default: 'https', auth: ':password', user: 'foo' },
305 | 'https://:password@gist.github.com/foo/feedbeef.git#branch': { ...defaults, default: 'https', auth: ':password', user: 'foo', committish: 'branch' },
306 | }
307 |
308 | t.test('valid urls parse properly', t => {
309 | t.plan(Object.keys(valid).length)
310 | for (const [url, result] of Object.entries(valid)) {
311 | t.hasStrict(HostedGit.fromUrl(url), result, `${url} parses`)
312 | }
313 | })
314 |
315 | t.test('invalid urls return undefined', t => {
316 | t.plan(invalid.length)
317 | for (const url of invalid) {
318 | t.equal(HostedGit.fromUrl(url), undefined, `${url} returns undefined`)
319 | }
320 | })
321 |
322 | t.test('toString respects defaults', t => {
323 | const sshurl = HostedGit.fromUrl('git+ssh://gist.github.com/foo/feedbeef')
324 | t.equal(sshurl.default, 'sshurl', 'got the right default')
325 | t.equal(sshurl.toString(), sshurl.sshurl(), 'toString calls sshurl')
326 |
327 | const https = HostedGit.fromUrl('https://gist.github.com/foo/feedbeef')
328 | t.equal(https.default, 'https', 'got the right default')
329 | t.equal(https.toString(), https.https(), 'toString calls https')
330 |
331 | const shortcut = HostedGit.fromUrl('gist:feedbeef')
332 | t.equal(shortcut.default, 'shortcut', 'got the right default')
333 | t.equal(shortcut.toString(), shortcut.shortcut(), 'toString calls shortcut')
334 |
335 | t.end()
336 | })
337 |
338 | t.test('string methods populate correctly', t => {
339 | const parsed = HostedGit.fromUrl('git+ssh://gist.github.com/foo/feedbeef')
340 | t.equal(parsed.getDefaultRepresentation(), parsed.default)
341 | t.equal(parsed.hash(), '', 'hash() returns empty string when committish is unset')
342 | t.equal(parsed.ssh(), 'git@gist.github.com:feedbeef.git')
343 | t.equal(parsed.sshurl(), 'git+ssh://git@gist.github.com/feedbeef.git')
344 | t.equal(parsed.edit(), 'https://gist.github.com/foo/feedbeef/edit', 'gist link only redirects with a user')
345 | t.equal(parsed.edit('/lib/index.js'), 'https://gist.github.com/foo/feedbeef/edit', 'gist link only redirects with a user')
346 | t.equal(parsed.browse(), 'https://gist.github.com/feedbeef')
347 | t.equal(parsed.browse('/lib/index.js'), 'https://gist.github.com/feedbeef#file-libindex-js')
348 | t.equal(parsed.browse('/lib/index.js', 'L100'), 'https://gist.github.com/feedbeef#file-libindex-js')
349 | t.equal(parsed.browseFile('/lib/index.js'), 'https://gist.github.com/feedbeef#file-libindex-js')
350 | t.equal(parsed.browseFile('/lib/index.js', 'L100'), 'https://gist.github.com/feedbeef#file-libindex-js')
351 | t.equal(parsed.docs(), 'https://gist.github.com/feedbeef')
352 | t.equal(parsed.https(), 'git+https://gist.github.com/feedbeef.git')
353 | t.equal(parsed.shortcut(), 'gist:feedbeef')
354 | t.equal(parsed.path(), 'feedbeef')
355 | t.equal(parsed.tarball(), 'https://codeload.github.com/gist/feedbeef/tar.gz/HEAD')
356 | t.equal(parsed.file(), 'https://gist.githubusercontent.com/foo/feedbeef/raw/')
357 | t.equal(parsed.file('/lib/index.js'), 'https://gist.githubusercontent.com/foo/feedbeef/raw/lib/index.js')
358 | t.equal(parsed.git(), 'git://gist.github.com/feedbeef.git')
359 | t.equal(parsed.bugs(), 'https://gist.github.com/feedbeef')
360 |
361 | t.equal(parsed.ssh({ committish: 'fix/bug' }), 'git@gist.github.com:feedbeef.git#fix/bug', 'allows overriding options')
362 |
363 | const extra = HostedGit.fromUrl('https://user@gist.github.com/foo/feedbeef#fix/bug')
364 | t.equal(extra.hash(), '#fix/bug')
365 | t.equal(extra.https(), 'git+https://gist.github.com/feedbeef.git#fix/bug')
366 | t.equal(extra.shortcut(), 'gist:feedbeef#fix/bug')
367 | t.equal(extra.ssh(), 'git@gist.github.com:feedbeef.git#fix/bug')
368 | t.equal(extra.sshurl(), 'git+ssh://git@gist.github.com/feedbeef.git#fix/bug')
369 | t.equal(extra.browse(), 'https://gist.github.com/feedbeef/fix%2Fbug')
370 | t.equal(extra.browse('/lib/index.js'), 'https://gist.github.com/feedbeef/fix%2Fbug#file-libindex-js')
371 | t.equal(extra.browse('/lib/index.js', 'L200'), 'https://gist.github.com/feedbeef/fix%2Fbug#file-libindex-js')
372 | t.equal(extra.docs(), 'https://gist.github.com/feedbeef/fix%2Fbug')
373 | t.equal(extra.file(), 'https://gist.githubusercontent.com/foo/feedbeef/raw/fix%2Fbug/')
374 | t.equal(extra.file('/lib/index.js'), 'https://gist.githubusercontent.com/foo/feedbeef/raw/fix%2Fbug/lib/index.js')
375 | t.equal(extra.tarball(), 'https://codeload.github.com/gist/feedbeef/tar.gz/fix%2Fbug')
376 |
377 | t.equal(extra.sshurl({ noCommittish: true }), 'git+ssh://git@gist.github.com/feedbeef.git', 'noCommittish drops committish from urls')
378 | t.equal(extra.sshurl({ noGitPlus: true }), 'ssh://git@gist.github.com/feedbeef.git#fix/bug', 'noGitPlus drops git+ prefix from urls')
379 |
380 | t.end()
381 | })
382 |
--------------------------------------------------------------------------------
/test/github.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | const HostedGit = require('..')
3 | const t = require('tap')
4 |
5 | const invalid = [
6 | // foo/bar shorthand but specifying auth
7 | 'user@foo/bar',
8 | 'user:password@foo/bar',
9 | ':password@foo/bar',
10 | // foo/bar shorthand but with a space in it
11 | 'foo/ bar',
12 | // string that ends with a slash, probably a directory
13 | 'foo/bar/',
14 | // git@github.com style, but omitting the username
15 | 'github.com:foo/bar',
16 | 'github.com/foo/bar',
17 | // invalid URI encoding
18 | 'github:foo%0N/bar',
19 | // missing path
20 | 'git+ssh://git@github.com:',
21 | // a deep url to something we don't know
22 | 'https://github.com/foo/bar/issues',
23 | ]
24 |
25 | const defaults = { type: 'github', user: 'foo', project: 'bar' }
26 | // This is a valid git branch name that contains other occurences of the characters we check
27 | // for to determine the committish in order to test that we parse those correctly
28 | const committishDefaults = { committish: 'lk/br@nch.t#st:^1.0.0-pre.4' }
29 | const valid = {
30 | // extreme shorthand
31 | //
32 | // NOTE these do not accept auth at all
33 | 'foo/bar': { ...defaults, default: 'shortcut' },
34 | [`foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', ...committishDefaults },
35 |
36 | 'foo/bar.git': { ...defaults, default: 'shortcut' },
37 | [`foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', ...committishDefaults },
38 |
39 | // shortcuts
40 | //
41 | // NOTE auth is accepted but ignored
42 | 'github:foo/bar': { ...defaults, default: 'shortcut' },
43 | [`github:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', ...committishDefaults },
44 | 'github:user@foo/bar': { ...defaults, default: 'shortcut', auth: null },
45 | [`github:user@foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', auth: null, ...committishDefaults },
46 | 'github:user:password@foo/bar': { ...defaults, default: 'shortcut', auth: null },
47 | [`github:user:password@foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', auth: null, ...committishDefaults },
48 | 'github::password@foo/bar': { ...defaults, default: 'shortcut', auth: null },
49 | [`github::password@foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', auth: null, ...committishDefaults },
50 |
51 | 'github:foo/bar.git': { ...defaults, default: 'shortcut' },
52 | [`github:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', ...committishDefaults },
53 | 'github:user@foo/bar.git': { ...defaults, default: 'shortcut', auth: null },
54 | [`github:user@foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', auth: null, ...committishDefaults },
55 | 'github:user:password@foo/bar.git': { ...defaults, default: 'shortcut', auth: null },
56 | [`github:user:password@foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', auth: null, ...committishDefaults },
57 | 'github::password@foo/bar.git': { ...defaults, default: 'shortcut', auth: null },
58 | [`github::password@foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'shortcut', auth: null, ...committishDefaults },
59 |
60 | // git urls
61 | //
62 | // NOTE auth is accepted and respected
63 | 'git://github.com/foo/bar': { ...defaults, default: 'git' },
64 | [`git://github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'git', ...committishDefaults },
65 | 'git://user@github.com/foo/bar': { ...defaults, default: 'git', auth: 'user' },
66 | [`git://user@github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'git', auth: 'user', ...committishDefaults },
67 | 'git://user:password@github.com/foo/bar': { ...defaults, default: 'git', auth: 'user:password' },
68 | [`git://user:password@github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'git', auth: 'user:password', ...committishDefaults },
69 | 'git://:password@github.com/foo/bar': { ...defaults, default: 'git', auth: ':password' },
70 | [`git://:password@github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'git', auth: ':password', ...committishDefaults },
71 |
72 | 'git://github.com/foo/bar.git': { ...defaults, default: 'git' },
73 | [`git://github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'git', ...committishDefaults },
74 | 'git://git@github.com/foo/bar.git': { ...defaults, default: 'git', auth: 'git' },
75 | [`git://git@github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'git', auth: 'git', ...committishDefaults },
76 | 'git://user:password@github.com/foo/bar.git': { ...defaults, default: 'git', auth: 'user:password' },
77 | [`git://user:password@github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'git', auth: 'user:password', ...committishDefaults },
78 | 'git://:password@github.com/foo/bar.git': { ...defaults, default: 'git', auth: ':password' },
79 | [`git://:password@github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'git', auth: ':password', ...committishDefaults },
80 |
81 | // no-protocol git+ssh
82 | //
83 | // NOTE auth is _required_ (see invalid list) but ignored
84 | 'user@github.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
85 | [`user@github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
86 | 'user:password@github.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
87 | [`user:password@github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
88 | ':password@github.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
89 | [`:password@github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
90 |
91 | 'user@github.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
92 | [`user@github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
93 | 'user:password@github.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
94 | [`user:password@github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
95 | ':password@github.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
96 | [`:password@github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
97 |
98 | // git+ssh urls
99 | //
100 | // NOTE auth is accepted but ignored
101 | 'git+ssh://github.com:foo/bar': { ...defaults, default: 'sshurl' },
102 | [`git+ssh://github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', ...committishDefaults },
103 | 'git+ssh://user@github.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
104 | [`git+ssh://user@github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
105 | 'git+ssh://user:password@github.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
106 | [`git+ssh://user:password@github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
107 | 'git+ssh://:password@github.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
108 | [`git+ssh://:password@github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
109 |
110 | 'git+ssh://github.com:foo/bar.git': { ...defaults, default: 'sshurl' },
111 | [`git+ssh://github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', ...committishDefaults },
112 | 'git+ssh://user@github.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
113 | [`git+ssh://user@github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
114 | 'git+ssh://user:password@github.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
115 | [`git+ssh://user:password@github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
116 | 'git+ssh://:password@github.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
117 | [`git+ssh://:password@github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
118 |
119 | // ssh urls
120 | //
121 | // NOTE auth is accepted but ignored
122 | 'ssh://github.com:foo/bar': { ...defaults, default: 'sshurl' },
123 | [`ssh://github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', ...committishDefaults },
124 | 'ssh://user@github.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
125 | [`ssh://user@github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
126 | 'ssh://user:password@github.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
127 | [`ssh://user:password@github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
128 | 'ssh://:password@github.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
129 | [`ssh://:password@github.com:foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
130 |
131 | 'ssh://github.com:foo/bar.git': { ...defaults, default: 'sshurl' },
132 | [`ssh://github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', ...committishDefaults },
133 | 'ssh://user@github.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
134 | [`ssh://user@github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
135 | 'ssh://user:password@github.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
136 | [`ssh://user:password@github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
137 | 'ssh://:password@github.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
138 | [`ssh://:password@github.com:foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'sshurl', auth: null, ...committishDefaults },
139 |
140 | // git+https urls
141 | //
142 | // NOTE auth is accepted and respected
143 | 'git+https://github.com/foo/bar': { ...defaults, default: 'https' },
144 | [`git+https://github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'https', ...committishDefaults },
145 | 'git+https://user@github.com/foo/bar': { ...defaults, default: 'https', auth: 'user' },
146 | [`git+https://user@github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: 'user', ...committishDefaults },
147 | 'git+https://user:password@github.com/foo/bar': { ...defaults, default: 'https', auth: 'user:password' },
148 | [`git+https://user:password@github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: 'user:password', ...committishDefaults },
149 | 'git+https://:password@github.com/foo/bar': { ...defaults, default: 'https', auth: ':password' },
150 | [`git+https://:password@github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: ':password', ...committishDefaults },
151 |
152 | 'git+https://github.com/foo/bar.git': { ...defaults, default: 'https' },
153 | [`git+https://github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'https', ...committishDefaults },
154 | 'git+https://user@github.com/foo/bar.git': { ...defaults, default: 'https', auth: 'user' },
155 | [`git+https://user@github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: 'user', ...committishDefaults },
156 | 'git+https://user:password@github.com/foo/bar.git': { ...defaults, default: 'https', auth: 'user:password' },
157 | [`git+https://user:password@github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: 'user:password', ...committishDefaults },
158 | 'git+https://:password@github.com/foo/bar.git': { ...defaults, default: 'https', auth: ':password' },
159 | [`git+https://:password@github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: ':password', ...committishDefaults },
160 |
161 | // https urls
162 | //
163 | // NOTE auth is accepted and respected
164 | 'https://github.com/foo/bar': { ...defaults, default: 'https' },
165 | [`https://github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'https', ...committishDefaults },
166 | 'https://user@github.com/foo/bar': { ...defaults, default: 'https', auth: 'user' },
167 | [`https://user@github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: 'user', ...committishDefaults },
168 | 'https://user:password@github.com/foo/bar': { ...defaults, default: 'https', auth: 'user:password' },
169 | [`https://user:password@github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: 'user:password', ...committishDefaults },
170 | 'https://:password@github.com/foo/bar': { ...defaults, default: 'https', auth: ':password' },
171 | [`https://:password@github.com/foo/bar#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: ':password', ...committishDefaults },
172 |
173 | 'https://github.com/foo/bar.git': { ...defaults, default: 'https' },
174 | [`https://github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'https', ...committishDefaults },
175 | 'https://user@github.com/foo/bar.git': { ...defaults, default: 'https', auth: 'user' },
176 | [`https://user@github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: 'user', ...committishDefaults },
177 | 'https://user:password@github.com/foo/bar.git': { ...defaults, default: 'https', auth: 'user:password' },
178 | [`https://user:password@github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: 'user:password', ...committishDefaults },
179 | 'https://:password@github.com/foo/bar.git': { ...defaults, default: 'https', auth: ':password' },
180 | [`https://:password@github.com/foo/bar.git#${committishDefaults.committish}`]: { ...defaults, default: 'https', auth: ':password', ...committishDefaults },
181 |
182 | // inputs that are not quite proper but we accept anyway
183 | 'https://www.github.com/foo/bar': { ...defaults, default: 'https' },
184 | 'foo/bar#branch with space': { ...defaults, default: 'shortcut', committish: 'branch with space' },
185 | 'foo/bar#branch:with:colons': { ...defaults, default: 'shortcut', committish: 'branch:with:colons' },
186 | 'https://github.com/foo/bar/tree/branch': { ...defaults, default: 'https', committish: 'branch' },
187 | 'user..blerg--/..foo-js# . . . . . some . tags / / /': { ...defaults, default: 'shortcut', user: 'user..blerg--', project: '..foo-js', committish: ' . . . . . some . tags / / /' },
188 | }
189 |
190 | t.test('valid urls parse properly', t => {
191 | t.plan(Object.keys(valid).length)
192 | for (const [url, result] of Object.entries(valid)) {
193 | t.hasStrict(HostedGit.fromUrl(url), result, `${url} parses`)
194 | }
195 | })
196 |
197 | t.test('invalid urls return undefined', t => {
198 | t.plan(invalid.length)
199 | for (const url of invalid) {
200 | t.equal(HostedGit.fromUrl(url), undefined, `${url} returns undefined`)
201 | }
202 | })
203 |
204 | t.test('toString respects defaults', t => {
205 | const sshurl = HostedGit.fromUrl('git+ssh://github.com/foo/bar')
206 | t.equal(sshurl.default, 'sshurl', 'got the right default')
207 | t.equal(sshurl.toString(), sshurl.sshurl(), 'toString calls sshurl')
208 |
209 | const https = HostedGit.fromUrl('https://github.com/foo/bar')
210 | t.equal(https.default, 'https', 'got the right default')
211 | t.equal(https.toString(), https.https(), 'toString calls https')
212 |
213 | const http = HostedGit.fromUrl('http://github.com/foo/bar')
214 | t.equal(http.default, 'http', 'got the right default')
215 | t.equal(http.toString(), http.sshurl(), 'automatically upgrades toString to sshurl')
216 |
217 | const git = HostedGit.fromUrl('git://github.com/foo/bar')
218 | t.equal(git.default, 'git', 'got the right default')
219 | t.equal(git.toString(), git.git(), 'toString calls git')
220 |
221 | const shortcut = HostedGit.fromUrl('github:foo/bar')
222 | t.equal(shortcut.default, 'shortcut', 'got the right default')
223 | t.equal(shortcut.toString(), shortcut.shortcut(), 'got the right default')
224 |
225 | t.end()
226 | })
227 |
228 | t.test('string methods populate correctly', t => {
229 | const parsed = HostedGit.fromUrl('git+ssh://github.com/foo/bar')
230 | t.equal(parsed.getDefaultRepresentation(), parsed.default)
231 | t.equal(parsed.hash(), '', 'hash() returns empty string when committish is unset')
232 | t.equal(parsed.ssh(), 'git@github.com:foo/bar.git')
233 | t.equal(parsed.sshurl(), 'git+ssh://git@github.com/foo/bar.git')
234 | t.equal(parsed.edit(), 'https://github.com/foo/bar')
235 | t.equal(parsed.edit('/lib/index.js'), 'https://github.com/foo/bar/edit/HEAD/lib/index.js')
236 | t.equal(parsed.edit('/lib/index.js', { committish: 'docs' }), 'https://github.com/foo/bar/edit/docs/lib/index.js')
237 | t.equal(parsed.browse(), 'https://github.com/foo/bar')
238 | t.equal(parsed.browse('/lib/index.js'), 'https://github.com/foo/bar/tree/HEAD/lib/index.js')
239 | t.equal(parsed.browse('/lib/index.js', 'L100'), 'https://github.com/foo/bar/tree/HEAD/lib/index.js#l100')
240 | t.equal(parsed.browseFile('/lib/index.js'), 'https://github.com/foo/bar/blob/HEAD/lib/index.js')
241 | t.equal(parsed.browseFile('/lib/index.js', 'L100'), 'https://github.com/foo/bar/blob/HEAD/lib/index.js#l100')
242 | t.equal(parsed.docs(), 'https://github.com/foo/bar#readme')
243 | t.equal(parsed.https(), 'git+https://github.com/foo/bar.git')
244 | t.equal(parsed.shortcut(), 'github:foo/bar')
245 | t.equal(parsed.path(), 'foo/bar')
246 | t.equal(parsed.tarball(), 'https://codeload.github.com/foo/bar/tar.gz/HEAD')
247 | t.equal(parsed.file(), 'https://raw.githubusercontent.com/foo/bar/HEAD/')
248 | t.equal(parsed.file('/lib/index.js'), 'https://raw.githubusercontent.com/foo/bar/HEAD/lib/index.js')
249 | t.equal(parsed.git(), 'git://github.com/foo/bar.git')
250 | t.equal(parsed.bugs(), 'https://github.com/foo/bar/issues')
251 |
252 | t.equal(parsed.docs({ committish: 'fix/bug' }), 'https://github.com/foo/bar/tree/fix%2Fbug#readme', 'allows overriding options')
253 |
254 | const extra = HostedGit.fromUrl('https://user@github.com/foo/bar#fix/bug')
255 | t.equal(extra.hash(), '#fix/bug')
256 | t.equal(extra.https(), 'git+https://user@github.com/foo/bar.git#fix/bug')
257 | t.equal(extra.shortcut(), 'github:foo/bar#fix/bug')
258 | t.equal(extra.ssh(), 'git@github.com:foo/bar.git#fix/bug')
259 | t.equal(extra.sshurl(), 'git+ssh://git@github.com/foo/bar.git#fix/bug')
260 | t.equal(extra.browse(), 'https://github.com/foo/bar/tree/fix%2Fbug')
261 | t.equal(extra.browse('/lib/index.js'), 'https://github.com/foo/bar/tree/fix%2Fbug/lib/index.js')
262 | t.equal(extra.browse('/lib/index.js', 'L200'), 'https://github.com/foo/bar/tree/fix%2Fbug/lib/index.js#l200')
263 | t.equal(extra.docs(), 'https://github.com/foo/bar/tree/fix%2Fbug#readme')
264 | t.equal(extra.file(), 'https://user@raw.githubusercontent.com/foo/bar/fix%2Fbug/')
265 | t.equal(extra.file('/lib/index.js'), 'https://user@raw.githubusercontent.com/foo/bar/fix%2Fbug/lib/index.js')
266 | t.equal(extra.tarball(), 'https://codeload.github.com/foo/bar/tar.gz/fix%2Fbug')
267 |
268 | t.equal(extra.sshurl({ noCommittish: true }), 'git+ssh://git@github.com/foo/bar.git', 'noCommittish drops committish from urls')
269 | t.equal(extra.sshurl({ noGitPlus: true }), 'ssh://git@github.com/foo/bar.git#fix/bug', 'noGitPlus drops git+ prefix from urls')
270 |
271 | t.end()
272 | })
273 |
274 | t.test('from manifest', t => {
275 | t.equal(HostedGit.fromManifest(), undefined, 'no manifest returns undefined')
276 | t.equal(HostedGit.fromManifest(), undefined, 'no manifest returns undefined')
277 | t.equal(HostedGit.fromManifest(false), undefined, 'false manifest returns undefined')
278 | t.equal(HostedGit.fromManifest(() => {}), undefined, 'function manifest returns undefined')
279 |
280 | const unknownHostRepo = {
281 | name: 'foo',
282 | repository: {
283 | url: 'https://nope.com',
284 | },
285 | }
286 | t.same(HostedGit.fromManifest(unknownHostRepo), 'https://nope.com/')
287 |
288 | const insecureUnknownHostRepo = {
289 | name: 'foo',
290 | repository: {
291 | url: 'http://nope.com',
292 | },
293 | }
294 | t.same(HostedGit.fromManifest(insecureUnknownHostRepo), 'https://nope.com/')
295 |
296 | const insecureGitUnknownHostRepo = {
297 | name: 'foo',
298 | repository: {
299 | url: 'git+http://nope.com',
300 | },
301 | }
302 | t.same(HostedGit.fromManifest(insecureGitUnknownHostRepo), 'http://nope.com')
303 |
304 | const badRepo = {
305 | name: 'foo',
306 | repository: {
307 | url: '#',
308 | },
309 | }
310 | t.equal(HostedGit.fromManifest(badRepo), null)
311 |
312 | const manifest = {
313 | name: 'foo',
314 | repository: {
315 | type: 'git',
316 | url: 'git+ssh://github.com/foo/bar.git',
317 | },
318 | }
319 |
320 | const parsed = HostedGit.fromManifest(manifest)
321 | t.same(parsed.browse(), 'https://github.com/foo/bar')
322 |
323 | const monorepo = {
324 | name: 'clowncar',
325 | repository: {
326 | type: 'git',
327 | url: 'git+ssh://github.com/foo/bar.git',
328 | directory: 'packages/foo',
329 | },
330 | }
331 |
332 | const honk = HostedGit.fromManifest(monorepo)
333 | t.same(honk.browse(monorepo.repository.directory), 'https://github.com/foo/bar/tree/HEAD/packages/foo')
334 |
335 | const stringRepo = {
336 | name: 'foo',
337 | repository: 'git+ssh://github.com/foo/bar.git',
338 | }
339 | const stringRepoParsed = HostedGit.fromManifest(stringRepo)
340 | t.same(stringRepoParsed.browse(), 'https://github.com/foo/bar')
341 |
342 | const nonStringRepo = {
343 | name: 'foo',
344 | repository: 42,
345 | }
346 | t.throws(() => HostedGit.fromManifest(nonStringRepo))
347 |
348 | t.end()
349 | })
350 |
--------------------------------------------------------------------------------
/test/gitlab.js:
--------------------------------------------------------------------------------
1 | /* eslint-disable max-len */
2 | 'use strict'
3 | const HostedGit = require('..')
4 | const t = require('tap')
5 |
6 | const invalid = [
7 | // gitlab urls can contain a /-/ segment, make sure we ignore those
8 | 'https://gitlab.com/foo/-/something',
9 | // missing project
10 | 'https://gitlab.com/foo',
11 | // tarball, this should not parse so that it can be used for pacote's remote fetcher
12 | 'https://gitlab.com/foo/bar/repository/archive.tar.gz',
13 | 'https://gitlab.com/foo/bar/repository/archive.tar.gz?ref=49b393e2ded775f2df36ef2ffcb61b0359c194c9',
14 | ]
15 |
16 | const defaults = { type: 'gitlab', user: 'foo', project: 'bar' }
17 | const subgroup = { type: 'gitlab', user: 'foo/bar', project: 'baz' }
18 | const valid = {
19 | // shortcuts
20 | //
21 | // NOTE auth is accepted but ignored
22 | // NOTE subgroups are respected, but the subgroup is treated as the project and the real project is lost
23 | 'gitlab:foo/bar': { ...defaults, default: 'shortcut' },
24 | 'gitlab:foo/bar#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
25 | 'gitlab:user@foo/bar': { ...defaults, default: 'shortcut', auth: null },
26 | 'gitlab:user@foo/bar#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
27 | 'gitlab:user:password@foo/bar': { ...defaults, default: 'shortcut', auth: null },
28 | 'gitlab:user:password@foo/bar#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
29 | 'gitlab::password@foo/bar': { ...defaults, default: 'shortcut', auth: null },
30 | 'gitlab::password@foo/bar#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
31 |
32 | 'gitlab:foo/bar.git': { ...defaults, default: 'shortcut' },
33 | 'gitlab:foo/bar.git#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
34 | 'gitlab:user@foo/bar.git': { ...defaults, default: 'shortcut', auth: null },
35 | 'gitlab:user@foo/bar.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
36 | 'gitlab:user:password@foo/bar.git': { ...defaults, default: 'shortcut', auth: null },
37 | 'gitlab:user:password@foo/bar.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
38 | 'gitlab::password@foo/bar.git': { ...defaults, default: 'shortcut', auth: null },
39 | 'gitlab::password@foo/bar.git#branch': { ...defaults, default: 'shortcut', auth: null, committish: 'branch' },
40 |
41 | 'gitlab:foo/bar/baz': { ...subgroup, default: 'shortcut' },
42 | 'gitlab:foo/bar/baz#branch': { ...subgroup, default: 'shortcut', committish: 'branch' },
43 | 'gitlab:user@foo/bar/baz': { ...subgroup, default: 'shortcut', auth: null },
44 | 'gitlab:user@foo/bar/baz#branch': { ...subgroup, default: 'shortcut', auth: null, committish: 'branch' },
45 | 'gitlab:user:password@foo/bar/baz': { ...subgroup, default: 'shortcut', auth: null },
46 | 'gitlab:user:password@foo/bar/baz#branch': { ...subgroup, default: 'shortcut', auth: null, committish: 'branch' },
47 | 'gitlab::password@foo/bar/baz': { ...subgroup, default: 'shortcut', auth: null },
48 | 'gitlab::password@foo/bar/baz#branch': { ...subgroup, default: 'shortcut', auth: null, committish: 'branch' },
49 |
50 | 'gitlab:foo/bar/baz.git': { ...subgroup, default: 'shortcut' },
51 | 'gitlab:foo/bar/baz.git#branch': { ...subgroup, default: 'shortcut', committish: 'branch' },
52 | 'gitlab:user@foo/bar/baz.git': { ...subgroup, default: 'shortcut', auth: null },
53 | 'gitlab:user@foo/bar/baz.git#branch': { ...subgroup, default: 'shortcut', auth: null, committish: 'branch' },
54 | 'gitlab:user:password@foo/bar/baz.git': { ...subgroup, default: 'shortcut', auth: null },
55 | 'gitlab:user:password@foo/bar/baz.git#branch': { ...subgroup, default: 'shortcut', auth: null, committish: 'branch' },
56 | 'gitlab::password@foo/bar/baz.git': { ...subgroup, default: 'shortcut', auth: null },
57 | 'gitlab::password@foo/bar/baz.git#branch': { ...subgroup, default: 'shortcut', auth: null, committish: 'branch' },
58 |
59 | // no-protocol git+ssh
60 | //
61 | // NOTE auth is _required_ (see invalid list) but ignored
62 | 'user@gitlab.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
63 | 'user@gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
64 | 'user:password@gitlab.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
65 | 'user:password@gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
66 | ':password@gitlab.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
67 | ':password@gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
68 |
69 | 'user@gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
70 | 'user@gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
71 | 'user:password@gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
72 | 'user:password@gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
73 | ':password@gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
74 | ':password@gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
75 |
76 | 'user@gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl', auth: null },
77 | 'user@gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
78 | 'user:password@gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl', auth: null },
79 | 'user:password@gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
80 | ':password@gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl', auth: null },
81 | ':password@gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
82 |
83 | 'user@gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl', auth: null },
84 | 'user@gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
85 | 'user:password@gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl', auth: null },
86 | 'user:password@gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
87 | ':password@gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl', auth: null },
88 | ':password@gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
89 |
90 | // git+ssh urls
91 | //
92 | // NOTE auth is accepted but ignored
93 | // NOTE subprojects are accepted, but the subproject is treated as the project and the real project is lost
94 | 'git+ssh://gitlab.com:foo/bar': { ...defaults, default: 'sshurl' },
95 | 'git+ssh://gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', committish: 'branch' },
96 | 'git+ssh://user@gitlab.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
97 | 'git+ssh://user@gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
98 | 'git+ssh://user:password@gitlab.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
99 | 'git+ssh://user:password@gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
100 | 'git+ssh://:password@gitlab.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
101 | 'git+ssh://:password@gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
102 |
103 | 'git+ssh://gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl' },
104 | 'git+ssh://gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', committish: 'branch' },
105 | 'git+ssh://user@gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
106 | 'git+ssh://user@gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
107 | 'git+ssh://user:password@gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
108 | 'git+ssh://user:password@gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
109 | 'git+ssh://:password@gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
110 | 'git+ssh://:password@gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
111 |
112 | 'git+ssh://gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl' },
113 | 'git+ssh://gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', committish: 'branch' },
114 | 'git+ssh://user@gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl', auth: null },
115 | 'git+ssh://user@gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
116 | 'git+ssh://user:password@gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl', auth: null },
117 | 'git+ssh://user:password@gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
118 | 'git+ssh://:password@gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl', auth: null },
119 | 'git+ssh://:password@gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
120 |
121 | 'git+ssh://gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl' },
122 | 'git+ssh://gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', committish: 'branch' },
123 | 'git+ssh://user@gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl', auth: null },
124 | 'git+ssh://user@gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
125 | 'git+ssh://user:password@gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl', auth: null },
126 | 'git+ssh://user:password@gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
127 | 'git+ssh://:password@gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl', auth: null },
128 | 'git+ssh://:password@gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
129 |
130 | // ssh urls
131 | //
132 | // NOTE auth is accepted but ignored
133 | // NOTE subprojects are accepted, but the subproject is treated as the project and the real project is lost
134 | 'ssh://gitlab.com:foo/bar': { ...defaults, default: 'sshurl' },
135 | 'ssh://gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', committish: 'branch' },
136 | 'ssh://user@gitlab.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
137 | 'ssh://user@gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
138 | 'ssh://user:password@gitlab.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
139 | 'ssh://user:password@gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
140 | 'ssh://:password@gitlab.com:foo/bar': { ...defaults, default: 'sshurl', auth: null },
141 | 'ssh://:password@gitlab.com:foo/bar#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
142 |
143 | 'ssh://gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl' },
144 | 'ssh://gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', committish: 'branch' },
145 | 'ssh://user@gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
146 | 'ssh://user@gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
147 | 'ssh://user:password@gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
148 | 'ssh://user:password@gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
149 | 'ssh://:password@gitlab.com:foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
150 | 'ssh://:password@gitlab.com:foo/bar.git#branch': { ...defaults, default: 'sshurl', auth: null, committish: 'branch' },
151 |
152 | 'ssh://gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl' },
153 | 'ssh://gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', committish: 'branch' },
154 | 'ssh://user@gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl', auth: null },
155 | 'ssh://user@gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
156 | 'ssh://user:password@gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl', auth: null },
157 | 'ssh://user:password@gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
158 | 'ssh://:password@gitlab.com:foo/bar/baz': { ...subgroup, default: 'sshurl', auth: null },
159 | 'ssh://:password@gitlab.com:foo/bar/baz#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
160 |
161 | 'ssh://gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl' },
162 | 'ssh://gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', committish: 'branch' },
163 | 'ssh://user@gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl', auth: null },
164 | 'ssh://user@gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
165 | 'ssh://user:password@gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl', auth: null },
166 | 'ssh://user:password@gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
167 | 'ssh://:password@gitlab.com:foo/bar/baz.git': { ...subgroup, default: 'sshurl', auth: null },
168 | 'ssh://:password@gitlab.com:foo/bar/baz.git#branch': { ...subgroup, default: 'sshurl', auth: null, committish: 'branch' },
169 |
170 | // git+https urls
171 | //
172 | // NOTE auth is accepted and respected
173 | // NOTE subprojects are accepted, but the subproject is treated as the project and the real project is lost
174 | 'git+https://gitlab.com/foo/bar': { ...defaults, default: 'https' },
175 | 'git+https://gitlab.com/foo/bar#branch': { ...defaults, default: 'https', committish: 'branch' },
176 | 'git+https://user@gitlab.com/foo/bar': { ...defaults, default: 'https', auth: 'user' },
177 | 'git+https://user@gitlab.com/foo/bar#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
178 | 'git+https://user:password@gitlab.com/foo/bar': { ...defaults, default: 'https', auth: 'user:password' },
179 | 'git+https://user:password@gitlab.com/foo/bar#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
180 | 'git+https://:password@gitlab.com/foo/bar': { ...defaults, default: 'https', auth: ':password' },
181 | 'git+https://:password@gitlab.com/foo/bar#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
182 |
183 | 'git+https://gitlab.com/foo/bar.git': { ...defaults, default: 'https' },
184 | 'git+https://gitlab.com/foo/bar.git#branch': { ...defaults, default: 'https', committish: 'branch' },
185 | 'git+https://user@gitlab.com/foo/bar.git': { ...defaults, default: 'https', auth: 'user' },
186 | 'git+https://user@gitlab.com/foo/bar.git#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
187 | 'git+https://user:password@gitlab.com/foo/bar.git': { ...defaults, default: 'https', auth: 'user:password' },
188 | 'git+https://user:password@gitlab.com/foo/bar.git#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
189 | 'git+https://:password@gitlab.com/foo/bar.git': { ...defaults, default: 'https', auth: ':password' },
190 | 'git+https://:password@gitlab.com/foo/bar.git#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
191 |
192 | 'git+https://gitlab.com/foo/bar/baz': { ...subgroup, default: 'https' },
193 | 'git+https://gitlab.com/foo/bar/baz#branch': { ...subgroup, default: 'https', committish: 'branch' },
194 | 'git+https://user@gitlab.com/foo/bar/baz': { ...subgroup, default: 'https', auth: 'user' },
195 | 'git+https://user@gitlab.com/foo/bar/baz#branch': { ...subgroup, default: 'https', auth: 'user', committish: 'branch' },
196 | 'git+https://user:password@gitlab.com/foo/bar/baz': { ...subgroup, default: 'https', auth: 'user:password' },
197 | 'git+https://user:password@gitlab.com/foo/bar/baz#branch': { ...subgroup, default: 'https', auth: 'user:password', committish: 'branch' },
198 | 'git+https://:password@gitlab.com/foo/bar/baz': { ...subgroup, default: 'https', auth: ':password' },
199 | 'git+https://:password@gitlab.com/foo/bar/baz#branch': { ...subgroup, default: 'https', auth: ':password', committish: 'branch' },
200 |
201 | 'git+https://gitlab.com/foo/bar/baz.git': { ...subgroup, default: 'https' },
202 | 'git+https://gitlab.com/foo/bar/baz.git#branch': { ...subgroup, default: 'https', committish: 'branch' },
203 | 'git+https://user@gitlab.com/foo/bar/baz.git': { ...subgroup, default: 'https', auth: 'user' },
204 | 'git+https://user@gitlab.com/foo/bar/baz.git#branch': { ...subgroup, default: 'https', auth: 'user', committish: 'branch' },
205 | 'git+https://user:password@gitlab.com/foo/bar/baz.git': { ...subgroup, default: 'https', auth: 'user:password' },
206 | 'git+https://user:password@gitlab.com/foo/bar/baz.git#branch': { ...subgroup, default: 'https', auth: 'user:password', committish: 'branch' },
207 | 'git+https://:password@gitlab.com/foo/bar/baz.git': { ...subgroup, default: 'https', auth: ':password' },
208 | 'git+https://:password@gitlab.com/foo/bar/baz.git#branch': { ...subgroup, default: 'https', auth: ':password', committish: 'branch' },
209 |
210 | // https urls
211 | //
212 | // NOTE auth is accepted and respected
213 | // NOTE subprojects are accepted, but the subproject is treated as the project and the real project is lost
214 | 'https://gitlab.com/foo/bar': { ...defaults, default: 'https' },
215 | 'https://gitlab.com/foo/bar#branch': { ...defaults, default: 'https', committish: 'branch' },
216 | 'https://user@gitlab.com/foo/bar': { ...defaults, default: 'https', auth: 'user' },
217 | 'https://user@gitlab.com/foo/bar#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
218 | 'https://user:password@gitlab.com/foo/bar': { ...defaults, default: 'https', auth: 'user:password' },
219 | 'https://user:password@gitlab.com/foo/bar#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
220 | 'https://:password@gitlab.com/foo/bar': { ...defaults, default: 'https', auth: ':password' },
221 | 'https://:password@gitlab.com/foo/bar#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
222 |
223 | 'https://gitlab.com/foo/bar.git': { ...defaults, default: 'https' },
224 | 'https://gitlab.com/foo/bar.git#branch': { ...defaults, default: 'https', committish: 'branch' },
225 | 'https://user@gitlab.com/foo/bar.git': { ...defaults, default: 'https', auth: 'user' },
226 | 'https://user@gitlab.com/foo/bar.git#branch': { ...defaults, default: 'https', auth: 'user', committish: 'branch' },
227 | 'https://user:password@gitlab.com/foo/bar.git': { ...defaults, default: 'https', auth: 'user:password' },
228 | 'https://user:password@gitlab.com/foo/bar.git#branch': { ...defaults, default: 'https', auth: 'user:password', committish: 'branch' },
229 | 'https://:password@gitlab.com/foo/bar.git': { ...defaults, default: 'https', auth: ':password' },
230 | 'https://:password@gitlab.com/foo/bar.git#branch': { ...defaults, default: 'https', auth: ':password', committish: 'branch' },
231 |
232 | 'https://gitlab.com/foo/bar/baz': { ...subgroup, default: 'https' },
233 | 'https://gitlab.com/foo/bar/baz#branch': { ...subgroup, default: 'https', committish: 'branch' },
234 | 'https://user@gitlab.com/foo/bar/baz': { ...subgroup, default: 'https', auth: 'user' },
235 | 'https://user@gitlab.com/foo/bar/baz#branch': { ...subgroup, default: 'https', auth: 'user', committish: 'branch' },
236 | 'https://user:password@gitlab.com/foo/bar/baz': { ...subgroup, default: 'https', auth: 'user:password' },
237 | 'https://user:password@gitlab.com/foo/bar/baz#branch': { ...subgroup, default: 'https', auth: 'user:password', committish: 'branch' },
238 | 'https://:password@gitlab.com/foo/bar/baz': { ...subgroup, default: 'https', auth: ':password' },
239 | 'https://:password@gitlab.com/foo/bar/baz#branch': { ...subgroup, default: 'https', auth: ':password', committish: 'branch' },
240 |
241 | 'https://gitlab.com/foo/bar/baz.git': { ...subgroup, default: 'https' },
242 | 'https://gitlab.com/foo/bar/baz.git#branch': { ...subgroup, default: 'https', committish: 'branch' },
243 | 'https://user@gitlab.com/foo/bar/baz.git': { ...subgroup, default: 'https', auth: 'user' },
244 | 'https://user@gitlab.com/foo/bar/baz.git#branch': { ...subgroup, default: 'https', auth: 'user', committish: 'branch' },
245 | 'https://user:password@gitlab.com/foo/bar/baz.git': { ...subgroup, default: 'https', auth: 'user:password' },
246 | 'https://user:password@gitlab.com/foo/bar/baz.git#branch': { ...subgroup, default: 'https', auth: 'user:password', committish: 'branch' },
247 | 'https://:password@gitlab.com/foo/bar/baz.git': { ...subgroup, default: 'https', auth: ':password' },
248 | 'https://:password@gitlab.com/foo/bar/baz.git#branch': { ...subgroup, default: 'https', auth: ':password', committish: 'branch' },
249 | }
250 |
251 | t.test('valid urls parse properly', t => {
252 | t.plan(Object.keys(valid).length)
253 | for (const [url, result] of Object.entries(valid)) {
254 | t.hasStrict(HostedGit.fromUrl(url), result, `${url} parses`)
255 | }
256 | })
257 |
258 | t.test('invalid urls return undefined', t => {
259 | t.plan(invalid.length)
260 | for (const url of invalid) {
261 | t.equal(HostedGit.fromUrl(url), undefined, `${url} returns undefined`)
262 | }
263 | })
264 |
265 | t.test('toString respects defaults', t => {
266 | const sshurl = HostedGit.fromUrl('git+ssh://gitlab.com/foo/bar')
267 | t.equal(sshurl.default, 'sshurl', 'got the right default')
268 | t.equal(sshurl.toString(), sshurl.sshurl(), 'toString calls sshurl')
269 |
270 | const https = HostedGit.fromUrl('https://gitlab.com/foo/bar')
271 | t.equal(https.default, 'https', 'got the right default')
272 | t.equal(https.toString(), https.https(), 'toString calls https')
273 |
274 | const shortcut = HostedGit.fromUrl('gitlab:foo/bar')
275 | t.equal(shortcut.default, 'shortcut', 'got the right default')
276 | t.equal(shortcut.toString(), shortcut.shortcut(), 'toString calls shortcut')
277 |
278 | t.end()
279 | })
280 |
281 | t.test('string methods populate correctly', t => {
282 | const parsed = HostedGit.fromUrl('git+ssh://gitlab.com/foo/bar')
283 | t.equal(parsed.getDefaultRepresentation(), parsed.default)
284 | t.equal(parsed.hash(), '', 'hash() returns empty string when committish is unset')
285 | t.equal(parsed.ssh(), 'git@gitlab.com:foo/bar.git')
286 | t.equal(parsed.sshurl(), 'git+ssh://git@gitlab.com/foo/bar.git')
287 | t.equal(parsed.edit(), 'https://gitlab.com/foo/bar')
288 | t.equal(parsed.edit('/lib/index.js'), 'https://gitlab.com/foo/bar/-/edit/HEAD/lib/index.js')
289 | t.equal(parsed.browse(), 'https://gitlab.com/foo/bar')
290 | t.equal(parsed.browse('/lib/index.js'), 'https://gitlab.com/foo/bar/tree/HEAD/lib/index.js')
291 | t.equal(parsed.browse('/lib/index.js', 'L100'), 'https://gitlab.com/foo/bar/tree/HEAD/lib/index.js#l100')
292 | t.equal(parsed.docs(), 'https://gitlab.com/foo/bar#readme')
293 | t.equal(parsed.https(), 'git+https://gitlab.com/foo/bar.git')
294 | t.equal(parsed.shortcut(), 'gitlab:foo/bar')
295 | t.equal(parsed.path(), 'foo/bar')
296 | t.equal(parsed.tarball(), 'https://gitlab.com/foo/bar/repository/archive.tar.gz?ref=HEAD')
297 | t.equal(parsed.file(), 'https://gitlab.com/foo/bar/raw/HEAD/')
298 | t.equal(parsed.file('/lib/index.js'), 'https://gitlab.com/foo/bar/raw/HEAD/lib/index.js')
299 | t.equal(parsed.bugs(), 'https://gitlab.com/foo/bar/issues')
300 |
301 | t.same(parsed.git(), null, 'git() returns null')
302 |
303 | t.equal(parsed.docs({ committish: 'fix/bug' }), 'https://gitlab.com/foo/bar/tree/fix%2Fbug#readme', 'allows overriding options')
304 |
305 | const extra = HostedGit.fromUrl('https://user@gitlab.com/foo/bar#fix/bug')
306 | t.equal(extra.hash(), '#fix/bug')
307 | t.equal(extra.https(), 'git+https://user@gitlab.com/foo/bar.git#fix/bug')
308 | t.equal(extra.shortcut(), 'gitlab:foo/bar#fix/bug')
309 | t.equal(extra.ssh(), 'git@gitlab.com:foo/bar.git#fix/bug')
310 | t.equal(extra.sshurl(), 'git+ssh://git@gitlab.com/foo/bar.git#fix/bug')
311 | t.equal(extra.browse(), 'https://gitlab.com/foo/bar/tree/fix%2Fbug')
312 | t.equal(extra.browse('/lib/index.js'), 'https://gitlab.com/foo/bar/tree/fix%2Fbug/lib/index.js')
313 | t.equal(extra.browse('/lib/index.js', 'L200'), 'https://gitlab.com/foo/bar/tree/fix%2Fbug/lib/index.js#l200')
314 | t.equal(extra.docs(), 'https://gitlab.com/foo/bar/tree/fix%2Fbug#readme')
315 | t.equal(extra.file(), 'https://gitlab.com/foo/bar/raw/fix%2Fbug/')
316 | t.equal(extra.file('/lib/index.js'), 'https://gitlab.com/foo/bar/raw/fix%2Fbug/lib/index.js')
317 | t.equal(extra.tarball(), 'https://gitlab.com/foo/bar/repository/archive.tar.gz?ref=fix%2Fbug')
318 |
319 | t.equal(extra.sshurl({ noCommittish: true }), 'git+ssh://git@gitlab.com/foo/bar.git', 'noCommittish drops committish from urls')
320 | t.equal(extra.sshurl({ noGitPlus: true }), 'ssh://git@gitlab.com/foo/bar.git#fix/bug', 'noGitPlus drops git+ prefix from urls')
321 |
322 | t.end()
323 | })
324 |
325 | t.test('from manifest', t => {
326 | t.equal(HostedGit.fromManifest(), undefined, 'no manifest returns undefined')
327 | t.equal(HostedGit.fromManifest(), undefined, 'no manifest returns undefined')
328 | t.equal(HostedGit.fromManifest(false), undefined, 'false manifest returns undefined')
329 | t.equal(HostedGit.fromManifest(() => {}), undefined, 'function manifest returns undefined')
330 |
331 | const unknownHostRepo = {
332 | name: 'foo',
333 | repository: {
334 | url: 'https://nope.com',
335 | },
336 | }
337 | t.same(HostedGit.fromManifest(unknownHostRepo), 'https://nope.com/')
338 |
339 | const insecureUnknownHostRepo = {
340 | name: 'foo',
341 | repository: {
342 | url: 'http://nope.com',
343 | },
344 | }
345 | t.same(HostedGit.fromManifest(insecureUnknownHostRepo), 'https://nope.com/')
346 |
347 | const insecureGitUnknownHostRepo = {
348 | name: 'foo',
349 | repository: {
350 | url: 'git+http://nope.com',
351 | },
352 | }
353 | t.same(HostedGit.fromManifest(insecureGitUnknownHostRepo), 'http://nope.com')
354 |
355 | const badRepo = {
356 | name: 'foo',
357 | repository: {
358 | url: '#',
359 | },
360 | }
361 | t.equal(HostedGit.fromManifest(badRepo), null)
362 |
363 | const manifest = {
364 | name: 'foo',
365 | repository: {
366 | type: 'git',
367 | url: 'git+ssh://gitlab.com/foo/bar.git',
368 | },
369 | }
370 |
371 | const parsed = HostedGit.fromManifest(manifest)
372 | t.same(parsed.browse(), 'https://gitlab.com/foo/bar')
373 |
374 | const monorepo = {
375 | name: 'clowncar',
376 | repository: {
377 | type: 'git',
378 | url: 'git+ssh://gitlab.com/foo/bar.git',
379 | directory: 'packages/foo',
380 | },
381 | }
382 |
383 | const honk = HostedGit.fromManifest(monorepo)
384 | t.same(honk.browse(monorepo.repository.directory), 'https://gitlab.com/foo/bar/tree/HEAD/packages/foo')
385 |
386 | const stringRepo = {
387 | name: 'foo',
388 | repository: 'git+ssh://gitlab.com/foo/bar.git',
389 | }
390 | const stringRepoParsed = HostedGit.fromManifest(stringRepo)
391 | t.same(stringRepoParsed.browse(), 'https://gitlab.com/foo/bar')
392 |
393 | const nonStringRepo = {
394 | name: 'foo',
395 | repository: 42,
396 | }
397 | t.throws(() => HostedGit.fromManifest(nonStringRepo))
398 |
399 | t.end()
400 | })
401 |
--------------------------------------------------------------------------------
/test/invalid.js:
--------------------------------------------------------------------------------
1 | const HostedGit = require('..')
2 | const t = require('tap')
3 |
4 | // each of these urls should return `undefined`
5 | // none should throw
6 | const urls = [
7 | 'https://google.com',
8 | 'git+ssh://git@nothosted.com/abc/def',
9 | 'git://nothosted.com',
10 | 'git+file:///foo/bar',
11 | 'git+ssh://git@git.unlucky.com:RND/electron-tools/some-tool#2.0.1',
12 | '::',
13 | '',
14 | null,
15 | undefined,
16 | ]
17 |
18 | t.test('invalid results parse to undefined', t => {
19 | t.plan(urls.length)
20 | for (const url of urls) {
21 | t.equal(HostedGit.fromUrl(url), undefined, `${url} returns undefined`)
22 | }
23 | })
24 |
--------------------------------------------------------------------------------
/test/localhost.js:
--------------------------------------------------------------------------------
1 | const HostedGit = require('..')
2 | const t = require('tap')
3 |
4 | t.test('supports extensions', t => {
5 | // An example of a custom setup, useful when testing modules like pacote,
6 | // which do various things with these git shortcuts.
7 | HostedGit.addHost('localhost', {
8 | protocols: ['git:'],
9 | domain: 'localhost',
10 | extract: (url) => {
11 | const [, user, project] = url.pathname.split('/')
12 | return { user, project, committish: url.hash.slice(1) }
13 | },
14 | })
15 |
16 | const hosted = HostedGit.fromUrl('git://localhost:12345/foo/bar')
17 | t.match(
18 | hosted,
19 | { type: 'localhost', default: 'git', user: 'foo', project: 'bar' },
20 | 'parsed correctly'
21 | )
22 |
23 | const shortcut = HostedGit.fromUrl('localhost:foo/bar')
24 | t.match(
25 | shortcut,
26 | { type: 'localhost', default: 'shortcut', user: 'foo', project: 'bar' },
27 | 'parsed correctly'
28 | )
29 |
30 | t.end()
31 | })
32 |
--------------------------------------------------------------------------------
/test/parse-url.js:
--------------------------------------------------------------------------------
1 | const t = require('tap')
2 | const HostedGit = require('..')
3 | const parseUrl = require('../lib/parse-url.js')
4 |
5 | t.test('can parse git+ssh urls', async t => {
6 | // https://github.com/npm/cli/issues/5278
7 | const u = 'git+ssh://git@abc:frontend/utils.git#6d45447e0c5eb6cd2e3edf05a8c5a9bb81950c79'
8 | t.ok(parseUrl(u))
9 | t.ok(HostedGit.parseUrl(u))
10 | })
11 |
12 | t.test('can parse file urls', async t => {
13 | // https://github.com/npm/cli/pull/5758#issuecomment-1292753331
14 | const u = 'file:../../../global-prefix/lib/node_modules/@myscope/bar'
15 | t.ok(parseUrl(u))
16 | t.ok(HostedGit.parseUrl(u))
17 | })
18 |
--------------------------------------------------------------------------------
/test/sourcehut.js:
--------------------------------------------------------------------------------
1 | 'use strict'
2 | const HostedGit = require('..')
3 | const t = require('tap')
4 |
5 | const invalid = [
6 | // missing project
7 | 'https://git.sr.ht/~foo',
8 | // invalid protocos
9 | 'git://git@git.sr.ht:~foo/bar',
10 | 'ssh://git.sr.ht:~foo/bar',
11 | // tarball url
12 | 'https://git.sr.ht/~foo/bar/archive/HEAD.tar.gz',
13 | ]
14 |
15 | const defaults = { type: 'sourcehut', user: '~foo', project: 'bar' }
16 |
17 | const valid = {
18 | // shortucts
19 | 'sourcehut:~foo/bar': { ...defaults, default: 'shortcut' },
20 | 'sourcehut:~foo/bar#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
21 |
22 | // shortcuts (.git)
23 | 'sourcehut:~foo/bar.git': { ...defaults, default: 'shortcut' },
24 | 'sourcehut:~foo/bar.git#branch': { ...defaults, default: 'shortcut', committish: 'branch' },
25 |
26 | // no-protocol git+ssh
27 | 'git@git.sr.ht:~foo/bar': { ...defaults, default: 'sshurl', auth: null },
28 | 'git@git.sr.ht:~foo/bar#branch': {
29 | ...defaults, default: 'sshurl', auth: null, committish: 'branch',
30 | },
31 |
32 | // no-protocol git+ssh (.git)
33 | 'git@git.sr.ht:~foo/bar.git': { ...defaults, default: 'sshurl', auth: null },
34 | 'git@git.sr.ht:~foo/bar.git#branch': {
35 | ...defaults, default: 'sshurl', auth: null, committish: 'branch',
36 | },
37 |
38 | // git+ssh urls
39 | 'git+ssh://git@git.sr.ht:~foo/bar': { ...defaults, default: 'sshurl' },
40 | 'git+ssh://git@git.sr.ht:~foo/bar#branch': {
41 | ...defaults, default: 'sshurl', committish: 'branch',
42 | },
43 |
44 | // git+ssh urls (.git)
45 | 'git+ssh://git@git.sr.ht:~foo/bar.git': { ...defaults, default: 'sshurl' },
46 | 'git+ssh://git@git.sr.ht:~foo/bar.git#branch': {
47 | ...defaults, default: 'sshurl', committish: 'branch',
48 | },
49 |
50 | // https urls
51 | 'https://git.sr.ht/~foo/bar': { ...defaults, default: 'https' },
52 | 'https://git.sr.ht/~foo/bar#branch': { ...defaults, default: 'https', committish: 'branch' },
53 |
54 | 'https://git.sr.ht/~foo/bar.git': { ...defaults, default: 'https' },
55 | 'https://git.sr.ht/~foo/bar.git#branch': { ...defaults, default: 'https', committish: 'branch' },
56 | }
57 |
58 | t.test('valid urls parse properly', t => {
59 | t.plan(Object.keys(valid).length)
60 | for (const [url, result] of Object.entries(valid)) {
61 | t.hasStrict(HostedGit.fromUrl(url), result, `${url} parses`)
62 | }
63 | })
64 |
65 | t.test('invalid urls return undefined', t => {
66 | t.plan(invalid.length)
67 | for (const url of invalid) {
68 | t.equal(HostedGit.fromUrl(url), undefined, `${url} returns undefined`)
69 | }
70 | })
71 |
72 | t.test('toString respects defaults', t => {
73 | const sshurl = HostedGit.fromUrl('git+ssh://git.sr.ht/~foo/bar')
74 | t.equal(sshurl.default, 'sshurl', 'got the right default')
75 | t.equal(sshurl.toString(), sshurl.sshurl(), 'toString calls sshurl')
76 |
77 | const https = HostedGit.fromUrl('https://git.sr.ht/~foo/bar')
78 | t.equal(https.default, 'https', 'got the right default')
79 | t.equal(https.toString(), https.https(), 'toString calls https')
80 |
81 | const shortcut = HostedGit.fromUrl('sourcehut:~foo/bar')
82 | t.equal(shortcut.default, 'shortcut', 'got the right default')
83 | t.equal(shortcut.toString(), shortcut.shortcut(), 'toString calls shortcut')
84 |
85 | t.end()
86 | })
87 |
88 | t.test('string methods populate correctly', t => {
89 | const parsed = HostedGit.fromUrl('git+ssh://git.sr.ht/~foo/bar')
90 | t.equal(parsed.getDefaultRepresentation(), parsed.default, 'getDefaultRepresentation()')
91 | t.equal(parsed.hash(), '', 'hash() returns empty string when committish is unset')
92 | t.equal(parsed.ssh(), 'git@git.sr.ht:~foo/bar.git')
93 | t.equal(parsed.sshurl(), 'git+ssh://git@git.sr.ht/~foo/bar.git')
94 | t.equal(parsed.edit('/lib/index.js'), 'https://git.sr.ht/~foo/bar', 'no editing, link to browse')
95 | t.equal(parsed.edit(), 'https://git.sr.ht/~foo/bar', 'no editing, link to browse')
96 | t.equal(parsed.browse(), 'https://git.sr.ht/~foo/bar')
97 | t.equal(parsed.browse('/lib/index.js'), 'https://git.sr.ht/~foo/bar/tree/HEAD/lib/index.js')
98 | t.equal(
99 | parsed.browse('/lib/index.js', 'L100'),
100 | 'https://git.sr.ht/~foo/bar/tree/HEAD/lib/index.js#l100'
101 | )
102 | t.equal(parsed.docs(), 'https://git.sr.ht/~foo/bar#readme')
103 | t.equal(parsed.https(), 'https://git.sr.ht/~foo/bar.git')
104 | t.equal(parsed.shortcut(), 'sourcehut:~foo/bar')
105 | t.equal(parsed.path(), '~foo/bar')
106 | t.equal(parsed.tarball(), 'https://git.sr.ht/~foo/bar/archive/HEAD.tar.gz')
107 | t.equal(parsed.file(), 'https://git.sr.ht/~foo/bar/blob/HEAD/')
108 | t.equal(parsed.file('/lib/index.js'), 'https://git.sr.ht/~foo/bar/blob/HEAD/lib/index.js')
109 | t.equal(parsed.bugs(), null)
110 |
111 | t.equal(
112 | parsed.docs({ committish: 'fix/bug' }),
113 | 'https://git.sr.ht/~foo/bar/tree/fix%2Fbug#readme',
114 | 'allows overriding options'
115 | )
116 |
117 | t.same(parsed.git(), null, 'git() returns null')
118 |
119 | const extra = HostedGit.fromUrl('https://@git.sr.ht/~foo/bar#fix/bug')
120 | t.equal(extra.hash(), '#fix/bug')
121 | t.equal(extra.https(), 'https://git.sr.ht/~foo/bar.git#fix/bug')
122 | t.equal(extra.shortcut(), 'sourcehut:~foo/bar#fix/bug')
123 | t.equal(extra.ssh(), 'git@git.sr.ht:~foo/bar.git#fix/bug')
124 | t.equal(extra.sshurl(), 'git+ssh://git@git.sr.ht/~foo/bar.git#fix/bug')
125 | t.equal(extra.browse(), 'https://git.sr.ht/~foo/bar/tree/fix%2Fbug')
126 | t.equal(extra.browse('/lib/index.js'), 'https://git.sr.ht/~foo/bar/tree/fix%2Fbug/lib/index.js')
127 | t.equal(
128 | extra.browse('/lib/index.js', 'L200'),
129 | 'https://git.sr.ht/~foo/bar/tree/fix%2Fbug/lib/index.js#l200'
130 | )
131 | t.equal(extra.docs(), 'https://git.sr.ht/~foo/bar/tree/fix%2Fbug#readme')
132 | t.equal(extra.file(), 'https://git.sr.ht/~foo/bar/blob/fix%2Fbug/')
133 | t.equal(extra.file('/lib/index.js'), 'https://git.sr.ht/~foo/bar/blob/fix%2Fbug/lib/index.js')
134 |
135 | t.equal(
136 | extra.sshurl({ noCommittish: true }),
137 | 'git+ssh://git@git.sr.ht/~foo/bar.git',
138 | 'noCommittish drops committish from urls'
139 | )
140 | t.equal(
141 | extra.sshurl({ noGitPlus: true }),
142 | 'ssh://git@git.sr.ht/~foo/bar.git#fix/bug',
143 | 'noGitPlus drops git+ prefix from urls'
144 | )
145 |
146 | t.end()
147 | })
148 |
--------------------------------------------------------------------------------