refactor(cli)!: namespace the profile and bundle manifests under dsh.profile and dsh.bundle
A profile manifest and a bundle manifest are different kinds and shared one flat `dsh` section: `dsh.plugins` listed bundles (not plugins) and `dsh.patch` declared a bundle's layer. Each kind now names its role — a bundle declares `dsh.bundle.patch`, a profile declares `dsh.profile.bundles` — so a package.json states which role it plays and the list name matches its contents. `DEFAULT_PROFILE_PLUGINS` becomes `DEFAULT_PROFILE_BUNDLES`, and `DshManifestSection` splits into `DshBundleManifest`/`DshProfileManifest`. Pre-release: no compatibility shim; turtle-ui moved with it (bd5ff10).
This commit is contained in:
@@ -1,12 +1,12 @@
|
||||
/**
|
||||
* `dsh plugin --profile <name> <args...>` — profile plugin management as a
|
||||
* thin pnpm forwarder: initialize the profile on first use, run
|
||||
* `pnpm <args...>` in the profile directory, then reconcile the `dsh.plugins`
|
||||
* bundle-layer list against the installed state (a dependency resolving to a
|
||||
* package that declares `dsh.patch` joins the layer stack; a removed or
|
||||
* patch-less dependency leaves it). Reconciling by installed state, not by
|
||||
* dependency diff, means `update` activates a package that gained its
|
||||
* `dsh.patch` in a newer version.
|
||||
* `pnpm <args...>` in the profile directory, then reconcile the
|
||||
* `dsh.profile.bundles` layer list against the installed state (a dependency
|
||||
* resolving to a package that declares `dsh.bundle` joins the layer stack; a
|
||||
* removed or bundle-less dependency leaves it). Reconciling by installed
|
||||
* state, not by dependency diff, means `update` activates a package that
|
||||
* gained its `dsh.bundle` declaration in a newer version.
|
||||
* @module @deepseek-ai/dsh/plugin
|
||||
*/
|
||||
|
||||
@@ -14,7 +14,7 @@ import { spawnSync } from 'node:child_process'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { join, resolve } from 'node:path'
|
||||
import {
|
||||
DEFAULT_PROFILE_PLUGINS,
|
||||
DEFAULT_PROFILE_BUNDLES,
|
||||
initProfile,
|
||||
PROFILE_TEMPLATES,
|
||||
readProfileManifest,
|
||||
@@ -31,7 +31,7 @@ const NAME = 'dsh'
|
||||
* Whether a resolved dependency exports a profile patch, i.e. is a bundle.
|
||||
* @param packageName - the dependency's package name.
|
||||
* @param profileDir - the profile directory (resolution anchor).
|
||||
* @returns true when the package manifest declares `dsh.patch`.
|
||||
* @returns true when the package manifest declares `dsh.bundle`.
|
||||
*/
|
||||
function exportsPatch(packageName: string, profileDir: string): boolean {
|
||||
let dir: string
|
||||
@@ -41,25 +41,26 @@ function exportsPatch(packageName: string, profileDir: string): boolean {
|
||||
return false // pnpm reported success yet the package is unresolvable — treat as plain
|
||||
}
|
||||
const manifest = readProfileManifest(NAME, dir)
|
||||
return manifest.dsh?.patch !== undefined
|
||||
return manifest.dsh?.bundle?.patch !== undefined
|
||||
}
|
||||
|
||||
/**
|
||||
* Reconcile `dsh.plugins` against the installed state: pnpm has already
|
||||
* written the real installed names (so a git/path/tarball/alias spec on the
|
||||
* command line reconciles by its true package name) and materialized the
|
||||
* packages. A dependency that resolves to a `dsh.patch`-declaring package
|
||||
* joins the layer stack (appended in dependency order); a dependency-listed
|
||||
* name that no longer does — removed, or the installed version dropped the
|
||||
* declaration — leaves it. In-box bundles from the profile template are not
|
||||
* dependencies and are never touched. Warns once per newly-added patch-less
|
||||
* dependency (a plain library is fine; the warning is orientation).
|
||||
* Reconcile `dsh.profile.bundles` against the installed state: pnpm has
|
||||
* already written the real installed names (so a git/path/tarball/alias spec
|
||||
* on the command line reconciles by its true package name) and materialized
|
||||
* the packages. A dependency that resolves to a `dsh.bundle`-declaring
|
||||
* package joins the layer stack (appended in dependency order); a
|
||||
* dependency-listed name that no longer does — removed, or the installed
|
||||
* version dropped the declaration — leaves it. In-box bundles from the
|
||||
* profile template are not dependencies and are never touched. Warns once
|
||||
* per newly-added bundle-less dependency (a plain library is fine; the
|
||||
* warning is orientation).
|
||||
*/
|
||||
function reconcilePlugins(before: ProfileManifest, profileDir: string): void {
|
||||
const after = readProfileManifest(NAME, profileDir)
|
||||
const beforeDeps = new Set(Object.keys(before.dependencies ?? {}))
|
||||
const dependencies = Object.keys(after.dependencies ?? {})
|
||||
const plugins = after.dsh?.plugins ?? []
|
||||
const plugins = after.dsh?.profile?.bundles ?? []
|
||||
let changed = false
|
||||
for (const packageName of dependencies) {
|
||||
const isBundle = exportsPatch(packageName, profileDir)
|
||||
@@ -68,7 +69,7 @@ function reconcilePlugins(before: ProfileManifest, profileDir: string): void {
|
||||
changed = true
|
||||
} else if (!isBundle && !beforeDeps.has(packageName)) {
|
||||
process.stderr.write(
|
||||
`${NAME}: warning: ${packageName} declares no dsh.patch — installed as a plain dependency, not a profile layer `
|
||||
`${NAME}: warning: ${packageName} declares no dsh.bundle — installed as a plain dependency, not a profile layer `
|
||||
+ '(a later update that gains one activates it automatically)\n',
|
||||
)
|
||||
}
|
||||
@@ -85,7 +86,7 @@ function reconcilePlugins(before: ProfileManifest, profileDir: string): void {
|
||||
}
|
||||
}
|
||||
if (!changed) return
|
||||
after.dsh = { ...after.dsh, plugins }
|
||||
after.dsh = { ...after.dsh, profile: { ...after.dsh?.profile, bundles: plugins } }
|
||||
writeProfileManifest(profileDir, after)
|
||||
}
|
||||
|
||||
@@ -119,7 +120,7 @@ function anchorPathSpec(argument: string, cwd: string): string {
|
||||
export function runPlugin(profile: string, args: readonly string[]): number {
|
||||
const dir = resolveProfileDir(profile)
|
||||
if (!existsSync(join(dir, 'package.json'))) {
|
||||
initProfile(dir, PROFILE_TEMPLATES[profile] ?? DEFAULT_PROFILE_PLUGINS)
|
||||
initProfile(dir, PROFILE_TEMPLATES[profile] ?? DEFAULT_PROFILE_BUNDLES)
|
||||
process.stderr.write(`${NAME}: initialized profile ${profile} at ${dir}\n`)
|
||||
}
|
||||
const before = readProfileManifest(NAME, dir)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
/**
|
||||
* Shared profile boot for every `dsh` surface: resolve the profile, stack its
|
||||
* patch layers (bundle layers in `dsh.plugins` order, the profile's own
|
||||
* patch layers (bundle layers in `dsh.profile.bundles` order, the profile's own
|
||||
* `cordis.patch.yml`, `--patch` overlays, flag-derived patches, the telemetry
|
||||
* switch), mount the tree over the profile's empty root config, keep the
|
||||
* profile patch layer live, and wire fail-loud plus bounded shutdown.
|
||||
@@ -21,7 +21,7 @@ import {
|
||||
loadOverlayPatches,
|
||||
loadProfile,
|
||||
PROFILE_PATCH_FILENAME,
|
||||
watchPersonalPatches,
|
||||
watchUserPatches,
|
||||
type Profile,
|
||||
} from '@deepseek-ai/dsh-app-boot'
|
||||
import { resolveDshHome } from '@deepseek-ai/dsh-paths'
|
||||
@@ -51,7 +51,7 @@ const HEADLESS_ROW_ID = 'headless-runner'
|
||||
|
||||
/** The empty root entry list every profile tree patches over. */
|
||||
const PROFILE_ROOT_CONFIG = `# dsh profile root — an empty entry list. The tree is composed as patches:
|
||||
# each bundle in package.json's dsh.plugins, then cordis.patch.yml, then any
|
||||
# each bundle in package.json's dsh.profile.bundles, then cordis.patch.yml, then any
|
||||
# --patch overlays. Edit cordis.patch.yml, not this file.
|
||||
[]
|
||||
`
|
||||
@@ -119,7 +119,7 @@ function allPatches(composed: ComposedProfile): PatchOptions[] {
|
||||
|
||||
/**
|
||||
* Load `name` and compose its effective patch stack: bundle layers in
|
||||
* `dsh.plugins` order, the profile's user layer, the home-level user layer
|
||||
* `dsh.profile.bundles` order, the profile's user layer, the home-level user layer
|
||||
* (`$DSH_HOME/cordis.patch.yml` — machine-local preferences that apply to
|
||||
* every profile, so it outranks the per-profile layer), `--patch` overlays,
|
||||
* then flag patches derived from the composed rows, then the telemetry
|
||||
@@ -251,12 +251,12 @@ export async function runProfile(options: RunProfileOptions): Promise<{ ctx: Con
|
||||
}
|
||||
await ctx.loader.create({ name: '@cordisjs/plugin-hmr', config: { root: [] } })
|
||||
}
|
||||
await watchPersonalPatches(ctx, {
|
||||
await watchUserPatches(ctx, {
|
||||
binName: NAME,
|
||||
filename: composed.profile.patchPath,
|
||||
compose: composeLive,
|
||||
})
|
||||
await watchPersonalPatches(ctx, {
|
||||
await watchUserPatches(ctx, {
|
||||
binName: NAME,
|
||||
filename: homePatchPath(),
|
||||
compose: composeLive,
|
||||
|
||||
Reference in New Issue
Block a user