fix(sdk): address remaining tooling review findings
This commit is contained in:
@@ -18,8 +18,23 @@ function hasLocalPluginPackages(root: string): boolean {
|
||||
}
|
||||
|
||||
function hasTsdownConfig(root: string): boolean {
|
||||
return ['tsdown.config.ts', 'tsdown.config.js', 'tsdown.config.mjs', 'tsdown.config.mts']
|
||||
const hasConfigFile = [
|
||||
'tsdown.config.ts', 'tsdown.config.mts', 'tsdown.config.cts',
|
||||
'tsdown.config.js', 'tsdown.config.mjs', 'tsdown.config.cjs',
|
||||
'tsdown.config.json',
|
||||
]
|
||||
.some(name => existsSync(resolve(root, name)))
|
||||
if (hasConfigFile) return true
|
||||
let manifestText: string
|
||||
try {
|
||||
manifestText = readFileSync(resolve(root, 'package.json'), 'utf8')
|
||||
} catch (error) {
|
||||
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return false
|
||||
throw error
|
||||
}
|
||||
const manifest: unknown = JSON.parse(manifestText)
|
||||
return manifest !== null && !Array.isArray(manifest) && typeof manifest === 'object'
|
||||
&& Object.hasOwn(manifest, 'tsdown')
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -17,6 +17,7 @@ import {
|
||||
type NestedMultiSelectValue,
|
||||
type ProjectCommitResult,
|
||||
type PromptPort,
|
||||
type RunInterface,
|
||||
type SdkProject,
|
||||
} from '@deepseek-ai/dsh-helper'
|
||||
import { DSH_SDK_TEMPLATES } from '../templates/dsh-sdk-templates.ts'
|
||||
@@ -39,6 +40,14 @@ function sameOptions(left: readonly string[], right: readonly string[]): boolean
|
||||
return [...left].sort().join('\0') === [...right].sort().join('\0')
|
||||
}
|
||||
|
||||
function targetRunInterface(
|
||||
current: RunInterface,
|
||||
desired: ReadonlyMap<string, NestedMultiSelectValue<string, string>>,
|
||||
): RunInterface {
|
||||
const selected = desired.get('feature:app')?.choices[0]
|
||||
return selected === 'acp' || selected === 'stdio' || selected === 'embed' ? selected : current
|
||||
}
|
||||
|
||||
/** Reconcile one tree selection into domain commands, then review and commit once. */
|
||||
export class ConfigWorkflow {
|
||||
private readonly port: PromptPort
|
||||
@@ -100,6 +109,13 @@ export class ConfigWorkflow {
|
||||
],
|
||||
}))
|
||||
const desiredByTarget = new Map(desired.map(item => [item.value, item]))
|
||||
const targetProfile = {
|
||||
...project.profile,
|
||||
runInterface: targetRunInterface(project.profile.runInterface, desiredByTarget),
|
||||
}
|
||||
for (const feature of features) {
|
||||
if (!feature.isApplicable(targetProfile)) desiredByTarget.delete(featureTarget(feature))
|
||||
}
|
||||
|
||||
for (const feature of features) {
|
||||
const installation = inspections.get(feature.id)
|
||||
|
||||
@@ -109,7 +109,7 @@ export async function startSDK(
|
||||
* @returns target main result or live Cordis context.
|
||||
*/
|
||||
export async function runSDK(
|
||||
target: string | undefined,
|
||||
target?: string,
|
||||
options: BootProjectOptions = {},
|
||||
): Promise<unknown> {
|
||||
/* v8 ignore next -- the bin always supplies cwd; direct consumers normally accept process.cwd() */
|
||||
|
||||
@@ -3,7 +3,7 @@ import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { PassThrough, Writable } from 'node:stream'
|
||||
import { fileURLToPath, pathToFileURL } from 'node:url'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import { afterEach, describe, expect, expectTypeOf, it, vi } from 'vitest'
|
||||
import {
|
||||
LocalPluginBlueprint,
|
||||
NpmPackageManager,
|
||||
@@ -82,6 +82,7 @@ function commandContext(cwd: string): DshSdkCommandContext & { readStdout: () =>
|
||||
function creation(
|
||||
extra: ProjectCreationRequest['features'] = [],
|
||||
localPlugins: readonly LocalPluginBlueprint[] = [],
|
||||
app: 'acp' | 'stdio' | 'embed' = 'embed',
|
||||
): ProjectCreationRequest {
|
||||
return {
|
||||
name: 'config-agent',
|
||||
@@ -92,7 +93,7 @@ function creation(
|
||||
features: [
|
||||
{ id: featureId('provider'), options: ['deepseek'], secrets: { apiKey: 'key' } },
|
||||
{ id: featureId('bash'), options: ['local'] },
|
||||
{ id: featureId('app'), options: ['embed'] },
|
||||
{ id: featureId('app'), options: [app] },
|
||||
{ id: featureId('persistence'), options: ['jsonl'] },
|
||||
...extra,
|
||||
],
|
||||
@@ -103,10 +104,11 @@ function creation(
|
||||
async function committedProject(
|
||||
extra: ProjectCreationRequest['features'] = [],
|
||||
localPlugins: readonly LocalPluginBlueprint[] = [],
|
||||
app: 'acp' | 'stdio' | 'embed' = 'embed',
|
||||
): Promise<SdkProject> {
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-config-workflow-'))
|
||||
temporary.push(root)
|
||||
const request = creation(extra, localPlugins)
|
||||
const request = creation(extra, localPlugins, app)
|
||||
const project = SdkProject.create(root, request)
|
||||
const registry = createBuiltinRegistry(project.profile)
|
||||
const edit = project.edit(registry)
|
||||
@@ -219,6 +221,33 @@ describe('build profiles and invocation', () => {
|
||||
await expect(runProjectBuild([], root, killed)).rejects.toThrow('killed by SIGTERM')
|
||||
})
|
||||
|
||||
it('recognizes every tsdown config source', async () => {
|
||||
const manifest = fileURLToPath(import.meta.resolve('tsdown/package.json'))
|
||||
for (const extension of ['cts', 'cjs', 'json']) {
|
||||
const root = await mkdtemp(join(tmpdir(), `dsh-build-${extension}-`))
|
||||
temporary.push(root)
|
||||
await writeFile(join(root, 'package.json'), '{"type":"module"}\n')
|
||||
await writeFile(join(root, `tsdown.config.${extension}`), '{}\n')
|
||||
await mkdir(join(root, 'node_modules'), { recursive: true })
|
||||
await symlink(dirname(manifest), join(root, 'node_modules', 'tsdown'))
|
||||
let called = false
|
||||
await runProjectBuild([], root, {
|
||||
run: async () => { called = true; return { exitCode: 0, signal: null } },
|
||||
})
|
||||
expect(called).toBe(true)
|
||||
}
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-build-package-json-'))
|
||||
temporary.push(root)
|
||||
await writeFile(join(root, 'package.json'), '{"type":"module","tsdown":{}}\n')
|
||||
await mkdir(join(root, 'node_modules'), { recursive: true })
|
||||
await symlink(dirname(manifest), join(root, 'node_modules', 'tsdown'))
|
||||
let called = false
|
||||
await runProjectBuild([], root, {
|
||||
run: async () => { called = true; return { exitCode: 0, signal: null } },
|
||||
})
|
||||
expect(called).toBe(true)
|
||||
})
|
||||
|
||||
it('reports missing and malformed project tsdown executables', async () => {
|
||||
const missing = await mkdtemp(join(tmpdir(), 'dsh-build-missing-'))
|
||||
temporary.push(missing)
|
||||
@@ -279,6 +308,7 @@ describe('build profiles and invocation', () => {
|
||||
})
|
||||
|
||||
it('boots empty Cordis configs and delegates targetless runs', async () => {
|
||||
expectTypeOf(runSDK).toBeCallableWith()
|
||||
const root = await mkdtemp(join(tmpdir(), 'dsh-start-sdk-'))
|
||||
temporary.push(root)
|
||||
await writeFile(join(root, 'cordis.yml'), '[]\n')
|
||||
@@ -480,4 +510,25 @@ describe('ConfigWorkflow', () => {
|
||||
expect(result.commit?.project.cordis.entry('agent-loop')).toBeDefined()
|
||||
expect(result.commit?.project.cordis.entry('agent-core')).toBeUndefined()
|
||||
})
|
||||
|
||||
it('disables ask-user when switching its app interface to embed', async () => {
|
||||
const project = await committedProject([
|
||||
{ id: featureId('ask-user'), options: ['default'] },
|
||||
], [], 'acp')
|
||||
const registry = createBuiltinRegistry(project.profile)
|
||||
const output = outputBuffer()
|
||||
const workflow = new ConfigWorkflow(new QueuePort([
|
||||
[
|
||||
{ value: 'feature:provider', choices: ['deepseek'] },
|
||||
{ value: 'feature:app', choices: ['embed'] },
|
||||
{ value: 'feature:persistence', choices: ['jsonl'] },
|
||||
{ value: 'feature:ask-user', choices: ['default'] },
|
||||
],
|
||||
true,
|
||||
]), output.stream, async () => {})
|
||||
const result = await workflow.run(project, registry)
|
||||
expect(result.commit?.project.profile.runInterface).toBe('embed')
|
||||
expect(result.commit?.project.cordis.entry('tool-ask-user')?.disabled).toBe(true)
|
||||
expect(output.read()).toContain('Disable feature: ask-user')
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user