1
- import { spawn , SpawnOptions } from 'child_process' ;
2
- import fs from 'fs' ;
1
+ import { spawn , SpawnOptions } from "child_process" ;
2
+ import fs from "fs" ;
3
+ import { z } from "zod" ;
3
4
import path from "path" ;
4
5
import { createRequire } from "module" ;
5
6
6
- export const NULL_DEVICE = ' /dev/null' ;
7
+ const NULL_DEVICE = " /dev/null" ;
7
8
8
9
type CommandResult = Promise < string > ;
9
10
10
- interface DevcontainerOptions {
11
- stdioFilePath ?: string ;
12
- }
11
+ const WS_FOLDER_DESC = "Path to the workspace folder (string)" ;
12
+ const STDIO_FILE_PATH = `Path for output logs ( string), default is ${ NULL_DEVICE } ` ;
13
+ const COMMAND = "Command to execute (array of string)" ;
13
14
14
- interface DevContainerUpOptions extends DevcontainerOptions {
15
- workspaceFolder : string ;
16
- }
15
+ export const DevUpSchema = z . object ( {
16
+ workspaceFolder : z . string ( ) . describe ( WS_FOLDER_DESC ) ,
17
+ stdioFilePath : z . string ( ) . describe ( STDIO_FILE_PATH ) . optional ( ) ,
18
+ } ) ;
17
19
18
- interface DevContainerRunUserCommandsOptions extends DevcontainerOptions {
19
- workspaceFolder : string ;
20
- }
20
+ export const DevRunSchema = z . object ( {
21
+ workspaceFolder : z . string ( ) . describe ( WS_FOLDER_DESC ) ,
22
+ stdioFilePath : z . string ( ) . describe ( STDIO_FILE_PATH ) . optional ( ) ,
23
+ } ) ;
21
24
22
- interface DevContainerExecOptions extends DevcontainerOptions {
23
- workspaceFolder : string ;
24
- command : string [ ] ;
25
- }
25
+ export const DevExecSchema = z . object ( {
26
+ workspaceFolder : z . string ( ) . describe ( WS_FOLDER_DESC ) ,
27
+ stdioFilePath : z . string ( ) . describe ( STDIO_FILE_PATH ) . optional ( ) ,
28
+ command : z . array ( z . string ( ) ) . min ( 1 ) . describe ( COMMAND ) ,
29
+ } ) ;
30
+
31
+ type DevUpArgs = z . infer < typeof DevUpSchema > ;
32
+ type DevRunArgs = z . infer < typeof DevRunSchema > ;
33
+ type DevExecArgs = z . infer < typeof DevExecSchema > ;
26
34
27
35
const require = createRequire ( import . meta. url ) ;
28
36
29
37
function devcontainerBinaryPath ( ) : string {
30
38
try {
31
- const pkgPath = require . resolve ( ' @devcontainers/cli/package.json' ) ;
39
+ const pkgPath = require . resolve ( " @devcontainers/cli/package.json" ) ;
32
40
const pkg = require ( pkgPath ) ;
33
41
return path . join ( path . dirname ( pkgPath ) , pkg . bin . devcontainer ) ;
34
42
} catch ( error ) {
35
- throw new Error ( 'Failed to locate devcontainer CLI: ' + ( error as Error ) . message ) ;
43
+ throw new Error (
44
+ "Failed to locate devcontainer CLI: " + ( error as Error ) . message
45
+ ) ;
36
46
}
37
47
}
38
48
39
- function createOutputStream ( stdioFilePath : string = NULL_DEVICE ) : fs . WriteStream {
49
+ function createOutputStream (
50
+ stdioFilePath : string = NULL_DEVICE
51
+ ) : fs . WriteStream {
40
52
try {
41
- return fs . createWriteStream ( stdioFilePath , { flags : 'w' } )
53
+ return fs . createWriteStream ( stdioFilePath , { flags : "w" } ) ;
42
54
} catch ( error ) {
43
- throw new Error ( `Failed to create output stream: ${ ( error as Error ) . message } ` ) ;
55
+ throw new Error (
56
+ `Failed to create output stream: ${ ( error as Error ) . message } `
57
+ ) ;
44
58
}
45
59
}
46
60
47
- async function runCommand ( args : string [ ] , stdoutStream : fs . WriteStream ) : CommandResult {
61
+ async function runCommand (
62
+ args : string [ ] ,
63
+ stdoutStream : fs . WriteStream
64
+ ) : CommandResult {
48
65
return new Promise ( ( resolve , reject ) => {
49
66
const binaryPath = devcontainerBinaryPath ( ) ;
50
- const child = spawn ( ' node' , [ binaryPath , ...args ] , {
51
- stdio : [ ' ignore' , ' pipe' , ' pipe' ] ,
67
+ const child = spawn ( " node" , [ binaryPath , ...args ] , {
68
+ stdio : [ " ignore" , " pipe" , " pipe" ] ,
52
69
} as SpawnOptions ) ;
53
70
54
71
const stdoutData : string [ ] = [ ] ;
55
72
const stderrData : string [ ] = [ ] ;
56
73
57
- child . on ( ' error' , ( error ) => {
74
+ child . on ( " error" , ( error ) => {
58
75
cleanup ( error ) ;
59
76
reject ( new Error ( `Process spawn failed: ${ error . message } ` ) ) ;
60
77
} ) ;
61
78
62
79
// Pipe stdout to the stream as before, but also collect it
63
- child . stdout ?. on ( ' data' , ( data ) => {
64
- stdoutData . push ( data . toString ( ) )
80
+ child . stdout ?. on ( " data" , ( data ) => {
81
+ stdoutData . push ( data . toString ( ) ) ;
65
82
stdoutStream . write ( data ) ;
66
83
} ) ;
67
84
68
85
// Collect stderr data instead of piping to process.stderr
69
- child . stderr ?. on ( ' data' , ( data ) => {
86
+ child . stderr ?. on ( " data" , ( data ) => {
70
87
stderrData . push ( data . toString ( ) . trim ( ) ) ;
71
88
} ) ;
72
89
@@ -81,39 +98,47 @@ async function runCommand(args: string[], stdoutStream: fs.WriteStream): Command
81
98
}
82
99
} ;
83
100
84
- child . on ( ' close' , ( code , signal ) => {
101
+ child . on ( " close" , ( code , signal ) => {
85
102
cleanup ( ) ;
86
103
if ( code === 0 ) {
87
- resolve ( `success with code ${ code } \n-------\n${ stdoutData . join ( '\n\n' ) } ` ) ;
104
+ resolve (
105
+ `success with code ${ code } \n-------\n${ stdoutData . join ( "\n\n" ) } `
106
+ ) ;
88
107
} else {
89
108
const reason = signal
90
109
? `terminated by signal ${ signal } `
91
110
: `exited with code ${ code } ` ;
92
-
111
+
93
112
// Combine the error message with the collected stderr output
94
- const errorMessage = `Command failed: devcontainer ${ args . join ( ' ' ) } (${ reason } )\n-------\n${ stderrData . join ( '\n\n' ) } ` ;
113
+ const errorMessage = `Command failed: devcontainer ${ args . join (
114
+ " "
115
+ ) } (${ reason } )\n-------\n${ stderrData . join ( "\n\n" ) } `;
95
116
reject ( new Error ( errorMessage ) ) ;
96
117
}
97
118
} ) ;
98
119
} ) ;
99
120
}
100
121
101
- export async function devUp ( options : DevContainerUpOptions ) : CommandResult {
122
+ export async function devUp ( options : DevUpArgs ) : CommandResult {
102
123
const stream = createOutputStream ( options . stdioFilePath ) ;
103
- return runCommand ( [ 'up' , '--workspace-folder' , options . workspaceFolder ] , stream ) ;
124
+ return runCommand (
125
+ [ "up" , "--workspace-folder" , options . workspaceFolder ] ,
126
+ stream
127
+ ) ;
104
128
}
105
129
106
- export async function devRunUserCommands ( options : DevContainerRunUserCommandsOptions ) : CommandResult {
130
+ export async function devRunUserCommands ( options : DevRunArgs ) : CommandResult {
107
131
const stream = createOutputStream ( options . stdioFilePath ) ;
108
- return runCommand ( [ 'run-user-commands' , '--workspace-folder' , options . workspaceFolder ] , stream ) ;
132
+ return runCommand (
133
+ [ "run-user-commands" , "--workspace-folder" , options . workspaceFolder ] ,
134
+ stream
135
+ ) ;
109
136
}
110
137
111
- export async function devExec ( options : DevContainerExecOptions ) : CommandResult {
138
+ export async function devExec ( options : DevExecArgs ) : CommandResult {
112
139
const stream = createOutputStream ( options . stdioFilePath ) ;
113
- return runCommand ( [
114
- 'exec' ,
115
- '--workspace-folder' ,
116
- options . workspaceFolder ,
117
- ...options . command
118
- ] , stream ) ;
140
+ return runCommand (
141
+ [ "exec" , "--workspace-folder" , options . workspaceFolder , ...options . command ] ,
142
+ stream
143
+ ) ;
119
144
}
0 commit comments