Skip to content

Commit c4d4adb

Browse files
committedMar 7, 2025··
refactor(command): Completely rewrite Command module with static methods and Docker support
- Replaced class-based implementation with static utility methods - Added Docker-specific command execution methods - Introduced new methods for piping and inheriting commands - Added color output using @cliffy/ansi - Removed test file and updated package version to 3.0.0 - Updated deno.lock with new dependencies
1 parent 866d272 commit c4d4adb

File tree

4 files changed

+137
-125
lines changed

4 files changed

+137
-125
lines changed
 

‎deno.lock

+8
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎src/command/Command.ts

+128-99
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,136 @@
1-
/**
2-
* @example
3-
* ```ts
4-
* import Command from "@ph/command";
5-
* import { assert } from "jsr:@std/assert";
6-
*
7-
* const ls = new Command("ls", {
8-
* args: ["-la", "--color=always"],
9-
* });
10-
*
11-
* const dump = ls.dump();
12-
*
13-
* assert(dump.command === "ls");
14-
* assert(dump.options?.args?.length === 2);
15-
* assert(dump.options?.args?.[0] === "-la");
16-
* assert(dump.options?.args?.[1] === "--color=always");
17-
*
18-
* const output = await ls.execute();
19-
*
20-
* assert(output.stdout !== undefined);
21-
* assert(output.stdout.length > 0);
22-
* assert(output.stderr !== undefined);
23-
* assert(output.stderr.length === 0);
24-
* assert(output.code === 0);
25-
* assert(output.signal === null);
26-
* assert(output.success === true);
27-
* ```
28-
*
29-
* A class for executing shell commands with flexible output handling.
30-
* Provides methods for both piped and inherited output modes.
31-
*/
32-
export default class Command {
33-
private readonly denoCommand: Deno.Command;
34-
private readonly command: string;
35-
private readonly options?: Deno.CommandOptions;
36-
private readonly decorator: TextDecoder;
37-
38-
public constructor(
39-
command: string,
40-
options?: Deno.CommandOptions,
41-
) {
42-
this.denoCommand = new Deno.Command(command, options);
43-
this.command = command;
44-
this.options = options;
45-
this.decorator = new TextDecoder("utf-8");
46-
}
1+
import { colors } from "jsr:@cliffy/ansi@^1.0.0-rc.7/colors";
472

48-
/**
49-
* Dumps the command and its options.
50-
*
51-
* @example
52-
* ```ts
53-
* import Command from "@ph/command";
54-
* import { assert } from "jsr:@std/assert";
55-
*
56-
* const ls = new Command("ls", {
57-
* args: ["-la", "--color=always"],
58-
* });
59-
*
60-
* console.log(ls.dump());
61-
*/
62-
public dump(): {
63-
command: string;
64-
options?: Deno.CommandOptions;
65-
} {
66-
return {
67-
command: this.command,
68-
options: this.options,
69-
};
70-
}
3+
export enum StdType {
4+
Piped = "piped",
5+
Inherit = "inherit",
6+
Null = "null",
7+
}
8+
9+
export type PipeResult = {
10+
success: boolean;
11+
output: string;
12+
error: string;
13+
};
14+
export class Command {
15+
public static async pipe(command: string, ...args: string[]): Promise<PipeResult> {
16+
const output = await (new Deno.Command(
17+
command,
18+
{
19+
args: args,
20+
stdout: StdType.Piped,
21+
stderr: StdType.Piped,
22+
},
23+
)).output();
7124

72-
/**
73-
* Executes the command and returns the output.
74-
*
75-
* @example
76-
* ```ts
77-
* import Command from "@ph/command";
78-
* const ls = new Command("ls", {
79-
* args: ["-la", "--color=always"],
80-
* });
81-
* console.log(await ls.execute());
82-
* ```
83-
*/
84-
public async execute(): Promise<{
85-
stdout: string | undefined;
86-
stderr: string | undefined;
87-
code: number;
88-
signal: Deno.Signal | null;
89-
success: boolean;
90-
}> {
91-
const output = await this.denoCommand.output();
25+
const decodedOutput = new TextDecoder().decode(output.stdout);
26+
const decodedError = new TextDecoder().decode(output.stderr);
9227

9328
return {
94-
stdout: this.options?.stdout === "inherit" ||
95-
this.options?.stdout === "null"
96-
? undefined
97-
: this.decorator.decode(output.stdout),
98-
stderr: this.options?.stderr === "inherit" ||
99-
this.options?.stderr === "null"
100-
? undefined
101-
: this.decorator.decode(output.stderr),
102-
code: output.code,
103-
signal: output.signal,
10429
success: output.success,
30+
output: decodedOutput,
31+
error: decodedError,
10532
};
10633
}
34+
35+
public static async inherit(command: string, ...args: string[]): Promise<void> {
36+
await (new Deno.Command(
37+
command,
38+
{
39+
args: args,
40+
stdout: StdType.Inherit,
41+
stderr: StdType.Inherit,
42+
},
43+
)).output();
44+
}
45+
46+
public static async getContainerId(): Promise<string> {
47+
const result = await Command.pipe(
48+
"docker",
49+
"ps",
50+
"-q",
51+
"-f",
52+
"name=favi_app",
53+
);
54+
if (!result.success) {
55+
console.error(colors.bold.underline.red(`🚨 ${result.error.toString()}`));
56+
}
57+
return result.output.trim();
58+
}
59+
60+
public static async pipeInContainer(
61+
command: string,
62+
beforeDump?: () => void,
63+
): Promise<void> {
64+
const result = await Command.pipe(
65+
"docker",
66+
"exec",
67+
await Command.getContainerId(),
68+
"sh",
69+
"-c",
70+
command,
71+
);
72+
73+
if (beforeDump) {
74+
beforeDump();
75+
}
76+
77+
if (!result.success) {
78+
console.log(colors.bold.red(`${result.output.toString()}`));
79+
console.log(colors.bold.red(`${result.error.toString()}`));
80+
} else {
81+
console.log(colors.dim(`${result.output.toString()}`));
82+
}
83+
}
84+
85+
public static async pipeFromContainer(command: string): Promise<PipeResult> {
86+
return await Command.pipe(
87+
"docker",
88+
"exec",
89+
await Command.getContainerId(),
90+
"sh",
91+
"-c",
92+
command,
93+
);
94+
}
95+
96+
public static async inheritInContainer(command: string): Promise<void> {
97+
await Command.inherit(
98+
"docker",
99+
"exec",
100+
await Command.getContainerId(),
101+
"sh",
102+
"-c",
103+
command,
104+
);
105+
}
106+
107+
public static async pipeInWorkingDirectory(command: string): Promise<void> {
108+
const result = await Command.pipe(
109+
"sh",
110+
"-c",
111+
command,
112+
);
113+
114+
if (!result.success) {
115+
console.log(colors.bold.red(`${result.output.toString()}`));
116+
console.log(colors.bold.red(`${result.error.toString()}`));
117+
} else {
118+
console.log(colors.dim(`${result.output.toString()}`));
119+
}
120+
}
121+
122+
public static async pipeFromWorkingDirectory(command: string): Promise<PipeResult> {
123+
const result = await Command.pipe(
124+
"sh",
125+
"-c",
126+
command,
127+
);
128+
129+
if (!result.success) {
130+
console.log(colors.bold.red(`${result.output.toString()}`));
131+
console.log(colors.bold.red(`${result.error.toString()}`));
132+
}
133+
134+
return result;
135+
}
107136
}

‎src/command/deno.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@ph/command",
3-
"version": "2.0.1",
3+
"version": "3.0.0",
44
"license": "MIT",
55
"exports": {
66
".": "./Command.ts"

‎src/command/tests/test.ts

-25
This file was deleted.

0 commit comments

Comments
 (0)
Please sign in to comment.