95 lines
3.7 KiB
TypeScript
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)}`)
|
|
}
|