refactor(cmdline): trim the command-line seams to existing interfaces
The web runtime creates its dev-mode client-hmr row in the root tree after Loader settlement with plain loader.create, deleting the vendored Entry.enableRuntime state machine and dsh-cmdline's enableRow export. Include declares the existing EntryGroup.key tree-carrier marker instead of the EntryConfigResolver protocol (its own path stays literal; nothing used a dynamic path). The launcher recognizes no app row: SIGTERM exits 0 on every surface, every boot watches its user patch layers, and the headless runner exits through ctx.appExit, deleting ctx.headlessIo. Also restores the vendor README rescope entry to the position the rescope-vendor exact-edit anchor requires, fixing the master hygiene regression.
This commit is contained in:
@@ -58,17 +58,20 @@ function fakeHttpServer(host: '127.0.0.1' | '0.0.0.0' = '127.0.0.1'): { server:
|
||||
return { server, seat: () => fallback }
|
||||
}
|
||||
|
||||
/** Install the optional HMR row the runtime sequences before client discovery. */
|
||||
/** A fake Loader capturing the dev-mode row creation the runtime performs after settlement. */
|
||||
function provideHmrRow(ctx: Context, settle: () => Promise<void> = async () => {}): string[] {
|
||||
const updates: string[] = []
|
||||
const created: string[] = []
|
||||
const entries: { options: { name: string } }[] = []
|
||||
ctx.provide('loader', {
|
||||
entries: () => [{
|
||||
options: { id: 'client-hmr' },
|
||||
enableRuntime: async () => { updates.push('client-hmr') },
|
||||
}],
|
||||
entries: () => entries[Symbol.iterator](),
|
||||
create: (options: { name: string }) => {
|
||||
created.push(options.name)
|
||||
entries.push({ options })
|
||||
return Promise.resolve(options.name)
|
||||
},
|
||||
await: settle,
|
||||
} as never)
|
||||
return updates
|
||||
return created
|
||||
}
|
||||
|
||||
interface BashContribution {
|
||||
@@ -92,13 +95,13 @@ describe('web-app runtime glue', () => {
|
||||
} as never)
|
||||
const enabledRows = provideHmrRow(ctx)
|
||||
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
await apply(ctx, new Config({ mode: 'development', printUrl: true, surfaceContext: true, trustedHosts: ['lab.internal'] }))
|
||||
apply(ctx, new Config({ mode: 'development', printUrl: true, surfaceContext: true, trustedHosts: ['lab.internal'] }))
|
||||
await ctx.plugin(SystemPrompt, { persona: '' })
|
||||
// Settle the injected registrations.
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
|
||||
expect(seat()).toBeDefined() // frontend-static claimed the fallback
|
||||
expect(enabledRows).toEqual(['client-hmr'])
|
||||
expect(enabledRows).toEqual(['@deepseek-ai/dsh-client-hmr'])
|
||||
expect(ctx.get('webRuntime')).toEqual({
|
||||
lanAddresses: ['192.168.1.5'],
|
||||
trustedHosts: ['192.168.1.5', 'lab.internal'],
|
||||
@@ -119,7 +122,7 @@ describe('web-app runtime glue', () => {
|
||||
const ctx = new Context()
|
||||
ctx.provide('httpServer', fakeHttpServer().server)
|
||||
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
await apply(ctx, new Config({ mode: 'production', printUrl: false, surfaceContext: true, trustedHosts: [] }))
|
||||
apply(ctx, new Config({ mode: 'production', printUrl: false, surfaceContext: true, trustedHosts: [] }))
|
||||
await ctx.plugin(SystemPrompt, { persona: '' })
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(log).not.toHaveBeenCalled()
|
||||
@@ -140,7 +143,7 @@ describe('web-app runtime glue', () => {
|
||||
return () => {}
|
||||
},
|
||||
} as never)
|
||||
await apply(ctx, new Config({ mode: 'production', printUrl: false, surfaceContext: false, trustedHosts: [] }))
|
||||
apply(ctx, new Config({ mode: 'production', printUrl: false, surfaceContext: false, trustedHosts: [] }))
|
||||
await ctx.plugin(SystemPrompt, { persona: '' })
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
const assembly = await ctx.systemPrompt.assemble()
|
||||
@@ -155,12 +158,94 @@ describe('web-app runtime glue', () => {
|
||||
const ctx = new Context()
|
||||
ctx.provide('httpServer', fakeHttpServer().server)
|
||||
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
await apply(ctx, new Config({ mode: 'production', printUrl: true, surfaceContext: true, trustedHosts: [] }))
|
||||
apply(ctx, new Config({ mode: 'production', printUrl: true, surfaceContext: true, trustedHosts: [] }))
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(log).toHaveBeenCalledWith('dsh web: http://127.0.0.1:4567')
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('creates the client-hmr row exactly once across runtime reloads', async () => {
|
||||
stageDist()
|
||||
const ctx = new Context()
|
||||
ctx.provide('httpServer', fakeHttpServer().server)
|
||||
const created = provideHmrRow(ctx)
|
||||
const mount = async (): Promise<() => Promise<void>> => {
|
||||
const fiber = ctx.plugin((child: Context) => {
|
||||
apply(child, new Config({ mode: 'development', printUrl: false, surfaceContext: false, trustedHosts: [] }))
|
||||
})
|
||||
await fiber
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
return () => fiber.dispose()
|
||||
}
|
||||
const disposeFirst = await mount()
|
||||
expect(created).toEqual(['@deepseek-ai/dsh-client-hmr'])
|
||||
await disposeFirst()
|
||||
// A reload generation must not duplicate the row the previous one created.
|
||||
const disposeSecond = await mount()
|
||||
expect(created).toEqual(['@deepseek-ai/dsh-client-hmr'])
|
||||
await disposeSecond()
|
||||
await ctx.fiber.dispose()
|
||||
})
|
||||
|
||||
it('skips the dev row when the tree is disposed during settlement and logs a creation failure', async () => {
|
||||
stageDist()
|
||||
const raced = new Context()
|
||||
raced.provide('httpServer', fakeHttpServer().server)
|
||||
let release!: () => void
|
||||
const settlement = new Promise<void>((resolve) => { release = resolve })
|
||||
const created: string[] = []
|
||||
const disposeLoader = raced.provide('loader', {
|
||||
entries: () => [][Symbol.iterator](),
|
||||
create: (options: { name: string }) => {
|
||||
created.push(options.name)
|
||||
return Promise.resolve(options.name)
|
||||
},
|
||||
await: () => settlement,
|
||||
} as never)
|
||||
apply(raced, new Config({ mode: 'development', printUrl: false, surfaceContext: false, trustedHosts: [] }))
|
||||
disposeLoader()
|
||||
release()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(created).toEqual([])
|
||||
await raced.fiber.dispose()
|
||||
|
||||
const failing = new Context()
|
||||
failing.provide('httpServer', fakeHttpServer().server)
|
||||
const failure = new Error('row creation failed')
|
||||
failing.provide('loader', {
|
||||
entries: () => [][Symbol.iterator](),
|
||||
create: () => Promise.reject(failure),
|
||||
await: () => Promise.resolve(),
|
||||
} as never)
|
||||
const errors: unknown[] = []
|
||||
failing.logger.error = ((error: unknown) => { errors.push(error) }) as typeof failing.logger.error
|
||||
apply(failing, new Config({ mode: 'development', printUrl: false, surfaceContext: false, trustedHosts: [] }))
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(errors).toEqual([failure])
|
||||
await failing.fiber.dispose()
|
||||
})
|
||||
|
||||
it('mounts no dev row in production and only warns without a Loader in development', async () => {
|
||||
stageDist()
|
||||
const prod = new Context()
|
||||
prod.provide('httpServer', fakeHttpServer().server)
|
||||
const created = provideHmrRow(prod)
|
||||
apply(prod, new Config({ mode: 'production', printUrl: false, surfaceContext: false, trustedHosts: [] }))
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(created).toEqual([])
|
||||
await prod.fiber.dispose()
|
||||
|
||||
const bare = new Context()
|
||||
bare.provide('httpServer', fakeHttpServer().server)
|
||||
const warnings: string[] = []
|
||||
bare.logger.warn = ((message: unknown) => { warnings.push(String(message)) }) as typeof bare.logger.warn
|
||||
apply(bare, new Config({ mode: 'development', printUrl: false, surfaceContext: false, trustedHosts: [] }))
|
||||
expect(warnings).toEqual(['web-app: development mode without a Loader tree mounts no client-hmr row'])
|
||||
// Let the vitest invariant host settle before tearing the root down.
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
await bare.fiber.dispose()
|
||||
})
|
||||
|
||||
it('defers the URL line until Loader settlement and drops it on failure or teardown', async () => {
|
||||
stageDist()
|
||||
// Settlement path: the line waits for loader.await() so supervisors can
|
||||
@@ -171,7 +256,7 @@ describe('web-app runtime glue', () => {
|
||||
const settlement = new Promise<void>((resolve) => { release = resolve })
|
||||
provideHmrRow(settled, () => settlement)
|
||||
const log = vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
await apply(settled, new Config({ mode: 'production', printUrl: true, surfaceContext: true, trustedHosts: [] }))
|
||||
apply(settled, new Config({ mode: 'production', printUrl: true, surfaceContext: true, trustedHosts: [] }))
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(log).not.toHaveBeenCalled()
|
||||
release!()
|
||||
@@ -185,7 +270,7 @@ describe('web-app runtime glue', () => {
|
||||
const failed = new Context()
|
||||
failed.provide('httpServer', fakeHttpServer().server)
|
||||
provideHmrRow(failed, async () => { throw new Error('boot failed') })
|
||||
await apply(failed, new Config({ mode: 'production', printUrl: true, surfaceContext: true, trustedHosts: [] }))
|
||||
apply(failed, new Config({ mode: 'production', printUrl: true, surfaceContext: true, trustedHosts: [] }))
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
expect(log).not.toHaveBeenCalled()
|
||||
await failed.fiber.dispose()
|
||||
@@ -201,7 +286,7 @@ describe('web-app runtime glue', () => {
|
||||
let releaseTorn: () => void
|
||||
const tornSettlement = new Promise<void>((resolve) => { releaseTorn = resolve })
|
||||
provideHmrRow(torn, () => tornSettlement)
|
||||
await apply(torn, new Config({ mode: 'production', printUrl: true, surfaceContext: true, trustedHosts: [] }))
|
||||
apply(torn, new Config({ mode: 'production', printUrl: true, surfaceContext: true, trustedHosts: [] }))
|
||||
await child.dispose() // the httpServer service goes away
|
||||
releaseTorn!()
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
@@ -217,7 +302,7 @@ describe('web-app runtime glue', () => {
|
||||
const { server } = fakeHttpServer()
|
||||
Object.defineProperty(server, 'port', { get: () => undefined })
|
||||
ctx.provide('httpServer', server)
|
||||
await apply(ctx, new Config({ mode: 'production', printUrl: false, surfaceContext: true, trustedHosts: [] }))
|
||||
apply(ctx, new Config({ mode: 'production', printUrl: false, surfaceContext: true, trustedHosts: [] }))
|
||||
await ctx.plugin(SystemPrompt, { persona: '' })
|
||||
await new Promise(resolve => setTimeout(resolve, 0))
|
||||
await expect(ctx.systemPrompt.assemble()).rejects.toThrow('httpServer service missing')
|
||||
|
||||
Reference in New Issue
Block a user