mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-08-21 14:46:19 +00:00
* fix(devx): fail with actionable message when unit-test build prerequisites are missing (#9149) Package-local unit tests in packages/cli import workspace packages (acp-bridge, sdk, web-templates, channels/*) through their built dist/ output plus the generated git-commit.ts. In a fresh worktree that shares the main checkout's node_modules, or after a deep clean, those artifacts do not exist and vitest fails during collection with resolution errors that blame the wrong thing. Add a vitest globalSetup guard that checks the prerequisites up front and exits with a message naming every missing piece and the command that fixes it (npm run build). Document the prerequisite in the AGENTS.md unit-testing section. * fix(devx): address R1 review findings on the unit-test prerequisite guard - normalize win32 path separators so the guard works on Windows (R1-1) - use Copyright 2026 Qwen Team header (R1-2) - drop over-included packages/sdk-typescript; every sdk import in the cli test graph is an aliased /daemon* subpath (R1-3) - add a sync assertion test: every builtin channel dynamically imported by channel-registry.ts must stay listed in DIST_PREREQUISITES (R1-4) - cover the vitest-invoked entry point via exported checkAndReport (R1-6) - mirror real manifest shapes (exports.default / import variants) in the test fixtures (R1-7) - report missing/unreadable package manifests through the normal exit path instead of crashing with a raw stack trace (R1-8) - derive the package key from vitest-s resolved project root, so vitest run --root packages/cli from elsewhere is covered (R1-9) - probe every exports entry targeting dist/, not only the . entry, so missing unaliased subpath builds are reported too (R2-1) * fix(devx): extend the unit-test prerequisite guard to packages/core Issue #9149's scope names packages/cli AND packages/core, but the guard only covered cli: eight core test files (providers/__tests__/presets/*, provider-config.test.ts) import the bare '@qwen-code/qwen-code-core' specifier, which resolves through the package's own exports to dist/index.js — on a fresh checkout 'cd packages/core && npx vitest run src/path/to/file.test.ts' (the AGENTS.md-documented command) still died with the opaque 'Failed to resolve entry for package' error. - Add 'packages/core': ['packages/core'] to DIST_PREREQUISITES - Wire the same globalSetup guard into packages/core/vitest.config.ts - Skip wildcard pattern exports entries ('./dist/*') in distEntryFiles: core's manifest carries them and they name no individual file — probing them literally would block core test runs even fully built - Generalize the fixture builder to every DIST/GENERATED_PREREQUISITES entry, add coverage for the core dist requirement and the wildcard skip, and move the 'no known prerequisites' example off packages/core - Note the core self-import in AGENTS.md Probe-verified both arms at this commit: dist moved aside -> the guard prints the actionable message and stops the run; dist restored -> the test file passes (11/11). * fix(devx): harden the prerequisite probe and its drift tests - Probe manifest 'main' entries spelled without a leading './': all guarded manifests use "main": "dist/index.js", which the old startsWith('./dist/') predicate never matched, so the documented main probe silently collected nothing. Normalize before the prefix check. - Tolerate digits in builtin channel names in the sync test's registry regex ('channel-[a-z0-9-]+'), or a future channel with a digit in its npm name escapes the drift check. - Add the reverse sync assertion: every listed packages/channels/* prerequisite must map back to a channel-registry import (channel-base excepted as the channels' build dependency), so removing a builtin channel cannot leave a stale entry that hard-blocks cli test runs with a misleading 'fresh checkout' message. - Cover the main-entry probe with a fixture test. * fix(devx): fail loud on stale probes, align key derivation under symlinks Round-3 review findings on the prerequisite guard: - R3-1: a listed package whose manifest enumerates zero ./dist/ targets (require-only or nested-condition entries) passed the probe silently — report 'exposes no dist/ entry files to check (guard probe may be stale)' instead, so a stale probe cannot resurrect the raw resolution error this guard exists to replace. - R3-3: when SOME dist entry files exist, the missing one is no longer diagnosed as 'has not been built' + a plain npm-run-build prescription (a successful build can legitimately leave a stale exports entry); the message now says the build output is incomplete or exports points at a file the build does not emit, and to check the package's exports entries when rebuilding does not help. - Key derivation now realpaths both cwd and root (with a fallback to the raw path): repoRoot descends from import.meta.url, which Node resolves through symlinks, while vitest resolves root with a plain path.resolve — comparing them raw let a symlinked ancestor silently disable the guard. - R3-2: the channel drift-check character class now tolerates digits, underscores and dots per npm naming rules. - R3-4: renamed the test that claimed win32-separator coverage it never exercised; its body is the degenerate repo-root silent-yield path and the comment now says so. - R1-6 (partial): added default-export coverage — project.config.root extraction and the process.cwd() fallback, asserting no exit in a built repo. The exit-1 arm of the default export stays untested: it needs a root-injectable seam the entry point deliberately does not have; checkAndReport's return-1 and message remain covered directly. Tests: 21/21; mutation probes confirm the zero-enumeration and symlink tests catch their regressions. * fix(devx): hermetic guard tests, alias-aware probe, explicit gitlab build - drive the default-export tests against a hermetic fixture checkout via QWEN_VITEST_GUARD_ROOT (in-process and subprocess), so they hold on an unbuilt worktree instead of depending on the real repository state - skip dist targets the consumer aliases to TypeScript source when probing, so a missing-but-aliased dist file no longer blocks runs that would pass - make the remedy message context-aware: the git-commit hint appears only when a generated file is missing, and a stale-probe line gets its own note - add packages/channels/gitlab to buildOrder: it is a cli channel-registry builtin like its siblings and used to build only transitively - add drift tests pinning the globalSetup wiring in both vitest configs * fix(devx): ignore commented vitest aliases * fix(devx): anchor the vitest globalSetup guard to the config file R1-1: a relative globalSetup path is resolved against vitest's root (the process cwd without --root), not the config file's directory, so the prerequisite guard only loaded when vitest happened to run from inside the package. `npx vitest run --config packages/<pkg>/vitest.config.ts` from the repository root died with "Cannot find module .../vitest-global-setup.js" before any test — the cause-hiding failure class this guard replaces. Resolve it with path.resolve(__dirname, ...) in both packages/cli and packages/core configs, and update the wiring-sync assertion (now robust to prettier line-wrapping; flip-verified red when reverted to a bare string). --------- Co-authored-by: yiliang114 <yiliang114@users.noreply.github.com>
118 lines
3.9 KiB
JavaScript
118 lines
3.9 KiB
JavaScript
/**
|
|
* @license
|
|
* Copyright 2025 Google LLC
|
|
* SPDX-License-Identifier: Apache-2.0
|
|
*/
|
|
|
|
//
|
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
// you may not use this file except in compliance with the License.
|
|
// You may obtain a copy of the License at
|
|
//
|
|
// http://www.apache.org/licenses/LICENSE-2.0
|
|
//
|
|
// Unless required by applicable law or agreed to in writing, software
|
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
// See the License for the specific language governing permissions and
|
|
// limitations under the License.
|
|
|
|
import { execSync } from 'node:child_process';
|
|
import { existsSync } from 'node:fs';
|
|
import { dirname, join } from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
const root = join(__dirname, '..');
|
|
|
|
// npm install if node_modules was removed (e.g. via npm run clean or scripts/clean.js)
|
|
if (!existsSync(join(root, 'node_modules'))) {
|
|
execSync('npm install', { stdio: 'inherit', cwd: root });
|
|
}
|
|
|
|
// build all workspaces/packages in dependency order
|
|
execSync('npm run generate', { stdio: 'inherit', cwd: root });
|
|
|
|
// --cli-only: skip packages not needed by the CLI bundle
|
|
// (webui, web-shell, vscode-ide-companion are for IDE/web use only)
|
|
const cliOnly = process.argv.includes('--cli-only');
|
|
|
|
// Build in dependency order:
|
|
// 1. core (foundation package, includes test-utils)
|
|
// 2. web-templates (embeddable web templates - used by cli)
|
|
// 3. channel-base (base channel infrastructure - used by channel adapters and cli)
|
|
// 4. channel adapters (depend on channel-base)
|
|
// 5. audio-capture (native microphone backend used by cli)
|
|
// 6. acp-bridge (depends on core - used by cli)
|
|
// 7. sdk (build-time devDep on acp-bridge for shared constants, used by cli channel worker)
|
|
// 8. cli (depends on core, acp-bridge, web-templates, channel packages, sdk)
|
|
// 9. webui (shared UI components - used by vscode companion)
|
|
// 10. web-shell (depends on webui and sdk)
|
|
// 11. vscode-ide-companion (depends on webui)
|
|
// 12. external-context (private Qwen extension)
|
|
const buildOrder = [
|
|
'packages/core',
|
|
'packages/web-templates',
|
|
'packages/channels/base',
|
|
'packages/channels/telegram',
|
|
'packages/channels/weixin',
|
|
'packages/channels/dingtalk',
|
|
'packages/channels/wecom',
|
|
'packages/channels/feishu',
|
|
'packages/channels/qqbot',
|
|
'packages/channels/github',
|
|
// gitlab is a builtin of the cli channel registry like its siblings; it
|
|
// used to build only transitively via cli's tsconfig project reference.
|
|
'packages/channels/gitlab',
|
|
'packages/channels/plugin-example',
|
|
'packages/audio-capture',
|
|
'packages/acp-bridge',
|
|
'packages/sdk-typescript',
|
|
'packages/cli',
|
|
...(cliOnly
|
|
? []
|
|
: [
|
|
'packages/webui',
|
|
'packages/web-shell',
|
|
'packages/vscode-ide-companion',
|
|
'packages/chrome-extension',
|
|
'integrations/external-context',
|
|
]),
|
|
];
|
|
|
|
for (const workspace of buildOrder) {
|
|
const command =
|
|
workspace === 'packages/audio-capture'
|
|
? `npm run build:ts --workspace=${workspace}`
|
|
: `npm run build --workspace=${workspace}`;
|
|
execSync(command, { stdio: 'inherit', cwd: root });
|
|
|
|
// After cli is built, generate the JSON Schema for settings
|
|
// so the vscode-ide-companion extension can provide IntelliSense
|
|
if (workspace === 'packages/cli') {
|
|
execSync('node --import tsx/esm scripts/generate-settings-schema.ts', {
|
|
stdio: 'inherit',
|
|
cwd: root,
|
|
});
|
|
}
|
|
}
|
|
|
|
// also build container image if sandboxing is enabled
|
|
// skip (-s) npm install + build since we did that above
|
|
try {
|
|
execSync('node scripts/sandbox_command.js -q', {
|
|
stdio: 'inherit',
|
|
cwd: root,
|
|
});
|
|
if (
|
|
process.env.BUILD_SANDBOX === '1' ||
|
|
process.env.BUILD_SANDBOX === 'true'
|
|
) {
|
|
execSync('node scripts/build_sandbox.js -s', {
|
|
stdio: 'inherit',
|
|
cwd: root,
|
|
});
|
|
}
|
|
} catch {
|
|
// ignore
|
|
}
|