feat: unified build and publish scripts with tag format fix#13
feat: unified build and publish scripts with tag format fix#13
Conversation
- Create tasks.sh as unified script for build/publish tasks - Convert build.sh and publish.sh to wrapper scripts for backward compatibility - Fix release.yml tag format to use 'v' prefix (v1.0.3 instead of 1.0.3) - Support both stable (v1.0.3) and pre-release (v1.0.3-pre) tags - Unified tag format across scripts and GitHub workflows Benefits: - Code reuse and maintainability (single source of truth) - Consistent tag format between local scripts and CI/CD - Backward compatible (old scripts still work) - Better UX with centralized help and options
演练此变更重构了发布工作流,引入统一的任务运行器脚本( 变更
预计代码审查工作量🎯 4 (复杂) | ⏱️ ~50 分钟 诗
🚥 Pre-merge checks | ✅ 2 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (2 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
@Auggie review |
🤖 Augment PR SummarySummary: This PR unifies local build/publish automation and aligns CI release tags with a Changes:
Technical Notes: The tag prefix change is intended to keep local scripts and GitHub workflow behavior consistent, supporting both stable and pre-release tags under a single convention. 🤖 Was this summary useful? React with 👍 or 👎 |
tasks.sh
Outdated
| SKIP_INSTALL=false | ||
|
|
||
| show_help() { | ||
| echo "Usage: $0 <TASK> [OPTIONS]" |
There was a problem hiding this comment.
|
|
||
| # Check for uncommitted changes | ||
| info "Checking for uncommitted changes..." | ||
| if ! git diff-index --quiet HEAD -- 2>/dev/null; then |
| if [ "$DRY_RUN" = false ]; then | ||
| info "Checking if tag ${TAG_NAME} already exists..." | ||
|
|
||
| if git ls-remote --tags origin 2>/dev/null | grep -q "refs/tags/${TAG_NAME}$"; then |
There was a problem hiding this comment.
Tag existence is checked only on the remote; if the tag already exists locally (e.g., after a previous failed run), the script can publish to the marketplace and then fail when creating the tag, leaving the release half-complete.
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
tasks.sh
Outdated
| SKIP_INSTALL=false | ||
|
|
||
| show_help() { | ||
| echo "Usage: $0 <TASK> [OPTIONS]" |
There was a problem hiding this comment.
tasks.sh
Outdated
|
|
||
| # Get default branch name | ||
| get_default_branch() { | ||
| git symbolic-ref refs/remotes/origin/HEAD 2>/dev/null | sed 's@^refs/remotes/origin/@@' || echo "master" |
There was a problem hiding this comment.
In get_default_branch, the || echo "master" fallback likely never runs because the pipeline’s exit status is from sed (usually 0) even when git symbolic-ref fails, so DEFAULT_BRANCH can become empty and break the branch check logic.
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
tasks.sh
Outdated
|
|
||
| # Build | ||
| info "Building extension..." | ||
| VSIX_FILE=$(do_build) |
There was a problem hiding this comment.
VSIX_FILE=$(do_build) captures all stdout from do_build (including [INFO]/[SUCCESS] lines), so build output is hidden during publish and the captured value can be more than just the filename until it’s overwritten by the subsequent ls.
🤖 Was this useful? React with 👍 or 👎, or 🚀 if it prevented an incident/outage.
There was a problem hiding this comment.
Pull request overview
This PR unifies local build/publish automation into a single tasks.sh entrypoint while aligning GitHub Actions release tag parsing with a v-prefixed tag format.
Changes:
- Added
tasks.shto centralize build/publish logic (including tag formatting for stable vs pre-release). - Converted
build.shandpublish.shinto thin wrapper scripts for backward compatibility. - Updated
.github/workflows/release.ymlto expect and generatevX.Y.Z-style tags and correctly extract versions from them.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 6 comments.
| File | Description |
|---|---|
tasks.sh |
New unified build/publish script; introduces tag-format logic and CLI UX improvements. |
build.sh |
Now delegates to tasks.sh --build to preserve existing entrypoint. |
publish.sh |
Now delegates to tasks.sh --publish to preserve existing entrypoint. |
.github/workflows/release.yml |
Updates tag generation/validation/extraction to use v-prefixed tags consistently. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| show_help() { | ||
| echo "Usage: $0 <TASK> [OPTIONS]" | ||
| echo "" | ||
| echo "Tasks:" | ||
| echo " -b, --build Build the extension (create .vsix file)" | ||
| echo " -p, --publish Build and publish to VS Code Marketplace" | ||
| echo "" |
There was a problem hiding this comment.
The help text says Usage: $0 <TASK> [OPTIONS], but the script doesn’t accept a positional TASK (only -b/--build and -p/--publish). Update the usage string to match the actual CLI, or add support for positional build|publish to avoid confusing users.
| # Check for uncommitted changes | ||
| info "Checking for uncommitted changes..." | ||
| if ! git diff-index --quiet HEAD -- 2>/dev/null; then | ||
| MODIFIED_FILES=$(git diff-index --name-only HEAD --) |
There was a problem hiding this comment.
git diff-index --quiet only checks tracked changes and ignores untracked files, but the script reports Working directory is clean and may proceed even if untracked files exist. Consider using git status --porcelain (or an explicit untracked check) so the cleanliness check matches the message and intended safety gate.
| # Check for uncommitted changes | |
| info "Checking for uncommitted changes..." | |
| if ! git diff-index --quiet HEAD -- 2>/dev/null; then | |
| MODIFIED_FILES=$(git diff-index --name-only HEAD --) | |
| # Check for uncommitted changes (including untracked files) | |
| info "Checking for uncommitted changes..." | |
| STATUS_OUTPUT=$(git status --porcelain 2>/dev/null || true) | |
| if [ -n "$STATUS_OUTPUT" ]; then | |
| MODIFIED_FILES=$(echo "$STATUS_OUTPUT" | sed 's/^.. //') |
| git push origin ":refs/tags/${TAG_NAME}" || true | ||
| git tag -d "${TAG_NAME}" 2>/dev/null || true | ||
| success "Remote tag deleted" |
There was a problem hiding this comment.
In the force-delete path, both tag deletion commands are || true but the script still prints Remote tag deleted unconditionally. This can report success even when deletion fails (e.g., permissions/network). Capture and check the return codes (or conditionally print a warning) so the user gets accurate feedback.
| git push origin ":refs/tags/${TAG_NAME}" || true | |
| git tag -d "${TAG_NAME}" 2>/dev/null || true | |
| success "Remote tag deleted" | |
| REMOTE_DELETED=false | |
| LOCAL_DELETED=false | |
| if git push origin ":refs/tags/${TAG_NAME}"; then | |
| REMOTE_DELETED=true | |
| else | |
| warning "Failed to delete remote tag ${TAG_NAME} from origin" | |
| fi | |
| if git tag -d "${TAG_NAME}" 2>/dev/null; then | |
| LOCAL_DELETED=true | |
| else | |
| warning "Failed to delete local tag ${TAG_NAME}" | |
| fi | |
| if [ "$REMOTE_DELETED" = true ] && [ "$LOCAL_DELETED" = true ]; then | |
| success "Remote tag deleted" | |
| else | |
| warning "Tag deletion was not fully successful; please verify tag ${TAG_NAME} locally and on origin." | |
| fi |
tasks.sh
Outdated
|
|
||
| # Build | ||
| info "Building extension..." | ||
| VSIX_FILE=$(do_build) |
There was a problem hiding this comment.
VSIX_FILE=$(do_build) captures all stdout from do_build (including [INFO]/[SUCCESS] lines), and then the variable is immediately overwritten by re-running ls. This suppresses build output during publish and makes the first assignment redundant. Prefer calling do_build normally (no capture) and then locate the VSIX, or redirect do_build logs to stderr and have it print only the filename on stdout.
| VSIX_FILE=$(do_build) | |
| do_build |
|
|
||
| # Get version from package.json | ||
| get_version() { | ||
| grep -o '"version": *"[^"]*"' package.json | sed 's/"version": *"\(.*\)"/\1/' |
There was a problem hiding this comment.
get_version parses package.json via grep|sed, which is brittle if formatting changes or if another "version" key appears in a different context. Since Node is already a required dependency (and the workflow uses it), consider using node -p "require('./package.json').version" here for a more robust single source of truth.
| grep -o '"version": *"[^"]*"' package.json | sed 's/"version": *"\(.*\)"/\1/' | |
| node -p "require('./package.json').version" |
|
|
||
| # Check npm | ||
| if ! command -v npm &>/dev/null; then | ||
| error "npm is not found. It should come with Node.js." |
There was a problem hiding this comment.
Regression vs the previous build.sh: after attempting to install Node via apt/yum, the script errors out if npm is missing, but on Debian/Ubuntu nodejs often does not include npm. Consider attempting to install npm via install_util npm (or a distro-appropriate package) when npm isn’t found, and adjust the error message accordingly.
| error "npm is not found. It should come with Node.js." | |
| warning "npm is not found. Attempting to install..." | |
| install_util npm || true | |
| fi | |
| if ! command -v npm &>/dev/null; then | |
| error "npm is not installed. On some systems, Node.js does not include npm. Please install npm via your package manager and re-run this script." |
There was a problem hiding this comment.
Actionable comments posted: 3
🤖 Fix all issues with AI agents
In `@build.sh`:
- Around line 5-6: Verify the script exits when directory change fails: make the
cd "$(dirname "$0")" call guarded so the script does not continue if it fails
(e.g., test the return and exit non‑zero or print an error), ensuring the
subsequent exec ./tasks.sh --build "$@" is only run after a successful cd;
update the build.sh around the cd and exec lines to perform that check (use a
conditional or || exit) so tasks.sh is not invoked from the wrong directory.
In `@publish.sh`:
- Around line 5-6: The script currently runs cd "$(dirname "$0")" without
handling failure, so exec ./tasks.sh --publish "$@" may run from the wrong
directory; update publish.sh to detect and handle a failed directory change
(e.g., test the result of cd "$(dirname "$0")" and exit with a non-zero status
if it fails) before calling exec ./tasks.sh --publish "$@"; ensure the check
references the existing commands (cd "$(dirname "$0")" and exec ./tasks.sh
--publish "$@") so the script aborts early on cd failure.
In `@tasks.sh`:
- Around line 412-457: The post-build flow can miss lockfile changes introduced
by do_build() (which runs npm install), so before creating or tagging (where
TAG_NAME and git tag -a are used) add a workspace validation step after
VSIX_FILE is determined: run a git status --porcelain check (or equivalent) that
ignores the generated .vsix (VSIX_FILE) and fails if any other tracked files
changed (e.g., package-lock.json, pnpm-lock.yaml); if changes exist, abort with
an error and instructions to commit or re-run with CI-safe install, or
alternatively modify do_build() to use npm ci instead of npm install to prevent
modifying lockfiles. Ensure this check runs before the git tag creation and
ask_yes_no push prompt.
🧹 Nitpick comments (1)
tasks.sh (1)
141-144:get_version建议用 Node 解析 JSON
grep/sed依赖格式细节,改为 Node 解析更稳健,且脚本已要求安装 Node。建议修复
-get_version() { - grep -o '"version": *"[^"]*"' package.json | sed 's/"version": *"\(.*\)"/\1/' -} +get_version() { + node -p "require('./package.json').version" +}
| # Build | ||
| info "Building extension..." | ||
| VSIX_FILE=$(do_build) | ||
| VSIX_FILE=$(ls -t *.vsix 2>/dev/null | head -1) | ||
|
|
||
| if [ -z "$VSIX_FILE" ]; then | ||
| error "Build failed - no .vsix file found" | ||
| exit 1 | ||
| fi | ||
|
|
||
| success "Built: ${VSIX_FILE}" | ||
|
|
||
| # Publish to marketplace | ||
| if [ "$DRY_RUN" = false ]; then | ||
| info "Publishing to VS Code Marketplace..." | ||
|
|
||
| if [ "$PRE_RELEASE" = true ]; then | ||
| vsce publish --pre-release --packagePath "$VSIX_FILE" | ||
| else | ||
| vsce publish --packagePath "$VSIX_FILE" | ||
| fi | ||
|
|
||
| success "Published to VS Code Marketplace!" | ||
| else | ||
| info "[DRY-RUN] Would publish: vsce publish $([ "$PRE_RELEASE" = true ] && echo "--pre-release ")--packagePath ${VSIX_FILE}" | ||
| fi | ||
|
|
||
| # Create and push tag | ||
| if [ "$DRY_RUN" = false ]; then | ||
| if [ "$SKIP_TAG" = true ]; then | ||
| info "Skipping tag creation (already exists)" | ||
| else | ||
| info "Creating tag ${TAG_NAME}..." | ||
| git tag -a "${TAG_NAME}" -m "Release ege v${VERSION}$([ "$PRE_RELEASE" = true ] && echo " (pre-release)")" | ||
| success "Tag created: ${TAG_NAME}" | ||
|
|
||
| if ask_yes_no "Push tag to remote?"; then | ||
| git push origin "${TAG_NAME}" | ||
| success "Tag pushed to remote" | ||
| else | ||
| warning "Tag not pushed. Run: git push origin ${TAG_NAME}" | ||
| fi | ||
| fi | ||
| else | ||
| info "[DRY-RUN] Would create and push tag: ${TAG_NAME}" | ||
| fi |
There was a problem hiding this comment.
🧩 Analysis chain
🏁 Script executed:
cat -n tasks.sh | head -50Repository: x-ege/ege-vscode-plugin
Length of output: 1826
🏁 Script executed:
git ls-files | grep -E "tasks\.sh|package-lock\.json|\.npmrc|\.nvmrc" | head -20Repository: x-ege/ege-vscode-plugin
Length of output: 75
🏁 Script executed:
rg "do_build" -A 30 tasks.shRepository: x-ege/ege-vscode-plugin
Length of output: 1918
🏁 Script executed:
rg "npm install" tasks.shRepository: x-ege/ege-vscode-plugin
Length of output: 309
🏁 Script executed:
rg "git status|git diff|porcelain" tasks.sh -B 5 -A 5Repository: x-ege/ege-vscode-plugin
Length of output: 498
🏁 Script executed:
rg "check_git_status|validate" tasks.sh -A 10Repository: x-ege/ege-vscode-plugin
Length of output: 49
🏁 Script executed:
rg "publish_main\|do_publish" tasks.sh -A 50 | head -100Repository: x-ege/ege-vscode-plugin
Length of output: 49
🏁 Script executed:
fd "package-lock.json" -o "pnpm-lock.yaml" -o "yarn.lock"Repository: x-ege/ege-vscode-plugin
Length of output: 214
🏁 Script executed:
rg "Check for uncommitted changes" tasks.sh -B 5 -A 30Repository: x-ege/ege-vscode-plugin
Length of output: 1410
🏁 Script executed:
git ls-files | grep -i "package-lock\|pnpm-lock\|yarn.lock"Repository: x-ege/ege-vscode-plugin
Length of output: 49
🏁 Script executed:
rg "git status|git diff" tasks.sh -B 3 -A 8Repository: x-ege/ege-vscode-plugin
Length of output: 673
🏁 Script executed:
rg "do_publish" tasks.sh -A 80 | head -120Repository: x-ege/ege-vscode-plugin
Length of output: 3078
🏁 Script executed:
cat -n tasks.sh | sed -n '200,450p'Repository: x-ege/ege-vscode-plugin
Length of output: 10290
构建后需再次校验工作区状态,防止锁文件修改导致发布产物与标签不一致
虽然发布前会检查未提交的更改(第 250-281 行),但该检查发生在 npm install 之前。do_build() 内的 npm install 调用(第 207 行)可能会修改 package-lock.json 或 pnpm-lock.yaml。构建完成后直接创建标签(第 445 行),工作区的锁文件变更不会被重新验证,导致标签指向的提交与实际构建产物使用的依赖版本不一致。
建议在构建完成后添加工作区验证,确保 .vsix 以外的跟踪文件未被修改,或改用 npm ci 代替 npm install 以保持锁文件不变。
建议修复(构建后再校验)
VSIX_FILE=$(do_build)
VSIX_FILE=$(ls -t *.vsix 2>/dev/null | head -1)
+ # Ensure build didn't modify tracked files (e.g., lockfiles)
+ if git status --porcelain | grep -vE '^\?\? .*\.vsix$' | grep -q .; then
+ error "Working tree changed during build (e.g., lockfile updates). Please commit or stash before publishing."
+ exit 1
+ fi📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| # Build | |
| info "Building extension..." | |
| VSIX_FILE=$(do_build) | |
| VSIX_FILE=$(ls -t *.vsix 2>/dev/null | head -1) | |
| if [ -z "$VSIX_FILE" ]; then | |
| error "Build failed - no .vsix file found" | |
| exit 1 | |
| fi | |
| success "Built: ${VSIX_FILE}" | |
| # Publish to marketplace | |
| if [ "$DRY_RUN" = false ]; then | |
| info "Publishing to VS Code Marketplace..." | |
| if [ "$PRE_RELEASE" = true ]; then | |
| vsce publish --pre-release --packagePath "$VSIX_FILE" | |
| else | |
| vsce publish --packagePath "$VSIX_FILE" | |
| fi | |
| success "Published to VS Code Marketplace!" | |
| else | |
| info "[DRY-RUN] Would publish: vsce publish $([ "$PRE_RELEASE" = true ] && echo "--pre-release ")--packagePath ${VSIX_FILE}" | |
| fi | |
| # Create and push tag | |
| if [ "$DRY_RUN" = false ]; then | |
| if [ "$SKIP_TAG" = true ]; then | |
| info "Skipping tag creation (already exists)" | |
| else | |
| info "Creating tag ${TAG_NAME}..." | |
| git tag -a "${TAG_NAME}" -m "Release ege v${VERSION}$([ "$PRE_RELEASE" = true ] && echo " (pre-release)")" | |
| success "Tag created: ${TAG_NAME}" | |
| if ask_yes_no "Push tag to remote?"; then | |
| git push origin "${TAG_NAME}" | |
| success "Tag pushed to remote" | |
| else | |
| warning "Tag not pushed. Run: git push origin ${TAG_NAME}" | |
| fi | |
| fi | |
| else | |
| info "[DRY-RUN] Would create and push tag: ${TAG_NAME}" | |
| fi | |
| # Build | |
| info "Building extension..." | |
| VSIX_FILE=$(do_build) | |
| VSIX_FILE=$(ls -t *.vsix 2>/dev/null | head -1) | |
| if [ -z "$VSIX_FILE" ]; then | |
| error "Build failed - no .vsix file found" | |
| exit 1 | |
| fi | |
| # Ensure build didn't modify tracked files (e.g., lockfiles) | |
| if git status --porcelain | grep -vE '^\?\? .*\.vsix$' | grep -q .; then | |
| error "Working tree changed during build (e.g., lockfile updates). Please commit or stash before publishing." | |
| exit 1 | |
| fi | |
| success "Built: ${VSIX_FILE}" | |
| # Publish to marketplace | |
| if [ "$DRY_RUN" = false ]; then | |
| info "Publishing to VS Code Marketplace..." | |
| if [ "$PRE_RELEASE" = true ]; then | |
| vsce publish --pre-release --packagePath "$VSIX_FILE" | |
| else | |
| vsce publish --packagePath "$VSIX_FILE" | |
| fi | |
| success "Published to VS Code Marketplace!" | |
| else | |
| info "[DRY-RUN] Would publish: vsce publish $([ "$PRE_RELEASE" = true ] && echo "--pre-release ")--packagePath ${VSIX_FILE}" | |
| fi | |
| # Create and push tag | |
| if [ "$DRY_RUN" = false ]; then | |
| if [ "$SKIP_TAG" = true ]; then | |
| info "Skipping tag creation (already exists)" | |
| else | |
| info "Creating tag ${TAG_NAME}..." | |
| git tag -a "${TAG_NAME}" -m "Release ege v${VERSION}$([ "$PRE_RELEASE" = true ] && echo " (pre-release)")" | |
| success "Tag created: ${TAG_NAME}" | |
| if ask_yes_no "Push tag to remote?"; then | |
| git push origin "${TAG_NAME}" | |
| success "Tag pushed to remote" | |
| else | |
| warning "Tag not pushed. Run: git push origin ${TAG_NAME}" | |
| fi | |
| fi | |
| else | |
| info "[DRY-RUN] Would create and push tag: ${TAG_NAME}" | |
| fi |
🤖 Prompt for AI Agents
In `@tasks.sh` around lines 412 - 457, The post-build flow can miss lockfile
changes introduced by do_build() (which runs npm install), so before creating or
tagging (where TAG_NAME and git tag -a are used) add a workspace validation step
after VSIX_FILE is determined: run a git status --porcelain check (or
equivalent) that ignores the generated .vsix (VSIX_FILE) and fails if any other
tracked files changed (e.g., package-lock.json, pnpm-lock.yaml); if changes
exist, abort with an error and instructions to commit or re-run with CI-safe
install, or alternatively modify do_build() to use npm ci instead of npm install
to prevent modifying lockfiles. Ensure this check runs before the git tag
creation and ask_yes_no push prompt.
- Fix Usage text: clarify that tasks use flags, not positional arguments - Fix get_default_branch: ensure fallback to 'master' when result is empty - Fix VSIX_FILE capture: call do_build directly to show output - Add cd failure protection to build.sh and publish.sh (|| exit 1)
概述
统一构建和发布脚本,修复了版本标签格式问题。
主要变更
tasks.sh作为统一的脚本,处理所有构建/发布任务build.sh和publish.sh转换为包装脚本以保持向后兼容release.yml标签格式,使用 'v' 前缀 (v1.0.3 而不是 1.0.3)优势
变更文件
.github/workflows/release.yml: 修改工作流配置build.sh: 精简,转为包装脚本publish.sh: 添加新的功能tasks.sh: 新增统一脚本(509 行代码)总计: 4 个文件改动,527 行新增,48 行删除
Summary by CodeRabbit
发布说明
New Features
Chores
Refactor
✏️ Tip: You can customize this high-level summary in your review settings.