libretto 0.5.3-experimental.4 → 0.5.3-experimental.6

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,10 @@
1
+ import { randomBytes } from "node:crypto";
1
2
  import { z } from "zod";
2
3
  import { buildHostedDeployTarball } from "../core/deploy-artifact.js";
3
4
  import { SimpleCLI } from "../framework/simple-cli.js";
5
+ function generateDeploymentName() {
6
+ return `deploy-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
7
+ }
4
8
  function getConfig() {
5
9
  const apiUrl = process.env.LIBRETTO_API_URL;
6
10
  const apiKey = process.env.LIBRETTO_API_KEY;
@@ -58,9 +62,6 @@ const deployInput = SimpleCLI.input({
58
62
  })
59
63
  ],
60
64
  named: {
61
- name: SimpleCLI.option(z.string(), {
62
- help: "Deployment name"
63
- }),
64
65
  description: SimpleCLI.option(z.string().optional(), {
65
66
  help: "Deployment description"
66
67
  }),
@@ -83,15 +84,15 @@ const deployCommand = SimpleCLI.command({
83
84
  experimental: true
84
85
  }).input(deployInput).handle(async ({ input }) => {
85
86
  const { apiUrl, apiKey } = getConfig();
87
+ const deploymentName = generateDeploymentName();
86
88
  console.log("Bundling hosted deployment artifact...");
87
89
  const { entryPoint, source } = await buildHostedDeployTarball({
88
90
  additionalExternals: input.external,
89
- deploymentName: input.name,
91
+ deploymentName,
90
92
  entryPoint: input.entryPoint,
91
93
  sourceDir: input.sourceDir
92
94
  });
93
95
  const createPayload = {
94
- name: input.name,
95
96
  source,
96
97
  entry_point: entryPoint
97
98
  };
@@ -109,10 +110,8 @@ const deployCommand = SimpleCLI.command({
109
110
  `Failed to create deployment (${res.status}): ${JSON.stringify(body)}`
110
111
  );
111
112
  }
112
- const { deployment_id, name, version, status } = body.json;
113
- console.log(
114
- `Deployment created: ${name} v${version} (${deployment_id})`
115
- );
113
+ const { deployment_id, status } = body.json;
114
+ console.log(`Deployment created: ${deployment_id}`);
116
115
  console.log(`Status: ${status}`);
117
116
  if (status === "building") {
118
117
  process.stdout.write("Waiting for build");
@@ -1,6 +1,7 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
3
  import {
4
+ cpSync,
4
5
  existsSync,
5
6
  mkdirSync,
6
7
  mkdtempSync,
@@ -39,6 +40,9 @@ const SOURCE_FILE_EXTENSIONS = [
39
40
  "/index.cjs"
40
41
  ];
41
42
  const CURRENT_LIBRETTO_VERSION = readCurrentLibrettoVersion();
43
+ const CURRENT_LIBRETTO_PACKAGE_DIR = fileURLToPath(
44
+ new URL("../../..", import.meta.url)
45
+ );
42
46
  function readCurrentLibrettoVersion() {
43
47
  const packageJsonPath = fileURLToPath(
44
48
  new URL("../../../package.json", import.meta.url)
@@ -103,7 +107,9 @@ function readWorkspacePatterns(rootDir) {
103
107
  if (existsSync(pnpmWorkspacePath)) {
104
108
  const patterns = [];
105
109
  let inPackagesBlock = false;
106
- for (const rawLine of readFileSync(pnpmWorkspacePath, "utf8").split(/\r?\n/)) {
110
+ for (const rawLine of readFileSync(pnpmWorkspacePath, "utf8").split(
111
+ /\r?\n/
112
+ )) {
107
113
  const trimmed = rawLine.trim();
108
114
  if (!inPackagesBlock) {
109
115
  if (trimmed === "packages:") {
@@ -233,7 +239,11 @@ function resolveExportTarget(exportValue, packageDir, replacement) {
233
239
  if (!(condition in record)) {
234
240
  continue;
235
241
  }
236
- const resolved = resolveExportTarget(record[condition], packageDir, replacement);
242
+ const resolved = resolveExportTarget(
243
+ record[condition],
244
+ packageDir,
245
+ replacement
246
+ );
237
247
  if (resolved) {
238
248
  return resolved;
239
249
  }
@@ -250,7 +260,7 @@ function resolveExportsSubpath(exportsField, packageDir, subpath) {
250
260
  if (!exportsField) {
251
261
  return null;
252
262
  }
253
- if (subpath === ".") {
263
+ if (subpath === "." && (typeof exportsField === "string" || Array.isArray(exportsField))) {
254
264
  const rootExport = resolveExportTarget(exportsField, packageDir);
255
265
  if (rootExport) {
256
266
  return rootExport;
@@ -260,7 +270,9 @@ function resolveExportsSubpath(exportsField, packageDir, subpath) {
260
270
  return null;
261
271
  }
262
272
  const record = exportsField;
263
- const hasExplicitSubpathKeys = Object.keys(record).some((key) => key.startsWith("."));
273
+ const hasExplicitSubpathKeys = Object.keys(record).some(
274
+ (key) => key.startsWith(".")
275
+ );
264
276
  if (!hasExplicitSubpathKeys) {
265
277
  return subpath === "." ? resolveExportTarget(record, packageDir) : null;
266
278
  }
@@ -278,7 +290,10 @@ function resolveExportsSubpath(exportsField, packageDir, subpath) {
278
290
  if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix)) {
279
291
  continue;
280
292
  }
281
- const replacement = subpath.slice(prefix.length, subpath.length - suffix.length);
293
+ const replacement = subpath.slice(
294
+ prefix.length,
295
+ subpath.length - suffix.length
296
+ );
282
297
  const resolved = resolveExportTarget(value, packageDir, replacement);
283
298
  if (resolved) {
284
299
  return resolved;
@@ -287,7 +302,11 @@ function resolveExportsSubpath(exportsField, packageDir, subpath) {
287
302
  return null;
288
303
  }
289
304
  function resolveWorkspaceSourcePath(info, subpath) {
290
- const viaExports = resolveExportsSubpath(info.manifest.exports, info.dir, subpath);
305
+ const viaExports = resolveExportsSubpath(
306
+ info.manifest.exports,
307
+ info.dir,
308
+ subpath
309
+ );
291
310
  if (viaExports) {
292
311
  return viaExports;
293
312
  }
@@ -318,11 +337,17 @@ function workspaceSourcePlugin(workspacePackages, externalPackages) {
318
337
  if (externalPackages.has(args.path)) {
319
338
  return null;
320
339
  }
321
- const match = findMatchingWorkspacePackage(args.path, workspacePackages);
340
+ const match = findMatchingWorkspacePackage(
341
+ args.path,
342
+ workspacePackages
343
+ );
322
344
  if (!match) {
323
345
  return null;
324
346
  }
325
- const resolvedPath = resolveWorkspaceSourcePath(match.info, match.subpath);
347
+ const resolvedPath = resolveWorkspaceSourcePath(
348
+ match.info,
349
+ match.subpath
350
+ );
326
351
  if (!resolvedPath) {
327
352
  throw new Error(
328
353
  `Unable to resolve workspace import "${args.path}" from ${match.info.dir}.`
@@ -378,17 +403,12 @@ function resolveDependencyVersion(sourceDir, packageName, fallbackVersion) {
378
403
  }
379
404
  function writeDeployManifest(args) {
380
405
  const dependencies = Object.fromEntries(
381
- [
382
- ...BUILT_IN_MANIFEST_DEPENDENCIES,
383
- ...args.additionalExternals
384
- ].map((packageName) => [
385
- packageName,
386
- resolveDependencyVersion(
387
- args.sourceDir,
406
+ [...BUILT_IN_MANIFEST_DEPENDENCIES, ...args.additionalExternals].map(
407
+ (packageName) => [
388
408
  packageName,
389
- packageName === "libretto" ? CURRENT_LIBRETTO_VERSION : void 0
390
- )
391
- ])
409
+ packageName === "libretto" ? args.librettoDependency : resolveDependencyVersion(args.sourceDir, packageName)
410
+ ]
411
+ )
392
412
  );
393
413
  writeFileSync(
394
414
  join(args.outputDir, "package.json"),
@@ -404,6 +424,33 @@ function writeDeployManifest(args) {
404
424
  ) + "\n"
405
425
  );
406
426
  }
427
+ function shouldVendorCurrentLibretto(versionSpec) {
428
+ return versionSpec.startsWith("file:") || versionSpec.startsWith("link:") || versionSpec.startsWith("workspace:") || versionSpec.startsWith("portal:") || versionSpec.includes("&path:");
429
+ }
430
+ function resolveLibrettoDependency(sourceDir) {
431
+ const versionSpec = resolveDependencyVersion(
432
+ sourceDir,
433
+ "libretto",
434
+ CURRENT_LIBRETTO_VERSION
435
+ );
436
+ if (shouldVendorCurrentLibretto(versionSpec)) {
437
+ return "file:./libretto";
438
+ }
439
+ return versionSpec;
440
+ }
441
+ function copyCurrentLibrettoPackage(outputDir) {
442
+ const bundledLibrettoDir = join(outputDir, "libretto");
443
+ mkdirSync(bundledLibrettoDir, { recursive: true });
444
+ cpSync(
445
+ join(CURRENT_LIBRETTO_PACKAGE_DIR, "dist"),
446
+ join(bundledLibrettoDir, "dist"),
447
+ { recursive: true }
448
+ );
449
+ cpSync(
450
+ join(CURRENT_LIBRETTO_PACKAGE_DIR, "package.json"),
451
+ join(bundledLibrettoDir, "package.json")
452
+ );
453
+ }
407
454
  function formatBuildError(error) {
408
455
  if (!(error instanceof Error)) {
409
456
  return String(error);
@@ -450,7 +497,9 @@ function extractExportNamesFromEsmBundle(bundleSource) {
450
497
  }
451
498
  function createBootstrapSource(args) {
452
499
  const bundleHash = createHash("sha256").update(args.bundleBuffer).digest("hex").slice(0, 16);
453
- const bundleBase64 = gzipSync(args.bundleBuffer, { level: 9 }).toString("base64");
500
+ const bundleBase64 = gzipSync(args.bundleBuffer, { level: 9 }).toString(
501
+ "base64"
502
+ );
454
503
  const outputPrefix = `${normalizePackageName(args.deploymentName)}-`;
455
504
  const hasDefaultExport = args.exportNames.includes("default");
456
505
  const exportLines = args.exportNames.filter((name) => name !== "default").map(
@@ -470,7 +519,9 @@ const BUNDLE_FILENAME = join(
470
519
  tmpdir(),
471
520
  ${JSON.stringify(outputPrefix)} + BUNDLE_HASH + ".cjs",
472
521
  );
473
- const require = createRequire(import.meta.url);
522
+ const nativeRequire = createRequire(
523
+ join(tmpdir(), ${JSON.stringify("libretto-deploy-bootstrap.cjs")}),
524
+ );
474
525
 
475
526
  function ensureBundleFile() {
476
527
  if (!existsSync(BUNDLE_FILENAME)) {
@@ -485,7 +536,7 @@ function ensureBundleFile() {
485
536
 
486
537
  function createWorkflowProxy(exportName) {
487
538
  return workflow(exportName, async (ctx, input) => {
488
- const impl = require(ensureBundleFile());
539
+ const impl = nativeRequire(ensureBundleFile());
489
540
  const target = impl[exportName];
490
541
  if (!target || typeof target.run !== "function") {
491
542
  throw new Error(
@@ -500,29 +551,19 @@ ${exportLines}
500
551
  ${defaultExportLine}
501
552
  `;
502
553
  }
503
- async function createHostedDeployPackage(args) {
504
- const absSourceDir = resolve(args.sourceDir);
505
- ensureSourcePackageManifest(absSourceDir);
506
- const absEntryPoint = resolveEntryPointPath(absSourceDir, args.entryPoint);
507
- const tempRoot = mkdtempSync(join(tmpdir(), "libretto-deploy-"));
508
- const outputDir = join(tempRoot, "deploy");
509
- mkdirSync(outputDir, { recursive: true });
510
- const additionalExternals = [...new Set(args.additionalExternals ?? [])];
511
- const externalPackages = /* @__PURE__ */ new Set([
512
- ...DEFAULT_RUNTIME_EXTERNALS,
513
- ...additionalExternals
514
- ]);
515
- const workspacePackages = discoverWorkspacePackages(absSourceDir);
554
+ async function writeBundledDeployEntrypoint(args) {
516
555
  try {
517
556
  const implementationBuild = await build({
518
- absWorkingDir: absSourceDir,
557
+ absWorkingDir: args.absSourceDir,
519
558
  bundle: true,
520
- entryPoints: [absEntryPoint],
521
- external: [...externalPackages],
559
+ entryPoints: [args.absEntryPoint],
560
+ external: [...args.externalPackages],
522
561
  format: "cjs",
523
562
  outfile: "prebundled.cjs",
524
563
  platform: "node",
525
- plugins: [workspaceSourcePlugin(workspacePackages, externalPackages)],
564
+ plugins: [
565
+ workspaceSourcePlugin(args.workspacePackages, args.externalPackages)
566
+ ],
526
567
  splitting: false,
527
568
  target: "node20",
528
569
  write: false
@@ -531,17 +572,21 @@ async function createHostedDeployPackage(args) {
531
572
  (file) => file.path.endsWith("prebundled.cjs")
532
573
  );
533
574
  if (!bundledImplementation) {
534
- throw new Error("Bundler did not produce a deployment implementation file.");
575
+ throw new Error(
576
+ "Bundler did not produce a deployment implementation file."
577
+ );
535
578
  }
536
579
  const exportBuild = await build({
537
- absWorkingDir: absSourceDir,
580
+ absWorkingDir: args.absSourceDir,
538
581
  bundle: true,
539
- entryPoints: [absEntryPoint],
540
- external: [...externalPackages],
582
+ entryPoints: [args.absEntryPoint],
583
+ external: [...args.externalPackages],
541
584
  format: "esm",
542
585
  outfile: "entry-exports.js",
543
586
  platform: "node",
544
- plugins: [workspaceSourcePlugin(workspacePackages, externalPackages)],
587
+ plugins: [
588
+ workspaceSourcePlugin(args.workspacePackages, args.externalPackages)
589
+ ],
545
590
  splitting: false,
546
591
  target: "node20",
547
592
  write: false
@@ -555,11 +600,11 @@ async function createHostedDeployPackage(args) {
555
600
  const exportNames = extractExportNamesFromEsmBundle(bundledExports.text);
556
601
  if (exportNames.length === 0) {
557
602
  throw new Error(
558
- `No named exports were found in ${absEntryPoint}. Hosted deploy expects the entry point to export one or more workflows.`
603
+ `No named exports were found in ${args.absEntryPoint}. Hosted deploy expects the entry point to export one or more workflows.`
559
604
  );
560
605
  }
561
606
  writeFileSync(
562
- join(outputDir, "index.js"),
607
+ join(args.outputDir, "index.js"),
563
608
  createBootstrapSource({
564
609
  bundleBuffer: Buffer.from(bundledImplementation.contents),
565
610
  deploymentName: args.deploymentName,
@@ -567,25 +612,59 @@ async function createHostedDeployPackage(args) {
567
612
  })
568
613
  );
569
614
  } catch (error) {
570
- rmSync(tempRoot, { force: true, recursive: true });
571
615
  throw new Error(
572
- `Failed to bundle deploy entry point ${absEntryPoint}.
616
+ `Failed to bundle deploy entry point ${args.absEntryPoint}.
573
617
  ${formatBuildError(error)}`
574
618
  );
575
619
  }
576
- writeDeployManifest({
577
- additionalExternals,
578
- deploymentName: args.deploymentName,
579
- outputDir,
580
- sourceDir: absSourceDir
581
- });
582
- return {
583
- cleanup: () => {
620
+ }
621
+ async function createHostedDeployPackage(args) {
622
+ const absSourceDir = resolve(args.sourceDir);
623
+ ensureSourcePackageManifest(absSourceDir);
624
+ const absEntryPoint = resolveEntryPointPath(absSourceDir, args.entryPoint);
625
+ const tempRoot = mkdtempSync(join(tmpdir(), "libretto-deploy-"));
626
+ const outputDir = join(tempRoot, "deploy");
627
+ mkdirSync(outputDir, { recursive: true });
628
+ const librettoDependency = resolveLibrettoDependency(absSourceDir);
629
+ const additionalExternals = [...new Set(args.additionalExternals ?? [])];
630
+ const externalPackages = /* @__PURE__ */ new Set([
631
+ ...DEFAULT_RUNTIME_EXTERNALS,
632
+ ...additionalExternals
633
+ ]);
634
+ const workspacePackages = discoverWorkspacePackages(absSourceDir);
635
+ let callerOwnsTempRoot = false;
636
+ try {
637
+ await writeBundledDeployEntrypoint({
638
+ absEntryPoint,
639
+ absSourceDir,
640
+ deploymentName: args.deploymentName,
641
+ externalPackages,
642
+ outputDir,
643
+ workspacePackages
644
+ });
645
+ if (librettoDependency === "file:./libretto") {
646
+ copyCurrentLibrettoPackage(outputDir);
647
+ }
648
+ writeDeployManifest({
649
+ additionalExternals,
650
+ deploymentName: args.deploymentName,
651
+ librettoDependency,
652
+ outputDir,
653
+ sourceDir: absSourceDir
654
+ });
655
+ callerOwnsTempRoot = true;
656
+ return {
657
+ cleanup: () => {
658
+ rmSync(tempRoot, { force: true, recursive: true });
659
+ },
660
+ entryPoint: "index.js",
661
+ outputDir
662
+ };
663
+ } finally {
664
+ if (!callerOwnsTempRoot) {
584
665
  rmSync(tempRoot, { force: true, recursive: true });
585
- },
586
- entryPoint: "index.js",
587
- outputDir
588
- };
666
+ }
667
+ }
589
668
  }
590
669
  async function buildHostedDeployTarball(args) {
591
670
  const deployPackage = await createHostedDeployPackage(args);
@@ -177,7 +177,6 @@ async function runIntegrationInternal(args, options) {
177
177
  session: args.session,
178
178
  logger: integrationLogger,
179
179
  page: browserSession.page,
180
- services: {},
181
180
  credentials: args.credentials
182
181
  };
183
182
  try {
@@ -2,20 +2,19 @@ import { Page } from 'playwright';
2
2
  import { MinimalLogger } from '../logger/logger.js';
3
3
 
4
4
  declare const LIBRETTO_WORKFLOW_BRAND: unique symbol;
5
- type LibrettoWorkflowContext<S = {}> = {
5
+ type LibrettoWorkflowContext = {
6
6
  session: string;
7
7
  page: Page;
8
8
  logger: MinimalLogger;
9
- services: S;
10
9
  credentials?: Record<string, unknown>;
11
10
  };
12
- type LibrettoWorkflowHandler<Input = unknown, Output = unknown, S = {}> = (ctx: LibrettoWorkflowContext<S>, input: Input) => Promise<Output>;
13
- declare class LibrettoWorkflow<Input = unknown, Output = unknown, S = {}> {
11
+ type LibrettoWorkflowHandler<Input = unknown, Output = unknown> = (ctx: LibrettoWorkflowContext, input: Input) => Promise<Output>;
12
+ declare class LibrettoWorkflow<Input = unknown, Output = unknown> {
14
13
  readonly [LIBRETTO_WORKFLOW_BRAND] = true;
15
14
  readonly name: string;
16
15
  private readonly handler;
17
- constructor(name: string, handler: LibrettoWorkflowHandler<Input, Output, S>);
18
- run(ctx: LibrettoWorkflowContext<S>, input: Input): Promise<Output>;
16
+ constructor(name: string, handler: LibrettoWorkflowHandler<Input, Output>);
17
+ run(ctx: LibrettoWorkflowContext, input: Input): Promise<Output>;
19
18
  }
20
19
  type ExportedLibrettoWorkflow = {
21
20
  readonly [LIBRETTO_WORKFLOW_BRAND]: true;
@@ -26,6 +25,6 @@ type WorkflowModuleExports = Record<string, unknown>;
26
25
  declare function isLibrettoWorkflow(value: unknown): value is ExportedLibrettoWorkflow;
27
26
  declare function getWorkflowsFromModuleExports(moduleExports: WorkflowModuleExports): ExportedLibrettoWorkflow[];
28
27
  declare function getWorkflowFromModuleExports(moduleExports: WorkflowModuleExports, workflowName: string): ExportedLibrettoWorkflow | null;
29
- declare function workflow<Input = unknown, Output = unknown, S = {}>(name: string, handler: LibrettoWorkflowHandler<Input, Output, S>): LibrettoWorkflow<Input, Output, S>;
28
+ declare function workflow<Input = unknown, Output = unknown>(name: string, handler: LibrettoWorkflowHandler<Input, Output>): LibrettoWorkflow<Input, Output>;
30
29
 
31
30
  export { type ExportedLibrettoWorkflow, LIBRETTO_WORKFLOW_BRAND, LibrettoWorkflow, type LibrettoWorkflowContext, type LibrettoWorkflowHandler, getWorkflowFromModuleExports, getWorkflowsFromModuleExports, isLibrettoWorkflow, workflow };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libretto",
3
- "version": "0.5.3-experimental.4",
3
+ "version": "0.5.3-experimental.6",
4
4
  "description": "AI-powered browser automation library and CLI built on Playwright",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -40,48 +40,12 @@ Key points:
40
40
 
41
41
  - `workflow(name, handler)` takes a unique workflow name and returns the workflow object that Libretto can run.
42
42
  - `npx libretto run ./file.ts myWorkflow` resolves `myWorkflow` from the workflows exported by `./file.ts`, so export or re-export the workflow from that file directly or through a `workflows` object, and make sure the run argument matches the name passed to `workflow("myWorkflow", ...)`.
43
- - `ctx` provides `session`, `page`, `logger`, and `services` (generic, default `{}`)
43
+ - `ctx` provides `session`, `page`, `logger`, and optional `credentials`
44
44
  - `input` comes from `--params '{"query":"foo"}'` or `--params-file params.json` on the CLI
45
45
  - Use `await pause(ctx.session)` (or `await pause(session)`) to pause the workflow for debugging. It is a no-op in production.
46
46
  - After validation is complete and the workflow is confirmed working end to end, remove all `pause()` calls and pause-only workflow params unless the user explicitly says to keep them.
47
47
  - The browser is launched and closed automatically by the CLI. Do not launch or close it in the handler.
48
48
 
49
- ## Passing Application Dependencies via Services
50
-
51
- Use the third generic on `workflow<Input, Output, Services>` to inject
52
- dependencies that exist in your application but not in libretto's runtime
53
- (DB transactions, API clients, caches, etc.):
54
-
55
- ```typescript
56
- import { type Transaction } from "./db";
57
-
58
- type MyServices = { tx?: Transaction };
59
-
60
- export const myWorkflow = workflow<Input, Output, MyServices>(
61
- "myWorkflow",
62
- async (ctx, input) => {
63
- if (ctx.services.tx) {
64
- await ctx.services.tx.insert(/* ... */);
65
- } else {
66
- ctx.logger.info("No DB transaction — skipping write");
67
- }
68
- // ... browser automation ...
69
- },
70
- );
71
- ```
72
-
73
- In production, the caller passes services when invoking `.run()`:
74
-
75
- ```typescript
76
- await myWorkflow.run(
77
- { session: "debug-flow", page, logger, services: { tx } },
78
- input,
79
- );
80
- ```
81
-
82
- When running standalone via `npx libretto run`, services defaults to `{}`,
83
- so mark fields optional for anything unavailable in that context.
84
-
85
49
  ## Playwright DOM Interaction Rules
86
50
 
87
51
  Generated code must use Playwright locator APIs for all DOM interactions. Do not use `page.evaluate()` with `document.querySelector`, `querySelectorAll`, `textContent`, `click()`, or other DOM APIs when a Playwright locator can do the same thing.
@@ -1,3 +1,4 @@
1
+ import { randomBytes } from "node:crypto";
1
2
  import { z } from "zod";
2
3
  import { buildHostedDeployTarball } from "../core/deploy-artifact.js";
3
4
  import { SimpleCLI } from "../framework/simple-cli.js";
@@ -7,14 +8,16 @@ type DeploymentStatus = "building" | "ready" | "failed";
7
8
  type DeploymentResponse = {
8
9
  json: {
9
10
  deployment_id: string;
10
- name: string;
11
- version: number;
12
11
  status: DeploymentStatus;
13
12
  workflows?: string[] | null;
14
13
  build_error?: string | null;
15
14
  };
16
15
  };
17
16
 
17
+ function generateDeploymentName(): string {
18
+ return `deploy-${Date.now().toString(36)}-${randomBytes(4).toString("hex")}`;
19
+ }
20
+
18
21
  function getConfig() {
19
22
  const apiUrl = process.env.LIBRETTO_API_URL;
20
23
  const apiKey = process.env.LIBRETTO_API_KEY;
@@ -92,9 +95,6 @@ export const deployInput = SimpleCLI.input({
92
95
  }),
93
96
  ],
94
97
  named: {
95
- name: SimpleCLI.option(z.string(), {
96
- help: "Deployment name",
97
- }),
98
98
  description: SimpleCLI.option(z.string().optional(), {
99
99
  help: "Deployment description",
100
100
  }),
@@ -127,6 +127,7 @@ export const deployCommand = SimpleCLI.command({
127
127
  .input(deployInput)
128
128
  .handle(async ({ input }) => {
129
129
  const { apiUrl, apiKey } = getConfig();
130
+ const deploymentName = generateDeploymentName();
130
131
 
131
132
  // Hosted deploy uploads a generated artifact with a deploy entrypoint and
132
133
  // a minimal manifest. Bundled code is embedded in the generated files;
@@ -134,13 +135,12 @@ export const deployCommand = SimpleCLI.command({
134
135
  console.log("Bundling hosted deployment artifact...");
135
136
  const { entryPoint, source } = await buildHostedDeployTarball({
136
137
  additionalExternals: input.external,
137
- deploymentName: input.name,
138
+ deploymentName,
138
139
  entryPoint: input.entryPoint,
139
140
  sourceDir: input.sourceDir,
140
141
  });
141
142
 
142
143
  const createPayload: Record<string, unknown> = {
143
- name: input.name,
144
144
  source,
145
145
  entry_point: entryPoint,
146
146
  };
@@ -160,10 +160,8 @@ export const deployCommand = SimpleCLI.command({
160
160
  );
161
161
  }
162
162
 
163
- const { deployment_id, name, version, status } = body.json;
164
- console.log(
165
- `Deployment created: ${name} v${version} (${deployment_id})`,
166
- );
163
+ const { deployment_id, status } = body.json;
164
+ console.log(`Deployment created: ${deployment_id}`);
167
165
  console.log(`Status: ${status}`);
168
166
 
169
167
  if (status === "building") {
@@ -1,6 +1,7 @@
1
1
  import { execFileSync } from "node:child_process";
2
2
  import { createHash } from "node:crypto";
3
3
  import {
4
+ cpSync,
4
5
  existsSync,
5
6
  mkdirSync,
6
7
  mkdtempSync,
@@ -77,6 +78,9 @@ const SOURCE_FILE_EXTENSIONS = [
77
78
  "/index.cjs",
78
79
  ] as const;
79
80
  const CURRENT_LIBRETTO_VERSION = readCurrentLibrettoVersion();
81
+ const CURRENT_LIBRETTO_PACKAGE_DIR = fileURLToPath(
82
+ new URL("../../..", import.meta.url),
83
+ );
80
84
 
81
85
  function readCurrentLibrettoVersion(): string {
82
86
  const packageJsonPath = fileURLToPath(
@@ -157,7 +161,9 @@ function readWorkspacePatterns(rootDir: string): string[] {
157
161
  const patterns: string[] = [];
158
162
  let inPackagesBlock = false;
159
163
 
160
- for (const rawLine of readFileSync(pnpmWorkspacePath, "utf8").split(/\r?\n/)) {
164
+ for (const rawLine of readFileSync(pnpmWorkspacePath, "utf8").split(
165
+ /\r?\n/,
166
+ )) {
161
167
  const trimmed = rawLine.trim();
162
168
  if (!inPackagesBlock) {
163
169
  if (trimmed === "packages:") {
@@ -222,7 +228,9 @@ function expandWorkspacePattern(rootDir: string, pattern: string): string[] {
222
228
  .map((entry) => join(baseDir, entry.name));
223
229
  }
224
230
 
225
- function discoverWorkspacePackages(startDir: string): Map<string, WorkspacePackage> {
231
+ function discoverWorkspacePackages(
232
+ startDir: string,
233
+ ): Map<string, WorkspacePackage> {
226
234
  const workspaceRoot = findWorkspaceRoot(startDir);
227
235
  if (!workspaceRoot) {
228
236
  return new Map();
@@ -253,8 +261,8 @@ function findMatchingWorkspacePackage(
253
261
  info: WorkspacePackage;
254
262
  subpath: string;
255
263
  } | null {
256
- const names = [...workspacePackages.keys()].sort((left, right) =>
257
- right.length - left.length,
264
+ const names = [...workspacePackages.keys()].sort(
265
+ (left, right) => right.length - left.length,
258
266
  );
259
267
 
260
268
  for (const name of names) {
@@ -331,7 +339,11 @@ function resolveExportTarget(
331
339
  if (!(condition in record)) {
332
340
  continue;
333
341
  }
334
- const resolved = resolveExportTarget(record[condition], packageDir, replacement);
342
+ const resolved = resolveExportTarget(
343
+ record[condition],
344
+ packageDir,
345
+ replacement,
346
+ );
335
347
  if (resolved) {
336
348
  return resolved;
337
349
  }
@@ -356,7 +368,10 @@ function resolveExportsSubpath(
356
368
  return null;
357
369
  }
358
370
 
359
- if (subpath === ".") {
371
+ if (
372
+ subpath === "." &&
373
+ (typeof exportsField === "string" || Array.isArray(exportsField))
374
+ ) {
360
375
  const rootExport = resolveExportTarget(exportsField, packageDir);
361
376
  if (rootExport) {
362
377
  return rootExport;
@@ -368,7 +383,9 @@ function resolveExportsSubpath(
368
383
  }
369
384
 
370
385
  const record = exportsField as Record<string, unknown>;
371
- const hasExplicitSubpathKeys = Object.keys(record).some((key) => key.startsWith("."));
386
+ const hasExplicitSubpathKeys = Object.keys(record).some((key) =>
387
+ key.startsWith("."),
388
+ );
372
389
 
373
390
  if (!hasExplicitSubpathKeys) {
374
391
  return subpath === "." ? resolveExportTarget(record, packageDir) : null;
@@ -389,7 +406,10 @@ function resolveExportsSubpath(
389
406
  if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix)) {
390
407
  continue;
391
408
  }
392
- const replacement = subpath.slice(prefix.length, subpath.length - suffix.length);
409
+ const replacement = subpath.slice(
410
+ prefix.length,
411
+ subpath.length - suffix.length,
412
+ );
393
413
  const resolved = resolveExportTarget(value, packageDir, replacement);
394
414
  if (resolved) {
395
415
  return resolved;
@@ -403,7 +423,11 @@ function resolveWorkspaceSourcePath(
403
423
  info: WorkspacePackage,
404
424
  subpath: string,
405
425
  ): string | null {
406
- const viaExports = resolveExportsSubpath(info.manifest.exports, info.dir, subpath);
426
+ const viaExports = resolveExportsSubpath(
427
+ info.manifest.exports,
428
+ info.dir,
429
+ subpath,
430
+ );
407
431
  if (viaExports) {
408
432
  return viaExports;
409
433
  }
@@ -449,12 +473,18 @@ function workspaceSourcePlugin(
449
473
  return null;
450
474
  }
451
475
 
452
- const match = findMatchingWorkspacePackage(args.path, workspacePackages);
476
+ const match = findMatchingWorkspacePackage(
477
+ args.path,
478
+ workspacePackages,
479
+ );
453
480
  if (!match) {
454
481
  return null;
455
482
  }
456
483
 
457
- const resolvedPath = resolveWorkspaceSourcePath(match.info, match.subpath);
484
+ const resolvedPath = resolveWorkspaceSourcePath(
485
+ match.info,
486
+ match.subpath,
487
+ );
458
488
  if (!resolvedPath) {
459
489
  throw new Error(
460
490
  `Unable to resolve workspace import "${args.path}" from ${match.info.dir}.`,
@@ -531,21 +561,19 @@ function resolveDependencyVersion(
531
561
  function writeDeployManifest(args: {
532
562
  additionalExternals: readonly string[];
533
563
  deploymentName: string;
564
+ librettoDependency: string;
534
565
  outputDir: string;
535
566
  sourceDir: string;
536
567
  }): void {
537
568
  const dependencies = Object.fromEntries(
538
- [
539
- ...BUILT_IN_MANIFEST_DEPENDENCIES,
540
- ...args.additionalExternals,
541
- ].map((packageName) => [
542
- packageName,
543
- resolveDependencyVersion(
544
- args.sourceDir,
569
+ [...BUILT_IN_MANIFEST_DEPENDENCIES, ...args.additionalExternals].map(
570
+ (packageName) => [
545
571
  packageName,
546
- packageName === "libretto" ? CURRENT_LIBRETTO_VERSION : undefined,
547
- ),
548
- ]),
572
+ packageName === "libretto"
573
+ ? args.librettoDependency
574
+ : resolveDependencyVersion(args.sourceDir, packageName),
575
+ ],
576
+ ),
549
577
  );
550
578
 
551
579
  writeFileSync(
@@ -563,13 +591,54 @@ function writeDeployManifest(args: {
563
591
  );
564
592
  }
565
593
 
594
+ function shouldVendorCurrentLibretto(versionSpec: string): boolean {
595
+ return (
596
+ versionSpec.startsWith("file:") ||
597
+ versionSpec.startsWith("link:") ||
598
+ versionSpec.startsWith("workspace:") ||
599
+ versionSpec.startsWith("portal:") ||
600
+ versionSpec.includes("&path:")
601
+ );
602
+ }
603
+
604
+ function resolveLibrettoDependency(sourceDir: string): string {
605
+ const versionSpec = resolveDependencyVersion(
606
+ sourceDir,
607
+ "libretto",
608
+ CURRENT_LIBRETTO_VERSION,
609
+ );
610
+
611
+ if (shouldVendorCurrentLibretto(versionSpec)) {
612
+ return "file:./libretto";
613
+ }
614
+
615
+ return versionSpec;
616
+ }
617
+
618
+ function copyCurrentLibrettoPackage(outputDir: string): void {
619
+ const bundledLibrettoDir = join(outputDir, "libretto");
620
+ mkdirSync(bundledLibrettoDir, { recursive: true });
621
+ cpSync(
622
+ join(CURRENT_LIBRETTO_PACKAGE_DIR, "dist"),
623
+ join(bundledLibrettoDir, "dist"),
624
+ { recursive: true },
625
+ );
626
+ cpSync(
627
+ join(CURRENT_LIBRETTO_PACKAGE_DIR, "package.json"),
628
+ join(bundledLibrettoDir, "package.json"),
629
+ );
630
+ }
631
+
566
632
  function formatBuildError(error: unknown): string {
567
633
  if (!(error instanceof Error)) {
568
634
  return String(error);
569
635
  }
570
636
 
571
637
  const candidate = error as Error & {
572
- errors?: Array<{ location?: { file?: string; line?: number; column?: number }; text?: string }>;
638
+ errors?: Array<{
639
+ location?: { file?: string; line?: number; column?: number };
640
+ text?: string;
641
+ }>;
573
642
  };
574
643
  if (!Array.isArray(candidate.errors) || candidate.errors.length === 0) {
575
644
  return error.message;
@@ -630,13 +699,16 @@ function createBootstrapSource(args: {
630
699
  .update(args.bundleBuffer)
631
700
  .digest("hex")
632
701
  .slice(0, 16);
633
- const bundleBase64 = gzipSync(args.bundleBuffer, { level: 9 }).toString("base64");
702
+ const bundleBase64 = gzipSync(args.bundleBuffer, { level: 9 }).toString(
703
+ "base64",
704
+ );
634
705
  const outputPrefix = `${normalizePackageName(args.deploymentName)}-`;
635
706
  const hasDefaultExport = args.exportNames.includes("default");
636
707
  const exportLines = args.exportNames
637
708
  .filter((name) => name !== "default")
638
709
  .map(
639
- (name) => `export const ${name} = createWorkflowProxy(${JSON.stringify(name)});`,
710
+ (name) =>
711
+ `export const ${name} = createWorkflowProxy(${JSON.stringify(name)});`,
640
712
  )
641
713
  .join("\n");
642
714
  const defaultExportLine = hasDefaultExport
@@ -660,7 +732,9 @@ const BUNDLE_FILENAME = join(
660
732
  tmpdir(),
661
733
  ${JSON.stringify(outputPrefix)} + BUNDLE_HASH + ".cjs",
662
734
  );
663
- const require = createRequire(import.meta.url);
735
+ const nativeRequire = createRequire(
736
+ join(tmpdir(), ${JSON.stringify("libretto-deploy-bootstrap.cjs")}),
737
+ );
664
738
 
665
739
  function ensureBundleFile() {
666
740
  if (!existsSync(BUNDLE_FILENAME)) {
@@ -675,7 +749,7 @@ function ensureBundleFile() {
675
749
 
676
750
  function createWorkflowProxy(exportName) {
677
751
  return workflow(exportName, async (ctx, input) => {
678
- const impl = require(ensureBundleFile());
752
+ const impl = nativeRequire(ensureBundleFile());
679
753
  const target = impl[exportName];
680
754
  if (!target || typeof target.run !== "function") {
681
755
  throw new Error(
@@ -691,64 +765,57 @@ ${defaultExportLine}
691
765
  `;
692
766
  }
693
767
 
694
- export async function createHostedDeployPackage(
695
- args: CreateHostedDeployPackageArgs,
696
- ): Promise<HostedDeployPackage> {
697
- const absSourceDir = resolve(args.sourceDir);
698
- ensureSourcePackageManifest(absSourceDir);
699
-
700
- const absEntryPoint = resolveEntryPointPath(absSourceDir, args.entryPoint);
701
- const tempRoot = mkdtempSync(join(tmpdir(), "libretto-deploy-"));
702
- const outputDir = join(tempRoot, "deploy");
703
- mkdirSync(outputDir, { recursive: true });
704
-
705
- const additionalExternals = [...new Set(args.additionalExternals ?? [])];
706
- // These packages stay out of the implementation bundle. The generated
707
- // package.json carries them into deploy-time installation, and the deployed
708
- // code resolves them from node_modules.
709
- const externalPackages = new Set<string>([
710
- ...DEFAULT_RUNTIME_EXTERNALS,
711
- ...additionalExternals,
712
- ]);
713
- const workspacePackages = discoverWorkspacePackages(absSourceDir);
714
-
768
+ async function writeBundledDeployEntrypoint(args: {
769
+ absEntryPoint: string;
770
+ absSourceDir: string;
771
+ deploymentName: string;
772
+ externalPackages: ReadonlySet<string>;
773
+ outputDir: string;
774
+ workspacePackages: Map<string, WorkspacePackage>;
775
+ }): Promise<void> {
715
776
  try {
716
777
  // The implementation bundle is CommonJS so the bootstrap can load it lazily
717
778
  // with createRequire() after workflow discovery, while external packages
718
779
  // continue to load through normal Node module resolution.
719
780
  const implementationBuild = await build({
720
- absWorkingDir: absSourceDir,
781
+ absWorkingDir: args.absSourceDir,
721
782
  bundle: true,
722
- entryPoints: [absEntryPoint],
723
- external: [...externalPackages],
783
+ entryPoints: [args.absEntryPoint],
784
+ external: [...args.externalPackages],
724
785
  format: "cjs",
725
786
  outfile: "prebundled.cjs",
726
787
  platform: "node",
727
- plugins: [workspaceSourcePlugin(workspacePackages, externalPackages)],
788
+ plugins: [
789
+ workspaceSourcePlugin(args.workspacePackages, args.externalPackages),
790
+ ],
728
791
  splitting: false,
729
792
  target: "node20",
730
793
  write: false,
731
794
  });
732
795
 
733
- const bundledImplementation = implementationBuild.outputFiles?.find((file) =>
734
- file.path.endsWith("prebundled.cjs"),
796
+ const bundledImplementation = implementationBuild.outputFiles?.find(
797
+ (file) => file.path.endsWith("prebundled.cjs"),
735
798
  );
736
799
  if (!bundledImplementation) {
737
- throw new Error("Bundler did not produce a deployment implementation file.");
800
+ throw new Error(
801
+ "Bundler did not produce a deployment implementation file.",
802
+ );
738
803
  }
739
804
 
740
805
  // A separate ESM bundle is used only to read the entry module's exported
741
806
  // workflow names. Scanning the CommonJS bundle would also see exports from
742
807
  // bundled dependencies, which is not the deploy surface.
743
808
  const exportBuild = await build({
744
- absWorkingDir: absSourceDir,
809
+ absWorkingDir: args.absSourceDir,
745
810
  bundle: true,
746
- entryPoints: [absEntryPoint],
747
- external: [...externalPackages],
811
+ entryPoints: [args.absEntryPoint],
812
+ external: [...args.externalPackages],
748
813
  format: "esm",
749
814
  outfile: "entry-exports.js",
750
815
  platform: "node",
751
- plugins: [workspaceSourcePlugin(workspacePackages, externalPackages)],
816
+ plugins: [
817
+ workspaceSourcePlugin(args.workspacePackages, args.externalPackages),
818
+ ],
752
819
  splitting: false,
753
820
  target: "node20",
754
821
  write: false,
@@ -764,12 +831,12 @@ export async function createHostedDeployPackage(
764
831
  const exportNames = extractExportNamesFromEsmBundle(bundledExports.text);
765
832
  if (exportNames.length === 0) {
766
833
  throw new Error(
767
- `No named exports were found in ${absEntryPoint}. Hosted deploy expects the entry point to export one or more workflows.`,
834
+ `No named exports were found in ${args.absEntryPoint}. Hosted deploy expects the entry point to export one or more workflows.`,
768
835
  );
769
836
  }
770
837
 
771
838
  writeFileSync(
772
- join(outputDir, "index.js"),
839
+ join(args.outputDir, "index.js"),
773
840
  createBootstrapSource({
774
841
  bundleBuffer: Buffer.from(bundledImplementation.contents),
775
842
  deploymentName: args.deploymentName,
@@ -777,29 +844,77 @@ export async function createHostedDeployPackage(
777
844
  }),
778
845
  );
779
846
  } catch (error) {
780
- rmSync(tempRoot, { force: true, recursive: true });
781
847
  throw new Error(
782
- `Failed to bundle deploy entry point ${absEntryPoint}.\n${formatBuildError(error)}`,
848
+ `Failed to bundle deploy entry point ${args.absEntryPoint}.\n${formatBuildError(error)}`,
783
849
  );
784
850
  }
851
+ }
785
852
 
786
- // The generated manifest lists only packages that stay outside the
787
- // implementation bundle. Hosted deploy installs them into the deployed
788
- // package, and the deployed code loads them from node_modules.
789
- writeDeployManifest({
790
- additionalExternals,
791
- deploymentName: args.deploymentName,
792
- outputDir,
793
- sourceDir: absSourceDir,
794
- });
853
+ export async function createHostedDeployPackage(
854
+ args: CreateHostedDeployPackageArgs,
855
+ ): Promise<HostedDeployPackage> {
856
+ const absSourceDir = resolve(args.sourceDir);
857
+ ensureSourcePackageManifest(absSourceDir);
795
858
 
796
- return {
797
- cleanup: () => {
859
+ const absEntryPoint = resolveEntryPointPath(absSourceDir, args.entryPoint);
860
+ const tempRoot = mkdtempSync(join(tmpdir(), "libretto-deploy-"));
861
+ const outputDir = join(tempRoot, "deploy");
862
+ mkdirSync(outputDir, { recursive: true });
863
+ const librettoDependency = resolveLibrettoDependency(absSourceDir);
864
+
865
+ const additionalExternals = [...new Set(args.additionalExternals ?? [])];
866
+ // These packages stay out of the implementation bundle. The generated
867
+ // package.json carries them into deploy-time installation, and the deployed
868
+ // code resolves them from node_modules.
869
+ const externalPackages = new Set<string>([
870
+ ...DEFAULT_RUNTIME_EXTERNALS,
871
+ ...additionalExternals,
872
+ ]);
873
+ const workspacePackages = discoverWorkspacePackages(absSourceDir);
874
+ let callerOwnsTempRoot = false;
875
+
876
+ try {
877
+ await writeBundledDeployEntrypoint({
878
+ absEntryPoint,
879
+ absSourceDir,
880
+ deploymentName: args.deploymentName,
881
+ externalPackages,
882
+ outputDir,
883
+ workspacePackages,
884
+ });
885
+
886
+ if (librettoDependency === "file:./libretto") {
887
+ copyCurrentLibrettoPackage(outputDir);
888
+ }
889
+
890
+ // The generated manifest lists only packages that stay outside the
891
+ // implementation bundle. Hosted deploy installs them into the deployed
892
+ // package, and the deployed code loads them from node_modules.
893
+ writeDeployManifest({
894
+ additionalExternals,
895
+ deploymentName: args.deploymentName,
896
+ librettoDependency,
897
+ outputDir,
898
+ sourceDir: absSourceDir,
899
+ });
900
+
901
+ // Success transfers ownership of the temp directory to the caller, who is
902
+ // responsible for invoking cleanup() after the tarball/upload step.
903
+ callerOwnsTempRoot = true;
904
+ return {
905
+ cleanup: () => {
906
+ rmSync(tempRoot, { force: true, recursive: true });
907
+ },
908
+ entryPoint: "index.js",
909
+ outputDir,
910
+ };
911
+ } finally {
912
+ // On any failure before we return, this function still owns the temp dir
913
+ // and must remove it to avoid leaking deploy workspaces in /tmp.
914
+ if (!callerOwnsTempRoot) {
798
915
  rmSync(tempRoot, { force: true, recursive: true });
799
- },
800
- entryPoint: "index.js",
801
- outputDir,
802
- };
916
+ }
917
+ }
803
918
  }
804
919
 
805
920
  export async function buildHostedDeployTarball(
@@ -257,7 +257,6 @@ async function runIntegrationInternal(
257
257
  session: args.session,
258
258
  logger: integrationLogger,
259
259
  page: browserSession.page,
260
- services: {},
261
260
  credentials: args.credentials,
262
261
  };
263
262
 
package/src/index.ts CHANGED
@@ -112,7 +112,6 @@ export {
112
112
  type LibrettoWorkflowContext,
113
113
  type LibrettoWorkflowHandler,
114
114
  } from "./shared/workflow/workflow.js";
115
-
116
115
  const isDirectExecution = (): boolean => {
117
116
  const entryArg = process.argv[1];
118
117
  if (!entryArg) {
@@ -3,34 +3,32 @@ import type { MinimalLogger } from "../logger/logger.js";
3
3
 
4
4
  export const LIBRETTO_WORKFLOW_BRAND = Symbol.for("libretto.workflow");
5
5
 
6
- export type LibrettoWorkflowContext<S = {}> = {
6
+ export type LibrettoWorkflowContext = {
7
7
  session: string;
8
8
  page: Page;
9
9
  logger: MinimalLogger;
10
- services: S;
11
10
  credentials?: Record<string, unknown>;
12
11
  };
13
12
 
14
- export type LibrettoWorkflowHandler<
15
- Input = unknown,
16
- Output = unknown,
17
- S = {},
18
- > = (ctx: LibrettoWorkflowContext<S>, input: Input) => Promise<Output>;
13
+ export type LibrettoWorkflowHandler<Input = unknown, Output = unknown> = (
14
+ ctx: LibrettoWorkflowContext,
15
+ input: Input,
16
+ ) => Promise<Output>;
19
17
 
20
- export class LibrettoWorkflow<Input = unknown, Output = unknown, S = {}> {
18
+ export class LibrettoWorkflow<Input = unknown, Output = unknown> {
21
19
  public readonly [LIBRETTO_WORKFLOW_BRAND] = true;
22
20
  public readonly name: string;
23
- private readonly handler: LibrettoWorkflowHandler<Input, Output, S>;
21
+ private readonly handler: LibrettoWorkflowHandler<Input, Output>;
24
22
 
25
23
  constructor(
26
24
  name: string,
27
- handler: LibrettoWorkflowHandler<Input, Output, S>,
25
+ handler: LibrettoWorkflowHandler<Input, Output>,
28
26
  ) {
29
27
  this.name = name;
30
28
  this.handler = handler;
31
29
  }
32
30
 
33
- async run(ctx: LibrettoWorkflowContext<S>, input: Input): Promise<Output> {
31
+ async run(ctx: LibrettoWorkflowContext, input: Input): Promise<Output> {
34
32
  return this.handler(ctx, input);
35
33
  }
36
34
  }
@@ -114,9 +112,9 @@ export function getWorkflowFromModuleExports(
114
112
  return null;
115
113
  }
116
114
 
117
- export function workflow<Input = unknown, Output = unknown, S = {}>(
115
+ export function workflow<Input = unknown, Output = unknown>(
118
116
  name: string,
119
- handler: LibrettoWorkflowHandler<Input, Output, S>,
120
- ): LibrettoWorkflow<Input, Output, S> {
117
+ handler: LibrettoWorkflowHandler<Input, Output>,
118
+ ): LibrettoWorkflow<Input, Output> {
121
119
  return new LibrettoWorkflow(name, handler);
122
120
  }