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.
- package/README.md +114 -37
- package/README.template.md +160 -0
- package/dist/cli/cli.js +22 -97
- package/dist/cli/commands/browser.js +86 -59
- package/dist/cli/commands/deploy.js +148 -0
- package/dist/cli/commands/execution.js +218 -96
- package/dist/cli/commands/init.js +34 -29
- package/dist/cli/commands/logs.js +4 -5
- package/dist/cli/commands/shared.js +30 -29
- package/dist/cli/commands/snapshot.js +26 -39
- package/dist/cli/core/ai-config.js +21 -4
- package/dist/cli/core/api-snapshot-analyzer.js +15 -5
- package/dist/cli/core/browser.js +207 -37
- package/dist/cli/core/context.js +4 -1
- package/dist/cli/core/deploy-artifact.js +687 -0
- package/dist/cli/core/session-telemetry.js +434 -174
- package/dist/cli/core/session.js +21 -8
- package/dist/cli/core/snapshot-analyzer.js +14 -31
- package/dist/cli/core/snapshot-api-config.js +2 -6
- package/dist/cli/core/telemetry.js +20 -4
- package/dist/cli/framework/simple-cli.js +144 -43
- package/dist/cli/router.js +16 -21
- package/dist/cli/workers/run-integration-runtime.js +25 -45
- package/dist/cli/workers/run-integration-worker-protocol.js +3 -2
- package/dist/cli/workers/run-integration-worker.js +1 -4
- package/dist/index.d.ts +1 -2
- package/dist/index.js +13 -10
- package/dist/runtime/download/download.js +5 -1
- package/dist/runtime/extract/extract.js +11 -2
- package/dist/runtime/network/network.js +8 -1
- package/dist/runtime/recovery/agent.js +6 -2
- package/dist/runtime/recovery/errors.js +3 -1
- package/dist/runtime/recovery/recovery.js +3 -1
- package/dist/shared/condense-dom/condense-dom.js +17 -69
- package/dist/shared/config/config.d.ts +1 -9
- package/dist/shared/config/config.js +0 -18
- package/dist/shared/config/index.d.ts +2 -1
- package/dist/shared/config/index.js +0 -10
- package/dist/shared/debug/pause.js +9 -3
- package/dist/shared/dom-semantics.d.ts +8 -0
- package/dist/shared/dom-semantics.js +69 -0
- package/dist/shared/instrumentation/instrument.js +101 -5
- package/dist/shared/llm/ai-sdk-adapter.js +3 -1
- package/dist/shared/llm/client.js +3 -1
- package/dist/shared/logger/index.js +4 -1
- package/dist/shared/run/api.js +3 -1
- package/dist/shared/run/browser.js +47 -3
- package/dist/shared/state/session-state.d.ts +2 -1
- package/dist/shared/state/session-state.js +5 -2
- package/dist/shared/visualization/ghost-cursor.js +36 -14
- package/dist/shared/visualization/highlight.js +9 -6
- package/dist/shared/workflow/workflow.d.ts +18 -10
- package/dist/shared/workflow/workflow.js +50 -5
- package/package.json +14 -6
- package/scripts/generate-changelog.ts +132 -0
- package/scripts/postinstall.mjs +4 -3
- package/scripts/skills-libretto.mjs +2 -88
- package/scripts/summarize-evals.mjs +32 -10
- package/skills/libretto/SKILL.md +132 -62
- package/skills/libretto/references/action-logs.md +101 -0
- package/skills/libretto/references/auth-profiles.md +1 -2
- package/skills/libretto/references/code-generation-rules.md +176 -0
- package/skills/libretto/references/configuration-file-reference.md +53 -0
- package/skills/libretto/references/pages-and-page-targeting.md +1 -1
- package/skills/libretto/references/site-security-review.md +143 -0
- package/src/cli/cli.ts +23 -110
- package/src/cli/commands/browser.ts +94 -70
- package/src/cli/commands/deploy.ts +198 -0
- package/src/cli/commands/execution.ts +251 -111
- package/src/cli/commands/init.ts +37 -33
- package/src/cli/commands/logs.ts +7 -7
- package/src/cli/commands/shared.ts +36 -37
- package/src/cli/commands/snapshot.ts +44 -59
- package/src/cli/core/ai-config.ts +24 -4
- package/src/cli/core/api-snapshot-analyzer.ts +17 -6
- package/src/cli/core/browser.ts +260 -49
- package/src/cli/core/context.ts +7 -2
- package/src/cli/core/deploy-artifact.ts +938 -0
- package/src/cli/core/session-telemetry.ts +449 -197
- package/src/cli/core/session.ts +21 -7
- package/src/cli/core/snapshot-analyzer.ts +26 -46
- package/src/cli/core/snapshot-api-config.ts +170 -175
- package/src/cli/core/telemetry.ts +39 -4
- package/src/cli/framework/simple-cli.ts +281 -98
- package/src/cli/router.ts +15 -21
- package/src/cli/workers/run-integration-runtime.ts +35 -57
- package/src/cli/workers/run-integration-worker-protocol.ts +2 -1
- package/src/cli/workers/run-integration-worker.ts +1 -4
- package/src/index.ts +77 -67
- package/src/runtime/download/download.ts +62 -58
- package/src/runtime/download/index.ts +5 -5
- package/src/runtime/extract/extract.ts +71 -61
- package/src/runtime/network/index.ts +3 -3
- package/src/runtime/network/network.ts +99 -93
- package/src/runtime/recovery/agent.ts +217 -212
- package/src/runtime/recovery/errors.ts +107 -104
- package/src/runtime/recovery/index.ts +3 -3
- package/src/runtime/recovery/recovery.ts +38 -35
- package/src/shared/condense-dom/condense-dom.ts +27 -82
- package/src/shared/config/config.ts +0 -19
- package/src/shared/config/index.ts +0 -5
- package/src/shared/debug/pause.ts +57 -51
- package/src/shared/dom-semantics.ts +68 -0
- package/src/shared/instrumentation/errors.ts +64 -62
- package/src/shared/instrumentation/index.ts +5 -5
- package/src/shared/instrumentation/instrument.ts +339 -209
- package/src/shared/llm/ai-sdk-adapter.ts +58 -55
- package/src/shared/llm/client.ts +181 -174
- package/src/shared/llm/types.ts +39 -39
- package/src/shared/logger/index.ts +11 -4
- package/src/shared/logger/logger.ts +312 -306
- package/src/shared/logger/sinks.ts +118 -114
- package/src/shared/paths/paths.ts +50 -49
- package/src/shared/paths/repo-root.ts +17 -17
- package/src/shared/run/api.ts +5 -1
- package/src/shared/run/browser.ts +65 -3
- package/src/shared/state/index.ts +9 -9
- package/src/shared/state/session-state.ts +46 -43
- package/src/shared/visualization/ghost-cursor.ts +180 -149
- package/src/shared/visualization/highlight.ts +89 -86
- package/src/shared/visualization/index.ts +13 -13
- package/src/shared/workflow/workflow.ts +107 -30
- package/scripts/check-skills-sync.mjs +0 -23
- package/scripts/prepare-release.sh +0 -97
- package/skills/libretto/references/reverse-engineering-network-requests.md +0 -75
- package/skills/libretto/references/user-action-log.md +0 -31
|
@@ -0,0 +1,938 @@
|
|
|
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
|
+
|
|
19
|
+
type PackageManifest = {
|
|
20
|
+
name?: string;
|
|
21
|
+
version?: string;
|
|
22
|
+
packageManager?: string;
|
|
23
|
+
main?: string;
|
|
24
|
+
module?: string;
|
|
25
|
+
source?: string;
|
|
26
|
+
types?: string;
|
|
27
|
+
exports?: unknown;
|
|
28
|
+
dependencies?: Record<string, string>;
|
|
29
|
+
devDependencies?: Record<string, string>;
|
|
30
|
+
peerDependencies?: Record<string, string>;
|
|
31
|
+
optionalDependencies?: Record<string, string>;
|
|
32
|
+
workspaces?: string[] | { packages?: string[] };
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
type WorkspacePackage = {
|
|
36
|
+
dir: string;
|
|
37
|
+
manifest: PackageManifest;
|
|
38
|
+
name: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type HostedDeployPackage = {
|
|
42
|
+
cleanup: () => void;
|
|
43
|
+
entryPoint: string;
|
|
44
|
+
outputDir: string;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
type BuildHostedDeployTarballArgs = {
|
|
48
|
+
additionalExternals?: readonly string[];
|
|
49
|
+
deploymentName: string;
|
|
50
|
+
entryPoint?: string;
|
|
51
|
+
sourceDir: string;
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
type CreateHostedDeployPackageArgs = BuildHostedDeployTarballArgs;
|
|
55
|
+
|
|
56
|
+
const DEFAULT_RUNTIME_EXTERNALS = [
|
|
57
|
+
"libretto",
|
|
58
|
+
"playwright",
|
|
59
|
+
"playwright-core",
|
|
60
|
+
"chromium-bidi",
|
|
61
|
+
] as const;
|
|
62
|
+
const BUILT_IN_MANIFEST_DEPENDENCIES = ["libretto"] as const;
|
|
63
|
+
const SOURCE_FILE_EXTENSIONS = [
|
|
64
|
+
"",
|
|
65
|
+
".ts",
|
|
66
|
+
".tsx",
|
|
67
|
+
".mts",
|
|
68
|
+
".cts",
|
|
69
|
+
".js",
|
|
70
|
+
".mjs",
|
|
71
|
+
".cjs",
|
|
72
|
+
"/index.ts",
|
|
73
|
+
"/index.tsx",
|
|
74
|
+
"/index.mts",
|
|
75
|
+
"/index.cts",
|
|
76
|
+
"/index.js",
|
|
77
|
+
"/index.mjs",
|
|
78
|
+
"/index.cjs",
|
|
79
|
+
] as const;
|
|
80
|
+
const CURRENT_LIBRETTO_VERSION = readCurrentLibrettoVersion();
|
|
81
|
+
const CURRENT_LIBRETTO_PACKAGE_DIR = fileURLToPath(
|
|
82
|
+
new URL("../../..", import.meta.url),
|
|
83
|
+
);
|
|
84
|
+
|
|
85
|
+
function readCurrentLibrettoVersion(): string {
|
|
86
|
+
const packageJsonPath = fileURLToPath(
|
|
87
|
+
new URL("../../../package.json", import.meta.url),
|
|
88
|
+
);
|
|
89
|
+
const manifest = readJsonFile<PackageManifest>(packageJsonPath);
|
|
90
|
+
if (!manifest.version) {
|
|
91
|
+
throw new Error(
|
|
92
|
+
`Unable to determine current libretto version from ${packageJsonPath}.`,
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
return manifest.version;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function readJsonFile<T>(path: string): T {
|
|
99
|
+
return JSON.parse(readFileSync(path, "utf8")) as T;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function readPackageManifest(path: string): PackageManifest {
|
|
103
|
+
return readJsonFile<PackageManifest>(path);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function ensureSourcePackageManifest(sourceDir: string): PackageManifest {
|
|
107
|
+
const pkgJsonPath = join(sourceDir, "package.json");
|
|
108
|
+
if (!existsSync(pkgJsonPath)) {
|
|
109
|
+
throw new Error(
|
|
110
|
+
`No package.json found in ${sourceDir}. Deploy source must contain a package.json.`,
|
|
111
|
+
);
|
|
112
|
+
}
|
|
113
|
+
return readPackageManifest(pkgJsonPath);
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function resolveEntryPointPath(sourceDir: string, entryPoint?: string): string {
|
|
117
|
+
const candidate = entryPoint ?? "index.ts";
|
|
118
|
+
const absEntryPoint = isAbsolute(candidate)
|
|
119
|
+
? resolve(candidate)
|
|
120
|
+
: resolve(sourceDir, candidate);
|
|
121
|
+
|
|
122
|
+
if (!existsSync(absEntryPoint)) {
|
|
123
|
+
throw new Error(
|
|
124
|
+
`Deploy entry point not found: ${absEntryPoint}. Pass --entry-point to choose a workflow file.`,
|
|
125
|
+
);
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
return absEntryPoint;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function isRootPath(path: string): boolean {
|
|
132
|
+
return dirname(path) === path;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function findWorkspaceRoot(startDir: string): string | null {
|
|
136
|
+
let currentDir = resolve(startDir);
|
|
137
|
+
|
|
138
|
+
while (true) {
|
|
139
|
+
if (existsSync(join(currentDir, "pnpm-workspace.yaml"))) {
|
|
140
|
+
return currentDir;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
const pkgJsonPath = join(currentDir, "package.json");
|
|
144
|
+
if (existsSync(pkgJsonPath)) {
|
|
145
|
+
const manifest = readPackageManifest(pkgJsonPath);
|
|
146
|
+
if (manifest.workspaces) {
|
|
147
|
+
return currentDir;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (isRootPath(currentDir)) {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
currentDir = dirname(currentDir);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function readWorkspacePatterns(rootDir: string): string[] {
|
|
159
|
+
const pnpmWorkspacePath = join(rootDir, "pnpm-workspace.yaml");
|
|
160
|
+
if (existsSync(pnpmWorkspacePath)) {
|
|
161
|
+
const patterns: string[] = [];
|
|
162
|
+
let inPackagesBlock = false;
|
|
163
|
+
|
|
164
|
+
for (const rawLine of readFileSync(pnpmWorkspacePath, "utf8").split(
|
|
165
|
+
/\r?\n/,
|
|
166
|
+
)) {
|
|
167
|
+
const trimmed = rawLine.trim();
|
|
168
|
+
if (!inPackagesBlock) {
|
|
169
|
+
if (trimmed === "packages:") {
|
|
170
|
+
inPackagesBlock = true;
|
|
171
|
+
}
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (
|
|
176
|
+
trimmed.length > 0 &&
|
|
177
|
+
!trimmed.startsWith("-") &&
|
|
178
|
+
!rawLine.startsWith(" ") &&
|
|
179
|
+
!rawLine.startsWith("\t")
|
|
180
|
+
) {
|
|
181
|
+
break;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
const match = trimmed.match(/^-\s*["']?(.+?)["']?$/);
|
|
185
|
+
if (match?.[1]) {
|
|
186
|
+
patterns.push(match[1]);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
if (patterns.length > 0) {
|
|
191
|
+
return patterns;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const pkgJsonPath = join(rootDir, "package.json");
|
|
196
|
+
if (!existsSync(pkgJsonPath)) {
|
|
197
|
+
return [];
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
const manifest = readPackageManifest(pkgJsonPath);
|
|
201
|
+
if (Array.isArray(manifest.workspaces)) {
|
|
202
|
+
return manifest.workspaces;
|
|
203
|
+
}
|
|
204
|
+
if (manifest.workspaces && Array.isArray(manifest.workspaces.packages)) {
|
|
205
|
+
return manifest.workspaces.packages;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
return [];
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function expandWorkspacePattern(rootDir: string, pattern: string): string[] {
|
|
212
|
+
if (!pattern.includes("*")) {
|
|
213
|
+
const absDir = resolve(rootDir, pattern);
|
|
214
|
+
return existsSync(absDir) ? [absDir] : [];
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
if (!pattern.endsWith("/*")) {
|
|
218
|
+
return [];
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const baseDir = resolve(rootDir, pattern.slice(0, -2));
|
|
222
|
+
if (!existsSync(baseDir)) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
return readdirSync(baseDir, { withFileTypes: true })
|
|
227
|
+
.filter((entry) => entry.isDirectory())
|
|
228
|
+
.map((entry) => join(baseDir, entry.name));
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
function discoverWorkspacePackages(
|
|
232
|
+
startDir: string,
|
|
233
|
+
): Map<string, WorkspacePackage> {
|
|
234
|
+
const workspaceRoot = findWorkspaceRoot(startDir);
|
|
235
|
+
if (!workspaceRoot) {
|
|
236
|
+
return new Map();
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const packages = new Map<string, WorkspacePackage>();
|
|
240
|
+
for (const pattern of readWorkspacePatterns(workspaceRoot)) {
|
|
241
|
+
for (const dir of expandWorkspacePattern(workspaceRoot, pattern)) {
|
|
242
|
+
const pkgJsonPath = join(dir, "package.json");
|
|
243
|
+
if (!existsSync(pkgJsonPath)) {
|
|
244
|
+
continue;
|
|
245
|
+
}
|
|
246
|
+
const manifest = readPackageManifest(pkgJsonPath);
|
|
247
|
+
if (!manifest.name) {
|
|
248
|
+
continue;
|
|
249
|
+
}
|
|
250
|
+
packages.set(manifest.name, { dir, manifest, name: manifest.name });
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
return packages;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
function findMatchingWorkspacePackage(
|
|
258
|
+
importPath: string,
|
|
259
|
+
workspacePackages: Map<string, WorkspacePackage>,
|
|
260
|
+
): {
|
|
261
|
+
info: WorkspacePackage;
|
|
262
|
+
subpath: string;
|
|
263
|
+
} | null {
|
|
264
|
+
const names = [...workspacePackages.keys()].sort(
|
|
265
|
+
(left, right) => right.length - left.length,
|
|
266
|
+
);
|
|
267
|
+
|
|
268
|
+
for (const name of names) {
|
|
269
|
+
if (importPath === name) {
|
|
270
|
+
return {
|
|
271
|
+
info: workspacePackages.get(name)!,
|
|
272
|
+
subpath: ".",
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
if (importPath.startsWith(`${name}/`)) {
|
|
276
|
+
return {
|
|
277
|
+
info: workspacePackages.get(name)!,
|
|
278
|
+
subpath: `.${importPath.slice(name.length)}`,
|
|
279
|
+
};
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
return null;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
function resolvePathCandidates(
|
|
287
|
+
packageDir: string,
|
|
288
|
+
target: string,
|
|
289
|
+
replacement?: string,
|
|
290
|
+
): string | null {
|
|
291
|
+
const value = replacement ? target.replace(/\*/g, replacement) : target;
|
|
292
|
+
const absCandidate = resolve(packageDir, value);
|
|
293
|
+
if (existsSync(absCandidate)) {
|
|
294
|
+
return absCandidate;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
for (const suffix of SOURCE_FILE_EXTENSIONS) {
|
|
298
|
+
const fileCandidate = resolve(packageDir, `${value}${suffix}`);
|
|
299
|
+
if (existsSync(fileCandidate)) {
|
|
300
|
+
return fileCandidate;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
return null;
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function resolveExportTarget(
|
|
308
|
+
exportValue: unknown,
|
|
309
|
+
packageDir: string,
|
|
310
|
+
replacement?: string,
|
|
311
|
+
): string | null {
|
|
312
|
+
if (typeof exportValue === "string") {
|
|
313
|
+
return resolvePathCandidates(packageDir, exportValue, replacement);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (Array.isArray(exportValue)) {
|
|
317
|
+
for (const entry of exportValue) {
|
|
318
|
+
const resolved = resolveExportTarget(entry, packageDir, replacement);
|
|
319
|
+
if (resolved) {
|
|
320
|
+
return resolved;
|
|
321
|
+
}
|
|
322
|
+
}
|
|
323
|
+
return null;
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
if (!exportValue || typeof exportValue !== "object") {
|
|
327
|
+
return null;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
const record = exportValue as Record<string, unknown>;
|
|
331
|
+
for (const condition of [
|
|
332
|
+
"types",
|
|
333
|
+
"source",
|
|
334
|
+
"import",
|
|
335
|
+
"default",
|
|
336
|
+
"module",
|
|
337
|
+
"require",
|
|
338
|
+
]) {
|
|
339
|
+
if (!(condition in record)) {
|
|
340
|
+
continue;
|
|
341
|
+
}
|
|
342
|
+
const resolved = resolveExportTarget(
|
|
343
|
+
record[condition],
|
|
344
|
+
packageDir,
|
|
345
|
+
replacement,
|
|
346
|
+
);
|
|
347
|
+
if (resolved) {
|
|
348
|
+
return resolved;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
for (const value of Object.values(record)) {
|
|
353
|
+
const resolved = resolveExportTarget(value, packageDir, replacement);
|
|
354
|
+
if (resolved) {
|
|
355
|
+
return resolved;
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
return null;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
function resolveExportsSubpath(
|
|
363
|
+
exportsField: unknown,
|
|
364
|
+
packageDir: string,
|
|
365
|
+
subpath: string,
|
|
366
|
+
): string | null {
|
|
367
|
+
if (!exportsField) {
|
|
368
|
+
return null;
|
|
369
|
+
}
|
|
370
|
+
|
|
371
|
+
if (
|
|
372
|
+
subpath === "." &&
|
|
373
|
+
(typeof exportsField === "string" || Array.isArray(exportsField))
|
|
374
|
+
) {
|
|
375
|
+
const rootExport = resolveExportTarget(exportsField, packageDir);
|
|
376
|
+
if (rootExport) {
|
|
377
|
+
return rootExport;
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
if (typeof exportsField !== "object" || Array.isArray(exportsField)) {
|
|
382
|
+
return null;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
const record = exportsField as Record<string, unknown>;
|
|
386
|
+
const hasExplicitSubpathKeys = Object.keys(record).some((key) =>
|
|
387
|
+
key.startsWith("."),
|
|
388
|
+
);
|
|
389
|
+
|
|
390
|
+
if (!hasExplicitSubpathKeys) {
|
|
391
|
+
return subpath === "." ? resolveExportTarget(record, packageDir) : null;
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
const exactMatch = record[subpath];
|
|
395
|
+
if (exactMatch !== undefined) {
|
|
396
|
+
return resolveExportTarget(exactMatch, packageDir);
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
for (const [key, value] of Object.entries(record)) {
|
|
400
|
+
const starIndex = key.indexOf("*");
|
|
401
|
+
if (starIndex < 0) {
|
|
402
|
+
continue;
|
|
403
|
+
}
|
|
404
|
+
const prefix = key.slice(0, starIndex);
|
|
405
|
+
const suffix = key.slice(starIndex + 1);
|
|
406
|
+
if (!subpath.startsWith(prefix) || !subpath.endsWith(suffix)) {
|
|
407
|
+
continue;
|
|
408
|
+
}
|
|
409
|
+
const replacement = subpath.slice(
|
|
410
|
+
prefix.length,
|
|
411
|
+
subpath.length - suffix.length,
|
|
412
|
+
);
|
|
413
|
+
const resolved = resolveExportTarget(value, packageDir, replacement);
|
|
414
|
+
if (resolved) {
|
|
415
|
+
return resolved;
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
return null;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
function resolveWorkspaceSourcePath(
|
|
423
|
+
info: WorkspacePackage,
|
|
424
|
+
subpath: string,
|
|
425
|
+
): string | null {
|
|
426
|
+
const viaExports = resolveExportsSubpath(
|
|
427
|
+
info.manifest.exports,
|
|
428
|
+
info.dir,
|
|
429
|
+
subpath,
|
|
430
|
+
);
|
|
431
|
+
if (viaExports) {
|
|
432
|
+
return viaExports;
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
if (subpath === ".") {
|
|
436
|
+
for (const field of [
|
|
437
|
+
info.manifest.types,
|
|
438
|
+
info.manifest.source,
|
|
439
|
+
info.manifest.module,
|
|
440
|
+
info.manifest.main,
|
|
441
|
+
]) {
|
|
442
|
+
if (!field) {
|
|
443
|
+
continue;
|
|
444
|
+
}
|
|
445
|
+
const resolved = resolvePathCandidates(info.dir, field);
|
|
446
|
+
if (resolved) {
|
|
447
|
+
return resolved;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
const directSubpath = subpath === "." ? "index" : subpath.slice(2);
|
|
453
|
+
return resolvePathCandidates(info.dir, directSubpath);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function workspaceSourcePlugin(
|
|
457
|
+
workspacePackages: Map<string, WorkspacePackage>,
|
|
458
|
+
externalPackages: ReadonlySet<string>,
|
|
459
|
+
) {
|
|
460
|
+
return {
|
|
461
|
+
name: "workspace-source-resolver",
|
|
462
|
+
setup(buildApi: {
|
|
463
|
+
onResolve: (
|
|
464
|
+
options: { filter: RegExp },
|
|
465
|
+
callback: (args: { path: string }) => { path: string } | null,
|
|
466
|
+
) => void;
|
|
467
|
+
}) {
|
|
468
|
+
// Workspace imports are treated as bundle input, so their code is
|
|
469
|
+
// embedded into the generated implementation file. The deployed package
|
|
470
|
+
// does not depend on the original monorepo layout or workspace:* links.
|
|
471
|
+
buildApi.onResolve({ filter: /^[^./].*/ }, (args) => {
|
|
472
|
+
if (externalPackages.has(args.path)) {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
const match = findMatchingWorkspacePackage(
|
|
477
|
+
args.path,
|
|
478
|
+
workspacePackages,
|
|
479
|
+
);
|
|
480
|
+
if (!match) {
|
|
481
|
+
return null;
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
const resolvedPath = resolveWorkspaceSourcePath(
|
|
485
|
+
match.info,
|
|
486
|
+
match.subpath,
|
|
487
|
+
);
|
|
488
|
+
if (!resolvedPath) {
|
|
489
|
+
throw new Error(
|
|
490
|
+
`Unable to resolve workspace import "${args.path}" from ${match.info.dir}.`,
|
|
491
|
+
);
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
return { path: resolvedPath };
|
|
495
|
+
});
|
|
496
|
+
},
|
|
497
|
+
};
|
|
498
|
+
}
|
|
499
|
+
|
|
500
|
+
function normalizePackageName(name: string): string {
|
|
501
|
+
const normalized = name
|
|
502
|
+
.toLowerCase()
|
|
503
|
+
.replace(/[^a-z0-9._-]+/g, "-")
|
|
504
|
+
.replace(/^-+|-+$/g, "");
|
|
505
|
+
return normalized || "libretto-deployment";
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
function readDependencyVersionFromManifest(
|
|
509
|
+
manifest: PackageManifest,
|
|
510
|
+
packageName: string,
|
|
511
|
+
): string | null {
|
|
512
|
+
for (const dependencyGroup of [
|
|
513
|
+
manifest.dependencies,
|
|
514
|
+
manifest.devDependencies,
|
|
515
|
+
manifest.peerDependencies,
|
|
516
|
+
manifest.optionalDependencies,
|
|
517
|
+
]) {
|
|
518
|
+
const version = dependencyGroup?.[packageName];
|
|
519
|
+
if (version) {
|
|
520
|
+
return version;
|
|
521
|
+
}
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
return null;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
function resolveDependencyVersion(
|
|
528
|
+
sourceDir: string,
|
|
529
|
+
packageName: string,
|
|
530
|
+
fallbackVersion?: string,
|
|
531
|
+
): string {
|
|
532
|
+
let currentDir = resolve(sourceDir);
|
|
533
|
+
|
|
534
|
+
while (true) {
|
|
535
|
+
const pkgJsonPath = join(currentDir, "package.json");
|
|
536
|
+
if (existsSync(pkgJsonPath)) {
|
|
537
|
+
const version = readDependencyVersionFromManifest(
|
|
538
|
+
readPackageManifest(pkgJsonPath),
|
|
539
|
+
packageName,
|
|
540
|
+
);
|
|
541
|
+
if (version) {
|
|
542
|
+
return version;
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
if (isRootPath(currentDir)) {
|
|
547
|
+
break;
|
|
548
|
+
}
|
|
549
|
+
currentDir = dirname(currentDir);
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
if (fallbackVersion) {
|
|
553
|
+
return fallbackVersion;
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
throw new Error(
|
|
557
|
+
`Unable to determine a version for external package "${packageName}". Add it to your package.json or remove it from --external.`,
|
|
558
|
+
);
|
|
559
|
+
}
|
|
560
|
+
|
|
561
|
+
function writeDeployManifest(args: {
|
|
562
|
+
additionalExternals: readonly string[];
|
|
563
|
+
deploymentName: string;
|
|
564
|
+
librettoDependency: string;
|
|
565
|
+
outputDir: string;
|
|
566
|
+
sourceDir: string;
|
|
567
|
+
}): void {
|
|
568
|
+
const dependencies = Object.fromEntries(
|
|
569
|
+
[...BUILT_IN_MANIFEST_DEPENDENCIES, ...args.additionalExternals].map(
|
|
570
|
+
(packageName) => [
|
|
571
|
+
packageName,
|
|
572
|
+
packageName === "libretto"
|
|
573
|
+
? args.librettoDependency
|
|
574
|
+
: resolveDependencyVersion(args.sourceDir, packageName),
|
|
575
|
+
],
|
|
576
|
+
),
|
|
577
|
+
);
|
|
578
|
+
|
|
579
|
+
writeFileSync(
|
|
580
|
+
join(args.outputDir, "package.json"),
|
|
581
|
+
JSON.stringify(
|
|
582
|
+
{
|
|
583
|
+
name: normalizePackageName(args.deploymentName),
|
|
584
|
+
private: true,
|
|
585
|
+
type: "module",
|
|
586
|
+
dependencies,
|
|
587
|
+
},
|
|
588
|
+
null,
|
|
589
|
+
2,
|
|
590
|
+
) + "\n",
|
|
591
|
+
);
|
|
592
|
+
}
|
|
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
|
+
|
|
632
|
+
function formatBuildError(error: unknown): string {
|
|
633
|
+
if (!(error instanceof Error)) {
|
|
634
|
+
return String(error);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
const candidate = error as Error & {
|
|
638
|
+
errors?: Array<{
|
|
639
|
+
location?: { file?: string; line?: number; column?: number };
|
|
640
|
+
text?: string;
|
|
641
|
+
}>;
|
|
642
|
+
};
|
|
643
|
+
if (!Array.isArray(candidate.errors) || candidate.errors.length === 0) {
|
|
644
|
+
return error.message;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
return candidate.errors
|
|
648
|
+
.map((entry) => {
|
|
649
|
+
const location = entry.location?.file
|
|
650
|
+
? `${entry.location.file}:${entry.location.line ?? 0}:${entry.location.column ?? 0}`
|
|
651
|
+
: "unknown";
|
|
652
|
+
return `${location} ${entry.text ?? error.message}`;
|
|
653
|
+
})
|
|
654
|
+
.join("\n");
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
function extractExportNamesFromEsmBundle(bundleSource: string): string[] {
|
|
658
|
+
const exportNames = new Set<string>();
|
|
659
|
+
|
|
660
|
+
for (const entry of bundleSource.matchAll(
|
|
661
|
+
/export\s+(?:const|let|var|function|class)\s+([A-Za-z_$][\w$]*)/g,
|
|
662
|
+
)) {
|
|
663
|
+
exportNames.add(entry[1]!);
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
for (const entry of bundleSource.matchAll(/export\s+\{([^}]+)\};/g)) {
|
|
667
|
+
const specifiers = entry[1]?.split(",") ?? [];
|
|
668
|
+
for (const specifier of specifiers) {
|
|
669
|
+
const trimmed = specifier.trim();
|
|
670
|
+
if (!trimmed) {
|
|
671
|
+
continue;
|
|
672
|
+
}
|
|
673
|
+
const aliasMatch = trimmed.match(
|
|
674
|
+
/^([A-Za-z_$][\w$]*)\s+as\s+([A-Za-z_$][\w$]*|default)$/,
|
|
675
|
+
);
|
|
676
|
+
if (aliasMatch?.[2]) {
|
|
677
|
+
exportNames.add(aliasMatch[2]);
|
|
678
|
+
continue;
|
|
679
|
+
}
|
|
680
|
+
if (/^[A-Za-z_$][\w$]*$/.test(trimmed)) {
|
|
681
|
+
exportNames.add(trimmed);
|
|
682
|
+
}
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
if (/\bexport\s+default\b/m.test(bundleSource)) {
|
|
687
|
+
exportNames.add("default");
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
return [...exportNames];
|
|
691
|
+
}
|
|
692
|
+
|
|
693
|
+
function createBootstrapSource(args: {
|
|
694
|
+
bundleBuffer: Buffer;
|
|
695
|
+
deploymentName: string;
|
|
696
|
+
exportNames: readonly string[];
|
|
697
|
+
}): string {
|
|
698
|
+
const bundleHash = createHash("sha256")
|
|
699
|
+
.update(args.bundleBuffer)
|
|
700
|
+
.digest("hex")
|
|
701
|
+
.slice(0, 16);
|
|
702
|
+
const bundleBase64 = gzipSync(args.bundleBuffer, { level: 9 }).toString(
|
|
703
|
+
"base64",
|
|
704
|
+
);
|
|
705
|
+
const outputPrefix = `${normalizePackageName(args.deploymentName)}-`;
|
|
706
|
+
const hasDefaultExport = args.exportNames.includes("default");
|
|
707
|
+
const exportLines = args.exportNames
|
|
708
|
+
.filter((name) => name !== "default")
|
|
709
|
+
.map(
|
|
710
|
+
(name) =>
|
|
711
|
+
`export const ${name} = createWorkflowProxy(${JSON.stringify(name)});`,
|
|
712
|
+
)
|
|
713
|
+
.join("\n");
|
|
714
|
+
const defaultExportLine = hasDefaultExport
|
|
715
|
+
? 'export default createWorkflowProxy("default");'
|
|
716
|
+
: "";
|
|
717
|
+
|
|
718
|
+
// The deploy entrypoint is tiny on purpose. Hosted build imports this module
|
|
719
|
+
// to discover workflow exports. The implementation bundle stays embedded in
|
|
720
|
+
// the file, while external packages are resolved from node_modules when the
|
|
721
|
+
// deployed code loads them.
|
|
722
|
+
return `import { createRequire } from "node:module";
|
|
723
|
+
import { existsSync, writeFileSync } from "node:fs";
|
|
724
|
+
import { tmpdir } from "node:os";
|
|
725
|
+
import { join } from "node:path";
|
|
726
|
+
import { gunzipSync } from "node:zlib";
|
|
727
|
+
import { workflow } from "libretto";
|
|
728
|
+
|
|
729
|
+
const BUNDLE_HASH = ${JSON.stringify(bundleHash)};
|
|
730
|
+
const BUNDLE_GZIP_BASE64 = ${JSON.stringify(bundleBase64)};
|
|
731
|
+
const BUNDLE_FILENAME = join(
|
|
732
|
+
tmpdir(),
|
|
733
|
+
${JSON.stringify(outputPrefix)} + BUNDLE_HASH + ".cjs",
|
|
734
|
+
);
|
|
735
|
+
const nativeRequire = createRequire(
|
|
736
|
+
join(tmpdir(), ${JSON.stringify("libretto-deploy-bootstrap.cjs")}),
|
|
737
|
+
);
|
|
738
|
+
|
|
739
|
+
function ensureBundleFile() {
|
|
740
|
+
if (!existsSync(BUNDLE_FILENAME)) {
|
|
741
|
+
writeFileSync(
|
|
742
|
+
BUNDLE_FILENAME,
|
|
743
|
+
gunzipSync(Buffer.from(BUNDLE_GZIP_BASE64, "base64")),
|
|
744
|
+
);
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
return BUNDLE_FILENAME;
|
|
748
|
+
}
|
|
749
|
+
|
|
750
|
+
function createWorkflowProxy(exportName) {
|
|
751
|
+
return workflow(exportName, async (ctx, input) => {
|
|
752
|
+
const impl = nativeRequire(ensureBundleFile());
|
|
753
|
+
const target = impl[exportName];
|
|
754
|
+
if (!target || typeof target.run !== "function") {
|
|
755
|
+
throw new Error(
|
|
756
|
+
\`Expected workflow export "\${exportName}" to be available in the bundled deployment implementation.\`,
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
return await target.run(ctx, input);
|
|
760
|
+
});
|
|
761
|
+
}
|
|
762
|
+
|
|
763
|
+
${exportLines}
|
|
764
|
+
${defaultExportLine}
|
|
765
|
+
`;
|
|
766
|
+
}
|
|
767
|
+
|
|
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> {
|
|
776
|
+
try {
|
|
777
|
+
// The implementation bundle is CommonJS so the bootstrap can load it lazily
|
|
778
|
+
// with createRequire() after workflow discovery, while external packages
|
|
779
|
+
// continue to load through normal Node module resolution.
|
|
780
|
+
const implementationBuild = await build({
|
|
781
|
+
absWorkingDir: args.absSourceDir,
|
|
782
|
+
bundle: true,
|
|
783
|
+
entryPoints: [args.absEntryPoint],
|
|
784
|
+
external: [...args.externalPackages],
|
|
785
|
+
format: "cjs",
|
|
786
|
+
outfile: "prebundled.cjs",
|
|
787
|
+
platform: "node",
|
|
788
|
+
plugins: [
|
|
789
|
+
workspaceSourcePlugin(args.workspacePackages, args.externalPackages),
|
|
790
|
+
],
|
|
791
|
+
splitting: false,
|
|
792
|
+
target: "node20",
|
|
793
|
+
write: false,
|
|
794
|
+
});
|
|
795
|
+
|
|
796
|
+
const bundledImplementation = implementationBuild.outputFiles?.find(
|
|
797
|
+
(file) => file.path.endsWith("prebundled.cjs"),
|
|
798
|
+
);
|
|
799
|
+
if (!bundledImplementation) {
|
|
800
|
+
throw new Error(
|
|
801
|
+
"Bundler did not produce a deployment implementation file.",
|
|
802
|
+
);
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
// A separate ESM bundle is used only to read the entry module's exported
|
|
806
|
+
// workflow names. Scanning the CommonJS bundle would also see exports from
|
|
807
|
+
// bundled dependencies, which is not the deploy surface.
|
|
808
|
+
const exportBuild = await build({
|
|
809
|
+
absWorkingDir: args.absSourceDir,
|
|
810
|
+
bundle: true,
|
|
811
|
+
entryPoints: [args.absEntryPoint],
|
|
812
|
+
external: [...args.externalPackages],
|
|
813
|
+
format: "esm",
|
|
814
|
+
outfile: "entry-exports.js",
|
|
815
|
+
platform: "node",
|
|
816
|
+
plugins: [
|
|
817
|
+
workspaceSourcePlugin(args.workspacePackages, args.externalPackages),
|
|
818
|
+
],
|
|
819
|
+
splitting: false,
|
|
820
|
+
target: "node20",
|
|
821
|
+
write: false,
|
|
822
|
+
});
|
|
823
|
+
|
|
824
|
+
const bundledExports = exportBuild.outputFiles?.find((file) =>
|
|
825
|
+
file.path.endsWith("entry-exports.js"),
|
|
826
|
+
);
|
|
827
|
+
if (!bundledExports) {
|
|
828
|
+
throw new Error("Bundler did not produce an export analysis file.");
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
const exportNames = extractExportNamesFromEsmBundle(bundledExports.text);
|
|
832
|
+
if (exportNames.length === 0) {
|
|
833
|
+
throw new Error(
|
|
834
|
+
`No named exports were found in ${args.absEntryPoint}. Hosted deploy expects the entry point to export one or more workflows.`,
|
|
835
|
+
);
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
writeFileSync(
|
|
839
|
+
join(args.outputDir, "index.js"),
|
|
840
|
+
createBootstrapSource({
|
|
841
|
+
bundleBuffer: Buffer.from(bundledImplementation.contents),
|
|
842
|
+
deploymentName: args.deploymentName,
|
|
843
|
+
exportNames,
|
|
844
|
+
}),
|
|
845
|
+
);
|
|
846
|
+
} catch (error) {
|
|
847
|
+
throw new Error(
|
|
848
|
+
`Failed to bundle deploy entry point ${args.absEntryPoint}.\n${formatBuildError(error)}`,
|
|
849
|
+
);
|
|
850
|
+
}
|
|
851
|
+
}
|
|
852
|
+
|
|
853
|
+
export async function createHostedDeployPackage(
|
|
854
|
+
args: CreateHostedDeployPackageArgs,
|
|
855
|
+
): Promise<HostedDeployPackage> {
|
|
856
|
+
const absSourceDir = resolve(args.sourceDir);
|
|
857
|
+
ensureSourcePackageManifest(absSourceDir);
|
|
858
|
+
|
|
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) {
|
|
915
|
+
rmSync(tempRoot, { force: true, recursive: true });
|
|
916
|
+
}
|
|
917
|
+
}
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
export async function buildHostedDeployTarball(
|
|
921
|
+
args: BuildHostedDeployTarballArgs,
|
|
922
|
+
): Promise<{ entryPoint: string; source: string }> {
|
|
923
|
+
const deployPackage = await createHostedDeployPackage(args);
|
|
924
|
+
|
|
925
|
+
try {
|
|
926
|
+
const tarPath = join(dirname(deployPackage.outputDir), "source.tar.gz");
|
|
927
|
+
execFileSync("tar", ["czf", tarPath, "-C", deployPackage.outputDir, "."], {
|
|
928
|
+
stdio: "pipe",
|
|
929
|
+
});
|
|
930
|
+
|
|
931
|
+
return {
|
|
932
|
+
entryPoint: deployPackage.entryPoint,
|
|
933
|
+
source: readFileSync(tarPath).toString("base64"),
|
|
934
|
+
};
|
|
935
|
+
} finally {
|
|
936
|
+
deployPackage.cleanup();
|
|
937
|
+
}
|
|
938
|
+
}
|