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" ;
34import path from "path" ;
45import { createRequire } from "module" ;
56
6- export const NULL_DEVICE = ' /dev/null' ;
7+ const NULL_DEVICE = " /dev/null" ;
78
89type CommandResult = Promise < string > ;
910
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)" ;
1314
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+ } ) ;
1719
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+ } ) ;
2124
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 > ;
2634
2735const require = createRequire ( import . meta. url ) ;
2836
2937function devcontainerBinaryPath ( ) : string {
3038 try {
31- const pkgPath = require . resolve ( ' @devcontainers/cli/package.json' ) ;
39+ const pkgPath = require . resolve ( " @devcontainers/cli/package.json" ) ;
3240 const pkg = require ( pkgPath ) ;
3341 return path . join ( path . dirname ( pkgPath ) , pkg . bin . devcontainer ) ;
3442 } 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+ ) ;
3646 }
3747}
3848
39- function createOutputStream ( stdioFilePath : string = NULL_DEVICE ) : fs . WriteStream {
49+ function createOutputStream (
50+ stdioFilePath : string = NULL_DEVICE
51+ ) : fs . WriteStream {
4052 try {
41- return fs . createWriteStream ( stdioFilePath , { flags : 'w' } )
53+ return fs . createWriteStream ( stdioFilePath , { flags : "w" } ) ;
4254 } 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+ ) ;
4458 }
4559}
4660
47- async function runCommand ( args : string [ ] , stdoutStream : fs . WriteStream ) : CommandResult {
61+ async function runCommand (
62+ args : string [ ] ,
63+ stdoutStream : fs . WriteStream
64+ ) : CommandResult {
4865 return new Promise ( ( resolve , reject ) => {
4966 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" ] ,
5269 } as SpawnOptions ) ;
5370
5471 const stdoutData : string [ ] = [ ] ;
5572 const stderrData : string [ ] = [ ] ;
5673
57- child . on ( ' error' , ( error ) => {
74+ child . on ( " error" , ( error ) => {
5875 cleanup ( error ) ;
5976 reject ( new Error ( `Process spawn failed: ${ error . message } ` ) ) ;
6077 } ) ;
6178
6279 // 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 ( ) ) ;
6582 stdoutStream . write ( data ) ;
6683 } ) ;
6784
6885 // Collect stderr data instead of piping to process.stderr
69- child . stderr ?. on ( ' data' , ( data ) => {
86+ child . stderr ?. on ( " data" , ( data ) => {
7087 stderrData . push ( data . toString ( ) . trim ( ) ) ;
7188 } ) ;
7289
@@ -81,39 +98,47 @@ async function runCommand(args: string[], stdoutStream: fs.WriteStream): Command
8198 }
8299 } ;
83100
84- child . on ( ' close' , ( code , signal ) => {
101+ child . on ( " close" , ( code , signal ) => {
85102 cleanup ( ) ;
86103 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+ ) ;
88107 } else {
89108 const reason = signal
90109 ? `terminated by signal ${ signal } `
91110 : `exited with code ${ code } ` ;
92-
111+
93112 // 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" ) } `;
95116 reject ( new Error ( errorMessage ) ) ;
96117 }
97118 } ) ;
98119 } ) ;
99120}
100121
101- export async function devUp ( options : DevContainerUpOptions ) : CommandResult {
122+ export async function devUp ( options : DevUpArgs ) : CommandResult {
102123 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+ ) ;
104128}
105129
106- export async function devRunUserCommands ( options : DevContainerRunUserCommandsOptions ) : CommandResult {
130+ export async function devRunUserCommands ( options : DevRunArgs ) : CommandResult {
107131 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+ ) ;
109136}
110137
111- export async function devExec ( options : DevContainerExecOptions ) : CommandResult {
138+ export async function devExec ( options : DevExecArgs ) : CommandResult {
112139 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+ ) ;
119144}
0 commit comments