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.
125 lines
4.8 KiB
TypeScript
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
|
|
}
|
|
}
|