Files
deepseek-harness/packages/sdk/scripts/src/build.ts
T

95 lines
3.7 KiB
TypeScript

/**
* User-owned tsdown configuration wrappers and child-process invocation.
*
* @module @deepseek-ai/dsh-scripts/build
*/
import { createRequire } from 'node:module'
import { existsSync, readFileSync, readdirSync } from 'node:fs'
import { dirname, resolve } from 'node:path'
import type { UserConfig } from 'tsdown'
import { NodeCommandRunner, type CommandRunner } from '@deepseek-ai/dsh-helper'
function hasLocalPluginPackages(root: string): boolean {
const directory = resolve(root, 'plugins')
return existsSync(directory) && readdirSync(directory, { withFileTypes: true }).some(
item => item.isDirectory() && existsSync(resolve(directory, item.name, 'package.json')),
)
}
function hasTsdownConfig(root: string): boolean {
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')
}
/**
* Preserve the developer's root config and append a separate workspace pass
* when generated local plugin packages exist.
* @param tsdownConfig - developer-owned root tsdown config.
* @returns root tsdown config and optional local-plugin workspace pass.
*/
export function ProjectBuild(tsdownConfig: UserConfig): UserConfig[] {
if (tsdownConfig.workspace !== undefined) {
throw new Error('ProjectBuild owns workspace discovery; remove config.workspace')
}
const root = resolve(tsdownConfig.cwd ?? process.cwd())
return hasLocalPluginPackages(root)
? [{ ...tsdownConfig }, { workspace: { include: ['plugins/*'] } }]
: [{ ...tsdownConfig }]
}
/**
* Preserve a local plugin package's developer-owned tsdown config.
* @param tsdownConfig - developer-owned plugin tsdown config.
* @returns validated tsdown config copy.
*/
export function PluginBuild(tsdownConfig: UserConfig): UserConfig {
if (tsdownConfig.workspace !== undefined) throw new Error('PluginBuild does not accept nested workspace config')
return { ...tsdownConfig }
}
function resolveTsdownBin(cwd: string): string {
const require = createRequire(resolve(cwd, 'package.json'))
let manifestPath: string
try {
manifestPath = require.resolve('tsdown/package.json')
} catch (error) {
throw new Error(`dsh-sdk build requires tsdown in this project: ${String(error)}`)
}
const manifest = JSON.parse(readFileSync(manifestPath, 'utf8')) as { bin?: unknown }
const bin = typeof manifest.bin === 'string'
? manifest.bin
: manifest.bin && typeof manifest.bin === 'object'
? (manifest.bin as Record<string, unknown>).tsdown
: undefined
if (typeof bin !== 'string') throw new Error('installed tsdown package has no executable')
return resolve(dirname(manifestPath), bin)
}
/** Invoke the project's installed tsdown, forwarding all build arguments. */
export async function runProjectBuild(
args: readonly string[],
cwd: string = process.cwd(),
runner: CommandRunner = new NodeCommandRunner(),
): Promise<void> {
if (!hasTsdownConfig(cwd)) return
const result = await runner.run(process.execPath, [resolveTsdownBin(cwd), ...args], resolve(cwd))
if (result.signal) throw new Error(`tsdown was killed by ${result.signal}`)
if (result.exitCode !== 0) throw new Error(`tsdown exited with code ${String(result.exitCode)}`)
}