libretto 0.5.3-experimental.1 → 0.5.3-experimental.2

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,12 +1,5 @@
1
- import { execSync } from "node:child_process";
2
- import {
3
- cpSync,
4
- mkdirSync,
5
- readFileSync
6
- } from "node:fs";
7
- import { tmpdir } from "node:os";
8
- import { join, resolve } from "node:path";
9
1
  import { z } from "zod";
2
+ import { buildHostedDeployTarball } from "../core/deploy-artifact.js";
10
3
  import { SimpleCLI } from "../framework/simple-cli.js";
11
4
  function getConfig() {
12
5
  const apiUrl = process.env.LIBRETTO_API_URL;
@@ -33,25 +26,6 @@ async function postJson(apiUrl, apiKey, path, input = {}) {
33
26
  body: JSON.stringify({ json: input })
34
27
  });
35
28
  }
36
- function buildSourceTarball(sourceDir) {
37
- const absSourceDir = resolve(sourceDir);
38
- const pkgJsonPath = join(absSourceDir, "package.json");
39
- try {
40
- readFileSync(pkgJsonPath, "utf8");
41
- } catch {
42
- throw new Error(
43
- `No package.json found in ${absSourceDir}. Deploy source must contain a package.json.`
44
- );
45
- }
46
- const dir = join(tmpdir(), `libretto-deploy-${Date.now()}`);
47
- mkdirSync(dir, { recursive: true });
48
- cpSync(absSourceDir, dir, { recursive: true });
49
- const tarPath = join(dir, "source.tar.gz");
50
- execSync(
51
- `tar czf "${tarPath}" --exclude=source.tar.gz --exclude=node_modules --exclude=.git -C "${dir}" .`
52
- );
53
- return readFileSync(tarPath).toString("base64");
54
- }
55
29
  async function pollDeployment(apiUrl, apiKey, deploymentId, pollIntervalMs, maxWaitMs) {
56
30
  const start = Date.now();
57
31
  let status = "building";
@@ -93,7 +67,15 @@ const deployInput = SimpleCLI.input({
93
67
  entryPoint: SimpleCLI.option(z.string().optional(), {
94
68
  name: "entry-point",
95
69
  help: "Entry point file (default: index.ts)"
96
- })
70
+ }),
71
+ external: SimpleCLI.option(
72
+ z.string().optional().transform(
73
+ (value) => value?.split(",").map((entry) => entry.trim()).filter((entry) => entry.length > 0) ?? []
74
+ ),
75
+ {
76
+ help: "Comma-separated packages to externalize and install at runtime"
77
+ }
78
+ )
97
79
  }
98
80
  });
99
81
  const deployCommand = SimpleCLI.command({
@@ -101,14 +83,19 @@ const deployCommand = SimpleCLI.command({
101
83
  experimental: true
102
84
  }).input(deployInput).handle(async ({ input }) => {
103
85
  const { apiUrl, apiKey } = getConfig();
104
- console.log(`Packaging source from ${resolve(input.sourceDir)}...`);
105
- const source = buildSourceTarball(input.sourceDir);
86
+ console.log("Bundling hosted deployment artifact...");
87
+ const { entryPoint, source } = await buildHostedDeployTarball({
88
+ additionalExternals: input.external,
89
+ deploymentName: input.name,
90
+ entryPoint: input.entryPoint,
91
+ sourceDir: input.sourceDir
92
+ });
106
93
  const createPayload = {
107
94
  name: input.name,
108
- source
95
+ source,
96
+ entry_point: entryPoint
109
97
  };
110
98
  if (input.description) createPayload.description = input.description;
111
- if (input.entryPoint) createPayload.entry_point = input.entryPoint;
112
99
  console.log("Uploading deployment...");
113
100
  const res = await postJson(
114
101
  apiUrl,
@@ -0,0 +1,608 @@
1
+ import { execFileSync } from "node:child_process";
2
+ import { createHash } from "node:crypto";
3
+ import {
4
+ existsSync,
5
+ mkdirSync,
6
+ mkdtempSync,
7
+ readFileSync,
8
+ readdirSync,
9
+ rmSync,
10
+ writeFileSync
11
+ } from "node:fs";
12
+ import { tmpdir } from "node:os";
13
+ import { dirname, isAbsolute, join, resolve } from "node:path";
14
+ import { fileURLToPath } from "node:url";
15
+ import { gzipSync } from "node:zlib";
16
+ import { build } from "esbuild";
17
+ const DEFAULT_RUNTIME_EXTERNALS = [
18
+ "libretto",
19
+ "playwright",
20
+ "playwright-core",
21
+ "chromium-bidi"
22
+ ];
23
+ const BUILT_IN_MANIFEST_DEPENDENCIES = ["libretto"];
24
+ const SOURCE_FILE_EXTENSIONS = [
25
+ "",
26
+ ".ts",
27
+ ".tsx",
28
+ ".mts",
29
+ ".cts",
30
+ ".js",
31
+ ".mjs",
32
+ ".cjs",
33
+ "/index.ts",
34
+ "/index.tsx",
35
+ "/index.mts",
36
+ "/index.cts",
37
+ "/index.js",
38
+ "/index.mjs",
39
+ "/index.cjs"
40
+ ];
41
+ const CURRENT_LIBRETTO_VERSION = readCurrentLibrettoVersion();
42
+ function readCurrentLibrettoVersion() {
43
+ const packageJsonPath = fileURLToPath(
44
+ new URL("../../../package.json", import.meta.url)
45
+ );
46
+ const manifest = readJsonFile(packageJsonPath);
47
+ if (!manifest.version) {
48
+ throw new Error(
49
+ `Unable to determine current libretto version from ${packageJsonPath}.`
50
+ );
51
+ }
52
+ return manifest.version;
53
+ }
54
+ function readJsonFile(path) {
55
+ return JSON.parse(readFileSync(path, "utf8"));
56
+ }
57
+ function readPackageManifest(path) {
58
+ return readJsonFile(path);
59
+ }
60
+ function ensureSourcePackageManifest(sourceDir) {
61
+ const pkgJsonPath = join(sourceDir, "package.json");
62
+ if (!existsSync(pkgJsonPath)) {
63
+ throw new Error(
64
+ `No package.json found in ${sourceDir}. Deploy source must contain a package.json.`
65
+ );
66
+ }
67
+ return readPackageManifest(pkgJsonPath);
68
+ }
69
+ function resolveEntryPointPath(sourceDir, entryPoint) {
70
+ const candidate = entryPoint ?? "index.ts";
71
+ const absEntryPoint = isAbsolute(candidate) ? resolve(candidate) : resolve(sourceDir, candidate);
72
+ if (!existsSync(absEntryPoint)) {
73
+ throw new Error(
74
+ `Deploy entry point not found: ${absEntryPoint}. Pass --entry-point to choose a workflow file.`
75
+ );
76
+ }
77
+ return absEntryPoint;
78
+ }
79
+ function isRootPath(path) {
80
+ return dirname(path) === path;
81
+ }
82
+ function findWorkspaceRoot(startDir) {
83
+ let currentDir = resolve(startDir);
84
+ while (true) {
85
+ if (existsSync(join(currentDir, "pnpm-workspace.yaml"))) {
86
+ return currentDir;
87
+ }
88
+ const pkgJsonPath = join(currentDir, "package.json");
89
+ if (existsSync(pkgJsonPath)) {
90
+ const manifest = readPackageManifest(pkgJsonPath);
91
+ if (manifest.workspaces) {
92
+ return currentDir;
93
+ }
94
+ }
95
+ if (isRootPath(currentDir)) {
96
+ return null;
97
+ }
98
+ currentDir = dirname(currentDir);
99
+ }
100
+ }
101
+ function readWorkspacePatterns(rootDir) {
102
+ const pnpmWorkspacePath = join(rootDir, "pnpm-workspace.yaml");
103
+ if (existsSync(pnpmWorkspacePath)) {
104
+ const patterns = [];
105
+ let inPackagesBlock = false;
106
+ for (const rawLine of readFileSync(pnpmWorkspacePath, "utf8").split(/\r?\n/)) {
107
+ const trimmed = rawLine.trim();
108
+ if (!inPackagesBlock) {
109
+ if (trimmed === "packages:") {
110
+ inPackagesBlock = true;
111
+ }
112
+ continue;
113
+ }
114
+ if (trimmed.length > 0 && !trimmed.startsWith("-") && !rawLine.startsWith(" ") && !rawLine.startsWith(" ")) {
115
+ break;
116
+ }
117
+ const match = trimmed.match(/^-\s*["']?(.+?)["']?$/);
118
+ if (match?.[1]) {
119
+ patterns.push(match[1]);
120
+ }
121
+ }
122
+ if (patterns.length > 0) {
123
+ return patterns;
124
+ }
125
+ }
126
+ const pkgJsonPath = join(rootDir, "package.json");
127
+ if (!existsSync(pkgJsonPath)) {
128
+ return [];
129
+ }
130
+ const manifest = readPackageManifest(pkgJsonPath);
131
+ if (Array.isArray(manifest.workspaces)) {
132
+ return manifest.workspaces;
133
+ }
134
+ if (manifest.workspaces && Array.isArray(manifest.workspaces.packages)) {
135
+ return manifest.workspaces.packages;
136
+ }
137
+ return [];
138
+ }
139
+ function expandWorkspacePattern(rootDir, pattern) {
140
+ if (!pattern.includes("*")) {
141
+ const absDir = resolve(rootDir, pattern);
142
+ return existsSync(absDir) ? [absDir] : [];
143
+ }
144
+ if (!pattern.endsWith("/*")) {
145
+ return [];
146
+ }
147
+ const baseDir = resolve(rootDir, pattern.slice(0, -2));
148
+ if (!existsSync(baseDir)) {
149
+ return [];
150
+ }
151
+ return readdirSync(baseDir, { withFileTypes: true }).filter((entry) => entry.isDirectory()).map((entry) => join(baseDir, entry.name));
152
+ }
153
+ function discoverWorkspacePackages(startDir) {
154
+ const workspaceRoot = findWorkspaceRoot(startDir);
155
+ if (!workspaceRoot) {
156
+ return /* @__PURE__ */ new Map();
157
+ }
158
+ const packages = /* @__PURE__ */ new Map();
159
+ for (const pattern of readWorkspacePatterns(workspaceRoot)) {
160
+ for (const dir of expandWorkspacePattern(workspaceRoot, pattern)) {
161
+ const pkgJsonPath = join(dir, "package.json");
162
+ if (!existsSync(pkgJsonPath)) {
163
+ continue;
164
+ }
165
+ const manifest = readPackageManifest(pkgJsonPath);
166
+ if (!manifest.name) {
167
+ continue;
168
+ }
169
+ packages.set(manifest.name, { dir, manifest, name: manifest.name });
170
+ }
171
+ }
172
+ return packages;
173
+ }
174
+ function findMatchingWorkspacePackage(importPath, workspacePackages) {
175
+ const names = [...workspacePackages.keys()].sort(
176
+ (left, right) => right.length - left.length
177
+ );
178
+ for (const name of names) {
179
+ if (importPath === name) {
180
+ return {
181
+ info: workspacePackages.get(name),
182
+ subpath: "."
183
+ };
184
+ }
185
+ if (importPath.startsWith(`${name}/`)) {
186
+ return {
187
+ info: workspacePackages.get(name),
188
+ subpath: `.${importPath.slice(name.length)}`
189
+ };
190
+ }
191
+ }
192
+ return null;
193
+ }
194
+ function resolvePathCandidates(packageDir, target, replacement) {
195
+ const value = replacement ? target.replace(/\*/g, replacement) : target;
196
+ const absCandidate = resolve(packageDir, value);
197
+ if (existsSync(absCandidate)) {
198
+ return absCandidate;
199
+ }
200
+ for (const suffix of SOURCE_FILE_EXTENSIONS) {
201
+ const fileCandidate = resolve(packageDir, `${value}${suffix}`);
202
+ if (existsSync(fileCandidate)) {
203
+ return fileCandidate;
204
+ }
205
+ }
206
+ return null;
207
+ }
208
+ function resolveExportTarget(exportValue, packageDir, replacement) {
209
+ if (typeof exportValue === "string") {
210
+ return resolvePathCandidates(packageDir, exportValue, replacement);
211
+ }
212
+ if (Array.isArray(exportValue)) {
213
+ for (const entry of exportValue) {
214
+ const resolved = resolveExportTarget(entry, packageDir, replacement);
215
+ if (resolved) {
216
+ return resolved;
217
+ }
218
+ }
219
+ return null;
220
+ }
221
+ if (!exportValue || typeof exportValue !== "object") {
222
+ return null;
223
+ }
224
+ const record = exportValue;
225
+ for (const condition of [
226
+ "types",
227
+ "source",
228
+ "import",
229
+ "default",
230
+ "module",
231
+ "require"
232
+ ]) {
233
+ if (!(condition in record)) {
234
+ continue;
235
+ }
236
+ const resolved = resolveExportTarget(record[condition], packageDir, replacement);
237
+ if (resolved) {
238
+ return resolved;
239
+ }
240
+ }
241
+ for (const value of Object.values(record)) {
242
+ const resolved = resolveExportTarget(value, packageDir, replacement);
243
+ if (resolved) {
244
+ return resolved;
245
+ }
246
+ }
247
+ return null;
248
+ }
249
+ function resolveExportsSubpath(exportsField, packageDir, subpath) {
250
+ if (!exportsField) {
251
+ return null;
252
+ }
253
+ if (subpath === ".") {
254
+ const rootExport = resolveExportTarget(exportsField, packageDir);
255
+ if (rootExport) {
256
+ return rootExport;
257
+ }
258
+ }
259
+ if (typeof exportsField !== "object" || Array.isArray(exportsField)) {
260
+ return null;
261
+ }
262
+ const record = exportsField;
263
+ const hasExplicitSubpathKeys = Object.keys(record).some((key) => key.startsWith("."));
264
+ if (!hasExplicitSubpathKeys) {
265
+ return subpath === "." ? resolveExportTarget(record, packageDir) : null;
266
+ }
267
+ const exactMatch = record[subpath];
268
+ if (exactMatch !== void 0) {
269
+ return resolveExportTarget(exactMatch, packageDir);
270
+ }
271
+ for (const [key, value] of Object.entries(record)) {
272
+ const starIndex = key.indexOf("*");
273
+ if (starIndex < 0) {
274
+ continue;
275
+ }
276
+ const prefix = key.slice(0, starIndex);
277
+ const suffix = key.slice(starIndex + 1);
278
+ if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix)) {
279
+ continue;
280
+ }
281
+ const replacement = subpath.slice(prefix.length, subpath.length - suffix.length);
282
+ const resolved = resolveExportTarget(value, packageDir, replacement);
283
+ if (resolved) {
284
+ return resolved;
285
+ }
286
+ }
287
+ return null;
288
+ }
289
+ function resolveWorkspaceSourcePath(info, subpath) {
290
+ const viaExports = resolveExportsSubpath(info.manifest.exports, info.dir, subpath);
291
+ if (viaExports) {
292
+ return viaExports;
293
+ }
294
+ if (subpath === ".") {
295
+ for (const field of [
296
+ info.manifest.types,
297
+ info.manifest.source,
298
+ info.manifest.module,
299
+ info.manifest.main
300
+ ]) {
301
+ if (!field) {
302
+ continue;
303
+ }
304
+ const resolved = resolvePathCandidates(info.dir, field);
305
+ if (resolved) {
306
+ return resolved;
307
+ }
308
+ }
309
+ }
310
+ const directSubpath = subpath === "." ? "index" : subpath.slice(2);
311
+ return resolvePathCandidates(info.dir, directSubpath);
312
+ }
313
+ function workspaceSourcePlugin(workspacePackages, externalPackages) {
314
+ return {
315
+ name: "workspace-source-resolver",
316
+ setup(buildApi) {
317
+ buildApi.onResolve({ filter: /^[^./].*/ }, (args) => {
318
+ if (externalPackages.has(args.path)) {
319
+ return null;
320
+ }
321
+ const match = findMatchingWorkspacePackage(args.path, workspacePackages);
322
+ if (!match) {
323
+ return null;
324
+ }
325
+ const resolvedPath = resolveWorkspaceSourcePath(match.info, match.subpath);
326
+ if (!resolvedPath) {
327
+ throw new Error(
328
+ `Unable to resolve workspace import "${args.path}" from ${match.info.dir}.`
329
+ );
330
+ }
331
+ return { path: resolvedPath };
332
+ });
333
+ }
334
+ };
335
+ }
336
+ function normalizePackageName(name) {
337
+ const normalized = name.toLowerCase().replace(/[^a-z0-9._-]+/g, "-").replace(/^-+|-+$/g, "");
338
+ return normalized || "libretto-deployment";
339
+ }
340
+ function readDependencyVersionFromManifest(manifest, packageName) {
341
+ for (const dependencyGroup of [
342
+ manifest.dependencies,
343
+ manifest.devDependencies,
344
+ manifest.peerDependencies,
345
+ manifest.optionalDependencies
346
+ ]) {
347
+ const version = dependencyGroup?.[packageName];
348
+ if (version) {
349
+ return version;
350
+ }
351
+ }
352
+ return null;
353
+ }
354
+ function resolveDependencyVersion(sourceDir, packageName, fallbackVersion) {
355
+ let currentDir = resolve(sourceDir);
356
+ while (true) {
357
+ const pkgJsonPath = join(currentDir, "package.json");
358
+ if (existsSync(pkgJsonPath)) {
359
+ const version = readDependencyVersionFromManifest(
360
+ readPackageManifest(pkgJsonPath),
361
+ packageName
362
+ );
363
+ if (version) {
364
+ return version;
365
+ }
366
+ }
367
+ if (isRootPath(currentDir)) {
368
+ break;
369
+ }
370
+ currentDir = dirname(currentDir);
371
+ }
372
+ if (fallbackVersion) {
373
+ return fallbackVersion;
374
+ }
375
+ throw new Error(
376
+ `Unable to determine a version for external package "${packageName}". Add it to your package.json or remove it from --external.`
377
+ );
378
+ }
379
+ function writeDeployManifest(args) {
380
+ const dependencies = Object.fromEntries(
381
+ [
382
+ ...BUILT_IN_MANIFEST_DEPENDENCIES,
383
+ ...args.additionalExternals
384
+ ].map((packageName) => [
385
+ packageName,
386
+ resolveDependencyVersion(
387
+ args.sourceDir,
388
+ packageName,
389
+ packageName === "libretto" ? CURRENT_LIBRETTO_VERSION : void 0
390
+ )
391
+ ])
392
+ );
393
+ writeFileSync(
394
+ join(args.outputDir, "package.json"),
395
+ JSON.stringify(
396
+ {
397
+ name: normalizePackageName(args.deploymentName),
398
+ private: true,
399
+ type: "module",
400
+ dependencies
401
+ },
402
+ null,
403
+ 2
404
+ ) + "\n"
405
+ );
406
+ }
407
+ function formatBuildError(error) {
408
+ if (!(error instanceof Error)) {
409
+ return String(error);
410
+ }
411
+ const candidate = error;
412
+ if (!Array.isArray(candidate.errors) || candidate.errors.length === 0) {
413
+ return error.message;
414
+ }
415
+ return candidate.errors.map((entry) => {
416
+ const location = entry.location?.file ? `${entry.location.file}:${entry.location.line ?? 0}:${entry.location.column ?? 0}` : "unknown";
417
+ return `${location} ${entry.text ?? error.message}`;
418
+ }).join("\n");
419
+ }
420
+ function extractExportNamesFromEsmBundle(bundleSource) {
421
+ const exportNames = /* @__PURE__ */ new Set();
422
+ for (const entry of bundleSource.matchAll(
423
+ /export\s+(?:const|let|var|function|class)\s+([A-Za-z_$][\w$]*)/g
424
+ )) {
425
+ exportNames.add(entry[1]);
426
+ }
427
+ for (const entry of bundleSource.matchAll(/export\s+\{([^}]+)\};/g)) {
428
+ const specifiers = entry[1]?.split(",") ?? [];
429
+ for (const specifier of specifiers) {
430
+ const trimmed = specifier.trim();
431
+ if (!trimmed) {
432
+ continue;
433
+ }
434
+ const aliasMatch = trimmed.match(
435
+ /^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*|default)$/
436
+ );
437
+ if (aliasMatch?.[2]) {
438
+ exportNames.add(aliasMatch[2]);
439
+ continue;
440
+ }
441
+ if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
442
+ exportNames.add(trimmed);
443
+ }
444
+ }
445
+ }
446
+ if (/\bexport\s+default\b/m.test(bundleSource)) {
447
+ exportNames.add("default");
448
+ }
449
+ return [...exportNames];
450
+ }
451
+ function createBootstrapSource(args) {
452
+ const bundleHash = createHash("sha256").update(args.bundleBuffer).digest("hex").slice(0, 16);
453
+ const bundleBase64 = gzipSync(args.bundleBuffer, { level: 9 }).toString("base64");
454
+ const outputPrefix = `${normalizePackageName(args.deploymentName)}-`;
455
+ const hasDefaultExport = args.exportNames.includes("default");
456
+ const exportLines = args.exportNames.filter((name) => name !== "default").map(
457
+ (name) => `export const ${name} = createWorkflowProxy(${JSON.stringify(name)});`
458
+ ).join("\n");
459
+ const defaultExportLine = hasDefaultExport ? 'export default createWorkflowProxy("default");' : "";
460
+ return `import { createRequire } from "node:module";
461
+ import { existsSync, writeFileSync } from "node:fs";
462
+ import { tmpdir } from "node:os";
463
+ import { join } from "node:path";
464
+ import { gunzipSync } from "node:zlib";
465
+ import { workflow } from "libretto";
466
+
467
+ const BUNDLE_HASH = ${JSON.stringify(bundleHash)};
468
+ const BUNDLE_GZIP_BASE64 = ${JSON.stringify(bundleBase64)};
469
+ const BUNDLE_FILENAME = join(
470
+ tmpdir(),
471
+ ${JSON.stringify(outputPrefix)} + BUNDLE_HASH + ".cjs",
472
+ );
473
+ const require = createRequire(import.meta.url);
474
+
475
+ function ensureBundleFile() {
476
+ if (!existsSync(BUNDLE_FILENAME)) {
477
+ writeFileSync(
478
+ BUNDLE_FILENAME,
479
+ gunzipSync(Buffer.from(BUNDLE_GZIP_BASE64, "base64")),
480
+ );
481
+ }
482
+
483
+ return BUNDLE_FILENAME;
484
+ }
485
+
486
+ function createWorkflowProxy(exportName) {
487
+ return workflow(exportName, async (ctx, input) => {
488
+ const impl = require(ensureBundleFile());
489
+ const target = impl[exportName];
490
+ if (!target || typeof target.run !== "function") {
491
+ throw new Error(
492
+ \`Expected workflow export "\${exportName}" to be available in the bundled deployment implementation.\`,
493
+ );
494
+ }
495
+ return await target.run(ctx, input);
496
+ });
497
+ }
498
+
499
+ ${exportLines}
500
+ ${defaultExportLine}
501
+ `;
502
+ }
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);
516
+ try {
517
+ const implementationBuild = await build({
518
+ absWorkingDir: absSourceDir,
519
+ bundle: true,
520
+ entryPoints: [absEntryPoint],
521
+ external: [...externalPackages],
522
+ format: "cjs",
523
+ outfile: "prebundled.cjs",
524
+ platform: "node",
525
+ plugins: [workspaceSourcePlugin(workspacePackages, externalPackages)],
526
+ splitting: false,
527
+ target: "node20",
528
+ write: false
529
+ });
530
+ const bundledImplementation = implementationBuild.outputFiles?.find(
531
+ (file) => file.path.endsWith("prebundled.cjs")
532
+ );
533
+ if (!bundledImplementation) {
534
+ throw new Error("Bundler did not produce a deployment implementation file.");
535
+ }
536
+ const exportBuild = await build({
537
+ absWorkingDir: absSourceDir,
538
+ bundle: true,
539
+ entryPoints: [absEntryPoint],
540
+ external: [...externalPackages],
541
+ format: "esm",
542
+ outfile: "entry-exports.js",
543
+ platform: "node",
544
+ plugins: [workspaceSourcePlugin(workspacePackages, externalPackages)],
545
+ splitting: false,
546
+ target: "node20",
547
+ write: false
548
+ });
549
+ const bundledExports = exportBuild.outputFiles?.find(
550
+ (file) => file.path.endsWith("entry-exports.js")
551
+ );
552
+ if (!bundledExports) {
553
+ throw new Error("Bundler did not produce an export analysis file.");
554
+ }
555
+ const exportNames = extractExportNamesFromEsmBundle(bundledExports.text);
556
+ if (exportNames.length === 0) {
557
+ throw new Error(
558
+ `No named exports were found in ${absEntryPoint}. Hosted deploy expects the entry point to export one or more workflows.`
559
+ );
560
+ }
561
+ writeFileSync(
562
+ join(outputDir, "index.js"),
563
+ createBootstrapSource({
564
+ bundleBuffer: Buffer.from(bundledImplementation.contents),
565
+ deploymentName: args.deploymentName,
566
+ exportNames
567
+ })
568
+ );
569
+ } catch (error) {
570
+ rmSync(tempRoot, { force: true, recursive: true });
571
+ throw new Error(
572
+ `Failed to bundle deploy entry point ${absEntryPoint}.
573
+ ${formatBuildError(error)}`
574
+ );
575
+ }
576
+ writeDeployManifest({
577
+ additionalExternals,
578
+ deploymentName: args.deploymentName,
579
+ outputDir,
580
+ sourceDir: absSourceDir
581
+ });
582
+ return {
583
+ cleanup: () => {
584
+ rmSync(tempRoot, { force: true, recursive: true });
585
+ },
586
+ entryPoint: "index.js",
587
+ outputDir
588
+ };
589
+ }
590
+ async function buildHostedDeployTarball(args) {
591
+ const deployPackage = await createHostedDeployPackage(args);
592
+ try {
593
+ const tarPath = join(dirname(deployPackage.outputDir), "source.tar.gz");
594
+ execFileSync("tar", ["czf", tarPath, "-C", deployPackage.outputDir, "."], {
595
+ stdio: "pipe"
596
+ });
597
+ return {
598
+ entryPoint: deployPackage.entryPoint,
599
+ source: readFileSync(tarPath).toString("base64")
600
+ };
601
+ } finally {
602
+ deployPackage.cleanup();
603
+ }
604
+ }
605
+ export {
606
+ buildHostedDeployTarball,
607
+ createHostedDeployPackage
608
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "libretto",
3
- "version": "0.5.3-experimental.1",
3
+ "version": "0.5.3-experimental.2",
4
4
  "description": "AI-powered browser automation library and CLI built on Playwright",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -81,6 +81,7 @@
81
81
  },
82
82
  "dependencies": {
83
83
  "ai": "^6.0.116",
84
+ "esbuild": "^0.27.0",
84
85
  "playwright": "^1.58.2",
85
86
  "tsx": "^4.21.0",
86
87
  "zod": "^4.3.6"