Files
deepseek-harness/packages/util/environment/src/index.ts
T
imccyu ec601ca13d 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:13 +08:00

125 lines
4.8 KiB
TypeScript

/**
* Immutable launch-time environment snapshot that records which layer
* supplied each value. Harness consumers resolve through it instead of a flattened
* `process.env`; launchers may still materialize accepted values for config
* expressions and third-party libraries.
* @module @deepseek-ai/dsh-environment
*/
import type { Context } from '@deepseek-ai/cordis'
/**
* Which layer supplied a value, from most to least trusted: the environment
* this process inherited, the invoking directory's `.env`, the Harness home's
* `.env`.
*/
export type EnvironmentSource = 'process' | 'project-env' | 'user-env'
/** Layer order, most trusted first. */
const SOURCE_ORDER: readonly EnvironmentSource[] = ['process', 'project-env', 'user-env']
/** One resolved variable and the layer it came from. */
export interface EnvironmentEntry {
/** The value as the layer supplied it; may be empty, which each owner judges for itself. */
value: string
/** The layer that supplied it. */
source: EnvironmentSource
/** Absolute path of the file that supplied it; absent for `process`. */
path?: string
}
/**
* The frozen environment of one launch. Construct through
* {@link createEnvironmentSnapshot}; nothing mutates it afterwards, so a
* later `chdir`, workspace switch, or resumed session observes the same
* values a consumer resolved at boot.
*/
export interface EnvironmentSnapshot {
/**
* Resolve one name across every layer, most trusted first.
* @param name - the variable name.
* @returns the winning entry, or `undefined` when no layer supplies it.
*/
get(name: string): EnvironmentEntry | undefined
/**
* Resolve one name only from `sources`, retaining canonical trust order;
* omitted layers are unreachable.
* @param name - the variable name.
* @param sources - the layers allowed in the canonical trust order.
* @returns the first matching entry, or `undefined`.
*/
getFrom(name: string, sources: readonly EnvironmentSource[]): EnvironmentEntry | undefined
}
/**
* The map key one variable name resolves under. Windows treats environment
* names case-insensitively; every other platform does not.
* @param name - the variable name as written.
* @returns the key to store and look up by.
*/
function lookupKey(name: string): string {
/* v8 ignore next -- native Windows coverage exercises the folding arm; POSIX covers the exact one */
return process.platform === 'win32' ? name.toUpperCase() : name
}
/** One layer's raw contents, as {@link createEnvironmentSnapshot} receives them. */
export interface EnvironmentLayerInput {
source: EnvironmentSource
/** Absolute path of the file behind this layer; omit for `process`. */
path?: string
values: Readonly<Record<string, string>>
}
/**
* Build the snapshot from each layer's contents.
* @param layers - the layers in any order; the result searches them by canonical trust order.
* @returns the immutable snapshot.
*/
export function createEnvironmentSnapshot(layers: readonly EnvironmentLayerInput[]): EnvironmentSnapshot {
// Copy every layer so later mutations cannot change the snapshot. Fold names
// on Windows so case variants cannot split precedence; POSIX remains exact.
const bySource = new Map<EnvironmentSource, { path?: string; values: Map<string, string> }>()
for (const layer of layers) {
bySource.set(layer.source, {
...layer.path === undefined ? {} : { path: layer.path },
values: new Map(Object.entries(layer.values).map(([name, value]) => [lookupKey(name), value])),
})
}
const getFrom = (name: string, sources: readonly EnvironmentSource[]): EnvironmentEntry | undefined => {
const key = lookupKey(name)
for (const source of SOURCE_ORDER) {
if (!sources.includes(source)) continue
const layer = bySource.get(source)
const value = layer?.values.get(key)
if (value === undefined) continue
return { value, source, ...layer?.path === undefined ? {} : { path: layer.path } }
}
return undefined
}
return {
get: name => getFrom(name, SOURCE_ORDER),
getFrom,
}
}
/** Context slot the launcher fills with this run's snapshot before any config entry mounts. */
export const DSH_ENVIRONMENT_KEY = 'launcherEnvironment'
/**
* Return the launcher's snapshot, or the inherited environment as the sole
* layer when the host provided none.
* @param ctx - the consuming plugin's context.
* @returns the snapshot to resolve user-facing values against.
*/
export function environmentOf(ctx: Context): EnvironmentSnapshot {
return ctx.get(DSH_ENVIRONMENT_KEY)
?? createEnvironmentSnapshot([{ source: 'process', values: process.env as Record<string, string> }])
}
declare module '@deepseek-ai/cordis' {
interface Context {
/** Launcher-owned snapshot of this run's environment; absent in compositions the product CLI did not boot. */
launcherEnvironment?: EnvironmentSnapshot
}
}