libretto 0.5.3-experimental.5 → 0.5.3

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