11import { Command } from "@cliffy/command" ;
22import { Sandbox } from "@deno/sandbox" ;
33import { green , magenta , red } from "@std/fmt/colors" ;
4+ import { pooledMap } from "@std/async" ;
5+ import { expandGlob } from "@std/fs" ;
6+ import { join } from "@std/path" ;
47
58import { getAppFromConfig , readConfig , writeConfig } from "./config.ts" ;
69import { error , renderTemporalTimestamp , withApp } from "./util.ts" ;
@@ -36,8 +39,7 @@ export const sandboxCreateCommand = new Command<SandboxContext>()
3639 debug : options . debug ,
3740 token,
3841 org,
39- // deno-lint-ignore no-explicit-any
40- lifetime : options . lifetime as any ,
42+ lifetime : options . lifetime as `${number } s` | `${number } m` | "session" ,
4143 } ) ;
4244
4345 if ( options . copy ) {
@@ -47,6 +49,8 @@ export const sandboxCreateCommand = new Command<SandboxContext>()
4749 }
4850
4951 if ( options . lifetime === "session" ) {
52+ console . log ( `Created sandbox with id '${ sandbox . id } '` ) ;
53+
5054 const success = await sshIntoSandbox ( sandbox ) ;
5155 const stopMessage = "Stopping the sandbox..." ;
5256 if ( success ) {
@@ -174,18 +178,18 @@ export const sandboxSshCommand = new Command<SandboxContext>()
174178
175179export const sandboxCopyCommand = new Command < SandboxContext > ( )
176180 . description ( "Copy files from or to a running sandbox" )
177- /* .example(
181+ . example (
178182 "Copy a file from a sandbox to the local machine" ,
179183 "copy someSandboxId:/app/remote-file.txt ./local-file.txt" ,
180- )*/
184+ )
181185 . example (
182186 "Copy a file from the local machine to a sandbox" ,
183187 "copy ./local-file.txt someSandboxId:/app/remote-file.txt" ,
184188 )
185- /* .example(
189+ . example (
186190 "Copy multiple files from a sandbox to the local machine" ,
187191 "copy someSandboxId:/app/remote-file.txt someSandboxId:/app/another-remote-file.txt ./" ,
188- )*/
192+ )
189193 . example (
190194 "Copy multiple files from the local machine to a sandbox" ,
191195 "copy ./local-file.txt ./another-local-file.txt someSandboxId:/app/" ,
@@ -194,10 +198,14 @@ export const sandboxCopyCommand = new Command<SandboxContext>()
194198 "Copy a directory from the local machine to a sandbox" ,
195199 "copy ./ ./another-local-file.txt someSandboxId:/app/" ,
196200 )
197- /* .example(
201+ . example (
198202 "Copy files from a sandbox to another sandbox" ,
199203 "copy someSandboxId:/app/remote-file.txt anotherSandboxId:/app/remote-file.txt" ,
200- )*/
204+ )
205+ . example (
206+ "Copy all files from a directory in a sandbox to the local machine" ,
207+ "copy someSandboxId:/app/* ./" ,
208+ )
201209 . arguments ( "<paths...:string>" )
202210 . action ( async ( options , ...paths ) => {
203211 if ( paths . length < 2 ) {
@@ -207,56 +215,75 @@ export const sandboxCopyCommand = new Command<SandboxContext>()
207215 const target = paths . pop ( ) ! ;
208216
209217 if ( target . includes ( ":" ) ) {
210- const [ sandboxId , sandboxPath ] = target . split ( ":" ) ;
211- await using sandbox = await connectToSandbox ( options , sandboxId ) ;
218+ const separatorIndex = target . indexOf ( ":" ) ;
219+ const sandboxId = target . slice ( 0 , separatorIndex ) ;
220+ const targetSandboxPath = target . slice ( separatorIndex + 1 ) ;
221+
222+ await using targetSandbox = await connectToSandbox ( options , sandboxId ) ;
212223
213- // const sourceSandboxPaths = [];
224+ const sourceSandboxPaths = [ ] ;
214225 const localPaths = [ ] ;
215226
216227 for ( const path of paths ) {
217228 if ( path . includes ( ":" ) ) {
218- error (
219- options . debug ,
220- "Copying between sandboxes is currently not supported" ,
221- ) ;
222-
223- //sourceSandboxPaths.push(path);
229+ sourceSandboxPaths . push ( path ) ;
224230 } else {
225231 localPaths . push ( path ) ;
226232 }
227233 }
228234
229- /* const sourceSandboxGroups = groupPathsBySandbox(sourceSandboxPaths);
235+ const sourceSandboxGroups = groupPathsBySandbox ( sourceSandboxPaths ) ;
230236 const sourceSandboxes : Record < string , Sandbox > = { } ;
231237
232238 for ( const sandboxId of Object . keys ( sourceSandboxGroups ) ) {
233239 sourceSandboxes [ sandboxId ] = await connectToSandbox ( options , sandboxId ) ;
234- }*/
240+ }
235241
236242 await Promise . all ( [
237243 ...localPaths . map ( ( path ) => {
238- return sandbox . upload ( path , sandboxPath ) ;
244+ return targetSandbox . upload ( path , targetSandboxPath ) ;
239245 } ) ,
240- /* ...Object.entries(sourceSandboxGroups).map(
246+ ...Object . entries ( sourceSandboxGroups ) . map (
241247 async ( [ sandboxId , sourceSandboxPaths ] ) => {
242248 const sourceSandbox = sourceSandboxes [ sandboxId ] ;
243249
244250 await Promise . all (
245251 sourceSandboxPaths . map ( async ( sourceSandboxPath ) => {
246252 const tempDir = await Deno . makeTempDir ( ) ;
247- await sourceSandbox.download(sourceSandboxPath, tempDir);
248- await sandbox.upload(tempDir, target);
253+
254+ await Array . fromAsync ( pooledMap (
255+ Infinity ,
256+ sourceSandbox . expandGlob ( sourceSandboxPath ) ,
257+ async ( sandboxEntry ) => {
258+ const tempPath = join ( tempDir , sandboxEntry . path ) ;
259+ await Deno . mkdir ( tempPath , { recursive : true } ) ;
260+ await sourceSandbox . download ( sandboxEntry . path , tempPath ) ;
261+
262+ await Array . fromAsync ( pooledMap (
263+ Infinity ,
264+ expandGlob ( `${ tempPath } /*` ) ,
265+ ( localEntry ) =>
266+ targetSandbox . upload (
267+ localEntry . path ,
268+ join (
269+ targetSandboxPath ,
270+ localEntry . isDirectory
271+ ? "./"
272+ : `./${ localEntry . name } ` ,
273+ ) ,
274+ ) ,
275+ ) ) ;
276+ } ,
277+ ) ) ;
249278 } ) ,
250279 ) ;
251280
252- await sandbox .close();
281+ await sourceSandbox . close ( ) ;
253282 } ,
254- ),*/
283+ ) ,
255284 ] ) ;
256285 } else {
257- error ( options . debug , "Copying from sandboxes is currently not supported" ) ;
258-
259- /*for (const path of paths) {
286+ for ( const path of paths ) {
260287 if ( ! path . includes ( ":" ) ) {
261288 error (
262289 options . debug ,
@@ -276,13 +303,17 @@ export const sandboxCopyCommand = new Command<SandboxContext>()
276303 Object . entries ( groups ) . map ( async ( [ sandboxId , sandboxPaths ] ) => {
277304 const sandbox = sandboxes [ sandboxId ] ;
278305
279- await Promise.all(sandboxPaths.map((sandboxPath) => {
280- return sandbox.download(sandboxPath, target);
306+ await Promise . all ( sandboxPaths . map ( async ( sandboxPath ) => {
307+ await Array . fromAsync ( pooledMap (
308+ Infinity ,
309+ sandbox . expandGlob ( sandboxPath ) ,
310+ ( entry ) => sandbox . download ( entry . path , target ) ,
311+ ) ) ;
281312 } ) ) ;
282313
283314 await sandbox . close ( ) ;
284315 } ) ,
285- );*/
316+ ) ;
286317 }
287318 } ) ;
288319
@@ -305,11 +336,14 @@ export const sandboxExecCommand = new Command<SandboxContext>()
305336 const child = await sandbox . spawn ( "bash" , {
306337 cwd : options . cwd ,
307338 args : [ "-c" , command . join ( " " ) ] ,
339+ stdin : "piped" ,
308340 stdout : options . quiet ? "null" : "inherit" ,
309341 stderr : options . quiet ? "null" : "inherit" ,
310342 } ) ;
311343
312- const status = await child . status ;
344+ const write = Deno . stdin . readable . pipeTo ( child . stdin ! ) ;
345+
346+ const [ status ] = await Promise . all ( [ child . status , write ] ) ;
313347 Deno . exit ( status . code ) ;
314348 } ) ;
315349
@@ -341,8 +375,7 @@ export const sandboxRunCommand = new Command<SandboxContext>()
341375 debug : options . debug ,
342376 token,
343377 org,
344- // deno-lint-ignore no-explicit-any
345- lifetime : options . lifetime as any ,
378+ lifetime : options . lifetime as `${number } s` | `${number } m` | "session" ,
346379 } ) ;
347380
348381 console . log ( `Created sandbox with id '${ sandbox . id } '` ) ;
@@ -371,39 +404,45 @@ export const sandboxRunCommand = new Command<SandboxContext>()
371404 const child = await sandbox . spawn ( "bash" , {
372405 cwd : options . cwd ,
373406 args : [ "-c" , command . join ( " " ) ] ,
407+ stdin : "piped" ,
374408 stdout : options . quiet ? "null" : "inherit" ,
375409 stderr : options . quiet ? "null" : "inherit" ,
376410 } ) ;
377411
378- const status = await child . status ;
412+ const write = Deno . stdin . readable . pipeTo ( child . stdin ! ) ;
413+
414+ const [ status ] = await Promise . all ( [ child . status , write ] ) ;
379415 Deno . exit ( status . code ) ;
380416 } ) ;
381417
382- /*
383418export const sandboxExtendCommand = new Command < SandboxContext > ( )
384419 . description ( "Extend the lifetime of a running sandbox" )
385420 . arguments ( "<sandbox-id:string> <lifetime:string>" )
386421 . action ( async ( options , sandboxId , lifetime ) => {
387422 await using sandbox = await connectToSandbox ( options , sandboxId ) ;
388423
389- console.log(await sandbox.extendLifetime(lifetime));
424+ console . log (
425+ await sandbox . extendLifetime ( lifetime as `${number } s` | `${number } m`) ,
426+ ) ;
390427 } ) ;
391- */
392428
393- /*
394429function groupPathsBySandbox ( paths : string [ ] ) : Record < string , string [ ] > {
395- const groups = {};
430+ const groups : Record < string , string [ ] > = { } ;
396431
397432 for ( const path of paths ) {
398- const [sandboxId, sandboxPath] = path.split(":");
433+ const separatorIndex = path . indexOf ( ":" ) ;
434+ const sandboxId = path . slice ( 0 , separatorIndex ) ;
435+ const sandboxPath = path . slice ( separatorIndex + 1 ) ;
436+
399437 if ( ! groups [ sandboxId ] ) {
400438 groups [ sandboxId ] = [ ] ;
401439 }
440+
402441 groups [ sandboxId ] . push ( sandboxPath ) ;
403442 }
404443
405444 return groups ;
406- }*/
445+ }
407446
408447async function ensureOrg ( options : SandboxContext , quiet : boolean = true ) {
409448 const config = await readConfig ( Deno . cwd ( ) , options . config ) ;
@@ -440,6 +479,7 @@ async function connectToSandbox(
440479
441480 return await Sandbox . connect ( {
442481 id : sandboxId ,
482+ apiEndpoint : options . endpoint ,
443483 region : cluster . region ,
444484 debug : options . debug ,
445485 token,
@@ -597,5 +637,5 @@ export const sandboxCommand = new Command<GlobalOptions>()
597637 . alias ( "cp" )
598638 . command ( "exec" , sandboxExecCommand )
599639 . command ( "run" , sandboxRunCommand )
600- // .command("extend", sandboxExtendCommand)
640+ . command ( "extend" , sandboxExtendCommand )
601641 . command ( "ssh" , sandboxSshCommand ) ;
0 commit comments