@@ -11,14 +11,15 @@ name: 'CD: Qwen CUA Driver'
1111# imported cert (no DEVELOPER_NAME secret needed).
1212# Windows ships UNSIGNED (matches upstream — no EV cert).
1313#
14- # Artifact convention (matches the qwen-code computer-use downloader) :
14+ # Artifact convention used by the @ qwen-code/cua-sdk native installer :
1515# macOS : cua-driver-rs-<v>-darwin-{arm64,x86_64,universal}.tar.gz
1616# (each contains the Qwen binary, SDK payload, and signed
1717# QwenCuaDriver.app) + *-binary.tar.gz (runtime + SDK payload)
1818# Linux : cua-driver-rs-<v>-linux-{x86_64,arm64}.tar.gz + *-binary.tar.gz
1919# Windows: cua-driver-rs-<v>-windows-{x86_64,arm64}.zip + *-binary.zip
20- # npm : one platform-neutral @qwen-code/cua-sdk package. Its postinstall
21- # downloads and verifies the matching *-binary archive above.
20+ # npm : @qwen-code/cua-sdk downloads and verifies the matching *-binary
21+ # archive above. @qwen-code/node-repl-mcp is independently
22+ # versioned and ships from the same verified workflow.
2223on :
2324 push :
2425 tags : ['cua-driver-rs-v*']
3738 default : true
3839 dry_run :
3940 description :
40- ' Build all release assets and clean-install the npm package without
41+ ' Build release assets and clean-install both npm packages without
4142 creating a GitHub Release, npm version, tag, or installer sync PR.'
4243 required : false
4344 type : ' boolean'
4445 default : true
46+ node_repl_only :
47+ description :
48+ ' Only verify/publish @qwen-code/node-repl-mcp; do not build or release
49+ cua-driver assets.'
50+ required : false
51+ type : ' boolean'
52+ default : false
4553permissions :
4654 contents : ' write'
4755jobs :
5058 runs-on : ' ubuntu-latest'
5159 outputs :
5260 version : ' ${{ steps.release.outputs.version }}'
61+ node_repl_version : ' ${{ steps.release.outputs.node_repl_version }}'
5362 steps :
5463 - uses : ' actions/checkout@v4'
5564 with :
@@ -82,11 +91,22 @@ jobs:
8291 exit 1
8392 fi
8493
94+ NODE_REPL_NAME=$(node -p "require('./packages/node-repl/package.json').name")
95+ NODE_REPL_VERSION=$(node -p "require('./packages/node-repl/package.json').version")
96+ if [[ "$NODE_REPL_NAME" != "@qwen-code/node-repl-mcp" ]]; then
97+ echo "::error::Expected @qwen-code/node-repl-mcp, found $NODE_REPL_NAME"
98+ exit 1
99+ fi
100+ if [[ ! "$NODE_REPL_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+(-[0-9A-Za-z.-]+)?$ ]]; then
101+ echo "::error::Invalid Node REPL package version $NODE_REPL_VERSION"
102+ exit 1
103+ fi
104+
85105 if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.dry_run }}" != "true" && "$GITHUB_REF" != "refs/heads/main" ]]; then
86106 echo "::error::A production dispatch must run from protected main"
87107 exit 1
88108 fi
89- if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.dry_run }}" != "true" && "${{ inputs.notarize }}" != "true" ]]; then
109+ if [[ "${{ github.event_name }}" == "workflow_dispatch" && "${{ inputs.dry_run }}" != "true" && "${{ inputs.node_repl_only }}" != "true" && "${{ inputs. notarize }}" != "true" ]]; then
90110 echo "::error::A production dispatch must enable macOS codesigning and notarization"
91111 exit 1
92112 fi
@@ -98,8 +118,15 @@ jobs:
98118 fi
99119 fi
100120 echo "version=$VERSION" >> "$GITHUB_OUTPUT"
121+ echo "node_repl_version=$NODE_REPL_VERSION" >> "$GITHUB_OUTPUT"
101122 - uses : ' dtolnay/rust-toolchain@1.97.1'
123+ if :
124+ " github.event_name != 'workflow_dispatch' || inputs.node_repl_only ==
125+ false"
102126 - name : ' Test model payload filtering'
127+ if :
128+ " github.event_name != 'workflow_dispatch' || inputs.node_repl_only ==
129+ false"
103130 working-directory : ' packages/cua-driver/rust'
104131 run : ' cargo test -p cua-driver-core --locked model_payload'
105132
@@ -110,6 +137,8 @@ jobs:
110137 build-linux :
111138 name : ' linux-${{ matrix.arch }}'
112139 needs : ['validate-version']
140+ if : " github.event_name != 'workflow_dispatch' || inputs.node_repl_only ==
141+ false"
113142 runs-on : ' ${{ matrix.runner }}'
114143 strategy :
115144 fail-fast : false
@@ -200,6 +229,8 @@ jobs:
200229 build-windows :
201230 name : ' windows-${{ matrix.arch }}'
202231 needs : ['validate-version']
232+ if : " github.event_name != 'workflow_dispatch' || inputs.node_repl_only ==
233+ false"
203234 runs-on : ' windows-latest'
204235 strategy :
205236 fail-fast : false
@@ -279,6 +310,8 @@ jobs:
279310 build-macos :
280311 name : ' macos-universal'
281312 needs : ['validate-version']
313+ if : " github.event_name != 'workflow_dispatch' || inputs.node_repl_only ==
314+ false"
282315 runs-on : ' macos-26'
283316 env :
284317 # Sign+notarize on tag push (real release); on manual dispatch honor the
@@ -513,7 +546,7 @@ jobs:
513546 done
514547
515548 verify-sdk-package :
516- name : ' single npm package dry-run'
549+ name : ' @qwen-code/cua-sdk package dry-run'
517550 needs : ['validate-version', 'build-linux', 'verify-release-artifacts']
518551 runs-on : ' ubuntu-latest'
519552 permissions :
@@ -578,6 +611,46 @@ jobs:
578611 if-no-files-found : ' error'
579612 retention-days : 7
580613
614+ verify-node-repl-package :
615+ name : ' @qwen-code/node-repl-mcp package dry-run'
616+ needs : ['validate-version']
617+ runs-on : ' ubuntu-latest'
618+ permissions :
619+ contents : ' read'
620+ steps :
621+ - uses : ' actions/checkout@v4'
622+ - uses : ' actions/setup-node@v4'
623+ with :
624+ node-version : ' 22'
625+ cache : ' npm'
626+ cache-dependency-path : ' package-lock.json'
627+ registry-url : ' https://registry.npmjs.org'
628+ scope : ' @qwen-code'
629+ - name : ' Install npm 11'
630+ run : ' npm install --global npm@11.19.0'
631+ - name : ' Install workspace dependencies'
632+ run : ' npm ci --ignore-scripts --no-audit --no-fund --progress=false'
633+ - name : ' Test standalone Node REPL package'
634+ working-directory : ' packages/node-repl'
635+ run : |
636+ npm run build
637+ npm run typecheck
638+ npm test
639+ npm run smoke
640+ npm run smoke:mcp
641+ npm run smoke:lifecycle
642+ - name : ' Pack, publish-dry-run, and clean-install Node REPL tarball'
643+ run : |
644+ node packages/node-repl/scripts/verify-package.mjs \
645+ --output-dir "${RUNNER_TEMP}/node-repl-package"
646+ - uses : ' actions/upload-artifact@v4'
647+ with :
648+ name : ' node-repl-mcp-npm-${{
649+ needs.validate-version.outputs.node_repl_version }}'
650+ path : ' ${{ runner.temp }}/node-repl-package/*.tgz'
651+ if-no-files-found : ' error'
652+ retention-days : 7
653+
581654 # ── Release ──────────────────────────────────────────────────────────────
582655 release :
583656 name : ' Create GitHub Release'
@@ -588,13 +661,15 @@ jobs:
588661 ' build-macos' ,
589662 ' verify-release-artifacts' ,
590663 ' verify-sdk-package' ,
664+ ' verify-node-repl-package' ,
591665 ]
592666 runs-on : ' ubuntu-latest'
593667 outputs :
594668 version : ' ${{ steps.version.outputs.version }}'
595669 if :
596670 " startsWith(github.ref, 'refs/tags/cua-driver-rs-v') || (github.event_name
597- == 'workflow_dispatch' && inputs.dry_run == false)"
671+ == 'workflow_dispatch' && inputs.dry_run == false &&
672+ inputs.node_repl_only == false)"
598673 steps :
599674 - uses : ' actions/checkout@v4'
600675 - uses : ' actions/download-artifact@v4'
@@ -691,9 +766,10 @@ jobs:
691766 - **macOS**: codesigned + notarized universal binary + `QwenCuaDriver.app`
692767 - **Linux**: unsigned (x86_64 + arm64, glibc 2.31 floor)
693768 - **Windows**: unsigned (x86_64 + arm64)
694- - **Node.js**: the same workflow publishes the single
695- `@qwen-code/cua-sdk` npm package after a clean install against
696- these release assets
769+ - **Node.js**: the same workflow publishes `@qwen-code/cua-sdk`
770+ after a clean install against these release assets and publishes
771+ the independently versioned `@qwen-code/node-repl-mcp` package
772+ after its MCP protocol smoke tests
697773
698774 Enable relative coordinates: `CUA_DRIVER_RS_COORDINATE_SPACE=1`
699775 (default `0` = off; optional `CUA_DRIVER_RS_COORDINATE_SCALE=1000`).
@@ -706,7 +782,8 @@ jobs:
706782 needs : ['validate-version', 'verify-sdk-package', 'release']
707783 if :
708784 " startsWith(github.ref, 'refs/tags/cua-driver-rs-v') || (github.event_name
709- == 'workflow_dispatch' && inputs.dry_run == false)"
785+ == 'workflow_dispatch' && inputs.dry_run == false &&
786+ inputs.node_repl_only == false)"
710787 runs-on : ' ubuntu-latest'
711788 environment :
712789 name : ' production-release'
@@ -827,9 +904,93 @@ jobs:
827904 echo '::error::Published npm version did not become visible'
828905 exit 1
829906
907+ publish-node-repl :
908+ name : ' Publish @qwen-code/node-repl-mcp'
909+ needs : ['validate-version', 'verify-node-repl-package', 'release']
910+ if : " always() && needs.validate-version.result == 'success' &&
911+ needs.verify-node-repl-package.result == 'success' &&
912+ (needs.release.result == 'success' || (github.event_name ==
913+ 'workflow_dispatch' && inputs.node_repl_only == true)) &&
914+ (startsWith(github.ref, 'refs/tags/cua-driver-rs-v') ||
915+ (github.event_name == 'workflow_dispatch' && inputs.dry_run == false))"
916+ runs-on : ' ubuntu-latest'
917+ environment :
918+ name : ' production-release'
919+ url : ' https://www.npmjs.com/package/@qwen-code/node-repl-mcp/v/${{
920+ needs.validate-version.outputs.node_repl_version }}'
921+ permissions :
922+ contents : ' read'
923+ id-token : ' write'
924+ steps :
925+ - uses : ' actions/setup-node@v4'
926+ with :
927+ node-version : ' 22'
928+ registry-url : ' https://registry.npmjs.org'
929+ scope : ' @qwen-code'
930+ - name : ' Install npm 11'
931+ run : ' npm install --global npm@11.19.0'
932+ - uses : ' actions/download-artifact@v4'
933+ with :
934+ name : ' node-repl-mcp-npm-${{
935+ needs.validate-version.outputs.node_repl_version }}'
936+ path : ' ${{ runner.temp }}/node-repl-package'
937+ - name : ' Require npm publishing token'
938+ env :
939+ NODE_AUTH_TOKEN : ' ${{ secrets.NPM_TOKEN }}'
940+ run : |
941+ if [[ -z "$NODE_AUTH_TOKEN" ]]; then
942+ echo '::error::production-release NPM_TOKEN is required for the first @qwen-code/node-repl-mcp publication'
943+ exit 1
944+ fi
945+ - name : ' Publish immutable Node REPL tarball'
946+ env :
947+ NODE_AUTH_TOKEN : ' ${{ secrets.NPM_TOKEN }}'
948+ VERSION : ' ${{ needs.validate-version.outputs.node_repl_version }}'
949+ run : |
950+ set -euo pipefail
951+ TARBALL=$(find "${RUNNER_TEMP}/node-repl-package" -type f -name '*.tgz' -print -quit)
952+ if [[ -z "$TARBALL" ]]; then
953+ echo '::error::Packed @qwen-code/node-repl-mcp tarball is missing'
954+ exit 1
955+ fi
956+ LOCAL_INTEGRITY="sha512-$(openssl dgst -sha512 -binary "$TARBALL" | openssl base64 -A)"
957+ REMOTE_INTEGRITY=$(npm view "@qwen-code/node-repl-mcp@${VERSION}" dist.integrity 2>/dev/null || true)
958+ if [[ -n "$REMOTE_INTEGRITY" ]]; then
959+ if [[ "$REMOTE_INTEGRITY" != "$LOCAL_INTEGRITY" ]]; then
960+ echo "::error::@qwen-code/node-repl-mcp@${VERSION} already exists with different integrity"
961+ exit 1
962+ fi
963+ echo "@qwen-code/node-repl-mcp@${VERSION} already contains this exact tarball"
964+ exit 0
965+ fi
966+ NPM_TAG=latest
967+ [[ "$VERSION" == *-* ]] && NPM_TAG=next
968+ npm publish "$TARBALL" --provenance --access public --tag "$NPM_TAG"
969+ - name : ' Verify npm registry integrity'
970+ env :
971+ VERSION : ' ${{ needs.validate-version.outputs.node_repl_version }}'
972+ run : |
973+ set -euo pipefail
974+ TARBALL=$(find "${RUNNER_TEMP}/node-repl-package" -type f -name '*.tgz' -print -quit)
975+ LOCAL_INTEGRITY="sha512-$(openssl dgst -sha512 -binary "$TARBALL" | openssl base64 -A)"
976+ for attempt in {1..20}; do
977+ REMOTE_INTEGRITY=$(npm view "@qwen-code/node-repl-mcp@${VERSION}" dist.integrity 2>/dev/null || true)
978+ if [[ "$REMOTE_INTEGRITY" == "$LOCAL_INTEGRITY" ]]; then
979+ echo "Verified npm registry integrity on attempt $attempt"
980+ exit 0
981+ fi
982+ if [[ -n "$REMOTE_INTEGRITY" ]]; then
983+ echo '::error::Published npm artifact integrity does not match the verified tarball'
984+ exit 1
985+ fi
986+ sleep 10
987+ done
988+ echo '::error::Published npm version did not become visible'
989+ exit 1
990+
830991 sync-installer-version :
831992 name : ' Sync installer version to main'
832- needs : ['release', 'publish-sdk']
993+ needs : ['release', 'publish-sdk', 'publish-node-repl' ]
833994 runs-on : ' ubuntu-latest'
834995 permissions :
835996 contents : ' write'
0 commit comments