fix(cmdline): reject multiple command-line owners
This commit is contained in:
@@ -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/cmdline/README.md
|
||||
README.md: 5a7e267691cd19548a19e812390865f140e3a620
|
||||
README.zh.md: 6d5892e97708a6bc464dafee3dcba2521129ea93
|
||||
README.md: dc267080d32d492e132df4592ddf742454a95ad2
|
||||
README.zh.md: e183156ab4a7907f8ae1e3259b2e09d458cec47d
|
||||
|
||||
@@ -35,7 +35,7 @@ The Loader-row injection is also its discovery declaration, so no bundle manifes
|
||||
inject: [cmdlineArgs]
|
||||
```
|
||||
|
||||
The launcher uses that injection only to reject arguments for a composition with no command-line owner. Loader mounts the composition once and holds each row until its own injections are active.
|
||||
The launcher uses that injection only to reject arguments for a composition with no command-line owner, and to reject a composition with multiple owners. Loader mounts the composition once and holds each row until its own injections are active.
|
||||
|
||||
Every row the app configures from flags then reads what the startup row resolved, naming the key it takes and the value it falls back to:
|
||||
|
||||
|
||||
@@ -35,7 +35,7 @@ Loader 行的注入同时也是发现声明,因此无需组合包 manifest 字
|
||||
inject: [cmdlineArgs]
|
||||
```
|
||||
|
||||
启动器只用该注入来拒绝那些没有命令行所有者却带有应用参数的组合。Loader 只挂载一次整套组合,并让每一行等待自身的注入激活。
|
||||
启动器只用该注入来拒绝那些没有命令行所有者却带有应用参数的组合,以及拒绝存在多个所有者的组合。Loader 只挂载一次整套组合,并让每一行等待自身的注入激活。
|
||||
|
||||
应用用 flag 配置的每一行随后读取启动行解析出的取值,各自点名自己取用的键,以及回退时使用的值:
|
||||
|
||||
|
||||
@@ -98,9 +98,28 @@ export function provideCmdline(ctx: Context, host: CmdlineHost): void {
|
||||
* adding the same injection its startup plugin already requires.
|
||||
* @param rows - the composed Loader rows.
|
||||
* @returns whether this composition has a command-line owner.
|
||||
* @throws when more than one active row claims the command line.
|
||||
*/
|
||||
export function hasCmdlineConsumer(rows: readonly EntryOptions[]): boolean {
|
||||
return rows.some(row => row.disabled !== true && waitsForAny(row.inject, ['cmdlineArgs']))
|
||||
const consumers: string[] = []
|
||||
const visit = (entries: readonly EntryOptions[], ancestorDisabled = false, prefix = ''): void => {
|
||||
for (const row of entries) {
|
||||
const id = prefix + row.id
|
||||
// Loader group containers stay active when disabled, but their children
|
||||
// inherit that disabled state.
|
||||
const active = row.group === true || (!ancestorDisabled && row.disabled !== true)
|
||||
if (active && waitsForAny(row.inject, ['cmdlineArgs'])) consumers.push(id)
|
||||
if (row.group === true && Array.isArray(row.config)) {
|
||||
visit(row.config, ancestorDisabled || row.disabled === true, `${id}:`)
|
||||
}
|
||||
}
|
||||
}
|
||||
visit(rows)
|
||||
if (consumers.length > 1) {
|
||||
const ids = consumers.map(id => JSON.stringify(id)).join(', ')
|
||||
throw new Error(`dsh-cmdline: multiple active rows inject cmdlineArgs (${ids}); disable all but one startup row`)
|
||||
}
|
||||
return consumers.length === 1
|
||||
}
|
||||
|
||||
/** The process streams commander output is written to; production writes to the process. */
|
||||
|
||||
@@ -130,6 +130,40 @@ describe('hasCmdlineConsumer', () => {
|
||||
{ id: 'ordinary', name: 'ordinary' },
|
||||
{ id: 'disabled-startup', name: 'disabled-startup', inject: ['cmdlineArgs'], disabled: true },
|
||||
])).toBe(false)
|
||||
expect(() => hasCmdlineConsumer([
|
||||
{ id: 'web-startup', name: 'web-startup', inject: ['cmdlineArgs'] },
|
||||
{ id: 'tui-startup', name: 'tui-startup', inject: ['cmdlineArgs'] },
|
||||
])).toThrow('multiple active rows inject cmdlineArgs ("web-startup", "tui-startup")')
|
||||
})
|
||||
|
||||
it('walks nested groups and ignores consumers disabled by an ancestor', () => {
|
||||
expect(hasCmdlineConsumer([{
|
||||
id: 'app',
|
||||
name: 'cordis:group',
|
||||
group: true,
|
||||
config: [{ id: 'startup', name: 'startup', inject: ['cmdlineArgs'] }],
|
||||
}])).toBe(true)
|
||||
expect(hasCmdlineConsumer([{
|
||||
id: 'app',
|
||||
name: 'cordis:group',
|
||||
group: true,
|
||||
disabled: true,
|
||||
config: [{ id: 'startup', name: 'startup', inject: ['cmdlineArgs'] }],
|
||||
}])).toBe(false)
|
||||
expect(() => hasCmdlineConsumer([
|
||||
{
|
||||
id: 'first',
|
||||
name: 'cordis:group',
|
||||
group: true,
|
||||
config: [{ id: 'startup', name: 'startup', inject: ['cmdlineArgs'] }],
|
||||
},
|
||||
{
|
||||
id: 'second',
|
||||
name: 'cordis:group',
|
||||
group: true,
|
||||
config: [{ id: 'startup', name: 'startup', inject: ['cmdlineArgs'] }],
|
||||
},
|
||||
])).toThrow('multiple active rows inject cmdlineArgs ("first:startup", "second:startup")')
|
||||
})
|
||||
})
|
||||
|
||||
@@ -187,9 +221,9 @@ describe('runStartup', () => {
|
||||
.toThrow('absentStartup: no row injects this startup service')
|
||||
})
|
||||
|
||||
it('provides an empty value when the app declares no plan', async () => {
|
||||
it('accepts a service-name list when the app declares no plan', async () => {
|
||||
const { ctx } = await bootFixture([], demoPlan, { withoutStartup: true })
|
||||
runStartup(ctx, 'demoStartup', demoCommand())
|
||||
runStartup(ctx, ['demoStartup'], demoCommand())
|
||||
expect(ctx.get('demoStartup')).toEqual({})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user