Files
registry/test/helpers/subprocess.helper.ts

209 lines
5.8 KiB
TypeScript
Raw Normal View History

/**
* Subprocess helper - utilities for running protocol clients in tests
*/
export interface ICommandResult {
success: boolean;
stdout: string;
stderr: string;
code: number;
signal?: Deno.Signal;
}
export interface ICommandOptions {
cwd?: string;
env?: Record<string, string>;
timeout?: number;
stdin?: string;
}
/**
* Execute a command and return the result
*/
export async function runCommand(
cmd: string[],
options: ICommandOptions = {}
): Promise<ICommandResult> {
const { cwd, env, timeout = 60000, stdin } = options;
const command = new Deno.Command(cmd[0], {
args: cmd.slice(1),
cwd,
env: { ...Deno.env.toObject(), ...env },
stdin: stdin ? 'piped' : 'null',
stdout: 'piped',
stderr: 'piped',
});
const child = command.spawn();
if (stdin && child.stdin) {
const writer = child.stdin.getWriter();
await writer.write(new TextEncoder().encode(stdin));
await writer.close();
}
const timeoutId = setTimeout(() => {
try {
child.kill('SIGTERM');
} catch {
/* ignore */
}
}, timeout);
const output = await child.output();
clearTimeout(timeoutId);
return {
success: output.success,
stdout: new TextDecoder().decode(output.stdout),
stderr: new TextDecoder().decode(output.stderr),
code: output.code,
signal: output.signal ?? undefined,
};
}
/**
* Check if a command is available
*/
export async function commandExists(cmd: string): Promise<boolean> {
try {
const result = await runCommand(['which', cmd], { timeout: 5000 });
return result.success;
} catch {
return false;
}
}
/**
* Protocol client wrappers
*/
export const clients = {
npm: {
check: () => commandExists('npm'),
publish: (dir: string, registry: string, token: string) =>
runCommand(['npm', 'publish', '--registry', registry], {
cwd: dir,
env: { NPM_TOKEN: token, npm_config__authToken: token },
}),
install: (pkg: string, registry: string, dir: string) =>
runCommand(['npm', 'install', pkg, '--registry', registry], { cwd: dir }),
unpublish: (pkg: string, registry: string, token: string) =>
runCommand(['npm', 'unpublish', pkg, '--registry', registry, '--force'], {
env: { NPM_TOKEN: token, npm_config__authToken: token },
}),
pack: (dir: string) => runCommand(['npm', 'pack'], { cwd: dir }),
},
docker: {
check: () => commandExists('docker'),
build: (dockerfile: string, tag: string, context: string) =>
runCommand(['docker', 'build', '-f', dockerfile, '-t', tag, context]),
push: (image: string) => runCommand(['docker', 'push', image]),
pull: (image: string) => runCommand(['docker', 'pull', image]),
rmi: (image: string, force = false) =>
runCommand(['docker', 'rmi', ...(force ? ['-f'] : []), image]),
login: (registry: string, username: string, password: string) =>
runCommand(['docker', 'login', registry, '-u', username, '--password-stdin'], {
stdin: password,
}),
tag: (source: string, target: string) => runCommand(['docker', 'tag', source, target]),
},
cargo: {
check: () => commandExists('cargo'),
package: (dir: string) => runCommand(['cargo', 'package', '--allow-dirty'], { cwd: dir }),
publish: (dir: string, registry: string, token: string) =>
runCommand(
['cargo', 'publish', '--registry', 'stack-test', '--token', token, '--allow-dirty'],
{ cwd: dir }
),
yank: (crate: string, version: string, token: string) =>
runCommand([
'cargo',
'yank',
crate,
'--version',
version,
'--registry',
'stack-test',
'--token',
token,
]),
},
pip: {
check: () => commandExists('pip'),
build: (dir: string) => runCommand(['python', '-m', 'build', dir]),
upload: (dist: string, repository: string, token: string) =>
runCommand([
'python',
'-m',
'twine',
'upload',
'--repository-url',
repository,
'-u',
'__token__',
'-p',
token,
`${dist}/*`,
]),
install: (pkg: string, indexUrl: string) =>
runCommand(['pip', 'install', pkg, '--index-url', indexUrl]),
},
composer: {
check: () => commandExists('composer'),
install: (pkg: string, repository: string, dir: string) =>
runCommand(
[
'composer',
'require',
pkg,
'--repository',
JSON.stringify({ type: 'composer', url: repository }),
],
{ cwd: dir }
),
},
gem: {
check: () => commandExists('gem'),
build: (gemspec: string, dir: string) => runCommand(['gem', 'build', gemspec], { cwd: dir }),
push: (gemFile: string, host: string, key: string) =>
runCommand(['gem', 'push', gemFile, '--host', host, '--key', key]),
install: (gemName: string, source: string) =>
runCommand(['gem', 'install', gemName, '--source', source]),
yank: (gemName: string, version: string, host: string, key: string) =>
runCommand(['gem', 'yank', gemName, '-v', version, '--host', host, '--key', key]),
},
maven: {
check: () => commandExists('mvn'),
deploy: (dir: string, repositoryUrl: string, username: string, password: string) =>
runCommand(
[
'mvn',
'deploy',
`-DaltDeploymentRepository=stack-test::default::${repositoryUrl}`,
`-Dusername=${username}`,
`-Dpassword=${password}`,
],
{ cwd: dir }
),
package: (dir: string) => runCommand(['mvn', 'package', '-DskipTests'], { cwd: dir }),
},
};
/**
* Skip test if command is not available
*/
export async function skipIfMissing(cmd: string): Promise<boolean> {
const exists = await commandExists(cmd);
if (!exists) {
console.warn(`[Skip] ${cmd} not available`);
}
return !exists;
}