Update markbind deploy success log message#2852
Update markbind deploy success log message#2852yihao03 wants to merge 3 commits intoMarkBind:masterfrom
Conversation
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master #2852 +/- ##
==========================================
+ Coverage 71.92% 72.04% +0.12%
==========================================
Files 132 132
Lines 7358 7362 +4
Branches 1516 1580 +64
==========================================
+ Hits 5292 5304 +12
+ Misses 2021 1931 -90
- Partials 45 127 +82 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
Updates MarkBind’s deploy command output to be less misleading by reporting both the GitHub Actions workflow status URL and the expected deployed site URL, while refactoring the CLI logging into a utility and expanding unit tests around URL construction.
Changes:
- Extend deploy URL generation to return both GitHub Pages and GitHub Actions URLs.
- Add a CLI utility to log deploy results in a clearer, less “success-looking” way.
- Add unit tests for GitHub remote parsing and URL construction helpers.
Reviewed changes
Copilot reviewed 5 out of 5 changed files in this pull request and generated 6 comments.
Show a summary per file
| File | Description |
|---|---|
| packages/core/src/Site/SiteDeployManager.ts | Introduces DeployResult and URL parsing/construction helpers; returns both GH Pages + Actions URLs from deployment URL resolution. |
| packages/core/index.ts | Re-exports DeployResult type for CLI consumption. |
| packages/cli/src/util/deploy.ts | Adds logDeployResult helper to centralize deploy logging behavior. |
| packages/cli/src/cmd/deploy.ts | Switches deploy command to use the new logging helper and updated deploy return shape. |
| packages/core/test/unit/Site/SiteDeployManager.test.ts | Adds unit tests for parsing remotes and constructing deploy URLs (including CNAME behavior). |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
You can also share your feedback on Copilot code review. Take the survey.
| // https://github.com/<name|org>/<repo>.git (HTTPS) | ||
| const repoNameWithExt = parts[parts.length - 1]; | ||
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | ||
| const name = parts[parts.length - 2].toLowerCase(); | ||
| return { name, repoName }; | ||
| } else if (remoteUrl.startsWith(SSH_PREAMBLE)) { | ||
| // git@github.com:<name|org>/<repo>.git (SSH) | ||
| const repoNameWithExt = parts[parts.length - 1]; | ||
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | ||
| const name = parts[0].substring(SSH_PREAMBLE.length); |
There was a problem hiding this comment.
parseGitHubRemoteUrl derives repoName via substring(0, lastIndexOf('.')), which becomes an empty string when the remote URL does not end with a file extension (e.g. https://github.com/user/repo). It can also truncate repo names containing dots when .git is absent. Consider stripping a trailing .git only when present (otherwise keep the full last path segment) so URL construction remains correct for common remote formats.
| // https://github.com/<name|org>/<repo>.git (HTTPS) | |
| const repoNameWithExt = parts[parts.length - 1]; | |
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | |
| const name = parts[parts.length - 2].toLowerCase(); | |
| return { name, repoName }; | |
| } else if (remoteUrl.startsWith(SSH_PREAMBLE)) { | |
| // git@github.com:<name|org>/<repo>.git (SSH) | |
| const repoNameWithExt = parts[parts.length - 1]; | |
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | |
| const name = parts[0].substring(SSH_PREAMBLE.length); | |
| // https://github.com/<name|org>/<repo>[.git] (HTTPS) | |
| const repoSegment = parts[parts.length - 1] || ''; | |
| const repoName = repoSegment.endsWith('.git') | |
| ? repoSegment.substring(0, repoSegment.length - 4) | |
| : repoSegment; | |
| const name = (parts[parts.length - 2] || '').toLowerCase(); | |
| if (!name || !repoName) { | |
| return null; | |
| } | |
| return { name, repoName }; | |
| } else if (remoteUrl.startsWith(SSH_PREAMBLE)) { | |
| // git@github.com:<name|org>/<repo>[.git] (SSH) | |
| const lastPart = parts[parts.length - 1] || ''; | |
| const repoName = lastPart.endsWith('.git') | |
| ? lastPart.substring(0, lastPart.length - 4) | |
| : lastPart; | |
| const ownerAndHost = parts[0] || ''; | |
| const name = ownerAndHost.substring(SSH_PREAMBLE.length).toLowerCase(); | |
| if (!name || !repoName) { | |
| return null; | |
| } |
| // https://github.com/<name|org>/<repo>.git (HTTPS) | ||
| const repoNameWithExt = parts[parts.length - 1]; | ||
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | ||
| const name = parts[parts.length - 2].toLowerCase(); | ||
| return { name, repoName }; | ||
| } else if (remoteUrl.startsWith(SSH_PREAMBLE)) { | ||
| // git@github.com:<name|org>/<repo>.git (SSH) | ||
| const repoNameWithExt = parts[parts.length - 1]; | ||
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); |
There was a problem hiding this comment.
Same issue in the SSH branch: using substring(0, lastIndexOf('.')) can yield an empty repoName for remotes like git@github.com:user/repo (no .git), or truncate dotted repo names. Prefer removing only a trailing .git (if present) and otherwise using the full last segment.
| // https://github.com/<name|org>/<repo>.git (HTTPS) | |
| const repoNameWithExt = parts[parts.length - 1]; | |
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | |
| const name = parts[parts.length - 2].toLowerCase(); | |
| return { name, repoName }; | |
| } else if (remoteUrl.startsWith(SSH_PREAMBLE)) { | |
| // git@github.com:<name|org>/<repo>.git (SSH) | |
| const repoNameWithExt = parts[parts.length - 1]; | |
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | |
| // https://github.com/<name|org>/<repo>[.git] (HTTPS) | |
| const repoNameWithExt = parts[parts.length - 1]; | |
| const repoName = repoNameWithExt.toLowerCase().endsWith('.git') | |
| ? repoNameWithExt.slice(0, -4) | |
| : repoNameWithExt; | |
| const name = parts[parts.length - 2].toLowerCase(); | |
| return { name, repoName }; | |
| } else if (remoteUrl.startsWith(SSH_PREAMBLE)) { | |
| // git@github.com:<name|org>/<repo>[.git] (SSH) | |
| const repoNameWithExt = parts[parts.length - 1]; | |
| const repoName = repoNameWithExt.toLowerCase().endsWith('.git') | |
| ? repoNameWithExt.slice(0, -4) | |
| : repoNameWithExt; |
| export function logDeployResult(result: DeployResult) { | ||
| if (result.ghActionsUrl) { | ||
| logger.info(`GitHub Actions deployment initiated. Check status at: ${result.ghActionsUrl}`); | ||
| } | ||
| if (result.ghPagesUrl) { | ||
| logger.info(`The website will be deployed at: ${result.ghPagesUrl}`); |
There was a problem hiding this comment.
ghPagesUrl can be a bare domain from CNAME (e.g. custom.domain.com), which is not a URL without a scheme. To match the intent of logging a deployed URL, consider normalizing ghPagesUrl for display (e.g. prefixing https:// when no scheme is present).
| export function logDeployResult(result: DeployResult) { | |
| if (result.ghActionsUrl) { | |
| logger.info(`GitHub Actions deployment initiated. Check status at: ${result.ghActionsUrl}`); | |
| } | |
| if (result.ghPagesUrl) { | |
| logger.info(`The website will be deployed at: ${result.ghPagesUrl}`); | |
| function normalizeUrlForDisplay(url: string): string { | |
| // If the URL already has a scheme (e.g. http://, https://), leave it as is. | |
| if (/^[a-zA-Z][\w+.-]*:\/\//.test(url)) { | |
| return url; | |
| } | |
| // Assume HTTPS for bare domains such as those from GitHub Pages CNAME. | |
| return `https://${url}`; | |
| } | |
| export function logDeployResult(result: DeployResult) { | |
| if (result.ghActionsUrl) { | |
| logger.info(`GitHub Actions deployment initiated. Check status at: ${result.ghActionsUrl}`); | |
| } | |
| if (result.ghPagesUrl) { | |
| const ghPagesDisplayUrl = normalizeUrlForDisplay(result.ghPagesUrl); | |
| logger.info(`The website will be deployed at: ${ghPagesDisplayUrl}`); |
| /** | ||
| * Constructs the GitHub Pages URL from a remote URL. | ||
| * Returns a URL in the format: https://<name>.github.io/<repo> | ||
| */ | ||
| static constructGhPagesUrl(repo: ParsedGitHubRepo): string { | ||
| return `https://${repo.name}.github.io/${repo.repoName}`; |
There was a problem hiding this comment.
Docstring says “Constructs the GitHub Pages URL from a remote URL”, but this function takes a parsed { name, repoName } object, not a URL. Consider updating the comment to avoid confusion about expected inputs.
| /** | ||
| * Constructs the GitHub Actions URL from a remote URL. | ||
| * Returns a URL in the format: https://github.com/<name>/<repo>/actions | ||
| */ | ||
| static constructGhActionsUrl(repo: ParsedGitHubRepo): string { | ||
| return `https://github.com/${repo.name}/${repo.repoName}/actions`; |
There was a problem hiding this comment.
Docstring says “Constructs the GitHub Actions URL from a remote URL”, but this function takes a parsed { name, repoName } object, not a URL. Consider updating the comment to reflect the actual input type.
| static parseGitHubRemoteUrl(remoteUrl: string): ParsedGitHubRepo | null { | ||
| const HTTPS_PREAMBLE = 'https://'; | ||
| const SSH_PREAMBLE = 'git@github.com:'; | ||
| const GITHUB_IO_PART = 'github.io'; | ||
|
|
||
| // https://<name|org name>.github.io/<repo name>/ | ||
| function constructGhPagesUrl(remoteUrl: string) { | ||
| if (!remoteUrl) { | ||
| return null; | ||
| } | ||
| const parts = remoteUrl.split('/'); | ||
| if (remoteUrl.startsWith(HTTPS_PREAMBLE)) { | ||
| // https://github.com/<name|org>/<repo>.git (HTTPS) | ||
| const repoNameWithExt = parts[parts.length - 1]; | ||
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | ||
| const name = parts[parts.length - 2].toLowerCase(); | ||
| return `https://${name}.${GITHUB_IO_PART}/${repoName}`; | ||
| } else if (remoteUrl.startsWith(SSH_PREAMBLE)) { | ||
| // git@github.com:<name|org>/<repo>.git (SSH) | ||
| const repoNameWithExt = parts[parts.length - 1]; | ||
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | ||
| const name = parts[0].substring(SSH_PREAMBLE.length); | ||
| return `https://${name}.${GITHUB_IO_PART}/${repoName}`; | ||
| } | ||
| if (!remoteUrl) { | ||
| return null; | ||
| } | ||
| const parts = remoteUrl.split('/'); | ||
| if (remoteUrl.startsWith(HTTPS_PREAMBLE)) { | ||
| // https://github.com/<name|org>/<repo>.git (HTTPS) | ||
| const repoNameWithExt = parts[parts.length - 1]; | ||
| const repoName = repoNameWithExt.substring(0, repoNameWithExt.lastIndexOf('.')); | ||
| const name = parts[parts.length - 2].toLowerCase(); | ||
| return { name, repoName }; |
There was a problem hiding this comment.
parseGitHubRemoteUrl treats any https://.../<owner>/<repo> URL as GitHub (it doesn’t verify the host is github.com). Since MarkBind supports deploying to non-GitHub remotes, this can generate a bogus GitHub Actions link (and GH Pages link) for e.g. GitLab/Bitbucket HTTPS remotes. Consider validating the hostname/path (e.g. https://github.com/...) before returning a parsed result.
What is the purpose of this pull request?
Overview of changes:
Addresses #2849.
Now update the log message to point to URL of the GitHub Actions and the deployed site URL
Anything you'd like to highlight/discuss:
Testing instructions:
Deploy a website using markbind and observe the log message.
Proposed commit message: (wrap lines at 72 characters)
Update deploy log message to include GitHub Actions URL and deployed site URL
Checklist: ☑️
Reviewer checklist:
Indicate the SEMVER impact of the PR:
At the end of the review, please label the PR with the appropriate label:
r.Major,r.Minor,r.Patch.Breaking change release note preparation (if applicable):