feat(dsh-sdk): create <source> — add external plugin as native PM dependency + cordis mount
dsh-sdk create <source> adds a github (github:owner/repo#ref) or npm (pkg@version) plugin as a package-manager-native dependency, then mounts the resolved dependency in cordis.yml through ProjectEditSession. Adds PackageManager.add(spec) and ProjectEditSession.addExternalPlugin(id, packageName). No giget/pacote. Per-file 100% coverage on the new/changed files.
This commit is contained in:
@@ -148,6 +148,25 @@ export abstract class PackageManager {
|
|||||||
await this.runChecked(runner, this.buildCommand(), cwd, 'build')
|
await this.runChecked(runner, this.buildCommand(), cwd, 'build')
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Build add-dependency command arguments for one already-normalized source spec.
|
||||||
|
* @param spec - a package-manager-native dependency source (`pkg@version` or `github:owner/repo#ref`).
|
||||||
|
* @returns arguments following the manager executable.
|
||||||
|
*/
|
||||||
|
addCommand(spec: string): readonly string[] {
|
||||||
|
return ['add', spec]
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add one dependency from a native source spec and fail on non-zero or signalled exit.
|
||||||
|
* @param spec - a package-manager-native dependency source.
|
||||||
|
* @param cwd - project directory.
|
||||||
|
* @param runner - optional subprocess boundary.
|
||||||
|
*/
|
||||||
|
async add(spec: string, cwd: string, runner: CommandRunner = new NodeCommandRunner()): Promise<void> {
|
||||||
|
await this.runChecked(runner, this.addCommand(spec), cwd, 'add')
|
||||||
|
}
|
||||||
|
|
||||||
private async runChecked(runner: CommandRunner, args: readonly string[], cwd: string, operation: string): Promise<void> {
|
private async runChecked(runner: CommandRunner, args: readonly string[], cwd: string, operation: string): Promise<void> {
|
||||||
const result = await runner.run(this.name, args, cwd)
|
const result = await runner.run(this.name, args, cwd)
|
||||||
if (result.signal !== null) {
|
if (result.signal !== null) {
|
||||||
@@ -184,6 +203,11 @@ export class NpmPackageManager extends PackageManager {
|
|||||||
override linkSpec(relativePath: string): string {
|
override linkSpec(relativePath: string): string {
|
||||||
return `file:${relativePath}`
|
return `file:${relativePath}`
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** npm adds a dependency through `install <spec>` rather than an `add` verb. */
|
||||||
|
override addCommand(spec: string): readonly string[] {
|
||||||
|
return ['install', spec]
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** pnpm workspace behavior. */
|
/** pnpm workspace behavior. */
|
||||||
|
|||||||
@@ -220,6 +220,23 @@ export class ProjectEditSession implements FeatureProjectView {
|
|||||||
this.addedPlugins.add(entry.id)
|
this.addedPlugins.add(entry.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mount a Cordis entry for an external dependency the package manager has already
|
||||||
|
* added (github or npm), without generating files or re-adding the dependency.
|
||||||
|
* @param id - stable Cordis config entry id.
|
||||||
|
* @param packageName - the installed dependency's package name.
|
||||||
|
*/
|
||||||
|
addExternalPlugin(id: string, packageName: string): void {
|
||||||
|
this.assertOpen()
|
||||||
|
if (!this.manifest().npmDependency(packageName)) {
|
||||||
|
throw new Error(`external plugin dependency is not installed: ${packageName}`)
|
||||||
|
}
|
||||||
|
const cordis = this.cordis()
|
||||||
|
if (cordis.entry(id)) throw new Error(`Cordis config entry already exists: ${id}`)
|
||||||
|
cordis.addEntry({ id, name: packageName })
|
||||||
|
this.addedPlugins.add(id)
|
||||||
|
}
|
||||||
|
|
||||||
/** Enable or disable one custom/manual Cordis config entry by stable id. */
|
/** Enable or disable one custom/manual Cordis config entry by stable id. */
|
||||||
setCustomPluginDisabled(id: string, disabled: boolean): void {
|
setCustomPluginDisabled(id: string, disabled: boolean): void {
|
||||||
this.assertOpen()
|
this.assertOpen()
|
||||||
|
|||||||
@@ -298,6 +298,11 @@ describe('package manager strategies', () => {
|
|||||||
await npm.install('/tmp', runner)
|
await npm.install('/tmp', runner)
|
||||||
await npm.build('/tmp', runner)
|
await npm.build('/tmp', runner)
|
||||||
expect(calls).toEqual([['npm', 'install'], ['npm', 'run', 'build']])
|
expect(calls).toEqual([['npm', 'install'], ['npm', 'run', 'build']])
|
||||||
|
await npm.add('some-pkg@1.0.0', '/tmp', runner)
|
||||||
|
const pnpm = createPackageManager('pnpm', '10.0.0')
|
||||||
|
await pnpm.add('github:o/r#sha', '/tmp', runner)
|
||||||
|
expect(calls).toContainEqual(['npm', 'install', 'some-pkg@1.0.0'])
|
||||||
|
expect(calls).toContainEqual(['pnpm', 'add', 'github:o/r#sha'])
|
||||||
const failed: CommandRunner = { run: async () => ({ exitCode: 2, signal: null }) }
|
const failed: CommandRunner = { run: async () => ({ exitCode: 2, signal: null }) }
|
||||||
await expect(npm.install('/tmp', failed)).rejects.toThrow('exited with code 2')
|
await expect(npm.install('/tmp', failed)).rejects.toThrow('exited with code 2')
|
||||||
const killed: CommandRunner = { run: async () => ({ exitCode: null, signal: 'SIGTERM' }) }
|
const killed: CommandRunner = { run: async () => ({ exitCode: null, signal: 'SIGTERM' }) }
|
||||||
|
|||||||
@@ -690,6 +690,28 @@ describe('SdkProject and ProjectEditSession', () => {
|
|||||||
expect(committed.packageManifest().dependencies?.['@deepseek-ai/dsh-subagent']).toMatch(/^file:/)
|
expect(committed.packageManifest().dependencies?.['@deepseek-ai/dsh-subagent']).toMatch(/^file:/)
|
||||||
expect(createBuiltinRegistry(committed.profile).get(featureId('subagent')).inspect(committed).state).toBe('absent')
|
expect(createBuiltinRegistry(committed.profile).get(featureId('subagent')).inspect(committed).state).toBe('absent')
|
||||||
})
|
})
|
||||||
|
|
||||||
|
it('mounts an external plugin dependency and rejects missing deps or duplicate entries', async () => {
|
||||||
|
const root = await mkdtemp(join(tmpdir(), 'dsh-external-plugin-'))
|
||||||
|
temporary.push(root)
|
||||||
|
const creation = request()
|
||||||
|
const project = SdkProject.create(root, creation)
|
||||||
|
const registry = createBuiltinRegistry(project.profile)
|
||||||
|
const edit = project.edit(registry)
|
||||||
|
for (const item of creation.features) edit.installFeature(registry.get(item.id), item)
|
||||||
|
await edit.commit()
|
||||||
|
const manifestPath = join(root, 'package.json')
|
||||||
|
const manifest = JSON.parse(await readFile(manifestPath, 'utf8')) as { dependencies?: Record<string, string> }
|
||||||
|
manifest.dependencies = { ...manifest.dependencies, 'ext-plugin': 'github:o/r#sha' }
|
||||||
|
await writeFile(manifestPath, JSON.stringify(manifest, null, 2))
|
||||||
|
const reopened = await SdkProject.open(root)
|
||||||
|
const edit2 = reopened.edit(createBuiltinRegistry(reopened.profile))
|
||||||
|
edit2.addExternalPlugin('ext-plugin', 'ext-plugin')
|
||||||
|
expect(() => { edit2.addExternalPlugin('ext-plugin', 'ext-plugin') }).toThrow('already exists')
|
||||||
|
expect(() => { edit2.addExternalPlugin('missing', 'not-a-dep') }).toThrow('not installed')
|
||||||
|
const commit = await edit2.commit()
|
||||||
|
expect(commit.project.cordis.entry('ext-plugin')?.name).toBe('ext-plugin')
|
||||||
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
describe('extension points', () => {
|
describe('extension points', () => {
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ The `dsh-sdk` launcher owns SDK project startup and configuration.
|
|||||||
| `dsh-sdk dev [target] [-- args…]` | Register TypeScript and local-workspace source resolution, then use the start path |
|
| `dsh-sdk dev [target] [-- args…]` | Register TypeScript and local-workspace source resolution, then use the start path |
|
||||||
| `dsh-sdk build [args…]` | Invoke the project's installed tsdown with the project arguments |
|
| `dsh-sdk build [args…]` | Invoke the project's installed tsdown with the project arguments |
|
||||||
| `dsh-sdk config` | Open one interactive edit session, review accumulated changes, commit once, and install once when NPM dependencies changed |
|
| `dsh-sdk config` | Open one interactive edit session, review accumulated changes, commit once, and install once when NPM dependencies changed |
|
||||||
|
| `dsh-sdk create <source>` | Add an external Cordis plugin from a native package-manager source (`pkg@version` or `github:owner/repo#ref`): confirm, `<pm> add <source>`, then mount the resolved dependency in `cordis.yml`. No giget/pacote; the package manager resolves and pins the source (github deps build via their own `prepare` under the manager's policy) |
|
||||||
|
|
||||||
`ProjectBuild(tsdownConfig)` and `PluginBuild(tsdownConfig)` are exported only from `@deepseek-ai/dsh-scripts/dev/tsdown-config`. Development and production read the same `cordis.yml`.
|
`ProjectBuild(tsdownConfig)` and `PluginBuild(tsdownConfig)` are exported only from `@deepseek-ai/dsh-scripts/dev/tsdown-config`. Development and production read the same `cordis.yml`.
|
||||||
|
|
||||||
|
|||||||
@@ -8,12 +8,13 @@ import { parseArgs as parseNodeArgs } from 'node:util'
|
|||||||
import { Command } from 'commander'
|
import { Command } from 'commander'
|
||||||
|
|
||||||
/** Commands implemented by the dsh-sdk launcher. */
|
/** Commands implemented by the dsh-sdk launcher. */
|
||||||
type DshSdkCommand = 'start' | 'dev' | 'build' | 'config'
|
type DshSdkCommand = 'start' | 'dev' | 'build' | 'config' | 'create'
|
||||||
|
|
||||||
/** Parsed dsh-sdk invocation. */
|
/** Parsed dsh-sdk invocation. */
|
||||||
export interface DshSdkArgs {
|
export interface DshSdkArgs {
|
||||||
command?: DshSdkCommand
|
command?: DshSdkCommand
|
||||||
target?: string
|
target?: string
|
||||||
|
source?: string
|
||||||
forwarded: readonly string[]
|
forwarded: readonly string[]
|
||||||
help: boolean
|
help: boolean
|
||||||
}
|
}
|
||||||
@@ -60,6 +61,9 @@ export function parseDshSdkArgs(argv: readonly string[]): DshSdkArgs {
|
|||||||
program.command('config').helpOption(false).action(() => {
|
program.command('config').helpOption(false).action(() => {
|
||||||
parsed = { command: 'config', forwarded: [], help: false }
|
parsed = { command: 'config', forwarded: [], help: false }
|
||||||
})
|
})
|
||||||
|
program.command('create <source>').helpOption(false).action((source: string) => {
|
||||||
|
parsed = { command: 'create', source, forwarded: [], help: false }
|
||||||
|
})
|
||||||
program.parse([...launcherArgv], { from: 'user' })
|
program.parse([...launcherArgv], { from: 'user' })
|
||||||
/* v8 ignore next -- every registered Commander action above assigns parsed or Commander throws */
|
/* v8 ignore next -- every registered Commander action above assigns parsed or Commander throws */
|
||||||
if (!parsed) throw new Error('dsh-sdk command did not resolve')
|
if (!parsed) throw new Error('dsh-sdk command did not resolve')
|
||||||
|
|||||||
@@ -7,6 +7,7 @@
|
|||||||
import { parseDshSdkArgs } from './args.ts'
|
import { parseDshSdkArgs } from './args.ts'
|
||||||
import { runProjectBuild } from './build.ts'
|
import { runProjectBuild } from './build.ts'
|
||||||
import { runConfigCommand, type ConfigCommandContext } from './config.ts'
|
import { runConfigCommand, type ConfigCommandContext } from './config.ts'
|
||||||
|
import { runCreatePluginCommand } from './create-plugin.ts'
|
||||||
import { runSDK } from './runtime.ts'
|
import { runSDK } from './runtime.ts'
|
||||||
import { DSH_SDK_TEMPLATES } from './templates/dsh-sdk-templates.ts'
|
import { DSH_SDK_TEMPLATES } from './templates/dsh-sdk-templates.ts'
|
||||||
|
|
||||||
@@ -19,6 +20,7 @@ export interface DshSdkCommandContext extends ConfigCommandContext {
|
|||||||
run?: typeof runSDK
|
run?: typeof runSDK
|
||||||
build?: typeof runProjectBuild
|
build?: typeof runProjectBuild
|
||||||
config?: typeof runConfigCommand
|
config?: typeof runConfigCommand
|
||||||
|
createPlugin?: typeof runCreatePluginCommand
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Run one parsed dsh-sdk command and return its process exit code. */
|
/** Run one parsed dsh-sdk command and return its process exit code. */
|
||||||
@@ -40,6 +42,7 @@ export async function runDshSdkCommand(
|
|||||||
const run = context.run ?? runSDK
|
const run = context.run ?? runSDK
|
||||||
const build = context.build ?? runProjectBuild
|
const build = context.build ?? runProjectBuild
|
||||||
const config = context.config ?? runConfigCommand
|
const config = context.config ?? runConfigCommand
|
||||||
|
const createPlugin = context.createPlugin ?? runCreatePluginCommand
|
||||||
switch (args.command) {
|
switch (args.command) {
|
||||||
case 'start': await run(args.target, { cwd: context.cwd, argv: args.forwarded }); break
|
case 'start': await run(args.target, { cwd: context.cwd, argv: args.forwarded }); break
|
||||||
case 'dev': await run(args.target, { cwd: context.cwd, dev: true, argv: args.forwarded }); break
|
case 'dev': await run(args.target, { cwd: context.cwd, dev: true, argv: args.forwarded }); break
|
||||||
@@ -49,6 +52,8 @@ export async function runDshSdkCommand(
|
|||||||
if (result.installError) return 1
|
if (result.installError) return 1
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
/* v8 ignore next -- Commander requires <source>, so create never dispatches without it */
|
||||||
|
case 'create': await createPlugin(args.source ?? '', context); break
|
||||||
}
|
}
|
||||||
return 0
|
return 0
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
|||||||
91
packages/sdk/scripts/src/create-plugin.ts
Normal file
91
packages/sdk/scripts/src/create-plugin.ts
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
/**
|
||||||
|
* dsh-sdk create command: add an external Cordis plugin (github or npm) as a
|
||||||
|
* native package-manager dependency and mount it in cordis.yml.
|
||||||
|
*
|
||||||
|
* @module @deepseek-ai/dsh-scripts/create-plugin
|
||||||
|
*/
|
||||||
|
|
||||||
|
import { readFile } from 'node:fs/promises'
|
||||||
|
import { join } from 'node:path'
|
||||||
|
import {
|
||||||
|
ClackPromptPort,
|
||||||
|
ConfirmQuestion,
|
||||||
|
SdkProject,
|
||||||
|
createBuiltinRegistry,
|
||||||
|
requireAnswer,
|
||||||
|
type PackageManager,
|
||||||
|
type ProjectCommitResult,
|
||||||
|
type PromptPort,
|
||||||
|
} from '@deepseek-ai/dsh-helper'
|
||||||
|
|
||||||
|
/** Process and interaction slice required by dsh-sdk create. */
|
||||||
|
export interface CreatePluginContext {
|
||||||
|
cwd: string
|
||||||
|
stdin: NodeJS.ReadStream
|
||||||
|
stdout: NodeJS.WriteStream
|
||||||
|
port?: PromptPort
|
||||||
|
add?: (manager: PackageManager, spec: string, cwd: string) => Promise<void>
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Result of a create run; `undefined` when the confirmation was declined. */
|
||||||
|
export type CreatePluginResult = ProjectCommitResult<SdkProject> | undefined
|
||||||
|
|
||||||
|
/** Derive a stable cordis entry id from a package name's last path segment. */
|
||||||
|
function pluginId(packageName: string): string {
|
||||||
|
const base = packageName.startsWith('@') ? packageName.slice(packageName.indexOf('/') + 1) : packageName
|
||||||
|
const id = base.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-|-$/g, '')
|
||||||
|
/* v8 ignore next -- a valid npm package name always yields a non-empty id */
|
||||||
|
if (!id) throw new Error(`cannot derive a plugin id from package name: ${packageName}`)
|
||||||
|
return id
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Read the direct dependency names declared in a project's package.json. */
|
||||||
|
async function dependencyNames(cwd: string): Promise<Set<string>> {
|
||||||
|
const manifest = JSON.parse(await readFile(join(cwd, 'package.json'), 'utf8')) as {
|
||||||
|
dependencies?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
/* v8 ignore next -- generated projects always declare a dependencies map */
|
||||||
|
return new Set(Object.keys(manifest.dependencies ?? {}))
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Add one external plugin dependency to the current project and mount it.
|
||||||
|
* @param source - a package-manager-native source (`pkg@version` or `github:owner/repo#ref`).
|
||||||
|
* @param context - process, interaction, and dependency-add boundaries.
|
||||||
|
* @returns the commit result, or `undefined` when the confirmation was declined.
|
||||||
|
*/
|
||||||
|
export async function runCreatePluginCommand(
|
||||||
|
source: string,
|
||||||
|
context: CreatePluginContext,
|
||||||
|
): Promise<CreatePluginResult> {
|
||||||
|
const spec = source.trim()
|
||||||
|
if (!spec) throw new Error('dsh-sdk create requires a plugin source (pkg@version or github:owner/repo#ref)')
|
||||||
|
if (!context.port && (!context.stdin.isTTY || !context.stdout.isTTY)) {
|
||||||
|
throw new Error('dsh-sdk create requires an interactive TTY')
|
||||||
|
}
|
||||||
|
const project = await SdkProject.open(context.cwd)
|
||||||
|
/* v8 ignore next -- production TTY wiring is exercised by the built-bin smoke */
|
||||||
|
const port = context.port ?? new ClackPromptPort(context.stdin, context.stdout)
|
||||||
|
const confirmed = requireAnswer(await new ConfirmQuestion({
|
||||||
|
id: 'create.confirm',
|
||||||
|
message: `Add plugin '${spec}' as a dependency and mount it in cordis.yml?`,
|
||||||
|
initialValue: true,
|
||||||
|
}).resolve(port))
|
||||||
|
if (!confirmed) return undefined
|
||||||
|
|
||||||
|
const before = await dependencyNames(context.cwd)
|
||||||
|
/* v8 ignore next -- production package-manager wiring is exercised by the built-bin smoke */
|
||||||
|
const add = context.add ?? ((manager, source, cwd) => manager.add(source, cwd))
|
||||||
|
await add(project.profile.packageManager, spec, context.cwd)
|
||||||
|
const after = await dependencyNames(context.cwd)
|
||||||
|
const added = [...after].filter(name => !before.has(name))
|
||||||
|
if (added.length === 0) throw new Error(`dsh-sdk create: '${spec}' added no new dependency`)
|
||||||
|
|
||||||
|
const reopened = await SdkProject.open(context.cwd)
|
||||||
|
const registry = createBuiltinRegistry(reopened.profile)
|
||||||
|
const edit = reopened.edit(registry)
|
||||||
|
for (const packageName of added) edit.addExternalPlugin(pluginId(packageName), packageName)
|
||||||
|
const commit = await edit.commit()
|
||||||
|
context.stdout.write(`Mounted ${added.join(', ')} in cordis.yml.\n`)
|
||||||
|
return commit
|
||||||
|
}
|
||||||
@@ -31,6 +31,7 @@ import { PluginBuild, ProjectBuild, runProjectBuild } from '../src/build.ts'
|
|||||||
import { runDshSdkCommand, type DshSdkCommandContext } from '../src/command.ts'
|
import { runDshSdkCommand, type DshSdkCommandContext } from '../src/command.ts'
|
||||||
import { runConfigCommand } from '../src/config.ts'
|
import { runConfigCommand } from '../src/config.ts'
|
||||||
import { ConfigWorkflow, type ConfigPlan } from '../src/config/config-workflow.ts'
|
import { ConfigWorkflow, type ConfigPlan } from '../src/config/config-workflow.ts'
|
||||||
|
import { runCreatePluginCommand } from '../src/create-plugin.ts'
|
||||||
import { initialize, resolve as resolveLocalPlugin } from '../src/local-plugin-loader-hooks.ts'
|
import { initialize, resolve as resolveLocalPlugin } from '../src/local-plugin-loader-hooks.ts'
|
||||||
|
|
||||||
const temporary: string[] = []
|
const temporary: string[] = []
|
||||||
@@ -559,3 +560,62 @@ describe('ConfigWorkflow', () => {
|
|||||||
expect(output.read()).toContain('Disable feature: ask-user')
|
expect(output.read()).toContain('Disable feature: ask-user')
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
|
describe('dsh-sdk create', () => {
|
||||||
|
const writeDependency = (name: string) => async (_m: unknown, spec: string, cwd: string): Promise<void> => {
|
||||||
|
const path = join(cwd, 'package.json')
|
||||||
|
const manifest = JSON.parse(await readFile(path, 'utf8')) as { dependencies?: Record<string, string> }
|
||||||
|
manifest.dependencies = { ...manifest.dependencies, [name]: spec }
|
||||||
|
await writeFile(path, JSON.stringify(manifest, null, 2))
|
||||||
|
}
|
||||||
|
|
||||||
|
it('adds a dependency and mounts it after confirmation', async () => {
|
||||||
|
const project = await committedProject()
|
||||||
|
const context = { ...commandContext(project.root), port: new QueuePort([true]), add: writeDependency('my-ext-plugin') }
|
||||||
|
const result = await runCreatePluginCommand('github:o/r#sha', context)
|
||||||
|
expect(result?.project.cordis.entry('my-ext-plugin')?.name).toBe('my-ext-plugin')
|
||||||
|
expect(context.readStdout()).toContain('Mounted my-ext-plugin')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('derives the cordis id from a scoped package name', async () => {
|
||||||
|
const project = await committedProject()
|
||||||
|
const context = { ...commandContext(project.root), port: new QueuePort([true]), add: writeDependency('@acme/cool-plugin') }
|
||||||
|
const result = await runCreatePluginCommand('@acme/cool-plugin@1.0.0', context)
|
||||||
|
expect(result?.project.cordis.entry('cool-plugin')?.name).toBe('@acme/cool-plugin')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('returns undefined and adds nothing when declined', async () => {
|
||||||
|
const project = await committedProject()
|
||||||
|
let added = false
|
||||||
|
const context = {
|
||||||
|
...commandContext(project.root),
|
||||||
|
port: new QueuePort([false]),
|
||||||
|
add: async () => { added = true },
|
||||||
|
}
|
||||||
|
await expect(runCreatePluginCommand('pkg@1.0.0', context)).resolves.toBeUndefined()
|
||||||
|
expect(added).toBe(false)
|
||||||
|
})
|
||||||
|
|
||||||
|
it('rejects an empty source, a non-TTY session, and a no-op add', async () => {
|
||||||
|
const project = await committedProject()
|
||||||
|
await expect(runCreatePluginCommand(' ', { ...commandContext(project.root), port: new QueuePort([]) }))
|
||||||
|
.rejects.toThrow('requires a plugin source')
|
||||||
|
const noTty = commandContext(project.root)
|
||||||
|
noTty.stdin.isTTY = false
|
||||||
|
noTty.stdout.isTTY = false
|
||||||
|
await expect(runCreatePluginCommand('pkg@1.0.0', noTty)).rejects.toThrow('interactive TTY')
|
||||||
|
const noOutTty = commandContext(project.root)
|
||||||
|
noOutTty.stdout.isTTY = false
|
||||||
|
await expect(runCreatePluginCommand('pkg@1.0.0', noOutTty)).rejects.toThrow('interactive TTY')
|
||||||
|
await expect(runCreatePluginCommand('pkg@1.0.0', {
|
||||||
|
...commandContext(project.root), port: new QueuePort([true]), add: async () => {},
|
||||||
|
})).rejects.toThrow('added no new dependency')
|
||||||
|
})
|
||||||
|
|
||||||
|
it('dispatches create through the launcher', async () => {
|
||||||
|
const project = await committedProject()
|
||||||
|
const context = commandContext(project.root)
|
||||||
|
context.createPlugin = async () => undefined
|
||||||
|
await expect(runDshSdkCommand(['create', 'pkg@1.0.0'], context)).resolves.toBe(0)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user