Repository navigation
fix(packaging): bundle audio capture for mirror installs #5747
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 6 commits
f6bd6f3
d4ed941
acf4881
de529fc
4cccaa5
059d48e
c869b5b
672750b
e293c78
5ab7349
ba21195
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -11,21 +11,32 @@ | |||||||||||||||||||||||||||||||||||
| */ | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| import fs from 'node:fs'; | ||||||||||||||||||||||||||||||||||||
| import { createRequire } from 'node:module'; | ||||||||||||||||||||||||||||||||||||
| import path from 'node:path'; | ||||||||||||||||||||||||||||||||||||
| import { fileURLToPath } from 'node:url'; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const __filename = fileURLToPath(import.meta.url); | ||||||||||||||||||||||||||||||||||||
| const __dirname = path.dirname(__filename); | ||||||||||||||||||||||||||||||||||||
| const defaultRootDir = path.resolve(__dirname, '..'); | ||||||||||||||||||||||||||||||||||||
| const nodeRequire = createRequire(import.meta.url); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| export function preparePackage({ rootDir = defaultRootDir } = {}) { | ||||||||||||||||||||||||||||||||||||
| export function preparePackage({ | ||||||||||||||||||||||||||||||||||||
| rootDir = defaultRootDir, | ||||||||||||||||||||||||||||||||||||
| requireNativeAudioCapture = process.env | ||||||||||||||||||||||||||||||||||||
| .QWEN_REQUIRE_AUDIO_CAPTURE_PREBUILD === '1', | ||||||||||||||||||||||||||||||||||||
| } = {}) { | ||||||||||||||||||||||||||||||||||||
| const distDir = path.join(rootDir, 'dist'); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| verifyBundleArtifacts(rootDir, distDir); | ||||||||||||||||||||||||||||||||||||
| copyDocumentationFiles(rootDir, distDir); | ||||||||||||||||||||||||||||||||||||
| copyLocales(rootDir, distDir); | ||||||||||||||||||||||||||||||||||||
| copyExtensionExamples(rootDir, distDir); | ||||||||||||||||||||||||||||||||||||
| writeDistPackageJson(rootDir, distDir); | ||||||||||||||||||||||||||||||||||||
| const bundleNativeAudioCapture = copyNativeAudioCapturePackage( | ||||||||||||||||||||||||||||||||||||
| rootDir, | ||||||||||||||||||||||||||||||||||||
| distDir, | ||||||||||||||||||||||||||||||||||||
| { required: requireNativeAudioCapture }, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| writeDistPackageJson(rootDir, distDir, { bundleNativeAudioCapture }); | ||||||||||||||||||||||||||||||||||||
| printPackageStructure(distDir); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
|
|
@@ -129,7 +140,97 @@ function copyExtensionExamples(rootDir, distDir) { | |||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| function writeDistPackageJson(rootDir, distDir) { | ||||||||||||||||||||||||||||||||||||
| function copyNativeAudioCapturePackage(rootDir, distDir, { required } = {}) { | ||||||||||||||||||||||||||||||||||||
| console.log('Copying native audio capture package...'); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const addonSrc = path.join(rootDir, 'packages', 'audio-capture'); | ||||||||||||||||||||||||||||||||||||
| const addonDest = path.join( | ||||||||||||||||||||||||||||||||||||
| distDir, | ||||||||||||||||||||||||||||||||||||
| 'node_modules', | ||||||||||||||||||||||||||||||||||||
| '@qwen-code', | ||||||||||||||||||||||||||||||||||||
| 'audio-capture', | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| const requiredPaths = [ | ||||||||||||||||||||||||||||||||||||
| path.join(addonSrc, 'dist'), | ||||||||||||||||||||||||||||||||||||
| path.join(addonSrc, 'prebuilds'), | ||||||||||||||||||||||||||||||||||||
| path.join(addonSrc, 'package.json'), | ||||||||||||||||||||||||||||||||||||
| ]; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| for (const requiredPath of requiredPaths) { | ||||||||||||||||||||||||||||||||||||
| if (!fs.existsSync(requiredPath)) { | ||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion] — claude-opus-4-8[1m] via Qwen Code /qreview |
||||||||||||||||||||||||||||||||||||
| const message = `audio capture package artifact not found at ${requiredPath}`; | ||||||||||||||||||||||||||||||||||||
| if (required) { | ||||||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||||||
| `Required ${message}. ` + | ||||||||||||||||||||||||||||||||||||
| 'Cannot publish package without native voice capture.', | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| console.warn(`Warning: ${message}`); | ||||||||||||||||||||||||||||||||||||
| return false; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const addonPkg = JSON.parse( | ||||||||||||||||||||||||||||||||||||
|
Collaborator
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. [Suggestion]
Suggested change
Matches the — qwen3.7-max via Qwen Code /review |
||||||||||||||||||||||||||||||||||||
| fs.readFileSync(path.join(addonSrc, 'package.json'), 'utf8'), | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| const dependencySources = []; | ||||||||||||||||||||||||||||||||||||
| for (const dependencyName of Object.keys(addonPkg.dependencies ?? {})) { | ||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||
| dependencySources.push([ | ||||||||||||||||||||||||||||||||||||
| dependencyName, | ||||||||||||||||||||||||||||||||||||
| path.dirname(nodeRequire.resolve(`${dependencyName}/package.json`)), | ||||||||||||||||||||||||||||||||||||
| ]); | ||||||||||||||||||||||||||||||||||||
| } catch { | ||||||||||||||||||||||||||||||||||||
| throw new Error( | ||||||||||||||||||||||||||||||||||||
| `Required audio capture dependency not resolvable: ${dependencyName}. ` + | ||||||||||||||||||||||||||||||||||||
| 'Cannot publish package without native voice capture.', | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| delete addonPkg.scripts; | ||||||||||||||||||||||||||||||||||||
| delete addonPkg.devDependencies; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const copyOpts = { | ||||||||||||||||||||||||||||||||||||
| recursive: true, | ||||||||||||||||||||||||||||||||||||
| dereference: true, | ||||||||||||||||||||||||||||||||||||
| verbatimSymlinks: false, | ||||||||||||||||||||||||||||||||||||
| }; | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| fs.rmSync(addonDest, { recursive: true, force: true }); | ||||||||||||||||||||||||||||||||||||
| fs.mkdirSync(addonDest, { recursive: true }); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| fs.writeFileSync( | ||||||||||||||||||||||||||||||||||||
| path.join(addonDest, 'package.json'), | ||||||||||||||||||||||||||||||||||||
| JSON.stringify(addonPkg, null, 2) + '\n', | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| fs.cpSync(path.join(addonSrc, 'dist'), path.join(addonDest, 'dist'), { | ||||||||||||||||||||||||||||||||||||
| ...copyOpts, | ||||||||||||||||||||||||||||||||||||
| filter: (src) => !/\.test\.(d\.)?[mc]?[jt]s(\.map)?$/.test(src), | ||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||
| fs.cpSync( | ||||||||||||||||||||||||||||||||||||
| path.join(addonSrc, 'prebuilds'), | ||||||||||||||||||||||||||||||||||||
| path.join(addonDest, 'prebuilds'), | ||||||||||||||||||||||||||||||||||||
| copyOpts, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| for (const [dependencyName, dependencySrc] of dependencySources) { | ||||||||||||||||||||||||||||||||||||
| fs.cpSync( | ||||||||||||||||||||||||||||||||||||
| dependencySrc, | ||||||||||||||||||||||||||||||||||||
| path.join(addonDest, 'node_modules', dependencyName), | ||||||||||||||||||||||||||||||||||||
| copyOpts, | ||||||||||||||||||||||||||||||||||||
| ); | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| console.log('Copied native audio capture package'); | ||||||||||||||||||||||||||||||||||||
| return true; | ||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| function writeDistPackageJson( | ||||||||||||||||||||||||||||||||||||
| rootDir, | ||||||||||||||||||||||||||||||||||||
| distDir, | ||||||||||||||||||||||||||||||||||||
| { bundleNativeAudioCapture = false } = {}, | ||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||
| console.log('Creating package.json for distribution...'); | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
| const cliEntryContent = `#!/usr/bin/env node | ||||||||||||||||||||||||||||||||||||
|
|
@@ -190,6 +291,9 @@ if (result.signal) { | |||||||||||||||||||||||||||||||||||
| 'bundled', | ||||||||||||||||||||||||||||||||||||
| 'web-shell', | ||||||||||||||||||||||||||||||||||||
| ], | ||||||||||||||||||||||||||||||||||||
| ...(bundleNativeAudioCapture | ||||||||||||||||||||||||||||||||||||
| ? { bundledDependencies: ['@qwen-code/audio-capture'] } | ||||||||||||||||||||||||||||||||||||
| : {}), | ||||||||||||||||||||||||||||||||||||
| config: rootPackageJson.config, | ||||||||||||||||||||||||||||||||||||
| dependencies: {}, | ||||||||||||||||||||||||||||||||||||
| optionalDependencies: { | ||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
[Critical] False-positive error rewrite.
packages/audio-capture/src/index.ts:62-70(loadBinding()) wraps any inner failure fromnode-gyp-buildor prebuild loading with'Native audio capture addon could not be loaded. Reinstall @qwen-code/audio-capture, ... (' + error.message + ')'. When the inner error is itself aCannot find module 'node-gyp-build'(e.g. the bundled dep was not copied, or its files are corrupted), the resulting message contains both@qwen-code/audio-captureandCannot find module, so this substring check passes and the error is rewritten to "package is missing, reinstall from npmjs.org".That confidently points the operator at the registry mirror when the package is installed and the real root cause is an incomplete bundled
node_modules/tree (precisely the failure mode the bundling added in this PR is supposed to prevent). The originalCannot find module 'node-gyp-build'detail ends up buried in parentheses at the end of the rewritten message.The
startsWithguard rejectsloadBinding()'s wrapped message (which starts withNative audio capture addon could not be loaded) while still matching the ESM/CJS loader's genuine resolution failures (which start withCannot find package '@qwen-code/audio-capture' ...).— qwen3.7-max via Qwen Code /review