mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-11-09 20:07:43 +08:00
fix(web): scan plain src for a dual-face browser package without src/client
This commit is contained in:
+2
-2
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-17-web-feature-routes-and-route-gate.md
|
||||
2026-09-17-web-feature-routes-and-route-gate.md: 5c18e23e827c4556c0012147f857f214ecc5641a
|
||||
2026-09-17-web-feature-routes-and-route-gate.zh.md: 2ab045e288e61eb9ba8dc6df222bf3048a11363a
|
||||
2026-09-17-web-feature-routes-and-route-gate.md: 3939bdcd0c53e766827e8fcf05f64fb4d284c59b
|
||||
2026-09-17-web-feature-routes-and-route-gate.zh.md: fb329c8fc1f93b9f979612b7163e838cc521428b
|
||||
|
||||
+1
-1
@@ -28,4 +28,4 @@ The gate reads inline literal fragments only, so a composed reference (`comboRef
|
||||
|
||||
## Testing
|
||||
|
||||
The gate's own spec pins a negative control per rule: interpolated targets (`${origin}/api/file`), protocol-relative and absolute forms, an unstripped shared key, a relative route resolved against a `location` read, and root-absolute producer fields. Each feature package's specs assert the relative route its browser half sends; `markdown-images.e2e.ts` keeps the local-image path covered end to end.
|
||||
The gate's own spec pins a negative control per rule: interpolated targets (`${origin}/api/file`), protocol-relative and absolute forms, an unstripped shared key, a relative route resolved against a `location` read, and root-absolute producer fields. Each feature package's specs assert the relative route its browser half sends; `markdown-images.e2e.ts` keeps the local-image path covered end to end. The discovery spec pins which client projects contribute browser sources: a DOM project the Host aggregate also compiles contributes its plain `src/` unless it has a `src/client` half.
|
||||
|
||||
+1
-1
@@ -28,4 +28,4 @@ Status: implemented
|
||||
|
||||
## 测试
|
||||
|
||||
门禁自身的 spec 为每条规则固定了负向对照:插值目标(`${origin}/api/file`)、协议相对与绝对形式、未剥前导斜杠的共享键、相对路由相对 `location` 读取解析,以及生产者中根绝对的引用字段。各功能包的 spec 断言其浏览器半侧发出的相对路由;`markdown-images.e2e.ts` 端到端维持本地图片路径的覆盖。
|
||||
门禁自身的 spec 为每条规则固定了负向对照:插值目标(`${origin}/api/file`)、协议相对与绝对形式、未剥前导斜杠的共享键、相对路由相对 `location` 读取解析,以及生产者中根绝对的引用字段。各功能包的 spec 断言其浏览器半侧发出的相对路由;`markdown-images.e2e.ts` 端到端维持本地图片路径的覆盖。发现逻辑的 spec 固定了哪些 client 项目贡献浏览器源文件:同时被 Host 聚合编译的 DOM 项目,若没有 `src/client` 半边,则其整个 `src/` 都计入。
|
||||
|
||||
@@ -157,15 +157,26 @@ function writeFaceAggregates(root: string, client: readonly string[], host: read
|
||||
}
|
||||
|
||||
describe('browser face discovery', () => {
|
||||
it('takes sources from the Client aggregate alone', () => {
|
||||
it('takes every DOM client project, and only its src/client half when the Host aggregate also compiles it', () => {
|
||||
const root = mkdtempSync(join(tmpdir(), 'dsh-route-face-'))
|
||||
try {
|
||||
writeProject(root, 'browser-package', { lib: ['ES2024', 'DOM'], sources: ['src/view.ts', 'src/globals.d.ts'] })
|
||||
writeProject(root, 'shared-package', { lib: ['ES2024', 'DOM'], sources: ['src/both-faces.ts'] })
|
||||
writeProject(root, 'host-package', { lib: ['ES2024'], sources: ['src/host.ts'] })
|
||||
writeFaceAggregates(root, ['browser-package', 'shared-package', 'host-package'], ['shared-package', 'host-package'])
|
||||
writeProject(root, 'packages/client/browser-package', { lib: ['ES2024', 'DOM'], sources: ['src/view.ts', 'src/globals.d.ts'] })
|
||||
// Compiles in both aggregates and keeps its browser code in plain `src`.
|
||||
writeProject(root, 'packages/client/shared-package', { lib: ['ES2024', 'DOM'], sources: ['src/both-faces.ts'] })
|
||||
// Compiles in both aggregates but has a `src/client` browser half, so its
|
||||
// plain `src` (the node half) is out of scope.
|
||||
writeProject(root, 'packages/client/dual-package', { lib: ['ES2024', 'DOM'], sources: ['src/client/View.tsx', 'src/node.ts'] })
|
||||
writeProject(root, 'packages/client/host-package', { lib: ['ES2024'], sources: ['src/host.ts'] })
|
||||
writeFaceAggregates(root, [
|
||||
'packages/client/browser-package', 'packages/client/shared-package',
|
||||
'packages/client/dual-package', 'packages/client/host-package',
|
||||
], ['packages/client/shared-package', 'packages/client/dual-package', 'packages/client/host-package'])
|
||||
|
||||
expect(browserFaceSources(root)).toEqual(['browser-package/src/view.ts'])
|
||||
expect(browserFaceSources(root)).toEqual([
|
||||
'packages/client/browser-package/src/view.ts',
|
||||
'packages/client/dual-package/src/client/View.tsx',
|
||||
'packages/client/shared-package/src/both-faces.ts',
|
||||
])
|
||||
} finally {
|
||||
rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
|
||||
@@ -256,11 +256,12 @@ export function findRouteResolutionViolations(
|
||||
*
|
||||
* A browser half belongs to `src/client`; a package that keeps its whole UI in
|
||||
* plain `src` is found through the project itself, which carries the Client
|
||||
* compiler shape (DOM libraries). A project the Host aggregate also compiles
|
||||
* has a host half rather than a browser-only surface, so only its `src/client`
|
||||
* tree is browser face. Every such face must contribute at least one source
|
||||
* file: a face that contributes none means the aggregate's layout moved out
|
||||
* from under this discovery, which must fail loud rather than scan less.
|
||||
* compiler shape (DOM libraries). A config the Host aggregate also compiles has
|
||||
* a host half rather than a browser-only surface, so its plain `src/` is
|
||||
* scanned only when it has no `src/client` half; that half arrives below. Every
|
||||
* such face must contribute at least one source file: a face that contributes
|
||||
* none means the aggregate's layout moved out from under this discovery, which
|
||||
* must fail loud rather than scan less.
|
||||
* @param projectRoot - repository root to discover from.
|
||||
* @returns sorted repository-relative source paths.
|
||||
*/
|
||||
@@ -274,12 +275,18 @@ export function browserFaceSources(projectRoot: string = root): string[] {
|
||||
const hostConfigs = faceConfigs(projectRoot, 'host').byPath
|
||||
|
||||
for (const [configPath, parsed] of faceConfigs(projectRoot, 'client').byPath) {
|
||||
if (parsed.options.lib?.includes('lib.dom.d.ts') !== true || hostConfigs.has(configPath)) continue
|
||||
const sourceRoot = `${dirname(configPath).replaceAll('\\', '/')}/src/`
|
||||
if (parsed.options.lib?.includes('lib.dom.d.ts') !== true) continue
|
||||
const configRoot = dirname(configPath).replaceAll('\\', '/')
|
||||
const sourceRoot = `${configRoot}/src/`
|
||||
// An aggregate or app shell (the root Client program) carries the compiler
|
||||
// shape but no `src/` tree of its own; its browser sources arrive through
|
||||
// the references and `src/client` discovery below.
|
||||
if (!existsSync(sourceRoot)) continue
|
||||
// A package the Host aggregate also compiles has a host half; when its
|
||||
// browser half is `src/client`, plain `src/` may hold the node half, and
|
||||
// `src/client` alone is scanned below. Without that half, all of `src/` is
|
||||
// the browser surface.
|
||||
if (hostConfigs.has(configPath) && existsSync(`${configRoot}/src/client`)) continue
|
||||
const faceSources = parsed.fileNames.filter((file) => {
|
||||
const normalized = file.replaceAll('\\', '/')
|
||||
return normalized.startsWith(sourceRoot) && /\.tsx?$/.test(normalized)
|
||||
|
||||
Reference in New Issue
Block a user