deepseek-harness/packages/bundle/base/tests/base.spec.ts

82 lines
3.8 KiB
TypeScript
Raw Normal View History

/**
* The bundle's substance is its patch file: the `dsh.bundle.patch` manifest
* field must name a real, parseable patch list.
*/
import { existsSync, readFileSync } from 'node:fs'
import { fileURLToPath } from 'node:url'
import { resolve } from 'node:path'
import { describe, expect, it } from 'vitest'
import * as yaml from 'js-yaml'
build(vendor): rescope the vendored Cordis packages into @deepseek-ai Machine-produced by `pnpm run rescope-vendor --apply` plus the regeneration it prints: `pnpm install` for the lockfile, `pnpm run gen-third-party-notices`, `verify-translation-pairing --write` for the touched bilingual pairs, `gen-doc-graphs`, and one typert snapshot whose ids embed character offsets. `pnpm run rescope-vendor --check` verifies the result. Renames nine vendored packages (cordis, cosmokit, schemastery and the six @cordisjs plugins) and every reference that resolves them: manifest names and dependency keys, module specifiers including declare-module merges, cordis.yml plugin names, tsconfig paths, every Markdown fence, and `docs/` prose. Directory names, upstream versions, and dependency ranges are unchanged, so vendor/README.md still reads as an upstream snapshot; its manifest table gains an upstream-name column so THIRD_PARTY_NOTICES keeps MIT attribution pointed at each fork's origin. The tutorial tier follows the rename end to end: its yaml fences named plugins the Loader can no longer resolve, its `ts ignore-check` fences disagreed with the compiled fences beside them, and its prose quoted both. The contracts that told readers to keep upstream names — the root convention and the vendoring cookbook's tree comment and manifest invariant — now say to rescope instead. Two rules read `@deepseek-ai/` as "another workspace plugin": the client bundle purity gate now names the vendored libraries a browser bundle inlines, and the files where a bare `cordis` is an agent-preset id keep that product data.
2026-08-10 22:04:06 +08:00
import { entryListSchema } from '@deepseek-ai/cordis-plugin-include'
import { evaluate } from '@deepseek-ai/cordis-plugin-loader'
describe('dsh-base bundle', () => {
it('declares a parseable patch list through the dsh.bundle.patch manifest field', () => {
const root = fileURLToPath(new URL('..', import.meta.url))
const manifest = JSON.parse(
readFileSync(resolve(root, 'package.json'), 'utf8'),
) as {
dependencies?: Record<string, string>
dsh?: { bundle?: { patch?: string } }
}
expect(manifest.dsh?.bundle?.patch).toBe('./cordis.patch.yml')
const parsed = yaml.load(
readFileSync(resolve(root, manifest.dsh!.bundle!.patch!), 'utf8'),
{ schema: entryListSchema },
)
expect(Array.isArray(parsed)).toBe(true)
// The base layer is one insert list over the empty profile root.
const rows = (parsed as { insert?: { id?: string; config?: Record<string, unknown>; disabled?: boolean }[] }[]).flatMap(
patch => patch.insert ?? [],
)
expect(rows.length).toBeGreaterThan(50)
expect(rows.some(row => row.id === 'agent-loop')).toBe(true)
expect(rows.find(row => row.id === 'session-telemetry-otel')?.config?.['mode']).toEqual({
__jsExpr: "process.env.DSH_TELEMETRY_MODE || 'DISABLED'",
})
expect(rows.find(row => row.id === 'hmr')).toMatchObject({
disabled: true,
config: { root: ['.'] },
})
expect(rows.filter(row => row.id === 'subagent-codex')).toHaveLength(0)
expect(rows.filter(row => row.id === 'subagent-claude-code')).toHaveLength(0)
expect(manifest.dependencies).not.toHaveProperty('@deepseek-ai/dsh-subagent-codex')
expect(manifest.dependencies).not.toHaveProperty('@deepseek-ai/dsh-subagent-claude-code')
})
it('gates each shell stack by platform with a symmetric disabled expression', () => {
const root = fileURLToPath(new URL('..', import.meta.url))
const parsed = yaml.load(
readFileSync(resolve(root, 'cordis.patch.yml'), 'utf8'),
{ schema: entryListSchema },
)
if (!Array.isArray(parsed)) throw new TypeError('base patch must parse to a patch list')
const rows = parsed.flatMap((patch): Record<string, unknown>[] =>
typeof patch === 'object' && patch !== null
? (patch as { insert?: Record<string, unknown>[] }).insert ?? []
: [],
)
// Symmetric gating: each stack's executor and tool rows carry the same
// platform fact, inverted between the bash and pwsh twins, so exactly one
// shell stack mounts per host. Evaluate with a platform-scoped context
// (the `with` scope shadows the global `process`) so both outcomes pin on
// every host.
for (const [id, win32, linux] of [
['bash-sandbox', true, false],
['tool-bash', true, false],
['pwsh-sandbox', false, true],
['tool-pwsh', false, true],
] as const) {
const row = rows.find(candidate => candidate.id === id)
if (row === undefined) throw new Error(`base patch must mount ${id}`)
const expression = (row.disabled as { __jsExpr?: string } | undefined)?.__jsExpr
if (expression === undefined) throw new Error(`${id} must gate on a !!js disabled expression`)
expect(Boolean(evaluate({ process: { platform: 'win32' } }, expression)), `${id} on win32`).toBe(win32)
expect(Boolean(evaluate({ process: { platform: 'linux' } }, expression)), `${id} on linux`).toBe(linux)
}
// The platform layer folded into these rows: no separate patch file ships.
expect(existsSync(resolve(root, 'windows.cordis.patch.yml'))).toBe(false)
})
})