refactor(boot): limit package metadata to bundle management
This commit is contained in:
1 parent
8fc785de3a
commit
d31c59b3b2
14 files changed
+44
-89
No files matched your search
-6
@@ -1,6 +0,0 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.md
|
||||
2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.md: 364e79d29095754e0297dfaf552d9783cc187a4c
|
||||
2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.zh.md: 80a22e1839e0a288b298f6b8b2fda70ecc76c448
|
||||
@@ -0,0 +1,6 @@
|
||||
# Bilingual-pair consistency record (docs/i18n/README.md): the git blob hash of each
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write .agents/notes/implemented/architecture/2026-09-11-native-entry-diagnostics.md
|
||||
2026-09-11-native-entry-diagnostics.md: 3f8e8eea3f9d436170579b7e77878e827fd76f55
|
||||
2026-09-11-native-entry-diagnostics.zh.md: f14430c3afab00ec1bb1f2301b494769ebbe0c3e
|
||||
+7
-7
@@ -1,8 +1,8 @@
|
||||
# Agent Note: Native entry diagnostics and static plugin declarations
|
||||
# Agent Note: Native entry diagnostics
|
||||
|
||||
Status: implemented
|
||||
|
||||
English | [中文](2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.zh.md)
|
||||
English | [中文](2026-09-11-native-entry-diagnostics.zh.md)
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -12,13 +12,13 @@ Third-party plugin failures must leave the application’s management endpoints
|
||||
|
||||
**Startup strictness remains consumer-owned.** App-boot uses the [global required entry ids](2026-09-09-consumer-owned-startup-strictness.md) and the bootstrap Include identity. Package origin does not change the audit: all other rows may fail with a warning. Bundle metadata has no trust or stage policy. Row ownership only attributes diagnostics and management operations; nested Includes inherit their owning entry’s package without matching unrelated root ids.
|
||||
|
||||
**Bundle enablement selects a whole patch layer.** Native rows and author-written groups retain their explicit ids and parents. Anonymous bundle rows receive deterministic ids on detached execution copies. Disabling a bundle removes its inserts and overrides across every target group; persistent user row overrides survive re-enablement. Ownership is checked before loading: strict layers claim ids first, optional collisions omit the whole losing bundle, and conflicting user inserts are omitted per row. Prefixing explicit ids would break author expressions and user patches; relying on authors to avoid collisions permits silent Loader reparenting.
|
||||
**Bundle enablement selects a whole patch layer.** Native rows and author-written groups retain their explicit ids and parents. Anonymous bundle rows receive deterministic ids on detached execution copies. Disabling a bundle removes its inserts and overrides across every target group; persistent user row overrides survive re-enablement. Ownership is checked before loading: bundle layers claim ids in their listed order, collisions omit the whole losing bundle, and conflicting user inserts are omitted per row. Prefixing explicit ids would break author expressions and user patches; relying on authors to avoid collisions permits silent Loader reparenting.
|
||||
|
||||
**Diagnostics describe attempts and running instances separately.** `Entry.lastFailure` passively retains import, activation or update errors where the Loader already catches or propagates them. It does not restore transactions or change native continuation. Asynchronous activation failures come from `fiber.await()`, pending dependencies from injection state, and disabled-expression failures from evaluating the effective gate. An active old fiber can coexist with an update failure. Removed entries need no separate failure registry. Nested continuation failures remain advisory; readiness does not promise every nested plugin callback succeeded.
|
||||
|
||||
**Recomposition is serialized and nontransactional.** The launcher provides `ProfileRuntime` before startup auditing. Preparation rejects malformed files before mutation. Accepted updates settle current Loader work and await removed fibers before publishing composition facts and returning issues. Old fiber references are necessary because removed entries are absent from Loader task enumeration. Successful sibling changes remain applied after another entry fails. Fresh patch copies prevent a later override from mutating data reused by another composition. `ProfileRuntime` declares Loader as a required injection so both root and plugin-context handles can reconcile and inspect the tree.
|
||||
|
||||
**Discovery reads declarations without executing modules.** `readPackageMetadata` reads package identity, bundle patches and explicit `dsh.plugins` entries. `.` names the main export; subpaths and default configs remain available for composition editors. Undeclared packages remain installed and unknown. A declaration does not certify importability, Config validity or activation. Physical Cordis peer resolution is advisory and cannot detect an inlined copy. Runtime validation belongs to each actual mounted row, including separate mounts of one module in different presets.
|
||||
**Metadata discovery does not execute installed code.** `readPackageMetadata` reads package identity and bundle patch rows. A bundle’s metadata describes its declared composition; packages without a bundle patch remain installed and unknown. Physical Cordis peer resolution is advisory and cannot detect an inlined copy. Config validation and execution diagnostics belong to actual mounted rows.
|
||||
|
||||
**Unhandled process failures stay fatal.** `installFailLoud` remains until shutdown. Only duplicate rejections already observed by an entry audit are coalesced through its process checkpoint. Unrelated detached rejections retain master’s teardown-and-exit policy. No in-process group protects against `process.exit`, a blocked event loop, native crashes or OOM. Preset generation owners retain their own strict mount-and-cleanup behavior.
|
||||
|
||||
@@ -26,7 +26,7 @@ Third-party plugin failures must leave the application’s management endpoints
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**Keep contained groups and child probes.** Contained groups protected against transactional Loader rollback and supplied failure records for removed rows. Native entries retain both siblings and failed rows, so wrapping adds identities without supplying bundle-wide enablement that layer composition lacks. Child probes bounded discovery-time execution and discovered undeclared exports and schemas. Static declarations avoid that execution; automatic main-export detection and pre-mount schemas are given up. A future execution sandbox needs its own process ownership and teardown design, not a discovery probe presented as runtime isolation.
|
||||
**Wrap every bundle in a synthetic parent group.** Native entries retain both successful siblings and failed rows. A wrapper adds another entry identity, while whole-bundle enablement already follows the selected patch layers. Preserving author-written ids keeps their expressions and user patches valid.
|
||||
|
||||
**Import inside the Host with try/catch.** Rejected because discovery could execute irreversible side effects, exit the Host or block its event loop. Catching exceptions cannot restore that isolation. Parsing source to infer plugin exports would add another JavaScript interpreter with incomplete results.
|
||||
|
||||
@@ -36,8 +36,8 @@ Third-party plugin failures must leave the application’s management endpoints
|
||||
|
||||
## Consequences
|
||||
|
||||
Installed metadata, enabled layer selection and current runtime health are independent. An enabled bundle may be partially running, pending or failed; updating a row may leave old behavior active with a visible diagnostic. Whole-bundle disable removes its patch effects without requiring a synthetic parent group. Startup can still fail when an optional provider leaves a required consumer pending. Old probe records are not activation evidence, and plugins need explicit `dsh.plugins` declarations for discovery as addable modules.
|
||||
Installed metadata, enabled layer selection and current runtime health are independent. An enabled bundle may be partially running, pending or failed; updating a row may leave old behavior active with a visible diagnostic. Whole-bundle disable removes its patch effects without requiring a synthetic parent group. Startup can still fail when an optional provider leaves a required consumer pending.
|
||||
|
||||
## Verification
|
||||
|
||||
`packages/boot/app-boot/tests/entry-issues.spec.ts` covers required-id policy, import/config/apply/disabled/pending failures, nested-entry and anonymous-row bundle ownership, active old config after failed updates, recovery, and awaited removed-fiber teardown. Composition tests cover duplicate ownership and unchanged parents. Static metadata tests use an import-time file-writing fixture and assert no execution, retain unknown packages, and reject malformed declarations. Inventory tests verify actual Loader failures and composition conflicts without a historical failure registry. Process-guard and user-patch tests retain fatal detached failures and best-effort live reload diagnostics.
|
||||
`packages/boot/app-boot/tests/entry-issues.spec.ts` covers required-id policy, import/config/apply/disabled/pending failures, nested-entry and anonymous-row bundle ownership, active old config after failed updates, recovery, and awaited removed-fiber teardown. Composition tests cover duplicate ownership and unchanged parents. Static metadata tests use an import-time file-writing fixture and assert no execution, retain unknown packages, and ignore unrelated manifest fields. Inventory tests verify actual Loader failures and composition conflicts without a historical failure registry. Process-guard and user-patch tests retain fatal detached failures and best-effort live reload diagnostics.
|
||||
+7
-7
@@ -1,8 +1,8 @@
|
||||
# Agent Note: 原生条目诊断与静态插件声明
|
||||
# Agent Note: 原生条目诊断
|
||||
|
||||
Status: implemented
|
||||
|
||||
[English](2026-09-11-native-entry-diagnostics-and-static-plugin-declarations.md) | 中文
|
||||
[English](2026-09-11-native-entry-diagnostics.md) | 中文
|
||||
|
||||
## Problem
|
||||
|
||||
@@ -12,13 +12,13 @@ Status: implemented
|
||||
|
||||
**启动严格程度仍由消费者负责。** App-boot 使用[全局必需条目 id](2026-09-09-consumer-owned-startup-strictness.zh.md) 和启动 Include 的身份。包来源不改变审查规则:其他行失败只产生警告。组合包元数据没有 trust 或 stage 策略。行归属只用于诊断和管理操作;嵌套 Include 继承所属条目的包,不能匹配无关的根级 id。
|
||||
|
||||
**组合包启停选择整份 patch 层。** 原生行和作者声明的组保留显式 id 与父组。匿名组合包行在独立执行副本中获得确定性的 id。禁用组合包会从所有目标组中移除其插入与覆盖;持久化的用户行覆盖在重新启用后保留。加载前检查所有权:严格层优先占有 id,可选层冲突时整包排除,用户插入冲突时逐行排除。为显式 id 加前缀会破坏作者表达式与用户 patch;依赖作者自行避免冲突则允许 Loader 静默改变父组。
|
||||
**组合包启停选择整份 patch 层。** 原生行和作者声明的组保留显式 id 与父组。匿名组合包行在独立执行副本中获得确定性的 id。禁用组合包会从所有目标组中移除其插入与覆盖;持久化的用户行覆盖在重新启用后保留。加载前检查所有权:组合包层按声明顺序占有 id,冲突时整包排除,用户插入冲突时逐行排除。为显式 id 加前缀会破坏作者表达式与用户 patch;依赖作者自行避免冲突则允许 Loader 静默改变父组。
|
||||
|
||||
**诊断分别描述操作尝试与运行实例。** `Entry.lastFailure` 在 Loader 已有的捕获或传播位置被动保留导入、激活或更新错误。它不恢复事务,也不改变原生继续加载行为。异步激活失败取自 `fiber.await()`,等待依赖取自注入状态,disabled 表达式失败取自有效条件的求值。活跃的旧 fiber 可以与更新失败同时存在。移除的条目无需独立失败注册表。嵌套延续任务失败仍为提示信息;就绪不保证每个嵌套插件回调均成功。
|
||||
|
||||
**重组串行执行且不具备事务性。** launcher 在启动审计前提供 `ProfileRuntime`。准备阶段在修改运行树前拒绝无效文件。已接受的更新等待当前 Loader 工作与已移除 fiber 完成后,发布组合信息并返回问题。必须保留旧 fiber 引用,因为已移除的条目不在 Loader 任务枚举中。某行失败后,其他行已成功的变化保留。新的 patch 副本避免后续覆盖修改其他重组会复用的数据。 `ProfileRuntime` 将 Loader 声明为必需注入,使根上下文与插件上下文取得的句柄都能重组并检查配置树。
|
||||
|
||||
**发现阶段只读声明,不执行模块。** `readPackageMetadata` 读取包身份、组合包 patch 和显式 `dsh.plugins` 条目。`.` 表示主入口;子路径与默认配置继续供组合编辑器使用。未声明的包保持已安装和未知状态。声明不证明可导入、Config 有效或激活成功。Cordis 物理 peer 解析仅供提示,无法识别内联副本。运行校验属于每个实际挂载行,包括同一模块在不同预设中的独立挂载。
|
||||
**发现元数据时不执行安装包代码。** `readPackageMetadata` 读取包身份和组合包 patch 行。组合包元数据描述其声明的组合;没有组合包 patch 的包保持已安装和未知状态。Cordis 物理 peer 解析仅供提示,无法识别内联副本。Config 校验和执行诊断属于实际挂载的行。
|
||||
|
||||
**未处理的进程级失败仍然致命。** `installFailLoud` 保持到应用关闭。只有条目审计已观测到的重复 rejection 会在其进程检查点内合并。无关的脱离管理 rejection 保留 master 的清理退出策略。进程内的组无法防护 `process.exit`、事件循环阻塞、原生崩溃或 OOM。预设代际的拥有者保留独立的严格挂载与清理行为。
|
||||
|
||||
@@ -26,7 +26,7 @@ Status: implemented
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
**保留 contained group 和子进程 probe。** contained group 防止事务式 Loader 回滚,并为已移除行提供失败记录。原生条目会同时保留成功行与失败行,包装只增加身份,并未提供整层组合所缺少的整包启停能力。子进程 probe 为发现阶段的执行设定边界,并发现未声明导出与 schema。静态声明避免这次执行,同时放弃自动主入口识别和挂载前 schema。未来的执行沙箱需要独立设计进程归属与清理,不能把发现 probe 视为运行隔离。
|
||||
**为每个组合包包装一个合成父组。** 原生条目同时保留成功行与失败行。包装会增加条目身份,而整包启停已经由选中的 patch 层决定。保留作者声明的 id,才能继续使用其表达式和用户 patch。
|
||||
|
||||
**在 Host 内 import 并 try/catch。** 拒绝,因为发现阶段可能产生不可逆副作用、退出 Host 或阻塞事件循环。捕获异常无法恢复隔离。通过解析源码推断插件导出会增加一个结果不完整的 JavaScript 解释器。
|
||||
|
||||
@@ -36,8 +36,8 @@ Status: implemented
|
||||
|
||||
## Consequences
|
||||
|
||||
已安装元信息、启用层选择与当前运行健康相互独立。启用的组合包可能部分运行、等待依赖或失败;行更新可能保留旧行为并显示诊断。整包禁用移除其 patch 效果,无需合成父组。可选提供方失败导致必需消费方等待时,启动仍可能失败。旧 probe 记录不是激活证据,插件需要显式 `dsh.plugins` 声明才能被发现为可添加模块。
|
||||
已安装元信息、启用层选择与当前运行健康相互独立。启用的组合包可能部分运行、等待依赖或失败;行更新可能保留旧行为并显示诊断。整包禁用移除其 patch 效果,无需合成父组。可选提供方失败导致必需消费方等待时,启动仍可能失败。
|
||||
|
||||
## Verification
|
||||
|
||||
`packages/boot/app-boot/tests/entry-issues.spec.ts` 覆盖必需 id 策略、导入/配置/apply/disabled/pending 失败、嵌套条目与匿名行的组合包归属、失败更新后的活跃旧配置、恢复及已移除 fiber 的清理等待。组合测试覆盖重复归属与父组保持。静态元信息测试使用导入时写文件的 fixture,确认未执行、保留未知包并拒绝无效声明。inventory 测试直接验证 Loader 失败与组合冲突,不依赖历史失败注册表。进程处理与用户 patch 测试保留脱离管理失败的致命策略,以及尽力执行的实时重载诊断。
|
||||
`packages/boot/app-boot/tests/entry-issues.spec.ts` 覆盖必需 id 策略、导入/配置/apply/disabled/pending 失败、嵌套条目与匿名行的组合包归属、失败更新后的活跃旧配置、恢复及已移除 fiber 的清理等待。组合测试覆盖重复归属与父组保持。静态元信息测试使用导入时写文件的 fixture,确认未执行、保留未知包并忽略无关的 manifest 字段。inventory 测试直接验证 Loader 失败与组合冲突,不依赖历史失败注册表。进程处理与用户 patch 测试保留脱离管理失败的致命策略,以及尽力执行的实时重载诊断。
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/boot/app-boot/README.md
|
||||
README.md: 7d7d612def6e9261fd64097fa1381761a33e087e
|
||||
README.zh.md: 3f76119dfbed4ec47c3cb6ecd435899ed8d813ad
|
||||
README.md: 1a5fc28eac65d2e61b2022007be48123702578f9
|
||||
README.zh.md: 170fe36aa8b847f5d06652a1920e3587d763dbe9
|
||||
@@ -174,7 +174,7 @@ These limits describe when this boot library is a poor fit or needs special care
|
||||
- **Overrides retain the target’s owner** — changing a built-in row can make that required row fail; successful updates to other rows remain applied. Disabling the bundle removes its whole patch layer, including overrides.
|
||||
- **A conflict is decided by order, not merit** — among external bundles the earlier layer in `dsh.profile.bundles` keeps a contested id, so uninstalling that bundle lets the later one mount on the next boot; the plugin list shows which bundle lost and to whom.
|
||||
- **The nested-fiber audit is advisory** — a failed `ctx.inject()` continuation under a built-in entry is reported, not fatal, until shipped compositions are known clean.
|
||||
- **Declarations are not activation results** — `readPackageMetadata` reads `dsh.plugins` without importing modules. Declare `plugins: [{ name: "." }]` for a main export, or subpaths such as `./tools`; undeclared packages remain `unknown`. Config validation and execution diagnostics appear only after an actual mount. Physical Cordis resolution cannot detect an inlined copy.
|
||||
- **Metadata is not an activation result** — `readPackageMetadata` reads package identity and bundle patch rows without importing modules. Packages without a bundle patch remain `unknown`. Config validation and execution diagnostics appear only after an actual mount. Physical Cordis resolution cannot detect an inlined copy.
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### Dev Note
|
||||
|
||||
@@ -174,7 +174,7 @@ Loader 结算后,app-boot 将 optional 失败报告为警告;若已启用的
|
||||
- **覆盖保留目标的所有者**——修改内置行可能使该必需行失败;其他行成功的更新仍然生效。禁用组合包移除其整份 patch 层,包括覆盖。
|
||||
- **冲突按顺序判定,不看是非**——外部组合包之间,`dsh.profile.bundles` 里靠前的那层保住争议 id,卸掉它之后靠后的那层在下次启动时挂上;插件列表显示谁输给了谁。
|
||||
- **嵌套 fiber 审计只是提示**——内置条目下失败的 `ctx.inject()` 延续会被报告而非致命,直到确认随附组合都没有这类失败。
|
||||
- **声明不代表激活结果**——`readPackageMetadata` 读取 `dsh.plugins`,不导入模块。主入口声明为 `plugins: [{ name: "." }]`,子路径可写为 `./tools`;未声明的包保持 `unknown`。Config 校验和执行诊断只在实际挂载后产生。Cordis 物理路径解析无法识别被内联打包的副本。
|
||||
- **元数据不代表激活结果**——`readPackageMetadata` 读取包身份和组合包 patch 行,不导入模块。没有组合包 patch 的包保持 `unknown`。Config 校验和执行诊断只在实际挂载后产生。Cordis 物理路径解析无法识别被内联打包的副本。
|
||||
|
||||
<a id="dev-note"></a>
|
||||
### 开发备注
|
||||
|
||||
@@ -6,7 +6,6 @@ import { fileURLToPath } from 'node:url'
|
||||
import { loadOverlayPatches } from './index.ts'
|
||||
import { readProfileManifest, resolveBundleDir } from './profile.ts'
|
||||
import { visitPatchRows } from './patch-rows.ts'
|
||||
import type { DshPluginDeclaration } from '@deepseek-ai/dsh-package-manifest'
|
||||
|
||||
/** One configured bundle row; conditions remain unevaluated until it mounts. */
|
||||
export interface PackageMetadataRow {
|
||||
@@ -21,13 +20,12 @@ export interface PackageMetadata {
|
||||
readonly version?: string
|
||||
readonly description?: string
|
||||
readonly title?: string
|
||||
readonly kind: 'bundle' | 'plugin' | 'unknown'
|
||||
readonly kind: 'bundle' | 'unknown'
|
||||
readonly enginesDsh?: string
|
||||
/** Physical resolution only; null when unavailable, not an execution guarantee. */
|
||||
readonly cordisSameCopy: boolean | null
|
||||
readonly rows: readonly PackageMetadataRow[]
|
||||
readonly overrides: readonly string[]
|
||||
readonly addable: readonly DshPluginDeclaration[]
|
||||
}
|
||||
|
||||
/** Locations needed to read one installed package. */
|
||||
@@ -39,39 +37,15 @@ export interface PackageMetadataOptions {
|
||||
}
|
||||
|
||||
/**
|
||||
* Read package identity, bundle rows and explicitly addable modules without loading code.
|
||||
* Read package identity and bundle rows without loading code.
|
||||
* @param options - installation and profile resolution anchors.
|
||||
* @returns declared metadata; an undeclared main export remains unknown.
|
||||
* @returns declared metadata; packages without a bundle patch remain unknown.
|
||||
* @throws when the package or its declaration cannot be read or parsed.
|
||||
*/
|
||||
export function readPackageMetadata(options: PackageMetadataOptions): PackageMetadata {
|
||||
const { binName, packageName, installAnchor, profileDir } = options
|
||||
const dir = resolveBundleDir(binName, packageName, installAnchor, profileDir)
|
||||
const manifest = readProfileManifest(binName, dir)
|
||||
const declarations: unknown = manifest.dsh?.plugins
|
||||
const addable: DshPluginDeclaration[] = []
|
||||
if (declarations !== undefined) {
|
||||
if (!Array.isArray(declarations)) throw new TypeError(`${packageName}: dsh.plugins must be an array`)
|
||||
const names = new Set<string>()
|
||||
for (const value of declarations as unknown[]) {
|
||||
if (!isRecord(value) || typeof value.name !== 'string' || value.name.length === 0
|
||||
|| (value.title !== undefined && typeof value.title !== 'string')) {
|
||||
throw new TypeError(`${packageName}: dsh.plugins entries require a name and an optional string title`)
|
||||
}
|
||||
const name = value.name
|
||||
const subpath = name.replace(/^\.\//, '')
|
||||
if (name !== '.' && (subpath.startsWith('/') || subpath.split('/').some(part => part === '..' || part === '.' || part === '')
|
||||
|| /[:\\]/.test(subpath))) throw new TypeError(`${packageName}: invalid plugin subpath ${JSON.stringify(name)}`)
|
||||
const key = name === '.' ? '.' : subpath
|
||||
if (names.has(key)) throw new TypeError(`${packageName}: repeated plugin subpath ${JSON.stringify(name)}`)
|
||||
names.add(key)
|
||||
addable.push({
|
||||
name,
|
||||
...value.title === undefined ? {} : { title: value.title },
|
||||
...value.config === undefined ? {} : { config: value.config },
|
||||
})
|
||||
}
|
||||
}
|
||||
const rows: PackageMetadataRow[] = []
|
||||
const own = new Set<string>()
|
||||
const declaredBundle = manifest.dsh?.bundle?.patch
|
||||
@@ -88,15 +62,11 @@ export function readPackageMetadata(options: PackageMetadataOptions): PackageMet
|
||||
...manifest.description === undefined ? {} : { description: manifest.description },
|
||||
...manifest.dsh?.title === undefined ? {} : { title: manifest.dsh.title },
|
||||
...manifest.engines?.dsh === undefined ? {} : { enginesDsh: manifest.engines.dsh },
|
||||
kind: declaredBundle !== undefined ? 'bundle' : addable.length > 0 ? 'plugin' : 'unknown',
|
||||
cordisSameCopy, rows, overrides, addable,
|
||||
kind: declaredBundle !== undefined ? 'bundle' : 'unknown',
|
||||
cordisSameCopy, rows, overrides,
|
||||
}
|
||||
}
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return typeof value === 'object' && value !== null && !Array.isArray(value)
|
||||
}
|
||||
|
||||
/** Resolve physical package identity through a packaged-runtime module proxy. */
|
||||
function cordisPackageDir(resolved: string): string | undefined {
|
||||
let dir = dirname(resolved.startsWith('file:') ? fileURLToPath(resolved) : resolved)
|
||||
|
||||
@@ -63,20 +63,18 @@ describe('readPackageMetadata', () => {
|
||||
expect(metadata.cordisSameCopy).toBe(kind === 'foreign' ? false : kind === 'proxy' ? true : null)
|
||||
})
|
||||
|
||||
it('reads explicit main and subpath entries without importing them or reading a probe cache', () => {
|
||||
it('reads package identity without executing the main export', () => {
|
||||
const staged = stage({ pkg: {
|
||||
main: "import { writeFileSync } from 'node:fs'; writeFileSync(new URL('./executed', import.meta.url), 'bad'); throw new Error('must never execute')",
|
||||
manifest: { dsh: { title: 'Package', plugins: [{ name: '.', title: 'Main', config: { a: 1 } }, { name: './tools', title: 'Tools' }] } },
|
||||
manifest: { dsh: { title: 'Package' } },
|
||||
} })
|
||||
const options = { ...staged, binName: NAME, packageName: 'pkg' }
|
||||
const result = readPackageMetadata(options)
|
||||
expect(result).toMatchObject({ kind: 'plugin', title: 'Package', version: '1.0.0',
|
||||
addable: [{ name: '.', title: 'Main', config: { a: 1 } }, { name: './tools', title: 'Tools' }],
|
||||
})
|
||||
expect(result).toMatchObject({ kind: 'unknown', title: 'Package', version: '1.0.0' })
|
||||
const dir = join(staged.profileDir, 'node_modules/pkg')
|
||||
expect(existsSync(join(dir, 'executed'))).toBe(false)
|
||||
writeFileSync(join(dir, 'package.json'), JSON.stringify({ name: 'pkg', version: '2.0.0' }))
|
||||
expect(readPackageMetadata(options)).toMatchObject({ kind: 'unknown', version: '2.0.0', addable: [] })
|
||||
expect(readPackageMetadata(options)).toMatchObject({ kind: 'unknown', version: '2.0.0' })
|
||||
})
|
||||
it('lists bundle rows, nested groups, unevaluated gates and external overrides', () => {
|
||||
const staged = stage({ pkg: {
|
||||
@@ -98,7 +96,7 @@ describe('readPackageMetadata', () => {
|
||||
expect(readPackageMetadata({ ...staged, binName: NAME, packageName: 'pkg' })).toMatchObject({
|
||||
kind: 'bundle', description: 'Bundle', enginesDsh: '>=0.1.0',
|
||||
rows: [{ id: 'group', name: 'cordis:group', gated: false }, { id: 'tool', name: 'pkg/tool', gated: true }],
|
||||
overrides: ['webserver'], addable: [],
|
||||
overrides: ['webserver'],
|
||||
})
|
||||
})
|
||||
it.each(['export default () => {}', 'export function apply() {}', 'throw new Error("broken")'])('keeps an undeclared package unknown: %s', (main) => {
|
||||
@@ -106,11 +104,13 @@ describe('readPackageMetadata', () => {
|
||||
expect(readPackageMetadata({ ...staged, binName: NAME, packageName: 'pkg' }).kind).toBe('unknown')
|
||||
expect(existsSync(join(staged.profileDir, 'node_modules/pkg/package.json'))).toBe(true)
|
||||
})
|
||||
it.each([
|
||||
'invalid', [{}], [{ name: '' }], [{ name: '../outside' }], [{ name: '/absolute' }],
|
||||
[{ name: './tools' }, { name: 'tools' }], [{ name: '.', title: 1 }],
|
||||
])('rejects malformed or ambiguous plugin declarations: %j', (plugins) => {
|
||||
const staged = stage({ pkg: { manifest: { dsh: { plugins } } } })
|
||||
expect(() => readPackageMetadata({ ...staged, binName: NAME, packageName: 'pkg' })).toThrow()
|
||||
it('ignores unrelated metadata while reading a bundle patch', () => {
|
||||
const staged = stage({ pkg: {
|
||||
manifest: { dsh: { bundle: { patch: './patch.yml' }, plugins: 'unrecognized metadata' } },
|
||||
files: { 'patch.yml': '- insert:\n - id: tool\n name: pkg/tool\n' },
|
||||
} })
|
||||
expect(readPackageMetadata({ ...staged, binName: NAME, packageName: 'pkg' })).toMatchObject({
|
||||
kind: 'bundle', rows: [{ id: 'tool', name: 'pkg/tool', gated: false }],
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -2,5 +2,5 @@
|
||||
# side as of the last confirmed-consistent state. Both languages carry equal authority;
|
||||
# after editing either side, bring the other along and re-record with:
|
||||
# pnpm run verify-translation-pairing --write packages/util/package-manifest/README.md
|
||||
README.md: 081b07d9a9123d688b288f1ad30dc9465b7dcbe9
|
||||
README.zh.md: d9edc50e6979a1d6664076c977ca0ad62b27dc6b
|
||||
README.md: ca14a4bd1929faa3d2009ba6c6d2c0bb331a5b37
|
||||
README.zh.md: e52f0699f225925eb99dcbfad8b4c00e72a52e72
|
||||
@@ -38,7 +38,6 @@ const manifest: DshPackageManifest = {
|
||||
dsh: {
|
||||
manifestVersion: 1,
|
||||
title: 'Example plugin',
|
||||
plugins: [{ name: './tool.js', config: { enabled: true } }],
|
||||
bundle: { patch: './cordis.patch.yml' },
|
||||
client,
|
||||
},
|
||||
|
||||
@@ -38,7 +38,6 @@ const manifest: DshPackageManifest = {
|
||||
dsh: {
|
||||
manifestVersion: 1,
|
||||
title: 'Example plugin',
|
||||
plugins: [{ name: './tool.js', config: { enabled: true } }],
|
||||
bundle: { patch: './cordis.patch.yml' },
|
||||
client,
|
||||
},
|
||||
|
||||
@@ -9,7 +9,6 @@ export type {
|
||||
DshEnginesManifest,
|
||||
DshManifest,
|
||||
DshPackageManifest,
|
||||
DshPluginDeclaration,
|
||||
DshProfileManifest,
|
||||
ProfilePatchReload,
|
||||
} from './types.ts'
|
||||
@@ -36,8 +36,6 @@ export interface DshManifest {
|
||||
client?: DshClientManifest
|
||||
/** Display title of the package, read without executing package code. */
|
||||
title?: string
|
||||
/** Agent-plane modules the package declares addable to a composition, read without executing package code. */
|
||||
plugins?: DshPluginDeclaration[]
|
||||
}
|
||||
|
||||
/** Runtime version requirements under `package.json.engines`. */
|
||||
@@ -52,16 +50,6 @@ export interface DshEnginesManifest {
|
||||
[engine: string]: string | undefined
|
||||
}
|
||||
|
||||
/** One agent-plane module a package declares addable to a composition. */
|
||||
export interface DshPluginDeclaration {
|
||||
/** The package's export subpath, with `.` naming its main entry and `./tools` a subpath. */
|
||||
name: string
|
||||
/** Display title. */
|
||||
title?: string
|
||||
/** Default row config. */
|
||||
config?: unknown
|
||||
}
|
||||
|
||||
/** The configuration layer exported by a bundle package. */
|
||||
export interface DshBundleManifest {
|
||||
/** Patch file path relative to the declaring package root. */
|
||||
|
||||
Reference in new issue
Block a user