fix(experimental): retain published names and centralize identity policy
This commit is contained in:
1 parent
5990ccbb4a
commit
582c9ab7b7
186 files changed
+1559
-1350
No files matched your search
@@ -3,6 +3,7 @@
|
||||
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
||||
import { dirname, isAbsolute, relative, resolve, sep } from 'node:path'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { hasExperimentalPackageReference, isExperimentalPackageName } from './experimental-package-policy.ts'
|
||||
|
||||
/** Package identities are cached only within one build. */
|
||||
export class BundleInputIsolation {
|
||||
@@ -35,7 +36,7 @@ export class BundleInputIsolation {
|
||||
}
|
||||
|
||||
private checkInput(id: string, allowMissing: boolean): void {
|
||||
if (/(?:^|[/:\u0000])@deepseek-ai\/dsh-experimental-[^/?#]+/.test(id)) {
|
||||
if (hasExperimentalPackageReference(id)) {
|
||||
throw new Error(`${this.label}: experimental input ${id}`)
|
||||
}
|
||||
const file = physicalBundleInput(id)
|
||||
@@ -54,7 +55,7 @@ export class BundleInputIsolation {
|
||||
const canonical = resolve(realpathSync(existing), relative(existing, file))
|
||||
if (canonical !== file) this.checkInput(canonical, allowMissing)
|
||||
const name = this.packageName(dirname(canonical))
|
||||
if (name?.startsWith('@deepseek-ai/dsh-experimental-')) {
|
||||
if (name !== undefined && isExperimentalPackageName(name)) {
|
||||
throw new Error(`${this.label}: ${id} belongs to experimental package ${name}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import { describe, expect, it, onTestFinished } from 'vitest'
|
||||
import {
|
||||
isPublicExperimentalPackageDirectory,
|
||||
PRIVATE_EXPERIMENTAL_PACKAGE_DIRECTORIES,
|
||||
EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS,
|
||||
} from './experimental-package-policy.ts'
|
||||
import {
|
||||
checkDshFamilyVersion,
|
||||
@@ -14,6 +15,7 @@ import {
|
||||
checkWorkspaceProtocol,
|
||||
checkExperimentalDependencyIsolation,
|
||||
checkExperimentalManifest,
|
||||
checkExperimentalNameExceptions,
|
||||
expectedDshPackageFiles,
|
||||
readWorkspaceManifests,
|
||||
type WorkspaceManifest,
|
||||
@@ -120,10 +122,41 @@ describe('experimental workspace constraints', () => {
|
||||
...experimental,
|
||||
manifest: { ...experimental.manifest, name: '@deepseek-ai/dsh-prototype' },
|
||||
})).toEqual([
|
||||
'@deepseek-ai/dsh-prototype: experimental package name must start with "@deepseek-ai/dsh-experimental-"',
|
||||
'@deepseek-ai/dsh-prototype: experimental package name must start with "@deepseek-ai/dsh-experimental-" or match its declared directory exception',
|
||||
])
|
||||
})
|
||||
|
||||
it.each(Object.entries(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS))('retains %s only at its declared directory', (dir, name) => {
|
||||
expect(checkExperimentalManifest({ dir, manifest: { name, publishConfig: { access: 'public' } } })).toEqual([])
|
||||
expect(checkExperimentalManifest({ ...experimental, manifest: { ...experimental.manifest, name } }))
|
||||
.toEqual([expect.stringContaining('declared directory exception')])
|
||||
})
|
||||
|
||||
it('rejects a missing manifest name even when the directory has no exception', () => {
|
||||
expect(checkExperimentalManifest({ ...experimental, manifest: { publishConfig: { access: 'public' } } }))
|
||||
.toEqual([expect.stringContaining('experimental package name must start')])
|
||||
})
|
||||
|
||||
it('rejects stale, moved and duplicated retained-name declarations', () => {
|
||||
const manifests = Object.entries(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS).map(([dir, name]) => ({ dir, manifest: { name } }))
|
||||
expect(checkExperimentalNameExceptions(manifests)).toEqual([])
|
||||
const [first, ...rest] = manifests
|
||||
for (const invalid of [
|
||||
rest,
|
||||
[{ ...first!, manifest: { name: '@deepseek-ai/dsh-experimental-replacement' } }, ...rest],
|
||||
[{ ...first!, dir: 'packages/core/replacement' }, ...rest],
|
||||
[...manifests, { ...first!, dir: 'packages/experimental/duplicate' }],
|
||||
]) expect(checkExperimentalNameExceptions(invalid)).toEqual([expect.stringContaining(first!.manifest.name)])
|
||||
})
|
||||
|
||||
it.each(Object.values(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS))('rejects retained runtime names and aliases: %s', (name) => {
|
||||
for (const dependencies of [{ [name]: 'workspace:*' }, { alias: `npm:${name}@1.0.0` }, { alias: `workspace:${name}@*` }]) {
|
||||
expect(checkExperimentalDependencyIsolation([{
|
||||
dir: 'apps/cli', manifest: { name: '@deepseek-ai/dsh', dependencies },
|
||||
}], [])).toEqual([expect.stringContaining('must not reference an experimental package')])
|
||||
}
|
||||
})
|
||||
|
||||
it('requires public metadata for unlisted experimental packages', () => {
|
||||
expect(checkExperimentalManifest(experimental)).toEqual([])
|
||||
expect(checkExperimentalManifest({
|
||||
|
||||
@@ -11,6 +11,10 @@ import { pathToFileURL } from 'node:url'
|
||||
import { load as loadYaml } from 'js-yaml'
|
||||
import {
|
||||
isPublicExperimentalPackageDirectory,
|
||||
isExperimentalPackageName,
|
||||
hasExperimentalPackageReference,
|
||||
EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS,
|
||||
EXPERIMENTAL_PACKAGE_NAME_PREFIX,
|
||||
PRIVATE_EXPERIMENTAL_PACKAGE_DIRECTORIES,
|
||||
} from './experimental-package-policy.ts'
|
||||
import { hasTypertRemoteNavigation, isForbiddenPublicationFile } from './publication-payload.ts'
|
||||
@@ -53,8 +57,6 @@ const publicationSourceAllowlist: Readonly<Record<string, readonly string[]>> =
|
||||
const publishedRepositoryUrl = 'git+https://github.com/deepseek-ai/deepseek-harness.git'
|
||||
/** Packages that participate in the experimental policy. */
|
||||
const experimentalPackageDirectory = /^packages\/experimental\/[^/]+$/
|
||||
/** npm namespace reserved for experimental packages. */
|
||||
const experimentalPackageNamePrefix = '@deepseek-ai/dsh-experimental-'
|
||||
/** Ordinary directories whose packages this repository publishes: one release member each. */
|
||||
const standardReleaseMemberDirectory = /^(?:packages\/(?!experimental\/)[^/]+\/[^/]+|apps\/(?!desktop(?:-host)?$)[^/]+|vendor\/[^/]+)$/
|
||||
/** Installable application assembled by electron-builder rather than published to npm. */
|
||||
@@ -168,7 +170,7 @@ export function readWorkspaceManifests(repositoryRoot: string): WorkspaceManifes
|
||||
}
|
||||
|
||||
const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
|
||||
'@deepseek-ai/dsh-experimental-webhook-github': ['examples/github-review/cordis.yml', 'examples/github-review/github-ready-review-rule.mjs'],
|
||||
'@deepseek-ai/dsh-webhook-github': ['examples/github-review/cordis.yml', 'examples/github-review/github-ready-review-rule.mjs'],
|
||||
// Owned Worker bundles import this public bootstrap before their business entry.
|
||||
'@deepseek-ai/dsh-app-boot': ['lib/worker/profile-resolution-bootstrap.js'],
|
||||
// Statically linked client libraries keep their stylesheets next to the emitted
|
||||
@@ -203,7 +205,7 @@ const packageFileExtras: Readonly<Record<string, readonly string[]>> = {
|
||||
// sandbox-local resolves it through the package's ./runner export. tsdown
|
||||
// also shares its generated FFI code through a hashed runtime chunk.
|
||||
'@deepseek-ai/dsh-sandbox-windows-acl': ['lib/runner.js', 'lib/types-*.js', 'assets'],
|
||||
'@deepseek-ai/dsh-experimental-skill-badge': ['assets'],
|
||||
'@deepseek-ai/dsh-skill-badge': ['assets'],
|
||||
'@deepseek-ai/dsh-skill-office': ['assets'],
|
||||
'@deepseek-ai/dsh-subprocess': ['lib/control.js'],
|
||||
// SSH launches a private helper and shares wire definitions and TLS setup
|
||||
@@ -343,8 +345,9 @@ export function checkExperimentalManifest(
|
||||
if (!experimentalPackageDirectory.test(dir)) return []
|
||||
const label = manifest.name ?? dir
|
||||
const errors: string[] = []
|
||||
if (manifest.name?.startsWith(experimentalPackageNamePrefix) !== true) {
|
||||
errors.push(`${label}: experimental package name must start with ${JSON.stringify(experimentalPackageNamePrefix)}`)
|
||||
if (manifest.name === undefined || !manifest.name.startsWith(EXPERIMENTAL_PACKAGE_NAME_PREFIX)
|
||||
&& manifest.name !== EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS[dir]) {
|
||||
errors.push(`${label}: experimental package name must start with ${JSON.stringify(EXPERIMENTAL_PACKAGE_NAME_PREFIX)} or match its declared directory exception`)
|
||||
}
|
||||
if (isPublicExperimentalPackageDirectory(dir, privateDirectories)) {
|
||||
if (manifest.private === true) errors.push(`${label}: public experimental package must not set "private": true`)
|
||||
@@ -358,6 +361,22 @@ export function checkExperimentalManifest(
|
||||
return errors
|
||||
}
|
||||
|
||||
/**
|
||||
* Reject missing, renamed, moved, or duplicated retained-name exceptions.
|
||||
* @param manifests - every workspace manifest, with repository-relative directories.
|
||||
* @returns Violations of the exact directory/name ownership declarations.
|
||||
*/
|
||||
export function checkExperimentalNameExceptions(manifests: readonly WorkspaceManifest[]): string[] {
|
||||
const errors: string[] = []
|
||||
for (const [dir, name] of Object.entries(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS)) {
|
||||
const owners = manifests.filter(entry => entry.manifest.name === name)
|
||||
if (owners.length !== 1 || owners[0]?.dir !== dir) {
|
||||
errors.push(`${dir}: retained experimental name ${name} must have exactly one workspace owner at this directory`)
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
||||
|
||||
function isReleaseMemberDirectory(dir: string): boolean {
|
||||
return standardReleaseMemberDirectory.test(dir) || isPublicExperimentalPackageDirectory(dir)
|
||||
}
|
||||
@@ -560,8 +579,10 @@ export function checkExperimentalDependencyIsolation(
|
||||
if (!standardReleaseMemberDirectory.test(dir) && dir !== 'python/sdk-runtime') continue
|
||||
const offered = manifest.name === '@deepseek-ai/dsh' ? new Set(optionalBundles) : new Set<string>()
|
||||
for (const section of runtimeDependencySections) {
|
||||
for (const name of Object.keys(manifest[section] ?? {})) {
|
||||
if (!experimentalNames.has(name)) continue
|
||||
for (const [name, range] of Object.entries(manifest[section] ?? {})) {
|
||||
const experimental = experimentalNames.has(name) || isExperimentalPackageName(name)
|
||||
|| /^(?:npm:|workspace:)/.test(range) && hasExperimentalPackageReference(range)
|
||||
if (!experimental) continue
|
||||
if (section === 'dependencies' && offered.has(name)) continue
|
||||
errors.push(`${manifest.name ?? dir}: ${section}.${name} must not reference an experimental package`)
|
||||
}
|
||||
@@ -608,6 +629,7 @@ export function main(): void {
|
||||
...checkRepositoryVersion(),
|
||||
...workspaceManifests().flatMap(checkWorkspaceManifest),
|
||||
...checkWorkspaceProtocol(manifests),
|
||||
...checkExperimentalNameExceptions(manifests),
|
||||
...checkExperimentalDependencyIsolation(manifests),
|
||||
...checkHierarchyShape(),
|
||||
...collectProjectReferenceFaceViolations(root),
|
||||
|
||||
@@ -269,6 +269,15 @@ describe('client bundle experimental input isolation', () => {
|
||||
expect(existsSync(join(owner, 'lib/index.js'))).toBe(false)
|
||||
})
|
||||
|
||||
it.each(['static', 'dynamic'] as const)('keeps retained-name experimental %s inputs outside ordinary artifacts', async (kind) => {
|
||||
const { root, owner, entry, prototype } = fixture()
|
||||
const name = '@deepseek-ai/dsh-tool-terminal'
|
||||
writeFileSync(join(root, 'prototype/package.json'), JSON.stringify({ name, type: 'module' }))
|
||||
writeFileSync(entry, `export { marker } from ${JSON.stringify(importPath(entry, prototype))}\n`)
|
||||
await expect(bundle(owner, config(kind))).rejects.toThrow(/client bundle isolation.*experimental/)
|
||||
await expect(bundle(owner, config(kind, name))).resolves.toContain('experimental sentinel')
|
||||
})
|
||||
|
||||
it('proves the static library otherwise hides the experimental input in its emitted JavaScript', async () => {
|
||||
const { owner, entry, prototype } = fixture()
|
||||
writeFileSync(entry, `export { marker } from ${JSON.stringify(importPath(entry, prototype))}\n`)
|
||||
|
||||
@@ -0,0 +1,38 @@
|
||||
/** Retained package names keep experimental isolation and persisted record namespaces. */
|
||||
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import {
|
||||
EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS,
|
||||
experimentalPackageRecordNamespace,
|
||||
hasExperimentalPackageReference,
|
||||
isExperimentalPackageName,
|
||||
} from './experimental-package-policy.ts'
|
||||
|
||||
const names = Object.values(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS)
|
||||
|
||||
describe('experimental package identities', () => {
|
||||
it.each([...names, '@deepseek-ai/dsh-experimental-terminal-bundle'])('recognizes %s independently of installation layout', (name) => {
|
||||
expect(isExperimentalPackageName(name)).toBe(true)
|
||||
for (const reference of [name, `${name}/client`, `${name}@1.2.3`, `npm:${name}@1.2.3`, `workspace:${name}@*`,
|
||||
`\0virtual:${name}/client?raw`, `/node_modules/${name}/lib/index.js`, `C:\\node_modules\\${name}\\lib\\index.js`, `C:\\node_modules\\${name.replaceAll('/', '\\')}\\lib\\index.js`]) {
|
||||
expect(hasExperimentalPackageReference(reference), reference).toBe(true)
|
||||
}
|
||||
})
|
||||
|
||||
it.each(names)('does not extend the retained-name exception %s to another package', (name) => {
|
||||
for (const reference of [`${name}-unrelated`, `other-${name}`, name.replace('@deepseek-ai/', '@other/')]) {
|
||||
expect(isExperimentalPackageName(reference)).toBe(false)
|
||||
expect(hasExperimentalPackageReference(reference)).toBe(false)
|
||||
expect(experimentalPackageRecordNamespace(reference)).toBeUndefined()
|
||||
}
|
||||
})
|
||||
|
||||
it('keeps new experimental packages under their prefixed namespaces', () => {
|
||||
expect(experimentalPackageRecordNamespace('@deepseek-ai/dsh-experimental-bridge')).toBe('bridge')
|
||||
expect(hasExperimentalPackageReference('@deepseek-ai/dsh-core')).toBe(false)
|
||||
})
|
||||
|
||||
it.each(Object.entries(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS))('keeps %s record names independent of the retained npm name', (directory, name) => {
|
||||
expect(experimentalPackageRecordNamespace(name)).toBe(directory.split('/').at(-1))
|
||||
})
|
||||
})
|
||||
@@ -1,3 +1,52 @@
|
||||
/** Experimental package identities, publication policy, and retained npm names. */
|
||||
|
||||
/** Existing npm names retained by capabilities now housed under experimental/. */
|
||||
export const EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS: Readonly<Record<string, string>> = {
|
||||
'packages/experimental/session-title-all-prompts-llm': '@deepseek-ai/dsh-session-title-all-prompts-llm',
|
||||
'packages/experimental/tool-terminal': '@deepseek-ai/dsh-tool-terminal',
|
||||
'packages/experimental/tool-ralph': '@deepseek-ai/dsh-tool-ralph',
|
||||
'packages/experimental/skill-badge': '@deepseek-ai/dsh-skill-badge',
|
||||
'packages/experimental/hook-protocol': '@deepseek-ai/dsh-hook-protocol',
|
||||
'packages/experimental/hooks-claude-code': '@deepseek-ai/dsh-hooks-claude-code',
|
||||
'packages/experimental/hooks-codex': '@deepseek-ai/dsh-hooks-codex',
|
||||
'packages/experimental/webhook': '@deepseek-ai/dsh-webhook',
|
||||
'packages/experimental/webhook-github': '@deepseek-ai/dsh-webhook-github',
|
||||
} satisfies Record<`packages/experimental/${string}`, `@deepseek-ai/dsh-${string}`>
|
||||
|
||||
/** npm namespace for experimental packages without retained-name exceptions. */
|
||||
export const EXPERIMENTAL_PACKAGE_NAME_PREFIX = '@deepseek-ai/dsh-experimental-'
|
||||
const retainedNames = new Set(Object.values(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS))
|
||||
|
||||
/**
|
||||
* Identify an experimental npm package independently of its installed directory.
|
||||
* @param name - complete npm package name, without a version or subpath.
|
||||
* @returns Whether the name uses the experimental prefix or a retained name.
|
||||
*/
|
||||
export function isExperimentalPackageName(name: string): boolean {
|
||||
return name.startsWith(EXPERIMENTAL_PACKAGE_NAME_PREFIX) || retainedNames.has(name)
|
||||
}
|
||||
|
||||
/**
|
||||
* Find experimental identities in module ids, paths, subpaths, or npm alias specifications.
|
||||
* @param reference - package reference or bundler/runtime module identifier.
|
||||
* @returns Whether a delimited package name identifies an experimental capability.
|
||||
*/
|
||||
export function hasExperimentalPackageReference(reference: string): boolean {
|
||||
const normalized = reference.replaceAll('\\', '/')
|
||||
return [...normalized.matchAll(/(?:^|[/:\u0000])(@deepseek-ai\/[^/@?#:\s]+)(?=$|[/@?#:])/g)]
|
||||
.some(([, name]) => name !== undefined && isExperimentalPackageName(name))
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive the package-owned plugin record namespace without changing persisted names.
|
||||
* @param name - complete experimental npm package name.
|
||||
* @returns Capability suffix, or undefined for a non-experimental name.
|
||||
*/
|
||||
export function experimentalPackageRecordNamespace(name: string): string | undefined {
|
||||
if (!isExperimentalPackageName(name)) return undefined
|
||||
return name.slice(name.startsWith(EXPERIMENTAL_PACKAGE_NAME_PREFIX) ? EXPERIMENTAL_PACKAGE_NAME_PREFIX.length : '@deepseek-ai/dsh-'.length)
|
||||
}
|
||||
|
||||
/** Experimental packages excluded from public releases and npm baselines. */
|
||||
export const PRIVATE_EXPERIMENTAL_PACKAGE_DIRECTORIES: readonly string[] = []
|
||||
|
||||
|
||||
+12
-12
@@ -376,7 +376,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
title: 'Durable session persistence seam',
|
||||
mode: 'seam',
|
||||
implementations: ['session-persistence-jsonl'],
|
||||
consumers: ['agent-loop', 'tool-bash', 'experimental-hooks-claude-code', 'experimental-hooks-codex', 'session-query', 'session-query-sqlite', 'message-feedback'],
|
||||
consumers: ['agent-loop', 'tool-bash', 'hooks-claude-code', 'hooks-codex', 'session-query', 'session-query-sqlite', 'message-feedback'],
|
||||
note: 'The JSONL backend persists the SessionEvent vocabulary as one artifact per Session.',
|
||||
},
|
||||
{
|
||||
@@ -531,7 +531,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'session-title',
|
||||
title: 'Log-backed session titles',
|
||||
mode: 'seam',
|
||||
implementations: ['session-title-first-prompt-llm', 'experimental-session-title-all-prompts-llm'],
|
||||
implementations: ['session-title-first-prompt-llm', 'session-title-all-prompts-llm'],
|
||||
note: 'Owns the deterministic fallback, latest-title fold, and sole optional asynchronous provider registration.',
|
||||
},
|
||||
{
|
||||
@@ -539,7 +539,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'system-prompt',
|
||||
title: 'System prompt assembly registry',
|
||||
mode: 'core',
|
||||
consumers: ['agent-loop', 'tools', 'tool-fs', 'experimental-tool-terminal', 'tool-web'],
|
||||
consumers: ['agent-loop', 'tools', 'tool-fs', 'tool-terminal', 'tool-web'],
|
||||
note: 'Collects prompt sections and model-facing tool schemas for each step.',
|
||||
},
|
||||
{
|
||||
@@ -547,7 +547,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'tools',
|
||||
title: 'Tool registry and guarded execution pipeline',
|
||||
mode: 'core',
|
||||
consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'experimental-tool-terminal', 'tool-skill', 'tool-subagent', 'tool-todo', 'tool-web'],
|
||||
consumers: ['agent-loop', 'tool-ask-user', 'tool-bash', 'tool-cordis', 'tool-fs', 'tool-terminal', 'tool-skill', 'tool-subagent', 'tool-todo', 'tool-web'],
|
||||
note: 'Registers capabilities, owns PTC mode transport, and routes calls through pre-policy, monotonic guards, around dispatch, post-policy, and final-result observation.',
|
||||
},
|
||||
{
|
||||
@@ -600,7 +600,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
pkg: 'skill',
|
||||
title: 'Skill provider registry',
|
||||
mode: 'seam',
|
||||
implementations: ['sandbox-windows-acl', 'experimental-skill-badge', 'skill-filesystem', 'skill-office'],
|
||||
implementations: ['sandbox-windows-acl', 'skill-badge', 'skill-filesystem', 'skill-office'],
|
||||
consumers: ['tool-skill'],
|
||||
note: 'Merges provider skill catalogs; tool-skill renders the session-prefix catalog and loads complete skill bodies.',
|
||||
},
|
||||
@@ -665,7 +665,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
title: 'Bash executor seam',
|
||||
mode: 'seam',
|
||||
implementations: ['bash-local', 'bash-sandbox', 'pwsh-local'],
|
||||
consumers: ['tool-bash', 'tool-pwsh', 'experimental-hooks-claude-code', 'experimental-hooks-codex'],
|
||||
consumers: ['tool-bash', 'tool-pwsh', 'hooks-claude-code', 'hooks-codex'],
|
||||
note: 'The model-facing shell tools and hook bridges consume this seam; sandboxed, remote, or PowerShell executors replace bash-local without touching them.',
|
||||
},
|
||||
{
|
||||
@@ -682,7 +682,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
title: 'Persistent PTY session registry',
|
||||
mode: 'seam',
|
||||
implementations: ['terminal-bash'],
|
||||
consumers: ['experimental-tool-terminal'],
|
||||
consumers: ['tool-terminal'],
|
||||
note: 'The registry owns exact-Agent session identity and cleanup; backends own terminal mechanics, while tool-terminal exposes the owner-scoped model tools.',
|
||||
},
|
||||
{
|
||||
@@ -754,7 +754,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
title: 'Subagent provider and continuation service',
|
||||
mode: 'seam',
|
||||
implementations: ['subagent-spawn-in-process', 'subagent-fork-in-process', 'subagent-acp', 'subagent-codex', 'subagent-claude-code', 'subagent-dsh-sdk'],
|
||||
consumers: ['tool-subagent', 'tool-subagent-control', 'experimental-tool-ralph'],
|
||||
consumers: ['tool-subagent', 'tool-subagent-control', 'tool-ralph'],
|
||||
note: 'Providers prepare local children or execute external tasks; one activation manager owns both lifecycles. The delegation tool selects a backend, control tools deliver local follow-ups, and workflows await activation results.',
|
||||
},
|
||||
{
|
||||
@@ -794,7 +794,7 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
title: 'Background job registry',
|
||||
mode: 'seam',
|
||||
implementations: ['jobs-local'],
|
||||
consumers: ['tool-bash', 'tool-pwsh', 'experimental-tool-terminal', 'tool-jobs', 'api-job-controller'],
|
||||
consumers: ['tool-bash', 'tool-pwsh', 'tool-terminal', 'tool-jobs', 'api-job-controller'],
|
||||
note: 'Producers (background bash/pwsh and PTY sends) register running work; record-declaring jobs additionally stream raw output for non-consuming observers; tool-jobs is the model-facing controller that reads, lists, and kills it; jobs-local is the process-local registry.',
|
||||
},
|
||||
{
|
||||
@@ -846,15 +846,15 @@ const SERVICE_ROLES: ServiceRole[] = [
|
||||
title: 'Workflow script engine',
|
||||
mode: 'seam',
|
||||
implementations: ['workflow-ptc'],
|
||||
consumers: ['tool-workflow', 'experimental-tool-ralph'],
|
||||
consumers: ['tool-workflow', 'tool-ralph'],
|
||||
note: 'One engine per context, as in bash, with no named-provider registry; the general workflow and fixed Ralph consumers start runs whose agent() calls fan out through ctx.subagents.',
|
||||
},
|
||||
{
|
||||
key: 'webhookRuntime',
|
||||
pkg: 'experimental-webhook',
|
||||
pkg: 'webhook',
|
||||
title: 'Webhook rule runtime',
|
||||
mode: 'core',
|
||||
consumers: ['experimental-webhook-github'],
|
||||
consumers: ['webhook-github'],
|
||||
note: 'Provider adapters dispatch authenticated deliveries; trusted plugins register independent process-local rules, and the runtime turns non-null results into ordinary Workspace-backed Sessions without delivery or completion state.',
|
||||
},
|
||||
{
|
||||
|
||||
@@ -54,7 +54,7 @@ import * as ToolFs from '@deepseek-ai/dsh-tool-fs'
|
||||
import * as ToolFsSearch from '@deepseek-ai/dsh-tool-fs-search'
|
||||
import * as ToolStrReplaceEditor from '@deepseek-ai/dsh-tool-str-replace-editor'
|
||||
import TerminalSessionService from '@deepseek-ai/dsh-terminal'
|
||||
import * as ToolPty from '@deepseek-ai/dsh-experimental-tool-terminal'
|
||||
import * as ToolPty from '@deepseek-ai/dsh-tool-terminal'
|
||||
import * as ToolGoal from '@deepseek-ai/dsh-tool-goal'
|
||||
import * as ToolSchedule from '@deepseek-ai/dsh-tool-schedule'
|
||||
import type ScheduleService from '@deepseek-ai/dsh-schedule'
|
||||
@@ -77,7 +77,7 @@ import { registerListSubagentModels } from '../packages/subagent/tool-subagent/s
|
||||
import * as ToolWeb from '@deepseek-ai/dsh-tool-web'
|
||||
import WorkflowEngine from '@deepseek-ai/dsh-workflow'
|
||||
import type { WorkflowRun, WorkflowStartRequest } from '@deepseek-ai/dsh-workflow'
|
||||
import * as ToolRalph from '@deepseek-ai/dsh-experimental-tool-ralph'
|
||||
import * as ToolRalph from '@deepseek-ai/dsh-tool-ralph'
|
||||
import * as ToolWorkflow from '@deepseek-ai/dsh-tool-workflow'
|
||||
import * as ToolWorkspaceDependencies from '@deepseek-ai/dsh-tool-workspace-dependencies'
|
||||
import { githubSlug } from './verify-md-links.ts'
|
||||
@@ -400,7 +400,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
'glob and grep are unconditional discovery tools that spawn the packaged ripgrep binary (`@vscode/ripgrep`) through ctx.subprocess as ordinary foreground calls (never background jobs) — no host `rg` install and no shell layer. The catalog uses `sampleOverCapGlobResults: true`; deployments must choose that behavior explicitly. Capped results save the complete formatted list through the optional ctx.spillStore backend; returned locators are follow-up-readable/searchable when the backend exposes local paths in co-located deployments.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-experimental-tool-terminal',
|
||||
pkg: '@deepseek-ai/dsh-tool-terminal',
|
||||
dir: 'tool-terminal',
|
||||
source: 'packages/experimental/tool-terminal/src/index.ts',
|
||||
requires: ['ctx.workingDirectory', 'ctx.tools', 'ctx.terminals', 'ctx.systemPrompt', 'ctx.jobs at call time for run_in_background'],
|
||||
@@ -460,7 +460,7 @@ const TOOL_PACKAGES: ToolPackage[] = [
|
||||
'The lsp tool keeps provider selection and language-server subprocesses behind ctx.lsp, so its model-visible schema stays stable across providers. Requires a registered provider (e.g. `@deepseek-ai/dsh-lsp-stdio`) at runtime; without one, a query returns the structured `LSP_UNAVAILABLE` error rather than changing the schema.',
|
||||
},
|
||||
{
|
||||
pkg: '@deepseek-ai/dsh-experimental-tool-ralph',
|
||||
pkg: '@deepseek-ai/dsh-tool-ralph',
|
||||
dir: 'tool-ralph',
|
||||
source: 'packages/experimental/tool-ralph/src/index.ts',
|
||||
requires: ['ctx.tools', 'ctx.workflowEngine', 'ctx.subagents', 'ctx.systemPrompt', 'a calling Agent (exec.agent parents every fresh round)'],
|
||||
|
||||
@@ -4,6 +4,7 @@ import { mkdirSync, mkdtempSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS } from './experimental-package-policy.ts'
|
||||
import { collectPluginRecords } from './plugin-record-catalog.ts'
|
||||
import { persistenceCatalogArtifacts, render, renderPluginRecordCatalog } from './gen-persistence-catalog.ts'
|
||||
import { extractPersistenceSchema } from './persistence-schema.ts'
|
||||
@@ -43,6 +44,20 @@ function augmentation(members: string, heritage = ''): string {
|
||||
const ENTRY = " /** Retains a bridge entry. */\n 'plugin:bridge/entry': SavedEntry"
|
||||
|
||||
describe('plugin record declarations', () => {
|
||||
it.each(Object.entries(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS))('retains the persisted namespace for %s', (directory, owner) => {
|
||||
const namespace = directory.split('/').at(-1)!
|
||||
const source = `${directory}/src/records.ts`
|
||||
const root = fixture({
|
||||
[`${directory}/package.json`]: JSON.stringify({ name: owner }),
|
||||
[source]: augmentation(`/** Retains the capability event. */\n'plugin:${namespace}/entry': SavedEntry`),
|
||||
})
|
||||
expect(collectPluginRecords(root)).toEqual([{
|
||||
name: `plugin:${namespace}/entry`, owner, payload: 'SavedEntry', doc: 'Retains the capability event.', source,
|
||||
}])
|
||||
put(root, source, augmentation("/** Another package event. */\n'plugin:unrelated/entry': SavedEntry"))
|
||||
expect(() => collectPluginRecords(root)).toThrow(`must use 'plugin:${namespace}/<name>'`)
|
||||
})
|
||||
|
||||
it('accepts an empty inventory only when the owning empty map exists', () => {
|
||||
expect(collectPluginRecords(fixture())).toEqual([])
|
||||
expect(() => collectPluginRecords(fixture({ [OWNER]: 'export {}\n' })))
|
||||
@@ -99,7 +114,7 @@ describe('plugin record declarations', () => {
|
||||
'packages/core/bridge/package.json': '{"name":"@deepseek-ai/dsh-experimental-bridge"}',
|
||||
'packages/core/bridge/src/records.ts': augmentation(ENTRY),
|
||||
})
|
||||
expect(() => collectPluginRecords(root)).toThrow('must belong to an experimental')
|
||||
expect(() => collectPluginRecords(root)).toThrow('must belong to a package declared experimental')
|
||||
})
|
||||
|
||||
it('requires the types module augmentation and verifiable package ownership', () => {
|
||||
|
||||
@@ -3,11 +3,11 @@
|
||||
import { globSync, readFileSync } from 'node:fs'
|
||||
import { resolve, sep } from 'node:path'
|
||||
import ts from 'typescript'
|
||||
import { experimentalPackageRecordNamespace } from './experimental-package-policy.ts'
|
||||
import { parseJsDoc, pointer, rawJsDoc, reportViolations } from './jsdoc.ts'
|
||||
|
||||
const SESSION_PACKAGE = '@deepseek-ai/dsh-session'
|
||||
const SESSION_TYPES_MODULE = `${SESSION_PACKAGE}/types`
|
||||
const PACKAGE_PREFIX = '@deepseek-ai/dsh-experimental-'
|
||||
const RECORD_NAME = /^plugin:[a-z0-9][a-z0-9._-]*(?:\/[a-z0-9][a-z0-9._-]*)+$/u
|
||||
const printer = ts.createPrinter({ removeComments: true })
|
||||
|
||||
@@ -94,11 +94,12 @@ export function collectPluginRecords(scanRoot: string): PluginRecordEntry[] {
|
||||
violations.push(`${where} must augment declare module '${SESSION_TYPES_MODULE}'.`)
|
||||
return
|
||||
}
|
||||
if (!file.startsWith('packages/experimental/') || owner === undefined || !owner.startsWith(PACKAGE_PREFIX)) {
|
||||
violations.push(`${where} must belong to an experimental ${PACKAGE_PREFIX}<name> package.`)
|
||||
const namespace = owner === undefined ? undefined : experimentalPackageRecordNamespace(owner)
|
||||
if (!file.startsWith('packages/experimental/') || owner === undefined || namespace === undefined) {
|
||||
violations.push(`${where} must belong to a package declared experimental by the package naming policy.`)
|
||||
return
|
||||
}
|
||||
const prefix = `plugin:${owner.slice(PACKAGE_PREFIX.length)}/`
|
||||
const prefix = `plugin:${namespace}/`
|
||||
for (const member of node.members) {
|
||||
const location = pointer(file, source, member)
|
||||
if (!ts.isPropertySignature(member) || member.type === undefined || member.questionToken !== undefined
|
||||
|
||||
@@ -64,25 +64,25 @@ describe('release families', () => {
|
||||
'@deepseek-ai/dsh-experimental-computer-use-cua-driver-mcp',
|
||||
'@deepseek-ai/dsh-experimental-computer-use-cua-driver-native',
|
||||
'@deepseek-ai/dsh-experimental-cot-translation-bundle',
|
||||
'@deepseek-ai/dsh-experimental-hook-protocol',
|
||||
'@deepseek-ai/dsh-experimental-hooks-claude-code',
|
||||
'@deepseek-ai/dsh-experimental-hooks-codex',
|
||||
'@deepseek-ai/dsh-hook-protocol',
|
||||
'@deepseek-ai/dsh-hooks-claude-code',
|
||||
'@deepseek-ai/dsh-hooks-codex',
|
||||
'@deepseek-ai/dsh-experimental-inspector-profile',
|
||||
'@deepseek-ai/dsh-experimental-inspector',
|
||||
'@deepseek-ai/dsh-experimental-ptc-runtime-python',
|
||||
'@deepseek-ai/dsh-experimental-session-inspector',
|
||||
'@deepseek-ai/dsh-experimental-session-title-all-prompts-llm',
|
||||
'@deepseek-ai/dsh-experimental-skill-badge',
|
||||
'@deepseek-ai/dsh-session-title-all-prompts-llm',
|
||||
'@deepseek-ai/dsh-skill-badge',
|
||||
'@deepseek-ai/dsh-experimental-speech-to-text-sensevoice',
|
||||
'@deepseek-ai/dsh-experimental-speech-to-text',
|
||||
'@deepseek-ai/dsh-experimental-tool-agent-team',
|
||||
'@deepseek-ai/dsh-experimental-tool-ralph',
|
||||
'@deepseek-ai/dsh-experimental-tool-terminal',
|
||||
'@deepseek-ai/dsh-tool-ralph',
|
||||
'@deepseek-ai/dsh-tool-terminal',
|
||||
'@deepseek-ai/dsh-experimental-tool-worktree',
|
||||
'@deepseek-ai/dsh-experimental-translator',
|
||||
'@deepseek-ai/dsh-experimental-voice-input-bundle',
|
||||
'@deepseek-ai/dsh-experimental-webhook-github',
|
||||
'@deepseek-ai/dsh-experimental-webhook',
|
||||
'@deepseek-ai/dsh-webhook-github',
|
||||
'@deepseek-ai/dsh-webhook',
|
||||
'@deepseek-ai/dsh-experimental-webworker-packer',
|
||||
'@deepseek-ai/dsh-experimental-webworker-runtime',
|
||||
'@deepseek-ai/dsh-experimental-worktree',
|
||||
|
||||
@@ -4,6 +4,7 @@ import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from 'node:fs'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS } from '../experimental-package-policy.ts'
|
||||
import { verifyInstalledProductIsolation } from './installed-product-isolation.ts'
|
||||
|
||||
const roots: string[] = []
|
||||
@@ -26,7 +27,7 @@ afterEach(() => {
|
||||
for (const root of roots.splice(0)) rmSync(root, { recursive: true, force: true })
|
||||
})
|
||||
|
||||
describe('installed default-product isolation', () => {
|
||||
describe.each([experimental, ...Object.values(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS)])('installed default-product isolation: %s', (experimental) => {
|
||||
it('ignores development dependencies and unrelated installed experimental packages', () => {
|
||||
const root = fixture()
|
||||
const entry = writePackage(root, '@deepseek-ai/dsh', {
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
import { existsSync, readFileSync, realpathSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { join } from 'node:path'
|
||||
import { hasExperimentalPackageReference } from '../experimental-package-policy.ts'
|
||||
import { OPTIONAL_BUNDLES } from '../../packages/boot/app-boot/src/profile.ts'
|
||||
|
||||
interface InstalledManifest {
|
||||
@@ -56,7 +57,7 @@ export function verifyInstalledProductIsolation(directory: string, optionalBundl
|
||||
}
|
||||
|
||||
function rejectExperimental(name: string, chain: readonly string[]): void {
|
||||
if (name.startsWith('@deepseek-ai/dsh-experimental-')) {
|
||||
if (hasExperimentalPackageReference(name)) {
|
||||
throw new Error(`default product includes an experimental package: ${chain.join(' -> ')}`)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import { tmpdir } from 'node:os'
|
||||
import { join } from 'node:path'
|
||||
import { pathToFileURL } from 'node:url'
|
||||
import { afterEach, describe, expect, it } from 'vitest'
|
||||
import { EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS } from './experimental-package-policy.ts'
|
||||
import { verifyDefaultProductIsolation } from './verify-default-product-isolation.ts'
|
||||
import { writeFixtureFile as write } from './fixture-file.ts'
|
||||
|
||||
@@ -47,6 +48,32 @@ afterEach(() => {
|
||||
})
|
||||
|
||||
describe('default product isolation', () => {
|
||||
describe.each(Object.entries(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS))('retained experimental identity %s', (directory, name) => {
|
||||
it.each(['direct dependency', 'transitive dependency', 'npm alias', 'value import', 'subpath import', 'profile row', 'preset row'])(
|
||||
'rejects default product use through a %s', (route) => {
|
||||
const root = fixture()
|
||||
write(root, `${directory}/package.json`, { name })
|
||||
if (route === 'direct dependency') manifest(root, 'apps/cli/package.json', { dependencies: { [name]: 'workspace:*' } })
|
||||
if (route === 'transitive dependency') manifest(root, 'packages/core/core/package.json', { dependencies: { [name]: 'workspace:*' } })
|
||||
if (route === 'npm alias') manifest(root, 'packages/core/core/package.json', { dependencies: { alias: `npm:${name}@1.0.0` } })
|
||||
if (route === 'value import') write(root, 'apps/cli/src/bin.ts', `import '${name}'\n`)
|
||||
if (route === 'subpath import') write(root, 'apps/cli/src/bin.ts', `import '${name}/feature'\n`)
|
||||
if (route === 'profile row') write(root, patch, [{ insert: [{ name }] }])
|
||||
if (route === 'preset row') write(root, preset, [{ insert: [{ name: '@deepseek-ai/dsh-agent-preset', config: { id: 'standard', plugins: [{ name }] } }] }])
|
||||
expect(verifyDefaultProductIsolation(root).failures).toEqual(expect.arrayContaining([
|
||||
expect.stringMatching(new RegExp(`${name}.*default product must not include experimental packages`)),
|
||||
]))
|
||||
},
|
||||
)
|
||||
|
||||
it('allows a type-only reference without promoting it to runtime use', () => {
|
||||
const root = fixture()
|
||||
write(root, `${directory}/package.json`, { name })
|
||||
write(root, 'apps/cli/src/bin.ts', `import type { Options } from '${name}'\nexport type { Options }\n`)
|
||||
expect(verifyDefaultProductIsolation(root).failures).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
it.each(['@deepseek-ai/libreoffice-kit'])(
|
||||
'accepts independently published %s but rejects unknown workspace packages', (name) => {
|
||||
const root = fixture()
|
||||
|
||||
@@ -20,7 +20,7 @@ import {
|
||||
collectRuntimeSourceSpecifiers,
|
||||
} from './verify-client-packages.ts'
|
||||
|
||||
const EXPERIMENTAL_PREFIX = '@deepseek-ai/dsh-experimental-'
|
||||
import { isExperimentalPackageName } from './experimental-package-policy.ts'
|
||||
// The independently published entry package owns platform-engine dependencies.
|
||||
const EXTERNAL_KIT_PACKAGES = new Set(['@deepseek-ai/libreoffice-kit'])
|
||||
const PROFILE_SOURCE = 'packages/boot/app-boot/src/profile.ts'
|
||||
@@ -107,7 +107,7 @@ export function verifyDefaultProductIsolation(root: string): ProductIsolationRes
|
||||
const sources = new Set<string>()
|
||||
const configs = new Set<string>()
|
||||
let webPluginCount = 0
|
||||
const isExperimental = (pkg: Package): boolean => pkg.manifest.name.startsWith(EXPERIMENTAL_PREFIX)
|
||||
const isExperimental = (pkg: Package): boolean => isExperimentalPackageName(pkg.manifest.name)
|
||||
|| display(pkg.directory).startsWith('packages/experimental/')
|
||||
const add = (pkg: Package, origin: string): void => {
|
||||
if (isExperimental(pkg)) {
|
||||
@@ -136,7 +136,7 @@ export function verifyDefaultProductIsolation(root: string): ProductIsolationRes
|
||||
return
|
||||
}
|
||||
const packageName = barePackageName(name)
|
||||
if (packageName.startsWith(EXPERIMENTAL_PREFIX)) {
|
||||
if (isExperimentalPackageName(packageName)) {
|
||||
failures.push(`${origin} -> ${name}: default product must not include experimental packages`)
|
||||
return
|
||||
}
|
||||
|
||||
@@ -170,7 +170,7 @@ const SENTENCE_MODEL_EXPERIENCE: Readonly<Record<string, SentenceContract>> = {
|
||||
'packages/host/directory-picker-browse': { kind: 'none', reason: 'The GUI-host picking backend registers nothing model-facing.' },
|
||||
'packages/host/directory-picker-native': { kind: 'none', reason: 'The GUI-host picking backend registers nothing model-facing.' },
|
||||
'packages/host/webserver': { kind: 'none', reason: 'The HTTP carrier bridges browser and API handler and registers nothing model-facing.' },
|
||||
'packages/experimental/webhook-github': { kind: 'indirect', reason: 'The adapter delegates model-visible text to matching rules and dsh-experimental-webhook.' },
|
||||
'packages/experimental/webhook-github': { kind: 'indirect', reason: 'The adapter delegates model-visible text to matching rules and dsh-webhook.' },
|
||||
'packages/host/frontend-static': { kind: 'none', reason: 'The SPA dist server answers browser asset requests and registers nothing model-facing.' },
|
||||
'packages/host/plugin-inventory': { kind: 'none', reason: 'Host-side read-only Loader projection; registers nothing model-facing.' },
|
||||
'packages/host/open-in-app': { kind: 'none', reason: 'Host routes opening desktop applications for a human; registers nothing model-facing.' },
|
||||
|
||||
@@ -8,6 +8,7 @@ import { pathToFileURL } from 'node:url'
|
||||
import { describe, expect, it, onTestFinished, vi } from 'vitest'
|
||||
import { browserDependencyAnalysis, productWebBundleIsolation } from '../apps/web/product-isolation.ts'
|
||||
import { WebProductBundleIsolation } from './web-product-bundle-isolation.ts'
|
||||
import { EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS } from './experimental-package-policy.ts'
|
||||
import { BundleInputIsolation } from './bundle-input-isolation.ts'
|
||||
|
||||
const filesystem = vi.hoisted(() => ({ missingRoot: false }))
|
||||
@@ -120,11 +121,11 @@ describe('default Web bundle input isolation', () => {
|
||||
.rejects.toThrow(/Web product isolation: experimental input/)
|
||||
})
|
||||
|
||||
it('checks npm alias package identity independently of its directory name', async () => {
|
||||
it.each(['@deepseek-ai/dsh-experimental-aliased', ...Object.values(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS)])('checks npm alias package identity %s independently of its directory name', async (name) => {
|
||||
const test = fixture()
|
||||
test.write('apps/web/src/main.js', 'import "ordinary-name"')
|
||||
test.write('apps/web/node_modules/ordinary-name/package.json',
|
||||
'{"name":"@deepseek-ai/dsh-experimental-aliased","type":"module","main":"index.js"}')
|
||||
JSON.stringify({ name, type: 'module', main: 'index.js' }))
|
||||
test.write('apps/web/node_modules/ordinary-name/index.js', 'globalThis.aliased = true')
|
||||
await expect(test.run()).rejects.toThrow(/belongs to experimental package/)
|
||||
})
|
||||
@@ -221,6 +222,19 @@ describe('default Web bundle input isolation', () => {
|
||||
})
|
||||
|
||||
describe('bundler input ownership', () => {
|
||||
it.each(Object.values(EXPERIMENTAL_PACKAGE_NAME_EXCEPTIONS))('rejects retained identity %s in unresolved inputs and source-map ownership', (name) => {
|
||||
const test = fixture()
|
||||
const ownership = new BundleInputIsolation(test.root, 'fixture')
|
||||
for (const reference of [name, `${name}/client`, `\u0000virtual:${name}/client?raw`]) {
|
||||
expect(() => { ownership.assertInput(reference) }).toThrow(/experimental input/)
|
||||
}
|
||||
const absentWindowsSource = `C:\\omitted\\node_modules\\${name.replaceAll('/', '\\')}\\src\\index.ts`
|
||||
expect(() => { ownership.assertSourceMapInput(absentWindowsSource) }).toThrow(/experimental input/)
|
||||
test.write('apps/web/node_modules/upstream/package.json', JSON.stringify({ name }))
|
||||
expect(() => { ownership.assertSourceMapInput(join(test.web, 'node_modules/upstream/src/omitted.js')) })
|
||||
.toThrow(/belongs to experimental package/)
|
||||
})
|
||||
|
||||
it('refuses source-map paths whose filesystem root is unavailable', () => {
|
||||
const test = fixture()
|
||||
const ownership = new BundleInputIsolation(test.root, 'fixture')
|
||||
|
||||
Reference in new issue
Block a user