Library

Stagehand

Scripts are phases, phases are steps. Steps have a handler and an optional rollback that fires when a later step fails, with a typed context threaded through the whole run.

npm install @michaelrwalker/stagehand
deploy.ts

// reference

One script, hover to learn it

Every highlighted token is real Stagehand API — hover or tab to it for what it does.

deploy.ts hover the highlighted tokens

				
				import { Script, fileStore } from "@michaelrwalker/stagehand";
			
				
				import { z } from "zod";
			
				
				 
			
				
				const cache = fileStore("./.stagehand-cache.json");
			
				
				 
			
				
				const deploy = new Script({
			
				
				name: "deploy",
			
				
				description: "Build, upload and release a service",
			
				
				})
			
				
				.defineInput(z.object({ service: z.string(), environment: z.enum(["staging", "production"]) }))
			
				
				.addPhase("Build", { cache })
			
				
				.addStep({
			
				
				name: "compile bundle",
			
				
				handler: async ({ input, progress }) => {
			
				
				const bar = progress({ total: 100, label: "compiling" });
			
				
				const artifact = await compile(input.service, (pct) => bar.update(pct));
			
				
				bar.done();
			
				
				return { artifact: artifact.path, bytes: artifact.bytes };
			
				
				},
			
				
				})
			
				
				 
			
				
				.addPhase("Release")
			
				
				.addStep({
			
				
				name: "upload artifact",
			
				
				cache,
			
				
				handler: async ({ ctx, progress }) => {
			
				
				const bar = progress({ total: ctx.bytes, label: "uploading" });
			
				
				const uploadId = await upload(ctx.artifact, (sent) => bar.update(sent));
			
				
				bar.done();
			
				
				return { uploadId };
			
				
				},
			
				
				clean: ["bytes"],
			
				
				rollbackKeys: ["artifact"],
			
				
				rollback: async ({ ctx, output, log }) => {
			
				
				await cdn.delete(output.uploadId);
			
				
				log(`deleted artifact built from ${ctx.artifact}`);
			
				
				},
			
				
				})
			
				
				.addStep({
			
				
				name: "shift traffic",
			
				
				handler: async ({ ctx, input, status }) => {
			
				
				status(`routing ${input.environment} to ${ctx.uploadId}`);
			
				
				const healthy = await shiftTraffic(ctx.uploadId);
			
				
				if (!healthy) throw new Error("health check failed");
			
				
				return { releaseId: crypto.randomUUID() };
			
				
				},
			
				
				});
			
				
				 
			
				
				const result = await deploy.run({
			
				
				service: "api",
			
				
				environment: "staging",
			
				
				});
			

Contents 16 pages