libretto 0.6.30 → 0.6.32

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,15 +1,13 @@
1
1
  import { randomBytes } from "node:crypto";
2
2
  import { z } from "zod";
3
- import {
4
- orpcCall,
5
- resolveApiUrl,
6
- } from "../core/auth-fetch.js";
3
+ import { orpcCall } from "../core/auth-fetch.js";
7
4
  import {
8
5
  buildHostedDeployTarball,
9
6
  type WorkflowDeployMetadata,
10
7
  } from "../core/deploy-artifact.js";
11
8
  import { readAuthState } from "../core/auth-storage.js";
12
9
  import { SimpleCLI } from "affordance";
10
+ import { withCloudApiKey } from "./shared.js";
13
11
 
14
12
  type DeploymentStatus = "building" | "ready" | "failed";
15
13
 
@@ -53,19 +51,6 @@ function deployApiKeyRequiredMessage(hasStoredSession: boolean): string {
53
51
  ].join("\n");
54
52
  }
55
53
 
56
- async function requireDeployApiKey() {
57
- const apiKey = process.env.LIBRETTO_API_KEY?.trim();
58
-
59
- if (!apiKey) {
60
- throw new Error(deployApiKeyRequiredMessage(await hasStoredCloudSession()));
61
- }
62
-
63
- return {
64
- apiUrl: resolveApiUrl(null),
65
- credential: { source: "env-api-key" as const, apiKey },
66
- };
67
- }
68
-
69
54
  async function hasStoredCloudSession(): Promise<boolean> {
70
55
  try {
71
56
  return Boolean((await readAuthState())?.session);
@@ -188,8 +173,12 @@ export const deployCommand = SimpleCLI.command({
188
173
  description: "Deploy workflows to the hosted platform",
189
174
  })
190
175
  .input(deployInput)
191
- .handle(async ({ input }) => {
192
- const { apiUrl, credential } = await requireDeployApiKey();
176
+ .use(withCloudApiKey(
177
+ "deploy to Libretto Cloud",
178
+ async () => deployApiKeyRequiredMessage(await hasStoredCloudSession()),
179
+ ))
180
+ .handle(async ({ input, ctx }) => {
181
+ const { apiUrl, credential } = ctx;
193
182
  const deploymentName = generateDeploymentName();
194
183
 
195
184
  // Hosted deploy uploads a generated artifact with a deploy entrypoint and
@@ -1,7 +1,8 @@
1
1
  import { z } from "zod";
2
2
  import { SimpleCLI } from "affordance";
3
- import { orpcCall, resolveApiUrl } from "../core/auth-fetch.js";
3
+ import { orpcCall } from "../core/auth-fetch.js";
4
4
  import { normalizeProfileName } from "../core/profiles.js";
5
+ import { withCloudApiKey } from "./shared.js";
5
6
 
6
7
  type ListProfilesResponse = {
7
8
  profiles: Array<{
@@ -17,30 +18,17 @@ type DeleteProfileResponse = {
17
18
  deleted_count: number;
18
19
  };
19
20
 
20
- function requireApiKeyCredential() {
21
- const apiKey = process.env.LIBRETTO_API_KEY?.trim();
22
- if (!apiKey) {
23
- throw new Error(
24
- "LIBRETTO_API_KEY is required to manage Libretto Cloud profiles. Issue one with `libretto cloud auth api-key issue --label <label>`.",
25
- );
26
- }
27
- return {
28
- apiUrl: resolveApiUrl(null),
29
- credential: { source: "env-api-key" as const, apiKey },
30
- };
31
- }
32
-
33
21
  export const listProfilesCommand = SimpleCLI.command({
34
22
  description: "List Libretto Cloud auth profiles",
35
23
  })
36
24
  .input(SimpleCLI.input({ positionals: [], named: {} }))
37
- .handle(async () => {
38
- const { apiUrl, credential } = requireApiKeyCredential();
25
+ .use(withCloudApiKey("manage Libretto Cloud profiles"))
26
+ .handle(async ({ ctx }) => {
39
27
  const response = await orpcCall<ListProfilesResponse>({
40
- apiUrl,
28
+ apiUrl: ctx.apiUrl,
41
29
  path: "/v1/browserProfiles/list",
42
30
  input: {},
43
- credential,
31
+ credential: ctx.credential,
44
32
  });
45
33
  if (response.profiles.length === 0) {
46
34
  console.log("No cloud profiles found.");
@@ -65,14 +53,14 @@ export const deleteProfileCommand = SimpleCLI.command({
65
53
  ],
66
54
  named: {},
67
55
  }))
68
- .handle(async ({ input }) => {
56
+ .use(withCloudApiKey("manage Libretto Cloud profiles"))
57
+ .handle(async ({ input, ctx }) => {
69
58
  const profileName = normalizeProfileName(input.profileName);
70
- const { apiUrl, credential } = requireApiKeyCredential();
71
59
  const response = await orpcCall<DeleteProfileResponse>({
72
- apiUrl,
60
+ apiUrl: ctx.apiUrl,
73
61
  path: "/v1/browserProfiles/delete",
74
62
  input: { name: profileName },
75
- credential,
63
+ credential: ctx.credential,
76
64
  });
77
65
  if (!response.success || response.deleted_count === 0) {
78
66
  console.log(`No cloud profile found for ${profileName}.`);
@@ -9,6 +9,7 @@ import {
9
9
  type SessionState,
10
10
  validateSessionName,
11
11
  } from "../core/session.js";
12
+ import { resolveApiUrl } from "../core/auth-fetch.js";
12
13
  import {
13
14
  SimpleCLI,
14
15
  type SimpleCLIContext,
@@ -40,6 +41,11 @@ export type ExperimentsContext = {
40
41
  experiments: Experiments;
41
42
  };
42
43
 
44
+ export type CloudApiKeyContext = {
45
+ apiUrl: string;
46
+ credential: { source: "env-api-key"; apiKey: string };
47
+ };
48
+
43
49
  export function withExperiments<
44
50
  TContext extends SimpleCLIContext,
45
51
  >(): SimpleCLIMiddleware<unknown, TContext, TContext & ExperimentsContext> {
@@ -49,6 +55,29 @@ export function withExperiments<
49
55
  });
50
56
  }
51
57
 
58
+ export function withCloudApiKey<
59
+ TContext extends SimpleCLIContext,
60
+ >(
61
+ action: string,
62
+ formatMissingMessage?: () => string | Promise<string>,
63
+ ): SimpleCLIMiddleware<unknown, TContext, TContext & CloudApiKeyContext> {
64
+ return async ({ ctx }) => {
65
+ const apiKey = process.env.LIBRETTO_API_KEY?.trim();
66
+ if (!apiKey) {
67
+ throw new Error(
68
+ formatMissingMessage
69
+ ? await formatMissingMessage()
70
+ : `LIBRETTO_API_KEY is required to ${action}. Issue one with \`libretto cloud auth api-key issue --label <label>\`.`,
71
+ );
72
+ }
73
+ return {
74
+ ...ctx,
75
+ apiUrl: resolveApiUrl(null),
76
+ credential: { source: "env-api-key", apiKey },
77
+ };
78
+ };
79
+ }
80
+
52
81
  export function withRequiredSession(): SimpleCLIMiddleware<
53
82
  { session?: string },
54
83
  {},
@@ -2,7 +2,7 @@ import { createHash } from "node:crypto";
2
2
  import type { ChildProcess } from "node:child_process";
3
3
  import { spawn } from "node:child_process";
4
4
  import { openSync, closeSync } from "node:fs";
5
- import { createRequire } from "node:module";
5
+ import * as moduleBuiltin from "node:module";
6
6
  import { homedir, userInfo } from "node:os";
7
7
  import { fileURLToPath } from "node:url";
8
8
  import { createIpcPeer, type IpcPeer } from "../../../shared/ipc/ipc.js";
@@ -246,25 +246,34 @@ export class DaemonClient {
246
246
  const daemonEntryPath = fileURLToPath(
247
247
  new URL("./daemon.js", import.meta.url),
248
248
  );
249
- const require = createRequire(import.meta.url);
250
- const tsxCliPath = require.resolve("tsx/cli");
249
+ const childArgs = [daemonEntryPath, JSON.stringify(config)];
250
+ const childEnv: NodeJS.ProcessEnv = { ...process.env };
251
+
252
+ if (config.workflow) {
253
+ const tsxPreflightPath = fileURLToPath(
254
+ import.meta.resolve("tsx/preflight"),
255
+ );
256
+ const tsxLoaderFlag =
257
+ typeof moduleBuiltin.register === "function" ? "--import" : "--loader";
258
+
259
+ childArgs.unshift(
260
+ "--require",
261
+ tsxPreflightPath,
262
+ tsxLoaderFlag,
263
+ import.meta.resolve("tsx"),
264
+ );
265
+
266
+ if (config.workflow.tsconfigPath) {
267
+ childEnv.TSX_TSCONFIG_PATH = config.workflow.tsconfigPath;
268
+ }
269
+ }
251
270
 
252
271
  const childStderrFd = openSync(logPath, "a");
253
- const child = spawn(
254
- process.execPath,
255
- [
256
- tsxCliPath,
257
- ...(config.workflow?.tsconfigPath
258
- ? ["--tsconfig", config.workflow.tsconfigPath]
259
- : []),
260
- daemonEntryPath,
261
- JSON.stringify(config),
262
- ],
263
- {
264
- detached: true,
265
- stdio: ["ignore", "ignore", childStderrFd, "ipc"],
266
- },
267
- );
272
+ const child = spawn(process.execPath, childArgs, {
273
+ detached: true,
274
+ stdio: ["ignore", "ignore", childStderrFd, "ipc"],
275
+ env: childEnv,
276
+ });
268
277
  closeSync(childStderrFd);
269
278
 
270
279
  const pid = child.pid!;
@@ -692,6 +692,10 @@ function toPortableRelativePath(args: {
692
692
  return relPath;
693
693
  }
694
694
 
695
+ function isShareableSourceRelPath(relPath: string): boolean {
696
+ return !relPath.split("/").includes("node_modules");
697
+ }
698
+
695
699
  function writeShareableSourceFiles(args: {
696
700
  absSourceDir: string;
697
701
  absSourcePaths: readonly string[];
@@ -702,7 +706,7 @@ function writeShareableSourceFiles(args: {
702
706
  absPath,
703
707
  absSourceDir: args.absSourceDir,
704
708
  }),
705
- ))].sort();
709
+ ))].filter(isShareableSourceRelPath).sort();
706
710
 
707
711
  for (const relPath of relPaths) {
708
712
  const targetPath = join(args.outputDir, ".libretto-share", "source", relPath);
@@ -1209,7 +1213,8 @@ async function writeBundledDeployEntrypoint(args: {
1209
1213
  relPath !== "" &&
1210
1214
  !relPath.startsWith("../") &&
1211
1215
  relPath !== ".." &&
1212
- !isAbsolute(relPath)
1216
+ !isAbsolute(relPath) &&
1217
+ isShareableSourceRelPath(relPath.replaceAll("\\", "/"))
1213
1218
  );
1214
1219
  });
1215
1220
  return { shareableSourceFiles, workflows };
package/src/cli/router.ts CHANGED
@@ -2,6 +2,9 @@ import { authCommands } from "./commands/auth.js";
2
2
  import { billingCommands } from "./commands/billing.js";
3
3
  import { browserCommands } from "./commands/browser.js";
4
4
  import { cloudCredentialCommands } from "./commands/cloud-credentials.js";
5
+ import { cloudJobCommands } from "./commands/cloud-jobs.js";
6
+ import { cloudScheduleCommands } from "./commands/cloud-schedules.js";
7
+ import { settingsCommands } from "./commands/cloud-settings.js";
5
8
  import { codeSharingCommands, shareWorkflowCommand } from "./commands/cloud-sharing.js";
6
9
  import { deployCommand } from "./commands/deploy.js";
7
10
  import { executionCommands } from "./commands/execution.js";
@@ -25,7 +28,10 @@ export const cliRoutes = {
25
28
  auth: authCommands,
26
29
  billing: billingCommands,
27
30
  credentials: cloudCredentialCommands,
31
+ jobs: cloudJobCommands,
28
32
  profiles: profileCommands,
33
+ schedules: cloudScheduleCommands,
34
+ settings: settingsCommands,
29
35
  share: shareWorkflowCommand,
30
36
  sharing: codeSharingCommands,
31
37
  },