|
|
@@ -11,23 +11,21 @@ import type { EntryOptions } from '@deepseek-ai/cordis-plugin-loader'
|
|
|
import type { PatchOptions } from '@deepseek-ai/cordis-plugin-include'
|
|
|
import { claimLayerIds, composeProfileStack, formatRowConflict, type ProfileLayer } from '../src/index.ts'
|
|
|
|
|
|
-const NAME = 'dsh-test-bin'
|
|
|
-
|
|
|
function layer(
|
|
|
- packageName: string, trust: ProfileLayer['trust'], patches: PatchOptions[], stage: ProfileLayer['stage'] = 'runtime',
|
|
|
+ packageName: string, patches: PatchOptions[],
|
|
|
): ProfileLayer {
|
|
|
- return { packageName, version: '1.0.0', packageDir: '/nowhere', patchPath: '/nowhere/cordis.patch.yml', trust, stage, patches }
|
|
|
+ return { packageName, version: '1.0.0', packageDir: '/nowhere', patchPath: '/nowhere/cordis.patch.yml', patches }
|
|
|
}
|
|
|
|
|
|
-const base = layer('@deepseek-ai/dsh-base', 'builtin', [{ insert: [
|
|
|
+const base = layer('@deepseek-ai/dsh-base', [{ insert: [
|
|
|
{ id: 'settings', name: 'settings' },
|
|
|
{ id: 'tools', name: 'cordis:group', group: true, config: [{ id: 'tool-bash', name: 'bash' }] },
|
|
|
] }])
|
|
|
|
|
|
describe('claimLayerIds', () => {
|
|
|
- it('lets built-in layers own their ids, including group children, before any external layer', () => {
|
|
|
- const ext = layer('ext', 'external', [{ insert: [{ id: 'tool-bash', name: 'ext' }] }])
|
|
|
- const { owners, skipped } = claimLayerIds([ext, base])
|
|
|
+ it('preserves earlier bundle ids, including group children', () => {
|
|
|
+ const ext = layer('ext', [{ insert: [{ id: 'tool-bash', name: 'ext' }] }])
|
|
|
+ const { owners, skipped } = claimLayerIds([base, ext])
|
|
|
expect(owners.get('tool-bash')?.packageName).toBe('@deepseek-ai/dsh-base')
|
|
|
expect(owners.get('tools')?.packageName).toBe('@deepseek-ai/dsh-base')
|
|
|
expect(skipped.get('ext')).toEqual([
|
|
|
@@ -38,15 +36,23 @@ describe('claimLayerIds', () => {
|
|
|
])
|
|
|
})
|
|
|
|
|
|
- it('throws when two built-in or boot-staged layers declare one id, or one declares it twice', () => {
|
|
|
- const twin = layer('twin', 'external', [{ insert: [{ id: 'settings', name: 'twin' }] }], 'boot')
|
|
|
- expect(() => claimLayerIds([base, twin])).toThrow(/row "settings" is declared by both @deepseek-ai\/dsh-base and twin/)
|
|
|
- const stutter = layer('stutter', 'builtin', [{ insert: [{ id: 'x', name: 'a' }] }, { insert: [{ id: 'x', name: 'b' }] }])
|
|
|
- expect(() => claimLayerIds([stutter])).toThrow(/row "x" is declared twice by stutter/)
|
|
|
+ it('uses declaration order even when a package has an official name', () => {
|
|
|
+ const earlier = layer('community', [{ insert: [{ id: 'settings', name: 'community/settings' }] }])
|
|
|
+ const result = claimLayerIds([earlier, base])
|
|
|
+ expect(result.owners.get('settings')?.packageName).toBe('community')
|
|
|
+ expect(result.skipped.get('@deepseek-ai/dsh-base')?.map(conflict => conflict.rowId)).toEqual(['settings'])
|
|
|
+ expect(result.owners.has('tools')).toBe(false)
|
|
|
+ })
|
|
|
+
|
|
|
+ it('omits a bundle that conflicts with an earlier layer or repeats its own id', () => {
|
|
|
+ const twin = layer('twin', [{ insert: [{ id: 'settings', name: 'twin' }] }])
|
|
|
+ expect(claimLayerIds([base, twin]).skipped.get('twin')?.map(conflict => conflict.rowId)).toEqual(['settings'])
|
|
|
+ const stutter = layer('stutter', [{ insert: [{ id: 'x', name: 'a' }] }, { insert: [{ id: 'x', name: 'b' }] }])
|
|
|
+ expect(claimLayerIds([stutter]).skipped.get('stutter')?.map(conflict => conflict.rowId)).toEqual(['x'])
|
|
|
})
|
|
|
|
|
|
it('leaves out a bundle whose config override sets a row another layer owns', () => {
|
|
|
- const hijack = layer('hijack', 'external', [
|
|
|
+ const hijack = layer('hijack', [
|
|
|
{ insert: [{ id: 'own', name: 'cordis:group', group: true, config: [] }] },
|
|
|
{ id: 'own', config: [{ id: 'settings', name: 'hijack/impostor' }] },
|
|
|
])
|
|
|
@@ -57,38 +63,38 @@ describe('claimLayerIds', () => {
|
|
|
message: 'row "settings" is already declared by @deepseek-ai/dsh-base',
|
|
|
},
|
|
|
])
|
|
|
- const stack = composeProfileStack(NAME, [base, hijack], [])
|
|
|
+ const stack = composeProfileStack([base, hijack], [])
|
|
|
expect(stack.skippedBundles).toEqual(['hijack'])
|
|
|
expect(stack.owners.get('settings')?.packageName).toBe('@deepseek-ai/dsh-base')
|
|
|
})
|
|
|
|
|
|
- it('lets a built-in layer restate its own rows through a config override, and rejects another layer\'s', () => {
|
|
|
- const restating = layer('restating', 'builtin', [
|
|
|
+ it('lets a layer restate its own rows through a config override, and rejects another layer\'s', () => {
|
|
|
+ const restating = layer('restating', [
|
|
|
{ insert: [{ id: 'g', name: 'cordis:group', group: true, config: [{ id: 'a', name: 'a' }] }] },
|
|
|
{ id: 'g', config: [{ id: 'a', name: 'a' }, { id: 'b', name: 'b' }] },
|
|
|
])
|
|
|
expect([...claimLayerIds([restating]).owners.keys()]).toEqual(['g', 'a', 'b'])
|
|
|
- const taking = layer('taking', 'builtin', [
|
|
|
+ const taking = layer('taking', [
|
|
|
{ insert: [{ id: 'h', name: 'cordis:group', group: true, config: [] }] },
|
|
|
{ id: 'h', config: [{ id: 'settings', name: 'taking/impostor' }] },
|
|
|
])
|
|
|
- expect(() => claimLayerIds([base, taking])).toThrow(/row "settings" is declared by both @deepseek-ai\/dsh-base and taking/)
|
|
|
+ expect(claimLayerIds([base, taking]).skipped.get('taking')?.map(conflict => conflict.rowId)).toEqual(['settings'])
|
|
|
// Setting a row under another group would move it; listing it twice would fail the group's update.
|
|
|
- const moving = layer('moving', 'builtin', [
|
|
|
+ const moving = layer('moving', [
|
|
|
{ insert: [
|
|
|
{ id: 'g', name: 'cordis:group', group: true, config: [{ id: 'a', name: 'a' }] },
|
|
|
{ id: 'h', name: 'cordis:group', group: true, config: [] },
|
|
|
] },
|
|
|
{ id: 'h', config: [{ id: 'a', name: 'a' }] },
|
|
|
])
|
|
|
- expect(() => claimLayerIds([moving])).toThrow(/row "a" is declared twice by moving/)
|
|
|
- const twice = layer('twice', 'builtin', [
|
|
|
+ expect(claimLayerIds([moving]).skipped.get('moving')?.map(conflict => conflict.rowId)).toEqual(['a'])
|
|
|
+ const twice = layer('twice', [
|
|
|
{ insert: [{ id: 'g', name: 'cordis:group', group: true, config: [] }] },
|
|
|
{ id: 'g', config: [{ id: 'a', name: 'a' }, { id: 'a', name: 'a' }] },
|
|
|
])
|
|
|
- expect(() => claimLayerIds([twice])).toThrow(/row "a" is declared twice by twice/)
|
|
|
+ expect(claimLayerIds([twice]).skipped.get('twice')?.map(conflict => conflict.rowId)).toEqual(['a'])
|
|
|
// A child of a group without an id cannot be restated: no patch can address that group.
|
|
|
- const anonymous = layer('anonymous', 'builtin', [
|
|
|
+ const anonymous = layer('anonymous', [
|
|
|
{ insert: [
|
|
|
// A row leaves its id to the Loader, as YAML rows may; the type asks for one.
|
|
|
{ name: 'cordis:group', group: true, config: [{ id: 'a', name: 'a' }] } as unknown as EntryOptions,
|
|
|
@@ -96,11 +102,11 @@ describe('claimLayerIds', () => {
|
|
|
] },
|
|
|
{ id: 'h', config: [{ id: 'a', name: 'a' }] },
|
|
|
])
|
|
|
- expect(() => claimLayerIds([anonymous])).toThrow(/row "a" is declared twice by anonymous/)
|
|
|
+ expect(claimLayerIds([anonymous]).skipped.get('anonymous')?.map(conflict => conflict.rowId)).toEqual(['a'])
|
|
|
})
|
|
|
|
|
|
it('leaves out a bundle whose config override of its own group lists an id twice', () => {
|
|
|
- const self = layer('self', 'external', [
|
|
|
+ const self = layer('self', [
|
|
|
{ insert: [{ id: 'self-group', name: 'cordis:group', group: true, config: [{ id: 'row', name: 'self/row' }] }] },
|
|
|
{ id: 'self-group', config: [{ id: 'row', name: 'self/row' }, { id: 'row', name: 'self/row-again' }] },
|
|
|
])
|
|
|
@@ -110,21 +116,21 @@ describe('claimLayerIds', () => {
|
|
|
})
|
|
|
|
|
|
it('leaves out a bundle that declares one of its own ids twice and composes each mounted bundle once', () => {
|
|
|
- const stutter = layer('stutter', 'external', [{ insert: [{ id: 'x', name: 'stutter/a' }, { id: 'x', name: 'stutter/b' }] }])
|
|
|
- const clean = layer('clean', 'external', [{ insert: [{ id: 'y', name: 'clean' }] }])
|
|
|
+ const stutter = layer('stutter', [{ insert: [{ id: 'x', name: 'stutter/a' }, { id: 'x', name: 'stutter/b' }] }])
|
|
|
+ const clean = layer('clean', [{ insert: [{ id: 'y', name: 'clean' }] }])
|
|
|
const { owners, skipped, composed } = claimLayerIds([base, stutter, clean])
|
|
|
expect(skipped.get('stutter')).toEqual([
|
|
|
{ rowId: 'x', moduleName: 'stutter/b', layer: 'stutter', packageName: 'stutter', declaredBy: 'stutter', message: 'row "x" is declared twice by stutter' },
|
|
|
])
|
|
|
expect(owners.has('x')).toBe(false)
|
|
|
expect(owners.get('y')?.packageName).toBe('clean')
|
|
|
- expect([...composed.keys()]).toEqual(['clean'])
|
|
|
+ expect([...composed.keys()]).toEqual(['@deepseek-ai/dsh-base', 'clean'])
|
|
|
expect(composed.get('clean')?.patches[0]).toEqual({ insert: [{ id: 'y', name: 'clean' }] })
|
|
|
})
|
|
|
|
|
|
it('gives the earlier external bundle the id and leaves the later one out whole', () => {
|
|
|
- const first = layer('first', 'external', [{ insert: [{ id: 'hello', name: 'first' }, { id: 'only-first', name: 'first/x' }] }])
|
|
|
- const second = layer('second', 'external', [{ insert: [{ id: 'hello', name: 'second' }, { id: 'only-second', name: 'second/x' }] }])
|
|
|
+ const first = layer('first', [{ insert: [{ id: 'hello', name: 'first' }, { id: 'only-first', name: 'first/x' }] }])
|
|
|
+ const second = layer('second', [{ insert: [{ id: 'hello', name: 'second' }, { id: 'only-second', name: 'second/x' }] }])
|
|
|
const { owners, skipped } = claimLayerIds([base, first, second])
|
|
|
expect(owners.get('hello')?.packageName).toBe('first')
|
|
|
expect(owners.has('bundle/first')).toBe(false)
|
|
|
@@ -135,7 +141,7 @@ describe('claimLayerIds', () => {
|
|
|
|
|
|
describe('composeProfileStack', () => {
|
|
|
it('collects the rows the user layers disable with a literal disabled: true', () => {
|
|
|
- const stack = composeProfileStack(NAME, [base], [{ label: '/p/cordis.patch.yml', patches: [
|
|
|
+ const stack = composeProfileStack([base], [{ label: '/p/cordis.patch.yml', patches: [
|
|
|
{ id: 'a', disabled: true },
|
|
|
// A gate read from disk is an expression node: a condition of the composition, not a decision.
|
|
|
{ id: 'b', disabled: { __jsExpr: 'true' } as unknown as boolean },
|
|
|
@@ -146,8 +152,8 @@ describe('composeProfileStack', () => {
|
|
|
})
|
|
|
|
|
|
it('mounts owning layers in manifest order and drops a user insert of a taken id', () => {
|
|
|
- const ext = layer('ext', 'external', [{ insert: [{ id: 'ext-tool', name: 'ext' }] }])
|
|
|
- const stack = composeProfileStack(NAME, [base, ext], [
|
|
|
+ const ext = layer('ext', [{ insert: [{ id: 'ext-tool', name: 'ext' }] }])
|
|
|
+ const stack = composeProfileStack([base, ext], [
|
|
|
{ label: '/p/cordis.patch.yml', patches: [
|
|
|
{ id: 'settings', config: { path: '/x' } },
|
|
|
{ insert: [{ id: 'mine', name: 'mine' }, { id: 'ext-tool', name: 'clash' }] },
|
|
|
@@ -183,8 +189,8 @@ describe('composeProfileStack', () => {
|
|
|
})
|
|
|
|
|
|
it('leaves a colliding external bundle out of the stack and reports it', () => {
|
|
|
- const clash = layer('clash', 'external', [{ insert: [{ id: 'settings', name: 'clash' }] }])
|
|
|
- const stack = composeProfileStack(NAME, [base, clash], [])
|
|
|
+ const clash = layer('clash', [{ insert: [{ id: 'settings', name: 'clash' }] }])
|
|
|
+ const stack = composeProfileStack([base, clash], [])
|
|
|
expect(stack.layers.map(current => current.label)).toEqual(['@deepseek-ai/dsh-base'])
|
|
|
expect(stack.skippedBundles).toEqual(['clash'])
|
|
|
expect(stack.conflicts).toEqual([
|
|
|
@@ -195,10 +201,6 @@ describe('composeProfileStack', () => {
|
|
|
])
|
|
|
})
|
|
|
|
|
|
- it('prefixes a built-in duplicate with the binary name', () => {
|
|
|
- const twin = layer('twin', 'builtin', [{ insert: [{ id: 'settings', name: 'twin' }] }])
|
|
|
- expect(() => composeProfileStack(NAME, [base, twin], [])).toThrow(/^dsh-test-bin: row "settings" is declared by both/)
|
|
|
- })
|
|
|
})
|
|
|
|
|
|
describe('formatRowConflict', () => {
|