libretto 0.5.2 → 0.5.3-experimental.1
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 +3 -2
- package/dist/cli/commands/deploy.js +162 -0
- package/dist/cli/commands/execution.js +38 -12
- package/dist/cli/framework/simple-cli.js +6 -0
- package/dist/cli/router.js +3 -1
- package/dist/cli/workers/run-integration-runtime.js +18 -41
- package/dist/cli/workers/run-integration-worker-protocol.js +2 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +6 -0
- package/dist/shared/workflow/workflow.d.ts +14 -3
- package/dist/shared/workflow/workflow.js +50 -3
- package/package.json +7 -4
- package/scripts/check-skills-sync.mjs +1 -1
- package/scripts/generate-changelog.ts +132 -0
- package/scripts/skills-libretto.mjs +1 -1
- package/scripts/sync-skills.mjs +1 -1
- package/skills/libretto/SKILL.md +4 -2
- package/skills/libretto/references/code-generation-rules.md +6 -4
- package/src/cli/commands/deploy.ts +209 -0
- package/src/cli/commands/execution.ts +39 -11
- package/src/cli/framework/simple-cli.ts +9 -0
- package/src/cli/router.ts +2 -0
- package/src/cli/workers/run-integration-runtime.ts +24 -52
- package/src/cli/workers/run-integration-worker-protocol.ts +2 -1
- package/src/index.ts +4 -0
- package/src/shared/workflow/workflow.ts +88 -2
- package/scripts/prepare-release.sh +0 -97
|
@@ -5,8 +5,11 @@ import { cwd } from "node:process";
|
|
|
5
5
|
import { isAbsolute, resolve } from "node:path";
|
|
6
6
|
import { pathToFileURL } from "node:url";
|
|
7
7
|
import {
|
|
8
|
+
getWorkflowFromModuleExports,
|
|
9
|
+
getWorkflowsFromModuleExports,
|
|
8
10
|
instrumentContext,
|
|
9
11
|
launchBrowser,
|
|
12
|
+
type ExportedLibrettoWorkflow,
|
|
10
13
|
type LibrettoWorkflowContext,
|
|
11
14
|
} from "../../index.js";
|
|
12
15
|
import type { LoggerApi } from "../../shared/logger/index.js";
|
|
@@ -25,9 +28,7 @@ import {
|
|
|
25
28
|
import { installSessionTelemetry } from "../core/session-telemetry.js";
|
|
26
29
|
import type { RunIntegrationWorkerRequest } from "./run-integration-worker-protocol.js";
|
|
27
30
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
type LoadedLibrettoWorkflow = {
|
|
31
|
+
type LoadedLibrettoWorkflow = ExportedLibrettoWorkflow & {
|
|
31
32
|
run: (ctx: LibrettoWorkflowContext, input: unknown) => Promise<unknown>;
|
|
32
33
|
};
|
|
33
34
|
|
|
@@ -108,17 +109,6 @@ async function waitForFailureSessionRelease(args: {
|
|
|
108
109
|
}
|
|
109
110
|
}
|
|
110
111
|
|
|
111
|
-
function isLoadedLibrettoWorkflow(
|
|
112
|
-
value: unknown,
|
|
113
|
-
): value is LoadedLibrettoWorkflow {
|
|
114
|
-
if (!value || typeof value !== "object") return false;
|
|
115
|
-
const candidate = value as Record<PropertyKey, unknown>;
|
|
116
|
-
return (
|
|
117
|
-
candidate[LIBRETTO_WORKFLOW_BRAND] === true &&
|
|
118
|
-
typeof candidate.run === "function"
|
|
119
|
-
);
|
|
120
|
-
}
|
|
121
|
-
|
|
122
112
|
function resolveLocalAuthProfilePath(domain: string): string {
|
|
123
113
|
return getProfilePath(normalizeDomain(domain));
|
|
124
114
|
}
|
|
@@ -149,9 +139,9 @@ function getAbsoluteIntegrationPath(integrationPath: string): string {
|
|
|
149
139
|
return absolutePath;
|
|
150
140
|
}
|
|
151
141
|
|
|
152
|
-
async function
|
|
142
|
+
async function loadWorkflowByName(
|
|
153
143
|
absolutePath: string,
|
|
154
|
-
|
|
144
|
+
workflowName: string,
|
|
155
145
|
): Promise<LoadedLibrettoWorkflow> {
|
|
156
146
|
let loadedModule: Record<string, unknown>;
|
|
157
147
|
try {
|
|
@@ -167,42 +157,23 @@ async function loadWorkflowExport(
|
|
|
167
157
|
);
|
|
168
158
|
}
|
|
169
159
|
|
|
170
|
-
const
|
|
171
|
-
if (
|
|
172
|
-
|
|
173
|
-
const detail =
|
|
174
|
-
availableExports.length > 0
|
|
175
|
-
? ` Available exports: ${availableExports.join(", ")}`
|
|
176
|
-
: " The module has no exports.";
|
|
177
|
-
throw new Error(
|
|
178
|
-
`Export "${exportName}" was not found in ${absolutePath}.${detail}`,
|
|
179
|
-
);
|
|
160
|
+
const workflow = getWorkflowFromModuleExports(loadedModule, workflowName);
|
|
161
|
+
if (workflow) {
|
|
162
|
+
return workflow as LoadedLibrettoWorkflow;
|
|
180
163
|
}
|
|
181
164
|
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
"",
|
|
191
|
-
` export const ${exportName} = workflow<InputType, OutputType>(`,
|
|
192
|
-
" async (ctx, input) => {",
|
|
193
|
-
" // ctx.session — libretto session name",
|
|
194
|
-
" // ctx.page — Playwright Page instance",
|
|
195
|
-
" // ctx.logger — MinimalLogger",
|
|
196
|
-
" // ctx.services — injected dependencies (generic, default {})",
|
|
197
|
-
" // input — JSON-serializable input matching InputType",
|
|
198
|
-
" return output; // must match OutputType",
|
|
199
|
-
" },",
|
|
200
|
-
" );",
|
|
201
|
-
].join("\n"),
|
|
202
|
-
);
|
|
203
|
-
}
|
|
165
|
+
const availableWorkflows = getWorkflowsFromModuleExports(loadedModule).map(
|
|
166
|
+
(candidate) => candidate.name,
|
|
167
|
+
);
|
|
168
|
+
|
|
169
|
+
const detail =
|
|
170
|
+
availableWorkflows.length > 0
|
|
171
|
+
? ` Available workflows: ${availableWorkflows.join(", ")}`
|
|
172
|
+
: ' No workflows found in this file. Export a workflow() instance from "libretto" directly or via `export const workflows = { ... }`.';
|
|
204
173
|
|
|
205
|
-
|
|
174
|
+
throw new Error(
|
|
175
|
+
`Workflow "${workflowName}" not found in ${absolutePath}.${detail}`,
|
|
176
|
+
);
|
|
206
177
|
}
|
|
207
178
|
|
|
208
179
|
export async function installHeadedWorkflowVisualization(args: {
|
|
@@ -224,7 +195,7 @@ async function runIntegrationInternal(
|
|
|
224
195
|
): Promise<RunIntegrationOutcome> {
|
|
225
196
|
const { logger } = options;
|
|
226
197
|
const absolutePath = getAbsoluteIntegrationPath(args.integrationPath);
|
|
227
|
-
const workflow = await
|
|
198
|
+
const workflow = await loadWorkflowByName(absolutePath, args.workflowName);
|
|
228
199
|
const signalPaths = getPauseSignalPaths(args.session);
|
|
229
200
|
await removeSignalIfExists(signalPaths.pausedSignalPath);
|
|
230
201
|
await removeSignalIfExists(signalPaths.resumeSignalPath);
|
|
@@ -233,12 +204,12 @@ async function runIntegrationInternal(
|
|
|
233
204
|
const restoreStdout = mirrorStdoutToFile(signalPaths.outputSignalPath);
|
|
234
205
|
|
|
235
206
|
console.log(
|
|
236
|
-
`Running
|
|
207
|
+
`Running workflow "${args.workflowName}" from ${absolutePath} (${args.headless ? "headless" : "headed"})...`,
|
|
237
208
|
);
|
|
238
209
|
|
|
239
210
|
const integrationLogger = logger.withScope("integration-run", {
|
|
240
211
|
integrationPath: absolutePath,
|
|
241
|
-
|
|
212
|
+
workflowName: args.workflowName,
|
|
242
213
|
session: args.session,
|
|
243
214
|
});
|
|
244
215
|
|
|
@@ -287,6 +258,7 @@ async function runIntegrationInternal(
|
|
|
287
258
|
logger: integrationLogger,
|
|
288
259
|
page: browserSession.page,
|
|
289
260
|
services: {},
|
|
261
|
+
credentials: args.credentials,
|
|
290
262
|
};
|
|
291
263
|
|
|
292
264
|
try {
|
|
@@ -2,9 +2,10 @@ import { z } from "zod";
|
|
|
2
2
|
|
|
3
3
|
export const RunIntegrationWorkerRequestSchema = z.object({
|
|
4
4
|
integrationPath: z.string().min(1),
|
|
5
|
-
|
|
5
|
+
workflowName: z.string().min(1),
|
|
6
6
|
session: z.string().min(1),
|
|
7
7
|
params: z.unknown(),
|
|
8
|
+
credentials: z.record(z.string(), z.unknown()).optional(),
|
|
8
9
|
headless: z.boolean(),
|
|
9
10
|
visualize: z.boolean().default(true),
|
|
10
11
|
authProfileDomain: z.string().optional(),
|
package/src/index.ts
CHANGED
|
@@ -102,9 +102,13 @@ export {
|
|
|
102
102
|
|
|
103
103
|
// Workflow helpers
|
|
104
104
|
export {
|
|
105
|
+
getWorkflowFromModuleExports,
|
|
106
|
+
getWorkflowsFromModuleExports,
|
|
107
|
+
isLibrettoWorkflow,
|
|
105
108
|
LibrettoWorkflow,
|
|
106
109
|
LIBRETTO_WORKFLOW_BRAND,
|
|
107
110
|
workflow,
|
|
111
|
+
type ExportedLibrettoWorkflow,
|
|
108
112
|
type LibrettoWorkflowContext,
|
|
109
113
|
type LibrettoWorkflowHandler,
|
|
110
114
|
} from "./shared/workflow/workflow.js";
|
|
@@ -8,6 +8,7 @@ export type LibrettoWorkflowContext<S = {}> = {
|
|
|
8
8
|
page: Page;
|
|
9
9
|
logger: MinimalLogger;
|
|
10
10
|
services: S;
|
|
11
|
+
credentials?: Record<string, unknown>;
|
|
11
12
|
};
|
|
12
13
|
|
|
13
14
|
export type LibrettoWorkflowHandler<
|
|
@@ -18,9 +19,14 @@ export type LibrettoWorkflowHandler<
|
|
|
18
19
|
|
|
19
20
|
export class LibrettoWorkflow<Input = unknown, Output = unknown, S = {}> {
|
|
20
21
|
public readonly [LIBRETTO_WORKFLOW_BRAND] = true;
|
|
22
|
+
public readonly name: string;
|
|
21
23
|
private readonly handler: LibrettoWorkflowHandler<Input, Output, S>;
|
|
22
24
|
|
|
23
|
-
constructor(
|
|
25
|
+
constructor(
|
|
26
|
+
name: string,
|
|
27
|
+
handler: LibrettoWorkflowHandler<Input, Output, S>,
|
|
28
|
+
) {
|
|
29
|
+
this.name = name;
|
|
24
30
|
this.handler = handler;
|
|
25
31
|
}
|
|
26
32
|
|
|
@@ -29,8 +35,88 @@ export class LibrettoWorkflow<Input = unknown, Output = unknown, S = {}> {
|
|
|
29
35
|
}
|
|
30
36
|
}
|
|
31
37
|
|
|
38
|
+
export type ExportedLibrettoWorkflow = {
|
|
39
|
+
readonly [LIBRETTO_WORKFLOW_BRAND]: true;
|
|
40
|
+
readonly name: string;
|
|
41
|
+
run: (ctx: LibrettoWorkflowContext, input: unknown) => Promise<unknown>;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
type WorkflowModuleExports = Record<string, unknown>;
|
|
45
|
+
|
|
46
|
+
// Use the workflow brand instead of `instanceof` so imported workflows are
|
|
47
|
+
// still recognized after loading the integration module dynamically.
|
|
48
|
+
export function isLibrettoWorkflow(
|
|
49
|
+
value: unknown,
|
|
50
|
+
): value is ExportedLibrettoWorkflow {
|
|
51
|
+
if (!value || typeof value !== "object") return false;
|
|
52
|
+
const candidate = value as Record<PropertyKey, unknown>;
|
|
53
|
+
return (
|
|
54
|
+
candidate[LIBRETTO_WORKFLOW_BRAND] === true &&
|
|
55
|
+
typeof candidate.name === "string" &&
|
|
56
|
+
typeof candidate.run === "function"
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function addWorkflowOrThrow(
|
|
61
|
+
workflowsByName: Map<string, ExportedLibrettoWorkflow>,
|
|
62
|
+
value: unknown,
|
|
63
|
+
): void {
|
|
64
|
+
if (!isLibrettoWorkflow(value)) return;
|
|
65
|
+
|
|
66
|
+
// Re-exporting the same workflow object is fine, but two distinct workflow
|
|
67
|
+
// instances cannot claim the same runtime name.
|
|
68
|
+
const existing = workflowsByName.get(value.name);
|
|
69
|
+
if (existing && existing !== value) {
|
|
70
|
+
throw new Error(
|
|
71
|
+
`Duplicate workflow name: "${value.name}". Each workflow() call must use a unique name.`,
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
workflowsByName.set(value.name, value);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export function getWorkflowsFromModuleExports(
|
|
79
|
+
moduleExports: WorkflowModuleExports,
|
|
80
|
+
): ExportedLibrettoWorkflow[] {
|
|
81
|
+
const workflowsByName = new Map<string, ExportedLibrettoWorkflow>();
|
|
82
|
+
|
|
83
|
+
for (const [exportName, value] of Object.entries(moduleExports)) {
|
|
84
|
+
if (exportName === "workflows" && value && typeof value === "object") {
|
|
85
|
+
// Support both `export const workflows = workflow(...)` and
|
|
86
|
+
// `export const workflows = { myWorkflow }`.
|
|
87
|
+
if (isLibrettoWorkflow(value)) {
|
|
88
|
+
addWorkflowOrThrow(workflowsByName, value);
|
|
89
|
+
} else {
|
|
90
|
+
for (const nestedValue of Object.values(
|
|
91
|
+
value as Record<string, unknown>,
|
|
92
|
+
)) {
|
|
93
|
+
addWorkflowOrThrow(workflowsByName, nestedValue);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
continue;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
addWorkflowOrThrow(workflowsByName, value);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
return [...workflowsByName.values()];
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function getWorkflowFromModuleExports(
|
|
106
|
+
moduleExports: WorkflowModuleExports,
|
|
107
|
+
workflowName: string,
|
|
108
|
+
): ExportedLibrettoWorkflow | null {
|
|
109
|
+
for (const workflow of getWorkflowsFromModuleExports(moduleExports)) {
|
|
110
|
+
if (workflow.name === workflowName) {
|
|
111
|
+
return workflow;
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
return null;
|
|
115
|
+
}
|
|
116
|
+
|
|
32
117
|
export function workflow<Input = unknown, Output = unknown, S = {}>(
|
|
118
|
+
name: string,
|
|
33
119
|
handler: LibrettoWorkflowHandler<Input, Output, S>,
|
|
34
120
|
): LibrettoWorkflow<Input, Output, S> {
|
|
35
|
-
return new LibrettoWorkflow(handler);
|
|
121
|
+
return new LibrettoWorkflow(name, handler);
|
|
36
122
|
}
|
|
@@ -1,97 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env bash
|
|
2
|
-
set -euo pipefail
|
|
3
|
-
|
|
4
|
-
usage() {
|
|
5
|
-
cat <<'EOF'
|
|
6
|
-
Usage: scripts/prepare-release.sh [patch|minor|major]
|
|
7
|
-
|
|
8
|
-
Creates a release PR branch from main, bumps package.json, pushes the branch,
|
|
9
|
-
and opens a pull request targeting main.
|
|
10
|
-
EOF
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
bump="${1:-patch}"
|
|
14
|
-
|
|
15
|
-
case "$bump" in
|
|
16
|
-
patch|minor|major)
|
|
17
|
-
;;
|
|
18
|
-
-h|--help|help)
|
|
19
|
-
usage
|
|
20
|
-
exit 0
|
|
21
|
-
;;
|
|
22
|
-
*)
|
|
23
|
-
echo "Invalid bump type: $bump" >&2
|
|
24
|
-
usage >&2
|
|
25
|
-
exit 1
|
|
26
|
-
;;
|
|
27
|
-
esac
|
|
28
|
-
|
|
29
|
-
if ! command -v gh >/dev/null 2>&1; then
|
|
30
|
-
echo "gh CLI is required." >&2
|
|
31
|
-
exit 1
|
|
32
|
-
fi
|
|
33
|
-
|
|
34
|
-
if [ -n "$(git status --porcelain)" ]; then
|
|
35
|
-
echo "Working tree must be clean before preparing a release." >&2
|
|
36
|
-
exit 1
|
|
37
|
-
fi
|
|
38
|
-
|
|
39
|
-
current_branch="$(git branch --show-current)"
|
|
40
|
-
if [ "$current_branch" != "main" ]; then
|
|
41
|
-
echo "Switching from $current_branch to main."
|
|
42
|
-
fi
|
|
43
|
-
|
|
44
|
-
git fetch origin
|
|
45
|
-
git checkout main
|
|
46
|
-
git pull --ff-only origin main
|
|
47
|
-
|
|
48
|
-
pnpm install --frozen-lockfile
|
|
49
|
-
pnpm type-check
|
|
50
|
-
pnpm test
|
|
51
|
-
|
|
52
|
-
current_version="$(node -p "require('./package.json').version")"
|
|
53
|
-
next_version="$(node -e '
|
|
54
|
-
const [major, minor, patch] = process.argv[1].split(".").map(Number)
|
|
55
|
-
const bump = process.argv[2]
|
|
56
|
-
|
|
57
|
-
let next
|
|
58
|
-
if (bump === "major") next = [major + 1, 0, 0]
|
|
59
|
-
else if (bump === "minor") next = [major, minor + 1, 0]
|
|
60
|
-
else next = [major, minor, patch + 1]
|
|
61
|
-
|
|
62
|
-
process.stdout.write(next.join("."))
|
|
63
|
-
' "$current_version" "$bump")"
|
|
64
|
-
branch_name="tk-release-v${next_version}"
|
|
65
|
-
|
|
66
|
-
if git show-ref --verify --quiet "refs/heads/${branch_name}"; then
|
|
67
|
-
echo "Local branch ${branch_name} already exists." >&2
|
|
68
|
-
exit 1
|
|
69
|
-
fi
|
|
70
|
-
|
|
71
|
-
if git ls-remote --exit-code --heads origin "${branch_name}" >/dev/null 2>&1; then
|
|
72
|
-
echo "Remote branch ${branch_name} already exists." >&2
|
|
73
|
-
exit 1
|
|
74
|
-
fi
|
|
75
|
-
|
|
76
|
-
npm version "$next_version" --no-git-tag-version >/dev/null
|
|
77
|
-
|
|
78
|
-
git checkout -b "$branch_name"
|
|
79
|
-
git add package.json
|
|
80
|
-
git commit -m "release: v${next_version}"
|
|
81
|
-
git push -u origin "$branch_name"
|
|
82
|
-
|
|
83
|
-
gh pr create \
|
|
84
|
-
--base main \
|
|
85
|
-
--head "$branch_name" \
|
|
86
|
-
--title "release: v${next_version}" \
|
|
87
|
-
--body "$(cat <<EOF
|
|
88
|
-
## Summary
|
|
89
|
-
|
|
90
|
-
- release libretto v${next_version}
|
|
91
|
-
|
|
92
|
-
## Verification
|
|
93
|
-
|
|
94
|
-
- pnpm type-check
|
|
95
|
-
- pnpm test
|
|
96
|
-
EOF
|
|
97
|
-
)"
|