@qawolf/cli 1.24.0 → 1.26.0
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/dist/cli.js +2116 -678
- package/dist/runner-sdk.js +724 -146
- package/package.json +2 -2
- package/skills/qawolf-cli/SKILL.md +6 -3
package/dist/cli.js
CHANGED
|
@@ -8390,11 +8390,82 @@ function extractMissingPackage(text) {
|
|
|
8390
8390
|
return specifier;
|
|
8391
8391
|
}
|
|
8392
8392
|
|
|
8393
|
+
// src/core/pluralize.ts
|
|
8394
|
+
function pluralize(count, singular, plural = `${singular}s`) {
|
|
8395
|
+
return `${String(count)} ${count === 1 ? singular : plural}`;
|
|
8396
|
+
}
|
|
8397
|
+
|
|
8393
8398
|
// src/core/formatSeconds.ts
|
|
8394
8399
|
function formatSeconds(ms) {
|
|
8395
8400
|
return `${Math.round(ms / 1000)}s`;
|
|
8396
8401
|
}
|
|
8397
8402
|
|
|
8403
|
+
// src/core/messages/authErrors.ts
|
|
8404
|
+
var authErrorMessages = {
|
|
8405
|
+
identity: {
|
|
8406
|
+
invalidOrUnauthorized: "API key is invalid or unauthorized",
|
|
8407
|
+
unexpectedFormat: "Could not verify API key: unexpected response format",
|
|
8408
|
+
couldNotVerify: (detail, status) => `Could not verify API key: ${detail || `HTTP ${status}`}`,
|
|
8409
|
+
couldNotVerifyNetwork: (cause) => `Could not verify API key: ${cause}`,
|
|
8410
|
+
timedOut: (timeoutMs) => `Could not verify API key: the QA Wolf API did not answer within ${formatSeconds(timeoutMs)}.`
|
|
8411
|
+
},
|
|
8412
|
+
request: {
|
|
8413
|
+
rejected401: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 401). Check your API key.`,
|
|
8414
|
+
rejected402: (noun) => `QA Wolf API refused the${noun ? ` ${noun}` : ""} request (HTTP 402): billing prevented it.`,
|
|
8415
|
+
rejected403: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 403). Check that your API key has access to this environment.`,
|
|
8416
|
+
notFound404: (noun) => `QA Wolf API could not find ${noun ? `${noun} for that environment` : "that environment"} (HTTP 404). Check the --env value.`,
|
|
8417
|
+
failedWithStatus: (status, noun) => `QA Wolf API${noun ? ` ${noun}` : ""} request failed (HTTP ${status}).`,
|
|
8418
|
+
networkUnreachable: (baseUrl, noun) => `Could not reach the QA Wolf API at ${baseUrl}${noun ? ` to fetch ${noun}` : ""}. Check your network connection and QAWOLF_HOST_URL.`,
|
|
8419
|
+
timedOut: (timeoutMs, noun) => `The QA Wolf API${noun ? ` ${noun}` : ""} request timed out after ${formatSeconds(timeoutMs)}. The work may still be finishing on the platform.`,
|
|
8420
|
+
unexpectedResponse: (noun) => `Unexpected${noun ? ` ${noun}` : ""} response from the QA Wolf API.`
|
|
8421
|
+
},
|
|
8422
|
+
workos: {
|
|
8423
|
+
unexpectedResponse: "WorkOS returned an unexpected response",
|
|
8424
|
+
unexpectedResponseWithStatus: (status) => `WorkOS returned an unexpected response (HTTP ${status})`,
|
|
8425
|
+
unreachable: (detail) => `Could not reach WorkOS: ${detail}`,
|
|
8426
|
+
redirected: "WorkOS answered with a redirect, which the CLI does not follow for a sign-in request",
|
|
8427
|
+
noClientForSession: "This session names no WorkOS client"
|
|
8428
|
+
},
|
|
8429
|
+
bundle: {
|
|
8430
|
+
linkExpired: "The flow bundle download link has expired. Please run `qawolf flows pull` again to refresh.",
|
|
8431
|
+
failedWithStatus: (status) => `Could not download the flow bundle (HTTP ${status}).`,
|
|
8432
|
+
networkUnreachable: "Could not reach the flow bundle storage. Check your network connection and try again.",
|
|
8433
|
+
timedOut: (timeoutMs) => `Downloading the flow bundle stalled — no data arrived for ${formatSeconds(timeoutMs)}. Please try again.`,
|
|
8434
|
+
malformed: "The flow bundle download was malformed. Please run `qawolf flows pull` again."
|
|
8435
|
+
}
|
|
8436
|
+
};
|
|
8437
|
+
|
|
8438
|
+
// src/core/messages/whoami.ts
|
|
8439
|
+
var whoamiMessages = {
|
|
8440
|
+
source: (source) => `Source: ${source}`,
|
|
8441
|
+
authFailed: (source, error) => `Authentication failed (source: ${source}): ${error}`,
|
|
8442
|
+
authenticatedAs: (teamName, source) => `Authenticated as ${teamName} (source: ${source})`,
|
|
8443
|
+
teamNote: (input) => [
|
|
8444
|
+
`Workspace: ${input.team.name}`,
|
|
8445
|
+
`ID: ${input.team.id}`,
|
|
8446
|
+
input.team.slug ? `Slug: ${input.team.slug}` : undefined,
|
|
8447
|
+
input.teamUrl ? `URL: ${input.teamUrl}` : undefined,
|
|
8448
|
+
`Source: ${input.source}`
|
|
8449
|
+
].filter((line) => Boolean(line)).join(`
|
|
8450
|
+
`),
|
|
8451
|
+
organizationNote: (input) => [
|
|
8452
|
+
`Organization: ${input.organization.name}`,
|
|
8453
|
+
`ID: ${input.organization.id}`,
|
|
8454
|
+
`Source: ${input.source}`
|
|
8455
|
+
].join(`
|
|
8456
|
+
`),
|
|
8457
|
+
activeWorkspace: (workspaceId, found) => found ? `${found.workspace} (${found.organization})` : `${workspaceId} — no longer in reach; run 'qawolf auth switch'`,
|
|
8458
|
+
userNote: (input) => [
|
|
8459
|
+
`User: ${input.user.email}`,
|
|
8460
|
+
`ID: ${input.user.id}`,
|
|
8461
|
+
`Organization: ${input.organization.name}`,
|
|
8462
|
+
input.activeWorkspace ? `Workspace: ${input.activeWorkspace}` : undefined,
|
|
8463
|
+
input.organizations.length > 0 ? `Workspaces: ${input.organizations.map((o) => `${o.name}: ${o.workspaces.map((w) => w.name).join(", ")}`).join(" | ")}` : undefined,
|
|
8464
|
+
`Source: ${input.source}`
|
|
8465
|
+
].filter((line) => Boolean(line)).join(`
|
|
8466
|
+
`)
|
|
8467
|
+
};
|
|
8468
|
+
|
|
8398
8469
|
// src/core/messages/auth.ts
|
|
8399
8470
|
var authMessages = {
|
|
8400
8471
|
title: "QA Wolf Authentication",
|
|
@@ -8412,7 +8483,47 @@ var authMessages = {
|
|
|
8412
8483
|
whoamiFailed: "Not authenticated",
|
|
8413
8484
|
login: {
|
|
8414
8485
|
nonInteractive: "auth login requires an interactive terminal. Set the QAWOLF_API_KEY environment variable for CI authentication.",
|
|
8415
|
-
reAuthPrompt: "You are already authenticated. Re-authenticate?"
|
|
8486
|
+
reAuthPrompt: "You are already authenticated. Re-authenticate?",
|
|
8487
|
+
chooseMethod: "How do you want to sign in?",
|
|
8488
|
+
methodBrowser: "Browser",
|
|
8489
|
+
methodBrowserHint: "Sign in with your QA Wolf account",
|
|
8490
|
+
methodApiKey: "API key",
|
|
8491
|
+
methodApiKeyHint: "Paste a team key — needed for flows pull and flows run",
|
|
8492
|
+
apiKeyPrecedence: {
|
|
8493
|
+
env: "QAWOLF_API_KEY is set, and an API key takes precedence over a browser session. Commands continue to use that key until you unset the variable.",
|
|
8494
|
+
stored: "A stored API key takes precedence over a browser session. Commands continue to use that key until you run 'qawolf auth logout' and sign in again."
|
|
8495
|
+
}
|
|
8496
|
+
},
|
|
8497
|
+
device: {
|
|
8498
|
+
unavailable: "This QA Wolf deployment does not offer browser sign-in. Run 'qawolf auth login' again and choose 'API key'.",
|
|
8499
|
+
configUnreachable: "Could not ask this QA Wolf deployment whether it offers browser sign-in. Check your connection, then try again.",
|
|
8500
|
+
confirmCode: (userCode) => `Your code is ${userCode}`,
|
|
8501
|
+
visitUrl: (url) => `Confirm it at ${url}`,
|
|
8502
|
+
visitUrlPlain: (url) => `Or go to ${url} and enter the code`,
|
|
8503
|
+
openFailed: (url) => `Could not open a browser automatically. Open ${url} yourself to continue.`,
|
|
8504
|
+
waiting: "Waiting for you to finish in the browser",
|
|
8505
|
+
signedIn: (email) => `Signed in as ${email}.`,
|
|
8506
|
+
failed: {
|
|
8507
|
+
"access-denied": "The sign-in request was rejected.",
|
|
8508
|
+
expired: "The sign-in request expired. Run 'qawolf auth login' to retry.",
|
|
8509
|
+
timeout: "The sign-in request timed out. Run 'qawolf auth login' to retry.",
|
|
8510
|
+
network: "Could not reach WorkOS to complete sign-in.",
|
|
8511
|
+
unavailable: "Could not start browser sign-in.",
|
|
8512
|
+
cancelled: "Sign-in cancelled."
|
|
8513
|
+
}
|
|
8514
|
+
},
|
|
8515
|
+
workspace: {
|
|
8516
|
+
chooseOrganization: "Which organization do you want to work in?",
|
|
8517
|
+
choose: "Which workspace do you want to use?",
|
|
8518
|
+
workspaceCount: (count) => pluralize(count, "workspace"),
|
|
8519
|
+
working: (organization, workspace) => `Working in ${workspace} (${organization}).`,
|
|
8520
|
+
none: "This account reaches no organizations yet.",
|
|
8521
|
+
cancelled: "Workspace not changed.",
|
|
8522
|
+
notSignedIn: "Workspace switching needs a browser sign-in. Run 'qawolf auth login' and choose Browser.",
|
|
8523
|
+
sessionExpired: "Your session could not be renewed. Run 'qawolf auth login' to sign in again.",
|
|
8524
|
+
nonInteractive: "auth switch needs an interactive terminal, or set QAWOLF_WORKSPACE to name a workspace.",
|
|
8525
|
+
sessionChanged: "The stored sign-in changed while you made your choice. Run the command again.",
|
|
8526
|
+
saveFailed: (detail) => `Could not save the workspace choice: ${detail}`
|
|
8416
8527
|
},
|
|
8417
8528
|
logout: {
|
|
8418
8529
|
title: "Log Out",
|
|
@@ -8424,58 +8535,8 @@ var authMessages = {
|
|
|
8424
8535
|
success: "Logged out successfully.",
|
|
8425
8536
|
cancelled: "Logout cancelled."
|
|
8426
8537
|
},
|
|
8427
|
-
errors:
|
|
8428
|
-
|
|
8429
|
-
invalidOrUnauthorized: "API key is invalid or unauthorized",
|
|
8430
|
-
unexpectedFormat: "Could not verify API key: unexpected response format",
|
|
8431
|
-
couldNotVerify: (detail, status) => `Could not verify API key: ${detail || `HTTP ${status}`}`,
|
|
8432
|
-
couldNotVerifyNetwork: (cause) => `Could not verify API key: ${cause}`,
|
|
8433
|
-
timedOut: (timeoutMs) => `Could not verify API key: the QA Wolf API did not answer within ${formatSeconds(timeoutMs)}.`
|
|
8434
|
-
},
|
|
8435
|
-
request: {
|
|
8436
|
-
rejected401: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 401). Check your API key.`,
|
|
8437
|
-
rejected402: (noun) => `QA Wolf API refused the${noun ? ` ${noun}` : ""} request (HTTP 402): billing prevented it.`,
|
|
8438
|
-
rejected403: (noun) => `QA Wolf API rejected the${noun ? ` ${noun}` : ""} request (HTTP 403). Check that your API key has access to this environment.`,
|
|
8439
|
-
notFound404: (noun) => `QA Wolf API could not find ${noun ? `${noun} for that environment` : "that environment"} (HTTP 404). Check the --env value.`,
|
|
8440
|
-
failedWithStatus: (status, noun) => `QA Wolf API${noun ? ` ${noun}` : ""} request failed (HTTP ${status}).`,
|
|
8441
|
-
networkUnreachable: (baseUrl, noun) => `Could not reach the QA Wolf API at ${baseUrl}${noun ? ` to fetch ${noun}` : ""}. Check your network connection and QAWOLF_HOST_URL.`,
|
|
8442
|
-
timedOut: (timeoutMs, noun) => `The QA Wolf API${noun ? ` ${noun}` : ""} request timed out after ${formatSeconds(timeoutMs)}. The work may still be finishing on the platform.`,
|
|
8443
|
-
unexpectedResponse: (noun) => `Unexpected${noun ? ` ${noun}` : ""} response from the QA Wolf API.`
|
|
8444
|
-
},
|
|
8445
|
-
bundle: {
|
|
8446
|
-
linkExpired: "The flow bundle download link has expired. Please run `qawolf flows pull` again to refresh.",
|
|
8447
|
-
failedWithStatus: (status) => `Could not download the flow bundle (HTTP ${status}).`,
|
|
8448
|
-
networkUnreachable: "Could not reach the flow bundle storage. Check your network connection and try again.",
|
|
8449
|
-
timedOut: (timeoutMs) => `Downloading the flow bundle stalled — no data arrived for ${formatSeconds(timeoutMs)}. Please try again.`,
|
|
8450
|
-
malformed: "The flow bundle download was malformed. Please run `qawolf flows pull` again."
|
|
8451
|
-
}
|
|
8452
|
-
},
|
|
8453
|
-
whoami: {
|
|
8454
|
-
source: (source) => `Source: ${source}`,
|
|
8455
|
-
authFailed: (source, error) => `Authentication failed (source: ${source}): ${error}`,
|
|
8456
|
-
authenticatedAs: (teamName, source) => `Authenticated as ${teamName} (source: ${source})`,
|
|
8457
|
-
teamNote: (input) => [
|
|
8458
|
-
`Team: ${input.team.name}`,
|
|
8459
|
-
`ID: ${input.team.id}`,
|
|
8460
|
-
input.team.slug ? `Slug: ${input.team.slug}` : undefined,
|
|
8461
|
-
input.teamUrl ? `URL: ${input.teamUrl}` : undefined,
|
|
8462
|
-
`Source: ${input.source}`
|
|
8463
|
-
].filter((line) => Boolean(line)).join(`
|
|
8464
|
-
`),
|
|
8465
|
-
organizationNote: (input) => [
|
|
8466
|
-
`Organization: ${input.organization.name}`,
|
|
8467
|
-
`ID: ${input.organization.id}`,
|
|
8468
|
-
`Source: ${input.source}`
|
|
8469
|
-
].join(`
|
|
8470
|
-
`),
|
|
8471
|
-
userNote: (input) => [
|
|
8472
|
-
`User: ${input.user.email}`,
|
|
8473
|
-
`ID: ${input.user.id}`,
|
|
8474
|
-
`Organization: ${input.organization.name}`,
|
|
8475
|
-
`Source: ${input.source}`
|
|
8476
|
-
].join(`
|
|
8477
|
-
`)
|
|
8478
|
-
}
|
|
8538
|
+
errors: authErrorMessages,
|
|
8539
|
+
whoami: whoamiMessages
|
|
8479
8540
|
};
|
|
8480
8541
|
// src/core/messages/toolNotFound.ts
|
|
8481
8542
|
var installRemedy = "Run `qawolf install` to install the runtime dependencies.";
|
|
@@ -8568,11 +8629,6 @@ var environmentsMessages = {
|
|
|
8568
8629
|
usingPulledEnv: (env) => `Could not reach the platform to resolve environment ${env}; using the copy pulled into .qawolf/${env}.`,
|
|
8569
8630
|
tooManyPages: (pages) => `Stopped listing environments after ${pages} pages. Pass --env explicitly.`
|
|
8570
8631
|
};
|
|
8571
|
-
// src/core/pluralize.ts
|
|
8572
|
-
function pluralize(count, singular, plural = `${singular}s`) {
|
|
8573
|
-
return `${String(count)} ${count === 1 ? singular : plural}`;
|
|
8574
|
-
}
|
|
8575
|
-
|
|
8576
8632
|
// src/core/messages/flows.ts
|
|
8577
8633
|
var flowsMessages = {
|
|
8578
8634
|
title: "Flows",
|
|
@@ -9027,25 +9083,32 @@ function exit(code, message, proc = process) {
|
|
|
9027
9083
|
}
|
|
9028
9084
|
return proc.exit(code);
|
|
9029
9085
|
}
|
|
9030
|
-
|
|
9031
|
-
|
|
9086
|
+
var backstopMs = 2000;
|
|
9087
|
+
function exitWhenIdle(code, proc = process, scheduleBackstop = (fn) => {
|
|
9088
|
+
setTimeout(fn, backstopMs).unref();
|
|
9032
9089
|
}) {
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9090
|
+
proc.exitCode = code;
|
|
9091
|
+
scheduleBackstop(() => proc.exit(code));
|
|
9092
|
+
}
|
|
9093
|
+
var signalExitCodes = {
|
|
9094
|
+
SIGINT: 130,
|
|
9095
|
+
SIGTERM: 143
|
|
9096
|
+
};
|
|
9097
|
+
var signalGraceMs = 150;
|
|
9098
|
+
function scheduleSignalGrace(fn) {
|
|
9099
|
+
return setTimeout(fn, signalGraceMs);
|
|
9100
|
+
}
|
|
9101
|
+
function createSignalExit(deps) {
|
|
9102
|
+
const proc = deps.proc ?? process;
|
|
9103
|
+
const scheduleGrace = deps.scheduleGrace ?? scheduleSignalGrace;
|
|
9104
|
+
let signalled = false;
|
|
9105
|
+
return (signal) => () => {
|
|
9106
|
+
const code = signalExitCodes[signal];
|
|
9107
|
+
if (signalled)
|
|
9108
|
+
proc.exit(code);
|
|
9109
|
+
signalled = true;
|
|
9110
|
+
deps.shutdown(signal).catch(() => {}).finally(() => exitWhenIdle(code, proc, scheduleGrace));
|
|
9045
9111
|
};
|
|
9046
|
-
proc.stdout.write("", onFlushed);
|
|
9047
|
-
proc.stderr.write("", onFlushed);
|
|
9048
|
-
scheduleBackstop(exitOnce);
|
|
9049
9112
|
}
|
|
9050
9113
|
|
|
9051
9114
|
// src/shell/fs.ts
|
|
@@ -9161,43 +9224,30 @@ function resolveHostUrl(env) {
|
|
|
9161
9224
|
}
|
|
9162
9225
|
|
|
9163
9226
|
// src/domains/auth/resolve.ts
|
|
9164
|
-
import { Entry as
|
|
9165
|
-
|
|
9166
|
-
// src/domains/auth/store/delete.ts
|
|
9167
|
-
import { join as join2 } from "node:path";
|
|
9168
|
-
import { Entry } from "@napi-rs/keyring";
|
|
9169
|
-
|
|
9170
|
-
// src/domains/auth/store/constants.ts
|
|
9171
|
-
var service = "qawolf-cli";
|
|
9172
|
-
var account = "api-key";
|
|
9173
|
-
var credentialsFile = "credentials.json";
|
|
9227
|
+
import { Entry as Entry5 } from "@napi-rs/keyring";
|
|
9174
9228
|
|
|
9175
|
-
// src/
|
|
9176
|
-
function
|
|
9177
|
-
|
|
9178
|
-
|
|
9179
|
-
return
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
|
|
9184
|
-
async function deleteFromFile(configDir, fs) {
|
|
9229
|
+
// src/core/deviceAuth/tokenExpiry.ts
|
|
9230
|
+
function readAccessTokenExpiry(accessToken) {
|
|
9231
|
+
const segments = accessToken.split(".");
|
|
9232
|
+
if (segments.length !== 3)
|
|
9233
|
+
return;
|
|
9234
|
+
const [, payload] = segments;
|
|
9235
|
+
if (!payload)
|
|
9236
|
+
return;
|
|
9237
|
+
let claims;
|
|
9185
9238
|
try {
|
|
9186
|
-
|
|
9187
|
-
return "deleted";
|
|
9239
|
+
claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
9188
9240
|
} catch {
|
|
9189
|
-
return
|
|
9241
|
+
return;
|
|
9190
9242
|
}
|
|
9243
|
+
if (typeof claims !== "object" || claims === null)
|
|
9244
|
+
return;
|
|
9245
|
+
const exp = claims.exp;
|
|
9246
|
+
if (typeof exp !== "number" || !Number.isFinite(exp))
|
|
9247
|
+
return;
|
|
9248
|
+
const expiresAtMs = exp * 1000;
|
|
9249
|
+
return Number.isFinite(expiresAtMs) ? expiresAtMs : undefined;
|
|
9191
9250
|
}
|
|
9192
|
-
async function deleteApiKey(configDir, fs) {
|
|
9193
|
-
const [keychain, file] = await Promise.all([
|
|
9194
|
-
Promise.resolve(deleteFromKeychain()),
|
|
9195
|
-
deleteFromFile(configDir, fs)
|
|
9196
|
-
]);
|
|
9197
|
-
return { keychain, file };
|
|
9198
|
-
}
|
|
9199
|
-
// src/domains/auth/store/load.ts
|
|
9200
|
-
import { join as join3 } from "node:path";
|
|
9201
9251
|
|
|
9202
9252
|
// node_modules/zod/v4/core/core.js
|
|
9203
9253
|
var _a;
|
|
@@ -14424,10 +14474,229 @@ function date4(params) {
|
|
|
14424
14474
|
|
|
14425
14475
|
// node_modules/zod/v4/classic/external.js
|
|
14426
14476
|
config(en_default());
|
|
14477
|
+
// src/shell/workos/types.ts
|
|
14478
|
+
var defaultWorkosBaseUrl = "https://api.workos.com";
|
|
14479
|
+
var defaultIntervalSec = 5;
|
|
14480
|
+
var deviceAuthorizationBody = object({
|
|
14481
|
+
device_code: string2().min(1),
|
|
14482
|
+
user_code: string2().min(1),
|
|
14483
|
+
verification_uri: string2().min(1),
|
|
14484
|
+
verification_uri_complete: string2().min(1).optional(),
|
|
14485
|
+
expires_in: number2().int().positive(),
|
|
14486
|
+
interval: number2().int().positive().optional()
|
|
14487
|
+
});
|
|
14488
|
+
var deviceTokenBody = object({
|
|
14489
|
+
access_token: string2().min(1),
|
|
14490
|
+
refresh_token: string2().min(1),
|
|
14491
|
+
user: object({ email: string2().min(1) }),
|
|
14492
|
+
organization_id: string2().min(1).optional()
|
|
14493
|
+
});
|
|
14494
|
+
var oauthErrorBody = object({
|
|
14495
|
+
error: string2().min(1),
|
|
14496
|
+
error_description: string2().min(1).optional()
|
|
14497
|
+
});
|
|
14498
|
+
var authenticationErrorBody = object({
|
|
14499
|
+
code: string2().min(1),
|
|
14500
|
+
message: string2().min(1).optional()
|
|
14501
|
+
});
|
|
14502
|
+
|
|
14503
|
+
// src/shell/workos/send.ts
|
|
14504
|
+
var timeoutMs = 15000;
|
|
14505
|
+
var unexpectedResponse = authErrorMessages.workos.unexpectedResponse;
|
|
14506
|
+
function isTransientStatus(status) {
|
|
14507
|
+
return status >= 500 || status === 429 || status === 408;
|
|
14508
|
+
}
|
|
14509
|
+
async function sendWorkosRequest(url, init, fetchFn) {
|
|
14510
|
+
let response;
|
|
14511
|
+
try {
|
|
14512
|
+
response = await fetchFn(url, {
|
|
14513
|
+
method: "POST",
|
|
14514
|
+
headers: init.headers,
|
|
14515
|
+
body: init.body,
|
|
14516
|
+
redirect: "manual",
|
|
14517
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
14518
|
+
});
|
|
14519
|
+
} catch (err) {
|
|
14520
|
+
return {
|
|
14521
|
+
kind: "failure",
|
|
14522
|
+
detail: authErrorMessages.workos.unreachable(errorMessage(err)),
|
|
14523
|
+
retryable: true
|
|
14524
|
+
};
|
|
14525
|
+
}
|
|
14526
|
+
if (response.status >= 300 && response.status < 400) {
|
|
14527
|
+
return {
|
|
14528
|
+
kind: "failure",
|
|
14529
|
+
detail: authErrorMessages.workos.redirected,
|
|
14530
|
+
retryable: false
|
|
14531
|
+
};
|
|
14532
|
+
}
|
|
14533
|
+
let json;
|
|
14534
|
+
try {
|
|
14535
|
+
json = await response.json();
|
|
14536
|
+
} catch {
|
|
14537
|
+
return {
|
|
14538
|
+
kind: "failure",
|
|
14539
|
+
detail: authErrorMessages.workos.unexpectedResponseWithStatus(response.status),
|
|
14540
|
+
retryable: response.ok || isTransientStatus(response.status)
|
|
14541
|
+
};
|
|
14542
|
+
}
|
|
14543
|
+
if (response.ok)
|
|
14544
|
+
return { kind: "json", json };
|
|
14545
|
+
if (isTransientStatus(response.status)) {
|
|
14546
|
+
return {
|
|
14547
|
+
kind: "failure",
|
|
14548
|
+
detail: authErrorMessages.workos.unexpectedResponseWithStatus(response.status),
|
|
14549
|
+
retryable: true
|
|
14550
|
+
};
|
|
14551
|
+
}
|
|
14552
|
+
const parsed = oauthErrorBody.safeParse(json);
|
|
14553
|
+
if (parsed.success) {
|
|
14554
|
+
return {
|
|
14555
|
+
kind: "oauth-error",
|
|
14556
|
+
code: parsed.data.error,
|
|
14557
|
+
description: parsed.data.error_description
|
|
14558
|
+
};
|
|
14559
|
+
}
|
|
14560
|
+
const authError = authenticationErrorBody.safeParse(json);
|
|
14561
|
+
if (authError.success) {
|
|
14562
|
+
return {
|
|
14563
|
+
kind: "oauth-error",
|
|
14564
|
+
code: authError.data.code,
|
|
14565
|
+
description: authError.data.message
|
|
14566
|
+
};
|
|
14567
|
+
}
|
|
14568
|
+
return {
|
|
14569
|
+
kind: "failure",
|
|
14570
|
+
detail: authErrorMessages.workos.unexpectedResponseWithStatus(response.status),
|
|
14571
|
+
retryable: false
|
|
14572
|
+
};
|
|
14573
|
+
}
|
|
14574
|
+
|
|
14575
|
+
// src/shell/workos/refreshAccessToken.ts
|
|
14576
|
+
async function refreshAccessToken(refreshToken, organizationId, deps) {
|
|
14577
|
+
const params = new URLSearchParams({
|
|
14578
|
+
grant_type: "refresh_token",
|
|
14579
|
+
refresh_token: refreshToken,
|
|
14580
|
+
client_id: deps.clientId
|
|
14581
|
+
});
|
|
14582
|
+
if (organizationId)
|
|
14583
|
+
params.set("organization_id", organizationId);
|
|
14584
|
+
const outcome = await sendWorkosRequest(`${deps.baseUrl}/user_management/authenticate`, {
|
|
14585
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
14586
|
+
body: params.toString()
|
|
14587
|
+
}, deps.fetch);
|
|
14588
|
+
if (outcome.kind === "failure") {
|
|
14589
|
+
return { ok: false, error: outcome.detail, retryable: outcome.retryable };
|
|
14590
|
+
}
|
|
14591
|
+
if (outcome.kind === "oauth-error") {
|
|
14592
|
+
return {
|
|
14593
|
+
ok: false,
|
|
14594
|
+
error: outcome.description ?? outcome.code,
|
|
14595
|
+
retryable: false
|
|
14596
|
+
};
|
|
14597
|
+
}
|
|
14598
|
+
const parsed = deviceTokenBody.safeParse(outcome.json);
|
|
14599
|
+
if (!parsed.success) {
|
|
14600
|
+
return { ok: false, error: unexpectedResponse, retryable: false };
|
|
14601
|
+
}
|
|
14602
|
+
return {
|
|
14603
|
+
ok: true,
|
|
14604
|
+
value: {
|
|
14605
|
+
accessToken: parsed.data.access_token,
|
|
14606
|
+
refreshToken: parsed.data.refresh_token,
|
|
14607
|
+
expiresAt: readAccessTokenExpiry(parsed.data.access_token),
|
|
14608
|
+
email: parsed.data.user.email,
|
|
14609
|
+
organizationId: parsed.data.organization_id
|
|
14610
|
+
}
|
|
14611
|
+
};
|
|
14612
|
+
}
|
|
14613
|
+
|
|
14614
|
+
// src/shell/workos/config.ts
|
|
14615
|
+
function resolveWorkosConfig(publishedClientId) {
|
|
14616
|
+
const clientId = publishedClientId?.trim();
|
|
14617
|
+
if (!clientId)
|
|
14618
|
+
return { configured: false };
|
|
14619
|
+
return { configured: true, clientId, baseUrl: defaultWorkosBaseUrl };
|
|
14620
|
+
}
|
|
14621
|
+
|
|
14622
|
+
// src/domains/auth/store/delete.ts
|
|
14623
|
+
import { join as join2 } from "node:path";
|
|
14624
|
+
import { Entry } from "@napi-rs/keyring";
|
|
14625
|
+
|
|
14626
|
+
// src/domains/auth/store/constants.ts
|
|
14627
|
+
var service = "qawolf-cli";
|
|
14628
|
+
var account = "api-key";
|
|
14629
|
+
var credentialsFile = "credentials.json";
|
|
14630
|
+
var tokensAccount = "oauth-tokens";
|
|
14631
|
+
var tokensFile = "tokens.json";
|
|
14632
|
+
|
|
14633
|
+
// src/domains/auth/store/delete.ts
|
|
14634
|
+
function deleteFromKeychain() {
|
|
14635
|
+
try {
|
|
14636
|
+
new Entry(service, account).deletePassword();
|
|
14637
|
+
return "deleted";
|
|
14638
|
+
} catch {
|
|
14639
|
+
return "unavailable";
|
|
14640
|
+
}
|
|
14641
|
+
}
|
|
14642
|
+
async function deleteFromFile(configDir, fs) {
|
|
14643
|
+
try {
|
|
14644
|
+
await fs.unlink(join2(configDir, credentialsFile));
|
|
14645
|
+
return "deleted";
|
|
14646
|
+
} catch (err) {
|
|
14647
|
+
if (isNoEntError(err))
|
|
14648
|
+
return "not-found";
|
|
14649
|
+
throw err;
|
|
14650
|
+
}
|
|
14651
|
+
}
|
|
14652
|
+
async function deleteApiKey(configDir, fs) {
|
|
14653
|
+
const [keychain, file] = await Promise.all([
|
|
14654
|
+
Promise.resolve(deleteFromKeychain()),
|
|
14655
|
+
deleteFromFile(configDir, fs)
|
|
14656
|
+
]);
|
|
14657
|
+
return { keychain, file };
|
|
14658
|
+
}
|
|
14659
|
+
// src/domains/auth/store/hasStoredCredentials.ts
|
|
14660
|
+
import { join as join3 } from "node:path";
|
|
14661
|
+
import { Entry as Entry2 } from "@napi-rs/keyring";
|
|
14662
|
+
async function fileExists(path, fs) {
|
|
14663
|
+
try {
|
|
14664
|
+
await fs.readFile(path);
|
|
14665
|
+
return true;
|
|
14666
|
+
} catch (err) {
|
|
14667
|
+
return !isNoEntError(err);
|
|
14668
|
+
}
|
|
14669
|
+
}
|
|
14670
|
+
function keychainHolds(entryAccount) {
|
|
14671
|
+
try {
|
|
14672
|
+
return Boolean(new Entry2(service, entryAccount).getPassword());
|
|
14673
|
+
} catch {
|
|
14674
|
+
return false;
|
|
14675
|
+
}
|
|
14676
|
+
}
|
|
14677
|
+
async function hasStoredCredentials(configDir, fs) {
|
|
14678
|
+
const [apiKeyFile, tokenFile] = await Promise.all([
|
|
14679
|
+
fileExists(join3(configDir, credentialsFile), fs),
|
|
14680
|
+
fileExists(join3(configDir, tokensFile), fs)
|
|
14681
|
+
]);
|
|
14682
|
+
return apiKeyFile || tokenFile || keychainHolds(account) || keychainHolds(tokensAccount);
|
|
14683
|
+
}
|
|
14684
|
+
// src/domains/auth/store/load.ts
|
|
14685
|
+
import { join as join4 } from "node:path";
|
|
14686
|
+
|
|
14427
14687
|
// src/domains/auth/store/types.ts
|
|
14428
14688
|
var credentialsFileSchema = object({
|
|
14429
14689
|
apiKey: string2().min(1)
|
|
14430
14690
|
});
|
|
14691
|
+
var oauthTokensSchema = object({
|
|
14692
|
+
accessToken: string2().min(1),
|
|
14693
|
+
refreshToken: string2().min(1),
|
|
14694
|
+
expiresAt: number2().int().optional(),
|
|
14695
|
+
email: string2().min(1),
|
|
14696
|
+
organizationId: string2().min(1).optional(),
|
|
14697
|
+
workspaceId: string2().min(1).optional(),
|
|
14698
|
+
clientId: string2().min(1).optional()
|
|
14699
|
+
});
|
|
14431
14700
|
|
|
14432
14701
|
// src/domains/auth/store/load.ts
|
|
14433
14702
|
async function loadApiKey(configDir, deps) {
|
|
@@ -14441,7 +14710,7 @@ async function loadApiKey(configDir, deps) {
|
|
|
14441
14710
|
errors.keychain = errorMessage(err);
|
|
14442
14711
|
}
|
|
14443
14712
|
try {
|
|
14444
|
-
const content = await deps.fs.readFile(
|
|
14713
|
+
const content = await deps.fs.readFile(join4(configDir, credentialsFile));
|
|
14445
14714
|
const parsed = credentialsFileSchema.safeParse(JSON.parse(content));
|
|
14446
14715
|
if (parsed.success) {
|
|
14447
14716
|
return { found: true, key: parsed.data.apiKey, source: "file" };
|
|
@@ -14453,16 +14722,16 @@ async function loadApiKey(configDir, deps) {
|
|
|
14453
14722
|
return { found: false, errors };
|
|
14454
14723
|
}
|
|
14455
14724
|
// src/domains/auth/store/save.ts
|
|
14456
|
-
import { join as
|
|
14457
|
-
import { Entry as
|
|
14725
|
+
import { join as join5 } from "node:path";
|
|
14726
|
+
import { Entry as Entry3 } from "@napi-rs/keyring";
|
|
14458
14727
|
async function saveToFile(configDir, key, fs) {
|
|
14459
14728
|
const payload = { apiKey: key };
|
|
14460
14729
|
await fs.mkdir(configDir, { recursive: true, mode: 448 });
|
|
14461
|
-
await fs.writeFile(
|
|
14730
|
+
await fs.writeFile(join5(configDir, credentialsFile), JSON.stringify(payload, undefined, 2), { mode: 384 });
|
|
14462
14731
|
}
|
|
14463
14732
|
async function saveApiKey(configDir, key, fs) {
|
|
14464
14733
|
try {
|
|
14465
|
-
const entry = new
|
|
14734
|
+
const entry = new Entry3(service, account);
|
|
14466
14735
|
entry.setPassword(key);
|
|
14467
14736
|
return { stored: "keychain" };
|
|
14468
14737
|
} catch (err) {
|
|
@@ -14470,10 +14739,159 @@ async function saveApiKey(configDir, key, fs) {
|
|
|
14470
14739
|
return { stored: "file", keychainError: errorMessage(err) };
|
|
14471
14740
|
}
|
|
14472
14741
|
}
|
|
14742
|
+
// src/domains/auth/store/loadTokens.ts
|
|
14743
|
+
import { join as join6 } from "node:path";
|
|
14744
|
+
var invalidPayload = "Invalid stored token format";
|
|
14745
|
+
function parseTokens(raw) {
|
|
14746
|
+
const parsed = oauthTokensSchema.safeParse(JSON.parse(raw));
|
|
14747
|
+
if (!parsed.success)
|
|
14748
|
+
return;
|
|
14749
|
+
return {
|
|
14750
|
+
accessToken: parsed.data.accessToken,
|
|
14751
|
+
refreshToken: parsed.data.refreshToken,
|
|
14752
|
+
expiresAt: parsed.data.expiresAt,
|
|
14753
|
+
email: parsed.data.email,
|
|
14754
|
+
organizationId: parsed.data.organizationId,
|
|
14755
|
+
workspaceId: parsed.data.workspaceId,
|
|
14756
|
+
clientId: parsed.data.clientId
|
|
14757
|
+
};
|
|
14758
|
+
}
|
|
14759
|
+
async function loadTokens(configDir, deps) {
|
|
14760
|
+
const errors = {};
|
|
14761
|
+
try {
|
|
14762
|
+
const raw = new deps.EntryClass(service, tokensAccount).getPassword();
|
|
14763
|
+
if (raw) {
|
|
14764
|
+
const tokens = parseTokens(raw);
|
|
14765
|
+
if (tokens)
|
|
14766
|
+
return { found: true, tokens, source: "keychain" };
|
|
14767
|
+
errors.keychain = invalidPayload;
|
|
14768
|
+
}
|
|
14769
|
+
} catch (err) {
|
|
14770
|
+
errors.keychain = errorMessage(err);
|
|
14771
|
+
}
|
|
14772
|
+
try {
|
|
14773
|
+
const raw = await deps.fs.readFile(join6(configDir, tokensFile));
|
|
14774
|
+
const tokens = parseTokens(raw);
|
|
14775
|
+
if (tokens)
|
|
14776
|
+
return { found: true, tokens, source: "file" };
|
|
14777
|
+
errors.file = invalidPayload;
|
|
14778
|
+
} catch (err) {
|
|
14779
|
+
errors.file = errorMessage(err);
|
|
14780
|
+
}
|
|
14781
|
+
return { found: false, errors };
|
|
14782
|
+
}
|
|
14783
|
+
|
|
14784
|
+
// src/domains/auth/store/saveTokens.ts
|
|
14785
|
+
import { randomUUID } from "node:crypto";
|
|
14786
|
+
import { join as join7 } from "node:path";
|
|
14787
|
+
import { Entry as Entry4 } from "@napi-rs/keyring";
|
|
14788
|
+
async function saveToFile2(configDir, tokens, fs) {
|
|
14789
|
+
await fs.mkdir(configDir, { recursive: true, mode: 448 });
|
|
14790
|
+
const target = join7(configDir, tokensFile);
|
|
14791
|
+
const staging = `${target}.${randomUUID()}.tmp`;
|
|
14792
|
+
try {
|
|
14793
|
+
await fs.writeFile(staging, JSON.stringify(tokens, undefined, 2), {
|
|
14794
|
+
mode: 384
|
|
14795
|
+
});
|
|
14796
|
+
await fs.rename(staging, target);
|
|
14797
|
+
} catch (err) {
|
|
14798
|
+
await fs.unlink(staging).catch(() => {});
|
|
14799
|
+
throw err;
|
|
14800
|
+
}
|
|
14801
|
+
}
|
|
14802
|
+
async function saveTokens(configDir, tokens, fs) {
|
|
14803
|
+
try {
|
|
14804
|
+
new Entry4(service, tokensAccount).setPassword(JSON.stringify(tokens));
|
|
14805
|
+
return { stored: "keychain" };
|
|
14806
|
+
} catch (err) {
|
|
14807
|
+
try {
|
|
14808
|
+
new Entry4(service, tokensAccount).deletePassword();
|
|
14809
|
+
} catch {}
|
|
14810
|
+
await saveToFile2(configDir, tokens, fs);
|
|
14811
|
+
return { stored: "file", keychainError: errorMessage(err) };
|
|
14812
|
+
}
|
|
14813
|
+
}
|
|
14814
|
+
|
|
14815
|
+
// src/domains/auth/resolveOauthToken.ts
|
|
14816
|
+
var expiryMarginMs = 30000;
|
|
14817
|
+
async function resolveOauthToken(configDir, deps) {
|
|
14818
|
+
const stored = await deps.loadTokens(configDir);
|
|
14819
|
+
if (!stored.found)
|
|
14820
|
+
return;
|
|
14821
|
+
const { tokens } = stored;
|
|
14822
|
+
const expiresAt = tokens.expiresAt;
|
|
14823
|
+
const isFresh = expiresAt !== undefined && expiresAt - expiryMarginMs > deps.now();
|
|
14824
|
+
if (isFresh) {
|
|
14825
|
+
return {
|
|
14826
|
+
key: tokens.accessToken,
|
|
14827
|
+
email: tokens.email,
|
|
14828
|
+
workspaceId: tokens.workspaceId
|
|
14829
|
+
};
|
|
14830
|
+
}
|
|
14831
|
+
const refreshed = await deps.refreshTokens({
|
|
14832
|
+
refreshToken: tokens.refreshToken,
|
|
14833
|
+
organizationId: tokens.organizationId,
|
|
14834
|
+
clientId: tokens.clientId
|
|
14835
|
+
});
|
|
14836
|
+
if (!refreshed.ok) {
|
|
14837
|
+
if (refreshed.retryable && expiresAt !== undefined && expiresAt > deps.now()) {
|
|
14838
|
+
return {
|
|
14839
|
+
key: tokens.accessToken,
|
|
14840
|
+
email: tokens.email,
|
|
14841
|
+
workspaceId: tokens.workspaceId
|
|
14842
|
+
};
|
|
14843
|
+
}
|
|
14844
|
+
const current = await deps.loadTokens(configDir);
|
|
14845
|
+
if (current.found && current.tokens.refreshToken !== tokens.refreshToken && current.tokens.email === tokens.email && current.tokens.organizationId === tokens.organizationId && current.tokens.clientId === tokens.clientId && current.tokens.expiresAt !== undefined && current.tokens.expiresAt > deps.now()) {
|
|
14846
|
+
return {
|
|
14847
|
+
key: current.tokens.accessToken,
|
|
14848
|
+
email: current.tokens.email,
|
|
14849
|
+
workspaceId: current.tokens.workspaceId
|
|
14850
|
+
};
|
|
14851
|
+
}
|
|
14852
|
+
return;
|
|
14853
|
+
}
|
|
14854
|
+
try {
|
|
14855
|
+
await deps.saveTokens(configDir, {
|
|
14856
|
+
...refreshed.value,
|
|
14857
|
+
workspaceId: tokens.workspaceId,
|
|
14858
|
+
clientId: tokens.clientId
|
|
14859
|
+
});
|
|
14860
|
+
} catch {}
|
|
14861
|
+
return {
|
|
14862
|
+
key: refreshed.value.accessToken,
|
|
14863
|
+
email: refreshed.value.email,
|
|
14864
|
+
workspaceId: tokens.workspaceId
|
|
14865
|
+
};
|
|
14866
|
+
}
|
|
14867
|
+
|
|
14473
14868
|
// src/domains/auth/resolve.ts
|
|
14869
|
+
function makeOauthDeps(fs) {
|
|
14870
|
+
return {
|
|
14871
|
+
loadTokens: (configDir) => loadTokens(configDir, { EntryClass: Entry5, fs }),
|
|
14872
|
+
refreshTokens: async ({ refreshToken, organizationId, clientId }) => {
|
|
14873
|
+
const config = resolveWorkosConfig(clientId);
|
|
14874
|
+
if (!config.configured) {
|
|
14875
|
+
return {
|
|
14876
|
+
ok: false,
|
|
14877
|
+
error: authErrorMessages.workos.noClientForSession,
|
|
14878
|
+
retryable: false
|
|
14879
|
+
};
|
|
14880
|
+
}
|
|
14881
|
+
return refreshAccessToken(refreshToken, organizationId, {
|
|
14882
|
+
fetch: globalThis.fetch,
|
|
14883
|
+
baseUrl: config.baseUrl,
|
|
14884
|
+
clientId: config.clientId
|
|
14885
|
+
});
|
|
14886
|
+
},
|
|
14887
|
+
saveTokens: (configDir, tokens) => saveTokens(configDir, tokens, fs),
|
|
14888
|
+
now: () => Date.now()
|
|
14889
|
+
};
|
|
14890
|
+
}
|
|
14474
14891
|
function makeDefaultDeps(fs) {
|
|
14475
14892
|
return {
|
|
14476
|
-
loadApiKey: (configDir) => loadApiKey(configDir, { EntryClass:
|
|
14893
|
+
loadApiKey: (configDir) => loadApiKey(configDir, { EntryClass: Entry5, fs }),
|
|
14894
|
+
resolveOauth: (configDir) => resolveOauthToken(configDir, makeOauthDeps(fs)),
|
|
14477
14895
|
env: process.env
|
|
14478
14896
|
};
|
|
14479
14897
|
}
|
|
@@ -14481,11 +14899,19 @@ async function resolveApiKey(configDir, fs, deps) {
|
|
|
14481
14899
|
const resolvedDeps = deps ?? makeDefaultDeps(fs);
|
|
14482
14900
|
const envKey = resolvedDeps.env["QAWOLF_API_KEY"];
|
|
14483
14901
|
if (envKey?.trim()) {
|
|
14484
|
-
return { key: envKey.trim(), source: "env" };
|
|
14902
|
+
return { key: envKey.trim(), source: "env", workspaceId: undefined };
|
|
14485
14903
|
}
|
|
14486
14904
|
const stored = await resolvedDeps.loadApiKey(configDir);
|
|
14487
14905
|
if (stored.found) {
|
|
14488
|
-
return { key: stored.key, source: stored.source };
|
|
14906
|
+
return { key: stored.key, source: stored.source, workspaceId: undefined };
|
|
14907
|
+
}
|
|
14908
|
+
const oauth = await resolvedDeps.resolveOauth(configDir);
|
|
14909
|
+
if (oauth) {
|
|
14910
|
+
return {
|
|
14911
|
+
key: oauth.key,
|
|
14912
|
+
source: "browser",
|
|
14913
|
+
workspaceId: oauth.workspaceId
|
|
14914
|
+
};
|
|
14489
14915
|
}
|
|
14490
14916
|
return;
|
|
14491
14917
|
}
|
|
@@ -14507,9 +14933,9 @@ async function validateApiKey(deps) {
|
|
|
14507
14933
|
// src/shell/logger.ts
|
|
14508
14934
|
var import_pino = __toESM(require_pino(), 1);
|
|
14509
14935
|
import { mkdirSync as mkdirSync2, openSync } from "node:fs";
|
|
14510
|
-
import { dirname, join as
|
|
14936
|
+
import { dirname, join as join8 } from "node:path";
|
|
14511
14937
|
function defaultLogPath() {
|
|
14512
|
-
return
|
|
14938
|
+
return join8(envPaths("qawolf", { suffix: "" }).log, "cli.log");
|
|
14513
14939
|
}
|
|
14514
14940
|
function resolveStderrLevel(env, verbose) {
|
|
14515
14941
|
if (verbose)
|
|
@@ -14577,9 +15003,179 @@ function createLoggingSystem(opts) {
|
|
|
14577
15003
|
};
|
|
14578
15004
|
}
|
|
14579
15005
|
|
|
14580
|
-
// src/
|
|
14581
|
-
|
|
14582
|
-
|
|
15006
|
+
// src/core/publicApi/flagKind.ts
|
|
15007
|
+
function flagKind(schema) {
|
|
15008
|
+
if (schema.type === "string")
|
|
15009
|
+
return { ok: true, kind: "string" };
|
|
15010
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
15011
|
+
return { ok: true, kind: "number" };
|
|
15012
|
+
}
|
|
15013
|
+
if (schema.type === "boolean")
|
|
15014
|
+
return { ok: true, kind: "boolean" };
|
|
15015
|
+
if (schema.type === "array") {
|
|
15016
|
+
if (schema.items?.type !== "string") {
|
|
15017
|
+
return {
|
|
15018
|
+
ok: false,
|
|
15019
|
+
reason: "only string arrays can be expressed as flags"
|
|
15020
|
+
};
|
|
15021
|
+
}
|
|
15022
|
+
return { ok: true, kind: "string-array" };
|
|
15023
|
+
}
|
|
15024
|
+
if (schema.type === "object") {
|
|
15025
|
+
if (schema.properties) {
|
|
15026
|
+
return {
|
|
15027
|
+
ok: false,
|
|
15028
|
+
reason: "nested objects cannot be expressed as flags"
|
|
15029
|
+
};
|
|
15030
|
+
}
|
|
15031
|
+
if (typeof schema.additionalProperties === "object" && schema.additionalProperties.type === "string") {
|
|
15032
|
+
return { ok: true, kind: "key-value-record" };
|
|
15033
|
+
}
|
|
15034
|
+
return { ok: false, reason: "nested objects cannot be expressed as flags" };
|
|
15035
|
+
}
|
|
15036
|
+
return {
|
|
15037
|
+
ok: false,
|
|
15038
|
+
reason: `type ${schema.type ?? "unknown"} cannot be expressed as a flag`
|
|
15039
|
+
};
|
|
15040
|
+
}
|
|
15041
|
+
|
|
15042
|
+
// src/core/publicApi/objectShape.ts
|
|
15043
|
+
var asObjectShape = (schema) => schema.type === "object" && schema.properties ? { properties: schema.properties, required: new Set(schema.required) } : undefined;
|
|
15044
|
+
function mergeIntersection(members) {
|
|
15045
|
+
const shape = { properties: {}, required: new Set };
|
|
15046
|
+
for (const member of members) {
|
|
15047
|
+
const memberShape = asObjectShape(member);
|
|
15048
|
+
if (!memberShape) {
|
|
15049
|
+
return {
|
|
15050
|
+
ok: false,
|
|
15051
|
+
field: "",
|
|
15052
|
+
reason: "intersection members must be object schemas"
|
|
15053
|
+
};
|
|
15054
|
+
}
|
|
15055
|
+
for (const [field, fieldSchema] of Object.entries(memberShape.properties)) {
|
|
15056
|
+
if (field in shape.properties) {
|
|
15057
|
+
return {
|
|
15058
|
+
ok: false,
|
|
15059
|
+
field,
|
|
15060
|
+
reason: "field appears in multiple intersection members"
|
|
15061
|
+
};
|
|
15062
|
+
}
|
|
15063
|
+
shape.properties[field] = fieldSchema;
|
|
15064
|
+
if (memberShape.required.has(field))
|
|
15065
|
+
shape.required.add(field);
|
|
15066
|
+
}
|
|
15067
|
+
}
|
|
15068
|
+
return { ok: true, shape };
|
|
15069
|
+
}
|
|
15070
|
+
function mergeUnion(branches) {
|
|
15071
|
+
const shapes = [];
|
|
15072
|
+
for (const branch of branches) {
|
|
15073
|
+
const branchShape = asObjectShape(branch);
|
|
15074
|
+
if (!branchShape) {
|
|
15075
|
+
return {
|
|
15076
|
+
ok: false,
|
|
15077
|
+
field: "",
|
|
15078
|
+
reason: "union branches must be object schemas"
|
|
15079
|
+
};
|
|
15080
|
+
}
|
|
15081
|
+
shapes.push(branchShape);
|
|
15082
|
+
}
|
|
15083
|
+
const shape = { properties: {}, required: new Set };
|
|
15084
|
+
for (const branchShape of shapes) {
|
|
15085
|
+
for (const [field, fieldSchema] of Object.entries(branchShape.properties)) {
|
|
15086
|
+
const existing = shape.properties[field];
|
|
15087
|
+
if (!existing) {
|
|
15088
|
+
shape.properties[field] = { ...fieldSchema };
|
|
15089
|
+
continue;
|
|
15090
|
+
}
|
|
15091
|
+
const kinds = [flagKind(existing), flagKind(fieldSchema)];
|
|
15092
|
+
if (kinds[0]?.ok && kinds[1]?.ok) {
|
|
15093
|
+
if (kinds[0].kind !== kinds[1].kind) {
|
|
15094
|
+
return {
|
|
15095
|
+
ok: false,
|
|
15096
|
+
field,
|
|
15097
|
+
reason: "field has conflicting types across union branches"
|
|
15098
|
+
};
|
|
15099
|
+
}
|
|
15100
|
+
} else if (JSON.stringify({ ...existing, description: undefined }) !== JSON.stringify({ ...fieldSchema, description: undefined })) {
|
|
15101
|
+
return {
|
|
15102
|
+
ok: false,
|
|
15103
|
+
field,
|
|
15104
|
+
reason: "field has conflicting object schemas across union branches"
|
|
15105
|
+
};
|
|
15106
|
+
}
|
|
15107
|
+
if (!existing.description && fieldSchema.description) {
|
|
15108
|
+
existing.description = fieldSchema.description;
|
|
15109
|
+
}
|
|
15110
|
+
}
|
|
15111
|
+
}
|
|
15112
|
+
const discriminator = Object.keys(shape.properties).find((field) => {
|
|
15113
|
+
const values = shapes.map((branch) => branch.properties[field]?.const);
|
|
15114
|
+
return values.every((value) => typeof value === "string") && new Set(values).size === shapes.length && shapes.every((branch) => branch.required.has(field));
|
|
15115
|
+
});
|
|
15116
|
+
for (const [field, fieldSchema] of Object.entries(shape.properties)) {
|
|
15117
|
+
const occurrences = shapes.filter((branch) => (field in branch.properties));
|
|
15118
|
+
if (occurrences.length === shapes.length && occurrences.every((branch) => branch.required.has(field))) {
|
|
15119
|
+
shape.required.add(field);
|
|
15120
|
+
}
|
|
15121
|
+
if (field === discriminator) {
|
|
15122
|
+
const values = shapes.map((branch) => branch.properties[field]?.const);
|
|
15123
|
+
fieldSchema.description = `One of: ${values.join(", ")}`;
|
|
15124
|
+
}
|
|
15125
|
+
}
|
|
15126
|
+
return { ok: true, shape };
|
|
15127
|
+
}
|
|
15128
|
+
function toObjectShape(schema) {
|
|
15129
|
+
if (schema.allOf)
|
|
15130
|
+
return mergeIntersection(schema.allOf);
|
|
15131
|
+
const branches = schema.oneOf ?? schema.anyOf;
|
|
15132
|
+
if (branches)
|
|
15133
|
+
return mergeUnion(branches);
|
|
15134
|
+
const shape = asObjectShape(schema);
|
|
15135
|
+
if (shape)
|
|
15136
|
+
return { ok: true, shape };
|
|
15137
|
+
return {
|
|
15138
|
+
ok: false,
|
|
15139
|
+
field: "",
|
|
15140
|
+
reason: "contract input must be an object schema"
|
|
15141
|
+
};
|
|
15142
|
+
}
|
|
15143
|
+
|
|
15144
|
+
// src/shell/platform/applyWorkspaceId.ts
|
|
15145
|
+
var acceptsWorkspace = new WeakMap;
|
|
15146
|
+
function takesWorkspaceId(inputSchema) {
|
|
15147
|
+
const cached = acceptsWorkspace.get(inputSchema);
|
|
15148
|
+
if (cached !== undefined)
|
|
15149
|
+
return cached;
|
|
15150
|
+
let accepted = false;
|
|
15151
|
+
try {
|
|
15152
|
+
const jsonSchema = toJSONSchema(inputSchema, {
|
|
15153
|
+
io: "input"
|
|
15154
|
+
});
|
|
15155
|
+
const shape = toObjectShape(jsonSchema);
|
|
15156
|
+
accepted = shape.ok && "workspaceId" in shape.shape.properties;
|
|
15157
|
+
} catch {
|
|
15158
|
+
accepted = false;
|
|
15159
|
+
}
|
|
15160
|
+
acceptsWorkspace.set(inputSchema, accepted);
|
|
15161
|
+
return accepted;
|
|
15162
|
+
}
|
|
15163
|
+
function applyWorkspaceId(inputSchema, input, workspaceId) {
|
|
15164
|
+
if (!workspaceId)
|
|
15165
|
+
return input;
|
|
15166
|
+
if (typeof input !== "object" || input === null)
|
|
15167
|
+
return input;
|
|
15168
|
+
const existing = input.workspaceId;
|
|
15169
|
+
if (existing !== undefined)
|
|
15170
|
+
return input;
|
|
15171
|
+
if (!takesWorkspaceId(inputSchema))
|
|
15172
|
+
return input;
|
|
15173
|
+
return { ...input, workspaceId };
|
|
15174
|
+
}
|
|
15175
|
+
|
|
15176
|
+
// src/shell/platform/parseErrorBody.ts
|
|
15177
|
+
var defaultMaxLength = 1024;
|
|
15178
|
+
function asRecord(value) {
|
|
14583
15179
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
14584
15180
|
}
|
|
14585
15181
|
function asString(value) {
|
|
@@ -14743,7 +15339,7 @@ function callPublicApi(trpc, contract, input, options) {
|
|
|
14743
15339
|
}
|
|
14744
15340
|
function makeCallPublicApiMethod(trpc, deps, readBackoffMs) {
|
|
14745
15341
|
return async (contract, input, options) => requestWithRetry({
|
|
14746
|
-
call: () => callPublicApi(trpc, contract, input, options),
|
|
15342
|
+
call: () => callPublicApi(trpc, contract, applyWorkspaceId(contract.input, input, deps.workspaceId), options),
|
|
14747
15343
|
backoffMs: contract.kind === "read" ? readBackoffMs : [],
|
|
14748
15344
|
describe: (err) => describeRequestError(err, deps.baseUrl, contract.name),
|
|
14749
15345
|
sleep: deps.sleep
|
|
@@ -15646,7 +16242,7 @@ function summarize(error) {
|
|
|
15646
16242
|
// src/shell/platform/downloadBundle.ts
|
|
15647
16243
|
import { randomBytes } from "node:crypto";
|
|
15648
16244
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
15649
|
-
import { join as
|
|
16245
|
+
import { join as join9 } from "node:path";
|
|
15650
16246
|
|
|
15651
16247
|
// src/shell/platform/fetchSignedUrl.ts
|
|
15652
16248
|
var defaultStallTimeoutMs = 30000;
|
|
@@ -15734,7 +16330,7 @@ async function fetchSignedUrl(args, deps = { fetch: globalThis.fetch }) {
|
|
|
15734
16330
|
|
|
15735
16331
|
// src/shell/platform/downloadBundle.ts
|
|
15736
16332
|
async function downloadBundle(signedUrl, deps) {
|
|
15737
|
-
const tmpArchive =
|
|
16333
|
+
const tmpArchive = join9(tmpdir2(), `qawolf-pull-${randomBytes(8).toString("hex")}.tar.gz`);
|
|
15738
16334
|
const result = await fetchSignedUrl({ url: signedUrl, dest: tmpArchive }, { fetch: deps.fetch, fs: deps.fs });
|
|
15739
16335
|
if (!result.ok) {
|
|
15740
16336
|
await deps.fs.unlink(tmpArchive).catch(() => {});
|
|
@@ -15795,6 +16391,10 @@ var makeEmailResourceSchema = () => resource({
|
|
|
15795
16391
|
replyTo: array(namedAddressSchema).optional(),
|
|
15796
16392
|
text: string2().optional().describe("The plain text body.")
|
|
15797
16393
|
}, { urlFieldDescription: emailUrlFieldDescription });
|
|
16394
|
+
var makeEmailAddressResourceSchema = () => resource({
|
|
16395
|
+
address: string2().describe("The inbox address. Mail sent to any plus-suffixed form, such as inbox+abc@example.com, lands in the same inbox."),
|
|
16396
|
+
isDefault: boolean2().describe("Whether this is the workspace's default inbox address.")
|
|
16397
|
+
}, { urlFieldDescription: emailUrlFieldDescription });
|
|
15798
16398
|
|
|
15799
16399
|
// node_modules/@qawolf/api-contracts/dist/v1/identity/organization.js
|
|
15800
16400
|
var identityOrganization = object({
|
|
@@ -15838,6 +16438,10 @@ var identityResponse = union([
|
|
|
15838
16438
|
user: never().optional()
|
|
15839
16439
|
}).transform(({ organization }) => ({ organization }))
|
|
15840
16440
|
]);
|
|
16441
|
+
// node_modules/@qawolf/api-contracts/dist/v1/identity/organizations.js
|
|
16442
|
+
var identityOrganizationsResponse = object({
|
|
16443
|
+
organizations: array(identityOrganization)
|
|
16444
|
+
});
|
|
15841
16445
|
// node_modules/@qawolf/api-contracts/dist/v1/ids.js
|
|
15842
16446
|
var defaultIdSchemas = {
|
|
15843
16447
|
aiTask: string2().min(1).describe("The id of the AI task."),
|
|
@@ -16014,6 +16618,8 @@ var runnerNameSchema = _enum([
|
|
|
16014
16618
|
"android",
|
|
16015
16619
|
"ios"
|
|
16016
16620
|
]);
|
|
16621
|
+
var runnerWorkspaceRequirement = "Required when the credential is not bound to a single workspace, such as a user API key or an OAuth connection whose organization owns several.";
|
|
16622
|
+
var runnerWorkspaceIdDescription = `The workspace the runner belongs to. ${runnerWorkspaceRequirement}`;
|
|
16017
16623
|
var runnerIdSchema = string2().min(1).max(63).regex(/^[a-z0-9]([a-z0-9-]*[a-z0-9])?$/, "A runner id may contain only lowercase letters, digits and dashes, and must start and end with a letter or digit.");
|
|
16018
16624
|
var makeRunnerSchema = () => object({
|
|
16019
16625
|
gpuAccelerated: boolean2().describe("Whether the runner was placed on GPU hardware. Chosen by QA Wolf from the team's configuration and what the fleet can provide, not requested."),
|
|
@@ -16041,10 +16647,11 @@ var inspectRequestSchema = discriminatedUnion("what", [
|
|
|
16041
16647
|
what: literal("variable")
|
|
16042
16648
|
})
|
|
16043
16649
|
]);
|
|
16044
|
-
var makeInspectOnRunnerContract = () => {
|
|
16650
|
+
var makeInspectOnRunnerContract = (ids) => {
|
|
16045
16651
|
const input = object({
|
|
16046
16652
|
id: runnerIdSchema.describe("Id of the runner to inspect."),
|
|
16047
|
-
request: inspectRequestSchema
|
|
16653
|
+
request: inspectRequestSchema,
|
|
16654
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
16048
16655
|
});
|
|
16049
16656
|
const output = discriminatedUnion("outcome", [
|
|
16050
16657
|
object({
|
|
@@ -16191,10 +16798,11 @@ var inspectMobileAnswerSchema = discriminatedUnion("what", [
|
|
|
16191
16798
|
what: literal("elements")
|
|
16192
16799
|
})
|
|
16193
16800
|
]);
|
|
16194
|
-
var makeInspectMobileOnRunnerContract = () => {
|
|
16801
|
+
var makeInspectMobileOnRunnerContract = (ids) => {
|
|
16195
16802
|
const input = object({
|
|
16196
16803
|
id: runnerIdSchema.describe("Id of the runner to inspect."),
|
|
16197
|
-
request: inspectMobileRequestSchema
|
|
16804
|
+
request: inspectMobileRequestSchema,
|
|
16805
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
16198
16806
|
});
|
|
16199
16807
|
const output = discriminatedUnion("outcome", [
|
|
16200
16808
|
inspectMobileAnswerSchema,
|
|
@@ -16278,20 +16886,26 @@ var screenFailureReasons = [
|
|
|
16278
16886
|
var screenNotReadyDescription = "`screen-not-ready` if the runner has a screen that cannot serve this instant. Retry in a second or two: the desktop restarts when a run changes the display size, and it serves one see-or-act request at a time, so a screenshot or action already in flight is the usual reason. Do not submit a run to clear this — a run may restart the display and discard what is on it.";
|
|
16279
16887
|
var screenNeedsARunDescription = "`screen-needs-a-run` if the runner's virtual desktop has never started. Waiting will not change this and retrying is pointless — call `runner.runFlow` with a flow that opens a browser, then ask for the screen again. On a runner image with a browser that has never run anything, any `runner.performAction` also starts the browser itself. Evaluating a snippet does not start the desktop.";
|
|
16280
16888
|
var runnerHasNoScreenDescription = "`runner-has-no-screen` if this runner is not one that runs a browser on a virtual desktop. Nothing about it can be seen or driven, and retrying will never help — launch a `playwright` runner instead.";
|
|
16889
|
+
var screenshotImageSchema = string2().min(1).describe("The screenshot, as a base64-encoded JPEG. Decode it and write the bytes to a `.jpg` file — writing this string to the file leaves you with base64 text rather than an image.");
|
|
16281
16890
|
|
|
16282
16891
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/performAction.js
|
|
16283
16892
|
var unreachableDescription = "`runner-unreachable` if the runner could not be reached: it may still be starting, it may have terminated after inactivity, or it may have stopped answering mid-action. This does not mean the action was not performed — take a screenshot before repeating it.";
|
|
16284
16893
|
var notSupportedOnMobileDescription = "`action-not-supported-on-mobile` if the runner is a mobile device and this action has no touchscreen equivalent: `double_click`, `scroll`, `move`, `keypress` and `navigate`, and a `click` whose `button` is not `left`, are all pointer-device concepts a touchscreen has nothing to offer for. `click` taps, `drag` swipes between its path's first and last point, and `type` types into whatever the last tap focused.";
|
|
16894
|
+
var withScreenshotDescription = "With `withScreenshot: true`, the answer also carries `imageJpegBase64`: a screenshot taken after the action, once the screen has changed from just before it or half a second has passed, whichever comes first. One call instead of `runner.performAction` followed by `runner.takeScreenshot`, with no fixed wait in between. Anything animating on the page counts as a change. The action itself is performed exactly as without the option; if the screen could serve no frame after it — a display restart, or a `navigate` on a desktop that is still starting — the action's result comes back without `imageJpegBase64`, so take a screenshot separately rather than repeating the action. On a mobile runner the screenshot is the device's own, taken right after a performed action.";
|
|
16285
16895
|
var maxActionErrorMessageLength = 1000;
|
|
16286
|
-
var makePerformActionOnRunnerContract = () => {
|
|
16896
|
+
var makePerformActionOnRunnerContract = (ids) => {
|
|
16287
16897
|
const input = object({
|
|
16288
16898
|
action: browserActionSchema.describe("The action to perform. Coordinates are pixels on the screenshot's own coordinate space."),
|
|
16289
|
-
id: runnerIdSchema.describe("Id of the runner to act on.")
|
|
16899
|
+
id: runnerIdSchema.describe("Id of the runner to act on."),
|
|
16900
|
+
withScreenshot: boolean2().optional().describe("Also answer with a screenshot taken after the action, once the screen has changed or half a second has passed. Saves the separate `runner.takeScreenshot` call and its fixed wait."),
|
|
16901
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
16290
16902
|
});
|
|
16903
|
+
const screenshotAfterAction = screenshotImageSchema.optional();
|
|
16291
16904
|
const failure = discriminatedUnion("failureReason", [
|
|
16292
16905
|
object({
|
|
16293
16906
|
errorMessage: string2().max(maxActionErrorMessageLength).describe("What stopped the action from taking effect."),
|
|
16294
16907
|
failureReason: literal("action-failed"),
|
|
16908
|
+
imageJpegBase64: screenshotAfterAction,
|
|
16295
16909
|
outcome: literal("failure")
|
|
16296
16910
|
}),
|
|
16297
16911
|
makeRunnerFailureSchema([
|
|
@@ -16301,11 +16915,14 @@ var makePerformActionOnRunnerContract = () => {
|
|
|
16301
16915
|
])
|
|
16302
16916
|
]);
|
|
16303
16917
|
const output = discriminatedUnion("outcome", [
|
|
16304
|
-
object({
|
|
16918
|
+
object({
|
|
16919
|
+
imageJpegBase64: screenshotAfterAction,
|
|
16920
|
+
outcome: literal("success")
|
|
16921
|
+
}),
|
|
16305
16922
|
failure
|
|
16306
16923
|
]);
|
|
16307
16924
|
return {
|
|
16308
|
-
description: `Perform one raw browser action on an interactive runner: click, double_click, move, drag, scroll, keypress, type, or navigate. Coordinates are whole pixels on the runner's virtual desktop, in the same space as \`runner.takeScreenshot\`. One action per request, and the runner serves one at a time. The action shapes follow the computer-use vocabulary, minus \`screenshot\` (use \`runner.takeScreenshot\`) and \`wait\` (delay on the caller's side). A success means the action took effect. \`action-failed\`, with a reason, if it reached the runner and did not take effect. On a runner image with a browser, the first action on a runner that has never run anything starts its browser and waits for it, so it can take up to a minute to answer — no \`runner.runFlow\` is needed before acting; if the browser is still starting when the wait runs out, the answer is \`screen-not-ready\` and retrying converges. On a mobile runner, the same \`screen-needs-a-run\` and \`screen-not-ready\` outcomes mean no Appium session has started yet, or it did not answer this instant; \`runner.runFlow\` is what starts one, same as a browser. \`screen-needs-a-run\` if the browser could not be started that way — usually a runner whose runs all finished without starting its desktop; call \`runner.runFlow\` with a flow that opens a browser. ${screenNotReadyDescription} A \`navigate\` does not go through the screen, so a screen that is not ready does not stop it. ${runnerHasNoScreenDescription} ${notSupportedOnMobileDescription} ${unreachableDescription}`,
|
|
16925
|
+
description: `Perform one raw browser action on an interactive runner: click, double_click, move, drag, scroll, keypress, type, or navigate. Coordinates are whole pixels on the runner's virtual desktop, in the same space as \`runner.takeScreenshot\`. One action per request, and the runner serves one at a time. The action shapes follow the computer-use vocabulary, minus \`screenshot\` (use \`runner.takeScreenshot\`) and \`wait\` (delay on the caller's side). A success means the action took effect. \`action-failed\`, with a reason, if it reached the runner and did not take effect. On a runner image with a browser, the first action on a runner that has never run anything starts its browser and waits for it, so it can take up to a minute to answer — no \`runner.runFlow\` is needed before acting; if the browser is still starting when the wait runs out, the answer is \`screen-not-ready\` and retrying converges. On a mobile runner, the same \`screen-needs-a-run\` and \`screen-not-ready\` outcomes mean no Appium session has started yet, or it did not answer this instant; \`runner.runFlow\` is what starts one, same as a browser. \`screen-needs-a-run\` if the browser could not be started that way — usually a runner whose runs all finished without starting its desktop; call \`runner.runFlow\` with a flow that opens a browser. ${screenNotReadyDescription} A \`navigate\` does not go through the screen, so a screen that is not ready does not stop it. ${runnerHasNoScreenDescription} ${notSupportedOnMobileDescription} ${withScreenshotDescription} ${unreachableDescription}`,
|
|
16309
16926
|
input,
|
|
16310
16927
|
kind: "write",
|
|
16311
16928
|
name: "runner.performAction",
|
|
@@ -16331,9 +16948,11 @@ var makeAgentGetContract = (ids) => {
|
|
|
16331
16948
|
replies: array(agentReplySchema).describe("Everything the AI has said so far, oldest first. A reply carrying choices is a question the work is blocked on."),
|
|
16332
16949
|
sessionId: ids.chatSession,
|
|
16333
16950
|
status: agentSessionStatusSchema
|
|
16334
|
-
}, {
|
|
16951
|
+
}, {
|
|
16952
|
+
urlFieldDescription: "Absolute URL of the live session in the QA Wolf app."
|
|
16953
|
+
});
|
|
16335
16954
|
return {
|
|
16336
|
-
description: '
|
|
16955
|
+
description: 'Monitor a QA Wolf AI session by reading its status and replies. After agent.send, share the returned session URL before monitoring. Wait 30 to 60 seconds between checks; do not call this in a tight loop. Replies accumulate, so compare them with what you have already seen. Continue monitoring silently when the status and replies are unchanged; do not narrate waiting, announce the next check, or ask whether to keep monitoring. Report only substantive new progress, questions, blockers, or the final outcome. A status of "waiting-for-you" means the last reply is a question the work is blocked on, and answering it with agent.send is what unblocks it. Surface an explicit request for user input even if the status still says "working". Include the session URL when reporting a blocker or final outcome. On "completed", stop status checks and verify the requested result before claiming success. For new flows, validation, publication in the target environment, and readiness are separate checks; a Git push or final reply does not prove the flow is active. If every requested result is verified but status remains "working", report the mismatch and stop monitoring. Stop on "failed" or "cancelled" and report any confirmed partial result.',
|
|
16337
16956
|
input,
|
|
16338
16957
|
kind: "read",
|
|
16339
16958
|
name: "agent.get",
|
|
@@ -16352,9 +16971,11 @@ var makeAgentSendContract = (ids) => {
|
|
|
16352
16971
|
const output = resource({
|
|
16353
16972
|
sessionId: ids.chatSession.describe("Follow the work by passing this to agent.get."),
|
|
16354
16973
|
status: agentSessionStatusSchema
|
|
16355
|
-
}, {
|
|
16974
|
+
}, {
|
|
16975
|
+
urlFieldDescription: "Absolute URL of the live session in the QA Wolf app."
|
|
16976
|
+
});
|
|
16356
16977
|
return {
|
|
16357
|
-
description: "
|
|
16978
|
+
description: "Start or continue work with the QA Wolf AI and return a live session URL to share with the user. Use it to cover a user journey, investigate a failing run, or fix a broken flow. This is the one verb that starts work from nothing: every other write acts on a flow, run or issue that already exists. Returns sessionId, status, and url as soon as the request is accepted; work can take minutes to tens of minutes. After each send, make the next action a normal user-visible assistant message containing the exact returned url, before any tool call or wait. Tool output and internal reasoning do not count as sharing the link. Do not run a timer or monitoring call alongside this send. Acceptance does not mean the work is complete. Then monitor the session with agent.get, reporting new progress, blockers, and the final outcome rather than unchanged status. Send here again to answer a question or add context to the same session.",
|
|
16358
16979
|
input,
|
|
16359
16980
|
kind: "write",
|
|
16360
16981
|
name: "agent.send",
|
|
@@ -16457,10 +17078,7 @@ var makeListEmailAddressesContract = (ids) => {
|
|
|
16457
17078
|
...makePaginationInputFields({ defaultLimit: 20, maxLimit: 50 })
|
|
16458
17079
|
});
|
|
16459
17080
|
const output = object({
|
|
16460
|
-
addresses: array(
|
|
16461
|
-
address: string2().describe("The inbox address. Mail sent to any plus-suffixed form, such as inbox+abc@example.com, lands in the same inbox."),
|
|
16462
|
-
isDefault: boolean2().describe("Whether this is the workspace's default inbox address.")
|
|
16463
|
-
}, { urlFieldDescription: emailUrlFieldDescription })).describe("The workspace's permanent inbox addresses, alphabetical."),
|
|
17081
|
+
addresses: array(makeEmailAddressResourceSchema()).describe("The workspace's permanent inbox addresses, alphabetical."),
|
|
16464
17082
|
nextCursor: nextCursorSchema
|
|
16465
17083
|
});
|
|
16466
17084
|
return {
|
|
@@ -16472,6 +17090,28 @@ var makeListEmailAddressesContract = (ids) => {
|
|
|
16472
17090
|
};
|
|
16473
17091
|
};
|
|
16474
17092
|
|
|
17093
|
+
// node_modules/@qawolf/api-contracts/dist/v1/email/registerAddress.js
|
|
17094
|
+
var hasNoPlusSuffix = (address) => {
|
|
17095
|
+
const [localPart] = address.split("@");
|
|
17096
|
+
return localPart !== undefined && !localPart.includes("+");
|
|
17097
|
+
};
|
|
17098
|
+
var makeRegisterEmailAddressContract = (ids) => {
|
|
17099
|
+
const input = object({
|
|
17100
|
+
address: email2().refine(hasNoPlusSuffix, {
|
|
17101
|
+
message: "Register the base address. Mail to a plus-suffixed form lands in the base inbox."
|
|
17102
|
+
}).describe("The inbox address to register. Its domain must be one QA Wolf serves for the workspace."),
|
|
17103
|
+
workspaceId: ids.workspace.optional().describe("The workspace to register the address in. Required when authenticating with an organization or user API key.")
|
|
17104
|
+
});
|
|
17105
|
+
const output = makeEmailAddressResourceSchema();
|
|
17106
|
+
return {
|
|
17107
|
+
description: "Register an inbox address for the workspace. Registering an address the workspace already has changes nothing. A refusal names the domains the workspace can use.",
|
|
17108
|
+
input,
|
|
17109
|
+
kind: "write",
|
|
17110
|
+
name: "email.registerAddress",
|
|
17111
|
+
output
|
|
17112
|
+
};
|
|
17113
|
+
};
|
|
17114
|
+
|
|
16475
17115
|
// node_modules/@qawolf/api-contracts/dist/v1/email/send.js
|
|
16476
17116
|
var recipientsSchema = array(email2()).max(50);
|
|
16477
17117
|
var maxAttachments = 10;
|
|
@@ -17112,14 +17752,31 @@ var makeReattemptRunContract = (ids) => {
|
|
|
17112
17752
|
};
|
|
17113
17753
|
};
|
|
17114
17754
|
|
|
17755
|
+
// node_modules/@qawolf/api-contracts/dist/v1/run/stop.js
|
|
17756
|
+
var makeStopRunContract = (ids) => {
|
|
17757
|
+
const input = object({ runId: ids.run });
|
|
17758
|
+
const output = resource({
|
|
17759
|
+
runId: ids.run,
|
|
17760
|
+
status: _enum(["accepted", "already-finished", "execution-not-found"]).describe('"accepted" means stopping was requested; poll run.get for the final result. "already-finished" means the run has already finished and its result is unchanged. "execution-not-found" means the run exists but no execution was found to stop; it may still be starting or may no longer be available. No stop request was accepted in that case.')
|
|
17761
|
+
}, { urlFieldDescription: "Absolute URL of the run page." });
|
|
17762
|
+
return {
|
|
17763
|
+
description: "Stop a run, including its queued flows and automatic retries. Stopping is asynchronous. Repeated requests are safe, and finished runs keep their results. A run that is still being created returns not found; retry once run.get returns the run. If run.get returns a different runId, use that ID. Poll run.get for results.",
|
|
17764
|
+
input,
|
|
17765
|
+
kind: "write",
|
|
17766
|
+
name: "run.stop",
|
|
17767
|
+
output
|
|
17768
|
+
};
|
|
17769
|
+
};
|
|
17770
|
+
|
|
17115
17771
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/evaluateSnippet.js
|
|
17116
17772
|
var maxSnippetCodeLength = 64 * 1024;
|
|
17117
|
-
var makeEvaluateSnippetOnRunnerContract = () => {
|
|
17773
|
+
var makeEvaluateSnippetOnRunnerContract = (ids) => {
|
|
17118
17774
|
const input = object({
|
|
17119
17775
|
code: string2().min(1).max(maxSnippetCodeLength).describe("The code to evaluate against the runner's live page, in the scope of `filePath` when one is given."),
|
|
17120
17776
|
filePath: runFilePathSchema.optional().describe("Path of the file the snippet is evaluated in the scope of. Omit to evaluate a snippet that imports nothing of yours."),
|
|
17121
17777
|
files: runFilesSchema.optional().describe("The file named by `filePath` and everything it imports. A runner holds no copy of your project, so a snippet's scope has to travel with it."),
|
|
17122
|
-
id: runnerIdSchema.describe("Id of the runner to evaluate the snippet on.")
|
|
17778
|
+
id: runnerIdSchema.describe("Id of the runner to evaluate the snippet on."),
|
|
17779
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17123
17780
|
}).refine((request) => request.filePath === undefined || Object.hasOwn(request.files ?? {}, request.filePath), {
|
|
17124
17781
|
error: "A snippet naming a file must carry that file in files.",
|
|
17125
17782
|
path: ["files"]
|
|
@@ -17145,9 +17802,10 @@ var makeEvaluateSnippetOnRunnerContract = () => {
|
|
|
17145
17802
|
};
|
|
17146
17803
|
|
|
17147
17804
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/get.js
|
|
17148
|
-
var makeGetRunnerContract = () => {
|
|
17805
|
+
var makeGetRunnerContract = (ids) => {
|
|
17149
17806
|
const input = object({
|
|
17150
|
-
id: runnerIdSchema.describe("Id of the runner to look up.")
|
|
17807
|
+
id: runnerIdSchema.describe("Id of the runner to look up."),
|
|
17808
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17151
17809
|
});
|
|
17152
17810
|
const output = object({
|
|
17153
17811
|
id: runnerIdSchema.describe("Id of the runner the call addressed."),
|
|
@@ -17165,10 +17823,11 @@ var makeGetRunnerContract = () => {
|
|
|
17165
17823
|
|
|
17166
17824
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/highlightSelector.js
|
|
17167
17825
|
var maxHighlightSelectorLength = 2000;
|
|
17168
|
-
var makeHighlightSelectorOnRunnerContract = () => {
|
|
17826
|
+
var makeHighlightSelectorOnRunnerContract = (ids) => {
|
|
17169
17827
|
const input = object({
|
|
17170
17828
|
id: runnerIdSchema.describe("Id of the runner to highlight on."),
|
|
17171
|
-
selector: string2().max(maxHighlightSelectorLength).describe("The selector to highlight. An empty string clears whatever is highlighted.")
|
|
17829
|
+
selector: string2().max(maxHighlightSelectorLength).describe("The selector to highlight. An empty string clears whatever is highlighted."),
|
|
17830
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17172
17831
|
});
|
|
17173
17832
|
const output = discriminatedUnion("outcome", [
|
|
17174
17833
|
object({
|
|
@@ -17203,12 +17862,13 @@ var makeHighlightSelectorOnRunnerContract = () => {
|
|
|
17203
17862
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/importPackage.js
|
|
17204
17863
|
var maxPackageNameLength = 214;
|
|
17205
17864
|
var maxPackageVersionLength = 256;
|
|
17206
|
-
var makeImportPackageOnRunnerContract = () => {
|
|
17865
|
+
var makeImportPackageOnRunnerContract = (ids) => {
|
|
17207
17866
|
const input = object({
|
|
17208
17867
|
id: runnerIdSchema.describe("Id of the runner to install into."),
|
|
17209
17868
|
npmDependencies: record(string2(), string2()).describe("The run's current npm dependencies, so the install resolves against them."),
|
|
17210
17869
|
packageName: string2().min(1).max(maxPackageNameLength),
|
|
17211
|
-
packageVersion: string2().min(1).max(maxPackageVersionLength)
|
|
17870
|
+
packageVersion: string2().min(1).max(maxPackageVersionLength),
|
|
17871
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17212
17872
|
});
|
|
17213
17873
|
const output = discriminatedUnion("outcome", [
|
|
17214
17874
|
object({ outcome: literal("success") }),
|
|
@@ -17228,10 +17888,11 @@ var makeImportPackageOnRunnerContract = () => {
|
|
|
17228
17888
|
};
|
|
17229
17889
|
|
|
17230
17890
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/index.js
|
|
17231
|
-
var makeLaunchRunnerContract = () => {
|
|
17891
|
+
var makeLaunchRunnerContract = (ids) => {
|
|
17232
17892
|
const input = object({
|
|
17233
17893
|
id: runnerIdSchema.describe("Id to launch the runner under, chosen by the caller. Launching the same id again returns the runner already running under it."),
|
|
17234
|
-
runnerName: runnerNameSchema.optional().describe("The runner family to launch. Defaults to the standard web family, playwright.")
|
|
17894
|
+
runnerName: runnerNameSchema.optional().describe("The runner family to launch. Defaults to the standard web family, playwright."),
|
|
17895
|
+
workspaceId: ids.workspace.optional().describe(`The workspace to launch the runner in. ${runnerWorkspaceRequirement}`)
|
|
17235
17896
|
});
|
|
17236
17897
|
const output = makeRunnerSchema().extend({
|
|
17237
17898
|
alreadyRunning: boolean2().describe("True when a runner was already running under this id and was returned unchanged, so no second runner was started and nothing new is billed. Not an error either way."),
|
|
@@ -17245,9 +17906,10 @@ var makeLaunchRunnerContract = () => {
|
|
|
17245
17906
|
output
|
|
17246
17907
|
};
|
|
17247
17908
|
};
|
|
17248
|
-
var makeTerminateRunnerContract = () => {
|
|
17909
|
+
var makeTerminateRunnerContract = (ids) => {
|
|
17249
17910
|
const input = object({
|
|
17250
|
-
id: runnerIdSchema.describe("Id of the runner to terminate.")
|
|
17911
|
+
id: runnerIdSchema.describe("Id of the runner to terminate."),
|
|
17912
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17251
17913
|
});
|
|
17252
17914
|
const output = object({
|
|
17253
17915
|
id: runnerIdSchema.describe("Id of the runner the call addressed."),
|
|
@@ -17264,14 +17926,16 @@ var makeTerminateRunnerContract = () => {
|
|
|
17264
17926
|
};
|
|
17265
17927
|
|
|
17266
17928
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/list.js
|
|
17267
|
-
var makeListRunnersContract = () => {
|
|
17268
|
-
const input = object({
|
|
17929
|
+
var makeListRunnersContract = (ids) => {
|
|
17930
|
+
const input = object({
|
|
17931
|
+
workspaceId: ids.workspace.optional().describe(`The workspace whose runners to list. ${runnerWorkspaceRequirement}`)
|
|
17932
|
+
});
|
|
17269
17933
|
const output = object({
|
|
17270
17934
|
outcome: literal("success"),
|
|
17271
17935
|
runners: array(makeRunnerSchema()).describe("The runners running right now, in no particular order. Each entry carries the same fields a launch reports, so any of them can be addressed like a runner you launched yourself.")
|
|
17272
17936
|
});
|
|
17273
17937
|
return {
|
|
17274
|
-
description: "List the runners running on the caller's team right now.
|
|
17938
|
+
description: "List the runners running on the caller's team right now. A team API key already names the team and sends nothing; a credential bound to no single workspace names one in `workspaceId`. A runner that was terminated, or that terminated on its own after inactivity, is not listed, because the run system keeps no record of a runner once it is gone. This is a lookup: it never starts a runner, and it does not reset a runner's inactivity clock the way reading its journal does. It is how to find a runner launched from another machine or in an earlier session, when the id it was launched under is no longer at hand.",
|
|
17275
17939
|
input,
|
|
17276
17940
|
kind: "read",
|
|
17277
17941
|
name: "runner.list",
|
|
@@ -17281,11 +17945,12 @@ var makeListRunnersContract = () => {
|
|
|
17281
17945
|
|
|
17282
17946
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/promoteSnapshot.js
|
|
17283
17947
|
var maxSnapshotPathLength = 1024;
|
|
17284
|
-
var makePromoteSnapshotOnRunnerContract = () => {
|
|
17948
|
+
var makePromoteSnapshotOnRunnerContract = (ids) => {
|
|
17285
17949
|
const input = object({
|
|
17286
17950
|
baselinePath: string2().min(1).max(maxSnapshotPathLength).describe("The baseline to replace, as named in the image diff."),
|
|
17287
17951
|
id: runnerIdSchema.describe("Id of the runner holding the screenshot."),
|
|
17288
|
-
screenshotPath: string2().min(1).max(maxSnapshotPathLength).describe("The screenshot to promote, as named in the image diff.")
|
|
17952
|
+
screenshotPath: string2().min(1).max(maxSnapshotPathLength).describe("The screenshot to promote, as named in the image diff."),
|
|
17953
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17289
17954
|
});
|
|
17290
17955
|
const output = discriminatedUnion("outcome", [
|
|
17291
17956
|
object({ outcome: literal("success") }),
|
|
@@ -17308,10 +17973,11 @@ var makePromoteSnapshotOnRunnerContract = () => {
|
|
|
17308
17973
|
};
|
|
17309
17974
|
|
|
17310
17975
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/readJournal.js
|
|
17311
|
-
var makeReadRunnerJournalContract = () => {
|
|
17976
|
+
var makeReadRunnerJournalContract = (ids) => {
|
|
17312
17977
|
const input = readJournalRequestSchema.extend({
|
|
17313
17978
|
id: runnerIdSchema.describe("Id of the runner to read the journal of."),
|
|
17314
|
-
stream: journalStreamSchema.describe(`The stream to read. QA Wolf writes ${knownJournalStreams.join(", ")}; an unwritten stream reads as empty rather than as an error.`)
|
|
17979
|
+
stream: journalStreamSchema.describe(`The stream to read. QA Wolf writes ${knownJournalStreams.join(", ")}; an unwritten stream reads as empty rather than as an error.`),
|
|
17980
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17315
17981
|
});
|
|
17316
17982
|
const output = discriminatedUnion("outcome", [
|
|
17317
17983
|
readJournalResponseSchema.extend({ outcome: literal("success") }),
|
|
@@ -17346,7 +18012,8 @@ var makeRunFlowOnRunnerContract = (ids) => {
|
|
|
17346
18012
|
files: runFilesSchema.describe("Every file the run needs keyed by its path — the flow file, everything it imports, package.json and tsconfig.json. A runner holds no copy of your project, so what runs is exactly what is sent here."),
|
|
17347
18013
|
id: runnerIdSchema.describe("Id of the runner to run the flow on."),
|
|
17348
18014
|
selection: runSelectionSchema.optional().describe("Run only these lines, inside the page the runner is already on, instead of running the whole flow from a fresh browser. Omit to run the whole entry point."),
|
|
17349
|
-
unchangedFiles: unchangedFilesSchema.optional().describe("Files this runner already holds from an earlier run, keyed by path with a hex SHA-256 of the content it should hold. Send this to make `files` only what changed. The runner refuses with `needs-full-sync` if it holds none of a referenced path, so it never runs something other than what was asked for. Omit it to send every file.")
|
|
18015
|
+
unchangedFiles: unchangedFilesSchema.optional().describe("Files this runner already holds from an earlier run, keyed by path with a hex SHA-256 of the content it should hold. Send this to make `files` only what changed. The runner refuses with `needs-full-sync` if it holds none of a referenced path, so it never runs something other than what was asked for. Omit it to send every file."),
|
|
18016
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17350
18017
|
}).refine((request) => request.env === undefined || request.environmentId === undefined, {
|
|
17351
18018
|
error: "Send env or environmentId, not both. An environment's variables are taken as they are stored, so there is no order in which the two would combine.",
|
|
17352
18019
|
path: ["environmentId"]
|
|
@@ -17398,9 +18065,10 @@ var makeRunFlowOnRunnerContract = (ids) => {
|
|
|
17398
18065
|
};
|
|
17399
18066
|
|
|
17400
18067
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/stopRun.js
|
|
17401
|
-
var makeStopRunOnRunnerContract = () => {
|
|
18068
|
+
var makeStopRunOnRunnerContract = (ids) => {
|
|
17402
18069
|
const input = object({
|
|
17403
|
-
id: runnerIdSchema.describe("Id of the runner to stop the run on.")
|
|
18070
|
+
id: runnerIdSchema.describe("Id of the runner to stop the run on."),
|
|
18071
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17404
18072
|
});
|
|
17405
18073
|
const output = discriminatedUnion("outcome", [
|
|
17406
18074
|
object({
|
|
@@ -17419,13 +18087,14 @@ var makeStopRunOnRunnerContract = () => {
|
|
|
17419
18087
|
};
|
|
17420
18088
|
|
|
17421
18089
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/takeScreenshot.js
|
|
17422
|
-
var makeTakeScreenshotOnRunnerContract = () => {
|
|
18090
|
+
var makeTakeScreenshotOnRunnerContract = (ids) => {
|
|
17423
18091
|
const input = object({
|
|
17424
|
-
id: runnerIdSchema.describe("Id of the runner to take a screenshot of.")
|
|
18092
|
+
id: runnerIdSchema.describe("Id of the runner to take a screenshot of."),
|
|
18093
|
+
workspaceId: ids.workspace.optional().describe(runnerWorkspaceIdDescription)
|
|
17425
18094
|
});
|
|
17426
18095
|
const output = discriminatedUnion("outcome", [
|
|
17427
18096
|
object({
|
|
17428
|
-
imageJpegBase64:
|
|
18097
|
+
imageJpegBase64: screenshotImageSchema,
|
|
17429
18098
|
outcome: literal("success")
|
|
17430
18099
|
}),
|
|
17431
18100
|
makeRunnerFailureSchema([
|
|
@@ -17479,6 +18148,7 @@ var makeContractsV1 = (ids) => {
|
|
|
17479
18148
|
get: makeGetEmailContract(resolvedIds),
|
|
17480
18149
|
getAttachment: makeGetEmailAttachmentContract(resolvedIds),
|
|
17481
18150
|
listAddresses: makeListEmailAddressesContract(resolvedIds),
|
|
18151
|
+
registerAddress: makeRegisterEmailAddressContract(resolvedIds),
|
|
17482
18152
|
send: makeSendEmailContract(resolvedIds)
|
|
17483
18153
|
},
|
|
17484
18154
|
environment: {
|
|
@@ -17510,24 +18180,25 @@ var makeContractsV1 = (ids) => {
|
|
|
17510
18180
|
diagnose: makeDiagnoseRunContract(resolvedIds),
|
|
17511
18181
|
find: makeFindRunsContract(resolvedIds),
|
|
17512
18182
|
get: makeGetRunContract(resolvedIds),
|
|
17513
|
-
reattempt: makeReattemptRunContract(resolvedIds)
|
|
18183
|
+
reattempt: makeReattemptRunContract(resolvedIds),
|
|
18184
|
+
stop: makeStopRunContract(resolvedIds)
|
|
17514
18185
|
},
|
|
17515
18186
|
runner: {
|
|
17516
|
-
evaluateSnippet: makeEvaluateSnippetOnRunnerContract(),
|
|
17517
|
-
get: makeGetRunnerContract(),
|
|
17518
|
-
highlightSelector: makeHighlightSelectorOnRunnerContract(),
|
|
17519
|
-
importPackage: makeImportPackageOnRunnerContract(),
|
|
17520
|
-
inspect: makeInspectOnRunnerContract(),
|
|
17521
|
-
inspectMobile: makeInspectMobileOnRunnerContract(),
|
|
17522
|
-
launch: makeLaunchRunnerContract(),
|
|
17523
|
-
list: makeListRunnersContract(),
|
|
17524
|
-
performAction: makePerformActionOnRunnerContract(),
|
|
17525
|
-
promoteSnapshot: makePromoteSnapshotOnRunnerContract(),
|
|
17526
|
-
readJournal: makeReadRunnerJournalContract(),
|
|
18187
|
+
evaluateSnippet: makeEvaluateSnippetOnRunnerContract(resolvedIds),
|
|
18188
|
+
get: makeGetRunnerContract(resolvedIds),
|
|
18189
|
+
highlightSelector: makeHighlightSelectorOnRunnerContract(resolvedIds),
|
|
18190
|
+
importPackage: makeImportPackageOnRunnerContract(resolvedIds),
|
|
18191
|
+
inspect: makeInspectOnRunnerContract(resolvedIds),
|
|
18192
|
+
inspectMobile: makeInspectMobileOnRunnerContract(resolvedIds),
|
|
18193
|
+
launch: makeLaunchRunnerContract(resolvedIds),
|
|
18194
|
+
list: makeListRunnersContract(resolvedIds),
|
|
18195
|
+
performAction: makePerformActionOnRunnerContract(resolvedIds),
|
|
18196
|
+
promoteSnapshot: makePromoteSnapshotOnRunnerContract(resolvedIds),
|
|
18197
|
+
readJournal: makeReadRunnerJournalContract(resolvedIds),
|
|
17527
18198
|
runFlow: makeRunFlowOnRunnerContract(resolvedIds),
|
|
17528
|
-
stopRun: makeStopRunOnRunnerContract(),
|
|
17529
|
-
takeScreenshot: makeTakeScreenshotOnRunnerContract(),
|
|
17530
|
-
terminate: makeTerminateRunnerContract()
|
|
18199
|
+
stopRun: makeStopRunOnRunnerContract(resolvedIds),
|
|
18200
|
+
takeScreenshot: makeTakeScreenshotOnRunnerContract(resolvedIds),
|
|
18201
|
+
terminate: makeTerminateRunnerContract(resolvedIds)
|
|
17531
18202
|
},
|
|
17532
18203
|
tag: {
|
|
17533
18204
|
create: makeCreateTagContract(resolvedIds),
|
|
@@ -17537,19 +18208,70 @@ var makeContractsV1 = (ids) => {
|
|
|
17537
18208
|
};
|
|
17538
18209
|
var publicContractsV1 = makeContractsV1(defaultIdSchemas);
|
|
17539
18210
|
|
|
18211
|
+
// src/shell/platform/organizations.ts
|
|
18212
|
+
function parseOrganizationsResponse(json) {
|
|
18213
|
+
const parsed = identityOrganizationsResponse.safeParse(json);
|
|
18214
|
+
return parsed.success ? parsed.data.organizations : undefined;
|
|
18215
|
+
}
|
|
18216
|
+
|
|
18217
|
+
// src/shell/platform/getAccessibleOrganizations.ts
|
|
18218
|
+
var timeoutMs2 = 1e4;
|
|
18219
|
+
async function getAccessibleOrganizations(apiKey, deps) {
|
|
18220
|
+
const url = `${deps.baseUrl}/api/v0/identity/organizations`;
|
|
18221
|
+
let response;
|
|
18222
|
+
try {
|
|
18223
|
+
response = await deps.fetch(url, {
|
|
18224
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
18225
|
+
signal: AbortSignal.timeout(timeoutMs2)
|
|
18226
|
+
});
|
|
18227
|
+
} catch (error) {
|
|
18228
|
+
if (isTimeoutError(error)) {
|
|
18229
|
+
return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs2 } };
|
|
18230
|
+
}
|
|
18231
|
+
return { ok: false, error: { kind: "network", cause: toError(error) } };
|
|
18232
|
+
}
|
|
18233
|
+
if (!response.ok) {
|
|
18234
|
+
const body = await response.text().catch(() => "");
|
|
18235
|
+
return {
|
|
18236
|
+
ok: false,
|
|
18237
|
+
error: { kind: "http", status: response.status, body }
|
|
18238
|
+
};
|
|
18239
|
+
}
|
|
18240
|
+
let json;
|
|
18241
|
+
try {
|
|
18242
|
+
json = await response.json();
|
|
18243
|
+
} catch (error) {
|
|
18244
|
+
if (isTimeoutError(error)) {
|
|
18245
|
+
return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs2 } };
|
|
18246
|
+
}
|
|
18247
|
+
json = undefined;
|
|
18248
|
+
}
|
|
18249
|
+
const organizations = parseOrganizationsResponse(json);
|
|
18250
|
+
if (!organizations) {
|
|
18251
|
+
return {
|
|
18252
|
+
ok: false,
|
|
18253
|
+
error: {
|
|
18254
|
+
kind: "parse",
|
|
18255
|
+
cause: Error("organizations response did not match the contract")
|
|
18256
|
+
}
|
|
18257
|
+
};
|
|
18258
|
+
}
|
|
18259
|
+
return { ok: true, data: organizations };
|
|
18260
|
+
}
|
|
18261
|
+
|
|
17540
18262
|
// src/shell/platform/getIdentity.ts
|
|
17541
|
-
var
|
|
18263
|
+
var timeoutMs3 = 1e4;
|
|
17542
18264
|
async function getIdentity(apiKey, deps) {
|
|
17543
18265
|
const url = `${deps.baseUrl}/api/v0/identity`;
|
|
17544
18266
|
let response;
|
|
17545
18267
|
try {
|
|
17546
18268
|
response = await deps.fetch(url, {
|
|
17547
18269
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
17548
|
-
signal: AbortSignal.timeout(
|
|
18270
|
+
signal: AbortSignal.timeout(timeoutMs3)
|
|
17549
18271
|
});
|
|
17550
18272
|
} catch (error) {
|
|
17551
18273
|
if (isTimeoutError(error)) {
|
|
17552
|
-
return { ok: false, error: { kind: "timeout", timeoutMs } };
|
|
18274
|
+
return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs3 } };
|
|
17553
18275
|
}
|
|
17554
18276
|
return {
|
|
17555
18277
|
ok: false,
|
|
@@ -17568,7 +18290,7 @@ async function getIdentity(apiKey, deps) {
|
|
|
17568
18290
|
json = await response.json();
|
|
17569
18291
|
} catch (error) {
|
|
17570
18292
|
if (isTimeoutError(error)) {
|
|
17571
|
-
return { ok: false, error: { kind: "timeout", timeoutMs } };
|
|
18293
|
+
return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs3 } };
|
|
17572
18294
|
}
|
|
17573
18295
|
json = undefined;
|
|
17574
18296
|
}
|
|
@@ -17579,6 +18301,24 @@ async function getIdentity(apiKey, deps) {
|
|
|
17579
18301
|
return { ok: true, data: parsed.data };
|
|
17580
18302
|
}
|
|
17581
18303
|
|
|
18304
|
+
// src/shell/platform/identityMethods.ts
|
|
18305
|
+
function createIdentityMethods(apiKey, deps, backoffMs) {
|
|
18306
|
+
return {
|
|
18307
|
+
getIdentity: () => requestWithRetry({
|
|
18308
|
+
call: () => getIdentity(apiKey, deps),
|
|
18309
|
+
backoffMs,
|
|
18310
|
+
describe: describeIdentityError,
|
|
18311
|
+
sleep: deps.sleep
|
|
18312
|
+
}),
|
|
18313
|
+
getAccessibleOrganizations: () => requestWithRetry({
|
|
18314
|
+
call: () => getAccessibleOrganizations(apiKey, deps),
|
|
18315
|
+
backoffMs,
|
|
18316
|
+
describe: describeIdentityError,
|
|
18317
|
+
sleep: deps.sleep
|
|
18318
|
+
})
|
|
18319
|
+
};
|
|
18320
|
+
}
|
|
18321
|
+
|
|
17582
18322
|
// src/shell/platform/types.ts
|
|
17583
18323
|
var environmentListItemSchema = object({
|
|
17584
18324
|
envId: string2(),
|
|
@@ -17706,7 +18446,7 @@ function isUnsafeSegment(segment) {
|
|
|
17706
18446
|
|
|
17707
18447
|
// src/shell/platform/teamStorageAssetSnapshot.ts
|
|
17708
18448
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
17709
|
-
import { join as
|
|
18449
|
+
import { join as join10, sep as sep2 } from "node:path";
|
|
17710
18450
|
function mintAssetSnapshotPath(assetsAbs, label) {
|
|
17711
18451
|
return `${assetsAbs}.${label}-${randomBytes2(8).toString("hex")}`;
|
|
17712
18452
|
}
|
|
@@ -17756,9 +18496,9 @@ function expectedEntries(filePaths) {
|
|
|
17756
18496
|
return entries;
|
|
17757
18497
|
}
|
|
17758
18498
|
async function collectEntries(root, relDir, out, fs) {
|
|
17759
|
-
const current = relDir ?
|
|
18499
|
+
const current = relDir ? join10(root, relDir) : root;
|
|
17760
18500
|
for (const entry of await fs.readdirWithTypes(current)) {
|
|
17761
|
-
const relPath = relDir ?
|
|
18501
|
+
const relPath = relDir ? join10(relDir, entry.name) : entry.name;
|
|
17762
18502
|
out.add(relPath);
|
|
17763
18503
|
if (entry.isDirectory()) {
|
|
17764
18504
|
await collectEntries(root, relPath, out, fs);
|
|
@@ -17772,7 +18512,7 @@ function setsEqual(left, right) {
|
|
|
17772
18512
|
}
|
|
17773
18513
|
|
|
17774
18514
|
// src/shell/platform/teamStorageAssetReuse.ts
|
|
17775
|
-
import { join as
|
|
18515
|
+
import { join as join11 } from "node:path";
|
|
17776
18516
|
async function reusableAssetPaths(assetsAbs, safeFiles, manifest, fs) {
|
|
17777
18517
|
const reusable = new Set;
|
|
17778
18518
|
for (const { file, relativePath } of safeFiles) {
|
|
@@ -17788,20 +18528,20 @@ async function canReuseAsset(assetsAbs, relativePath, file, manifest, fs) {
|
|
|
17788
18528
|
const previous = manifest.get(relativePath);
|
|
17789
18529
|
if (previous?.etag !== file.etag)
|
|
17790
18530
|
return false;
|
|
17791
|
-
return fs.pathExists(
|
|
18531
|
+
return fs.pathExists(join11(assetsAbs, relativePath));
|
|
17792
18532
|
}
|
|
17793
18533
|
|
|
17794
18534
|
// src/shell/platform/writeAssetSnapshot.ts
|
|
17795
|
-
import { dirname as dirname3, join as
|
|
18535
|
+
import { dirname as dirname3, join as join12 } from "node:path";
|
|
17796
18536
|
async function writeAssetSnapshot(args) {
|
|
17797
18537
|
let downloadedCount = 0;
|
|
17798
18538
|
const total = args.safeFiles.filter(({ relativePath }) => !args.reusable.has(relativePath)).length;
|
|
17799
18539
|
await args.deps.fs.mkdir(args.tmpAssets, { recursive: true });
|
|
17800
18540
|
for (const { file, relativePath } of args.safeFiles) {
|
|
17801
|
-
const dest =
|
|
18541
|
+
const dest = join12(args.tmpAssets, relativePath);
|
|
17802
18542
|
await args.deps.fs.mkdir(dirname3(dest), { recursive: true });
|
|
17803
18543
|
if (args.reusable.has(relativePath)) {
|
|
17804
|
-
await args.deps.fs.copyFile(
|
|
18544
|
+
await args.deps.fs.copyFile(join12(args.assetsAbs, relativePath), dest);
|
|
17805
18545
|
continue;
|
|
17806
18546
|
}
|
|
17807
18547
|
args.deps.onProgress?.({ current: downloadedCount + 1, total });
|
|
@@ -17861,6 +18601,34 @@ async function writeTeamStorageAssets(args, deps) {
|
|
|
17861
18601
|
}
|
|
17862
18602
|
}
|
|
17863
18603
|
|
|
18604
|
+
// src/shell/platform/teamStorageMethods.ts
|
|
18605
|
+
function createTeamStorageMethods(trpc, deps, fs, getIdentity) {
|
|
18606
|
+
async function list() {
|
|
18607
|
+
if (deps.workspaceId !== undefined) {
|
|
18608
|
+
return listTeamStorageFiles(trpc, { teamId: deps.workspaceId }, deps);
|
|
18609
|
+
}
|
|
18610
|
+
const identity = await getIdentity();
|
|
18611
|
+
if (!identity.ok)
|
|
18612
|
+
return identity;
|
|
18613
|
+
if (!("team" in identity.value)) {
|
|
18614
|
+
return {
|
|
18615
|
+
ok: false,
|
|
18616
|
+
error: flowsMessages.pull.teamStorageRequiresTeamKey
|
|
18617
|
+
};
|
|
18618
|
+
}
|
|
18619
|
+
return listTeamStorageFiles(trpc, { teamId: identity.value.team.id }, deps);
|
|
18620
|
+
}
|
|
18621
|
+
return {
|
|
18622
|
+
listTeamStorageFiles: list,
|
|
18623
|
+
async syncTeamStorageAssets(assetsAbs, opts) {
|
|
18624
|
+
const files = await list();
|
|
18625
|
+
if (!files.ok)
|
|
18626
|
+
return files;
|
|
18627
|
+
return downloadTeamStorageAssets({ assetsAbs, files: files.value }, { fetch: deps.fetch, fs, onProgress: opts?.onProgress });
|
|
18628
|
+
}
|
|
18629
|
+
};
|
|
18630
|
+
}
|
|
18631
|
+
|
|
17864
18632
|
// src/shell/platform/createPlatformClient.ts
|
|
17865
18633
|
var requestBackoffMs2 = [500, 1500];
|
|
17866
18634
|
function createPlatformClient(apiKey, deps) {
|
|
@@ -17877,15 +18645,10 @@ function createPlatformClient(apiKey, deps) {
|
|
|
17877
18645
|
return result;
|
|
17878
18646
|
return { ok: true, value: { signedUrl: result.value.url } };
|
|
17879
18647
|
}
|
|
18648
|
+
const identityMethods = createIdentityMethods(apiKey, deps, requestBackoffMs2);
|
|
17880
18649
|
return {
|
|
17881
|
-
|
|
17882
|
-
|
|
17883
|
-
call: () => getIdentity(apiKey, deps),
|
|
17884
|
-
backoffMs: requestBackoffMs2,
|
|
17885
|
-
describe: describeIdentityError,
|
|
17886
|
-
sleep: deps.sleep
|
|
17887
|
-
});
|
|
17888
|
-
},
|
|
18650
|
+
...identityMethods,
|
|
18651
|
+
...createTeamStorageMethods(trpc, deps, fs, identityMethods.getIdentity),
|
|
17889
18652
|
getFlowsBundleUrl: getFlowsBundleUrlImpl,
|
|
17890
18653
|
callPublicApi: makeCallPublicApiMethod(trpc, deps, requestBackoffMs2),
|
|
17891
18654
|
async getEnvVars(envId) {
|
|
@@ -17899,24 +18662,6 @@ function createPlatformClient(apiKey, deps) {
|
|
|
17899
18662
|
return result;
|
|
17900
18663
|
return { ok: true, value: result.value.environmentVariables };
|
|
17901
18664
|
},
|
|
17902
|
-
async listTeamStorageFiles() {
|
|
17903
|
-
const identity = await this.getIdentity();
|
|
17904
|
-
if (!identity.ok)
|
|
17905
|
-
return identity;
|
|
17906
|
-
if (!("team" in identity.value)) {
|
|
17907
|
-
return {
|
|
17908
|
-
ok: false,
|
|
17909
|
-
error: flowsMessages.pull.teamStorageRequiresTeamKey
|
|
17910
|
-
};
|
|
17911
|
-
}
|
|
17912
|
-
return listTeamStorageFiles(trpc, { teamId: identity.value.team.id }, deps);
|
|
17913
|
-
},
|
|
17914
|
-
async syncTeamStorageAssets(assetsAbs, opts) {
|
|
17915
|
-
const files = await this.listTeamStorageFiles();
|
|
17916
|
-
if (!files.ok)
|
|
17917
|
-
return files;
|
|
17918
|
-
return downloadTeamStorageAssets({ assetsAbs, files: files.value }, { fetch: deps.fetch, fs, onProgress: opts?.onProgress });
|
|
17919
|
-
},
|
|
17920
18665
|
async downloadBundle(envId) {
|
|
17921
18666
|
const urlResult = await getFlowsBundleUrlImpl(envId);
|
|
17922
18667
|
if (!urlResult.ok)
|
|
@@ -18584,8 +19329,88 @@ class V {
|
|
|
18584
19329
|
}
|
|
18585
19330
|
}
|
|
18586
19331
|
}
|
|
18587
|
-
|
|
18588
|
-
|
|
19332
|
+
function p$1(l, e) {
|
|
19333
|
+
if (l === undefined || e.length === 0)
|
|
19334
|
+
return 0;
|
|
19335
|
+
const i = e.findIndex((s) => s.value === l);
|
|
19336
|
+
return i !== -1 ? i : 0;
|
|
19337
|
+
}
|
|
19338
|
+
function g(l, e) {
|
|
19339
|
+
return (e.label ?? String(e.value)).toLowerCase().includes(l.toLowerCase());
|
|
19340
|
+
}
|
|
19341
|
+
function m2(l, e) {
|
|
19342
|
+
if (e)
|
|
19343
|
+
return l ? e : e[0];
|
|
19344
|
+
}
|
|
19345
|
+
var T$1 = class T extends V {
|
|
19346
|
+
filteredOptions;
|
|
19347
|
+
multiple;
|
|
19348
|
+
isNavigating = false;
|
|
19349
|
+
selectedValues = [];
|
|
19350
|
+
focusedValue;
|
|
19351
|
+
#e = 0;
|
|
19352
|
+
#s = "";
|
|
19353
|
+
#t;
|
|
19354
|
+
#i;
|
|
19355
|
+
#n;
|
|
19356
|
+
get cursor() {
|
|
19357
|
+
return this.#e;
|
|
19358
|
+
}
|
|
19359
|
+
get userInputWithCursor() {
|
|
19360
|
+
if (!this.userInput)
|
|
19361
|
+
return styleText(["inverse", "hidden"], "_");
|
|
19362
|
+
if (this._cursor >= this.userInput.length)
|
|
19363
|
+
return `${this.userInput}█`;
|
|
19364
|
+
const e = this.userInput.slice(0, this._cursor), [t, ...i] = this.userInput.slice(this._cursor);
|
|
19365
|
+
return `${e}${styleText("inverse", t)}${i.join("")}`;
|
|
19366
|
+
}
|
|
19367
|
+
get options() {
|
|
19368
|
+
return typeof this.#i == "function" ? this.#i() : this.#i;
|
|
19369
|
+
}
|
|
19370
|
+
constructor(e) {
|
|
19371
|
+
super(e), this.#i = e.options, this.#n = e.placeholder;
|
|
19372
|
+
const t = this.options;
|
|
19373
|
+
this.filteredOptions = [...t], this.multiple = e.multiple === true, this.#t = typeof e.options == "function" ? e.filter : e.filter ?? g;
|
|
19374
|
+
let i;
|
|
19375
|
+
if (e.initialValue && Array.isArray(e.initialValue) ? this.multiple ? i = e.initialValue : i = e.initialValue.slice(0, 1) : !this.multiple && this.options.length > 0 && (i = [this.options[0].value]), i)
|
|
19376
|
+
for (const s of i) {
|
|
19377
|
+
const n = t.findIndex((o) => o.value === s);
|
|
19378
|
+
n !== -1 && (this.toggleSelected(s), this.#e = n);
|
|
19379
|
+
}
|
|
19380
|
+
this.focusedValue = this.options[this.#e]?.value, this.on("key", (s, n) => this.#l(s, n)), this.on("userInput", (s) => this.#u(s));
|
|
19381
|
+
}
|
|
19382
|
+
_isActionKey(e, t) {
|
|
19383
|
+
return e === "\t" || this.multiple && this.isNavigating && t.name === "space" && e !== undefined && e !== "";
|
|
19384
|
+
}
|
|
19385
|
+
#l(e, t) {
|
|
19386
|
+
const i = t.name === "up", s = t.name === "down", n = t.name === "return", o = this.userInput === "" || this.userInput === "\t", u = this.#n, h = this.options, f = u !== undefined && u !== "" && h.some((r) => !r.disabled && (this.#t ? this.#t(u, r) : true));
|
|
19387
|
+
if (t.name === "tab" && o && f) {
|
|
19388
|
+
this.userInput === "\t" && this._clearUserInput(), this._setUserInput(u, true), this.isNavigating = false;
|
|
19389
|
+
return;
|
|
19390
|
+
}
|
|
19391
|
+
i || s ? (this.#e = findCursor(this.#e, i ? -1 : 1, this.filteredOptions), this.focusedValue = this.filteredOptions[this.#e]?.value, this.multiple || (this.selectedValues = [this.focusedValue]), this.isNavigating = true) : n ? this.value = m2(this.multiple, this.selectedValues) : this.multiple ? this.focusedValue !== undefined && (t.name === "tab" || this.isNavigating && t.name === "space") ? this.toggleSelected(this.focusedValue) : this.isNavigating = false : (this.focusedValue && (this.selectedValues = [this.focusedValue]), this.isNavigating = false);
|
|
19392
|
+
}
|
|
19393
|
+
deselectAll() {
|
|
19394
|
+
this.selectedValues = [];
|
|
19395
|
+
}
|
|
19396
|
+
toggleSelected(e) {
|
|
19397
|
+
this.filteredOptions.length !== 0 && (this.multiple ? this.selectedValues.includes(e) ? this.selectedValues = this.selectedValues.filter((t) => t !== e) : this.selectedValues = [...this.selectedValues, e] : this.selectedValues = [e]);
|
|
19398
|
+
}
|
|
19399
|
+
#u(e) {
|
|
19400
|
+
if (e !== this.#s) {
|
|
19401
|
+
this.#s = e;
|
|
19402
|
+
const t = this.options;
|
|
19403
|
+
e && this.#t ? this.filteredOptions = t.filter((n) => this.#t?.(e, n)) : this.filteredOptions = [...t];
|
|
19404
|
+
const i = p$1(this.focusedValue, this.filteredOptions);
|
|
19405
|
+
this.#e = findCursor(i, 0, this.filteredOptions);
|
|
19406
|
+
const s = this.filteredOptions[this.#e];
|
|
19407
|
+
s && !s.disabled ? this.focusedValue = s.value : this.focusedValue = undefined, this.multiple || (this.focusedValue !== undefined ? this.toggleSelected(this.focusedValue) : this.deselectAll());
|
|
19408
|
+
}
|
|
19409
|
+
}
|
|
19410
|
+
};
|
|
19411
|
+
|
|
19412
|
+
class r extends V {
|
|
19413
|
+
get cursor() {
|
|
18589
19414
|
return this.value ? 0 : 1;
|
|
18590
19415
|
}
|
|
18591
19416
|
get _value() {
|
|
@@ -18770,6 +19595,89 @@ var limitOptions = ({
|
|
|
18770
19595
|
b.push(n);
|
|
18771
19596
|
return u && b.push(C), b;
|
|
18772
19597
|
};
|
|
19598
|
+
function P(t) {
|
|
19599
|
+
return t.label ?? String(t.value ?? "");
|
|
19600
|
+
}
|
|
19601
|
+
function E(t, c) {
|
|
19602
|
+
if (!t)
|
|
19603
|
+
return true;
|
|
19604
|
+
const n = (c.label ?? String(c.value ?? "")).toLowerCase(), i = (c.hint ?? "").toLowerCase(), l = String(c.value).toLowerCase(), o = t.toLowerCase();
|
|
19605
|
+
return n.includes(o) || i.includes(o) || l.includes(o);
|
|
19606
|
+
}
|
|
19607
|
+
function N(t, c) {
|
|
19608
|
+
const n = [];
|
|
19609
|
+
for (const i of c)
|
|
19610
|
+
t.includes(i.value) && n.push(i);
|
|
19611
|
+
return n;
|
|
19612
|
+
}
|
|
19613
|
+
var autocomplete = (t) => new T$1({
|
|
19614
|
+
options: t.options,
|
|
19615
|
+
initialValue: t.initialValue ? [t.initialValue] : undefined,
|
|
19616
|
+
initialUserInput: t.initialUserInput,
|
|
19617
|
+
placeholder: t.placeholder,
|
|
19618
|
+
filter: t.filter ?? ((n, i) => E(n, i)),
|
|
19619
|
+
signal: t.signal,
|
|
19620
|
+
input: t.input,
|
|
19621
|
+
output: t.output,
|
|
19622
|
+
validate: t.validate,
|
|
19623
|
+
render() {
|
|
19624
|
+
const n = t.withGuide ?? settings.withGuide, i = n ? [`${styleText2("gray", S_BAR)}`, `${symbol(this.state)} ${t.message}`] : [`${symbol(this.state)} ${t.message}`], l = this.userInput, o = this.options, m = t.placeholder, p = l === "" && m !== undefined, $ = (r, s) => {
|
|
19625
|
+
const a = P(r), u = r.hint && r.value === this.focusedValue ? styleText2("dim", ` (${r.hint})`) : "";
|
|
19626
|
+
switch (s) {
|
|
19627
|
+
case "active":
|
|
19628
|
+
return `${styleText2("green", S_RADIO_ACTIVE)} ${a}${u}`;
|
|
19629
|
+
case "inactive":
|
|
19630
|
+
return `${styleText2("dim", S_RADIO_INACTIVE)} ${styleText2("dim", a)}`;
|
|
19631
|
+
case "disabled":
|
|
19632
|
+
return `${styleText2("gray", S_RADIO_INACTIVE)} ${styleText2(["strikethrough", "gray"], a)}`;
|
|
19633
|
+
}
|
|
19634
|
+
};
|
|
19635
|
+
switch (this.state) {
|
|
19636
|
+
case "submit": {
|
|
19637
|
+
const r = N(this.selectedValues, o), s = r.length > 0 ? ` ${styleText2("dim", r.map(P).join(", "))}` : "", a = n ? styleText2("gray", S_BAR) : "";
|
|
19638
|
+
return `${i.join(`
|
|
19639
|
+
`)}
|
|
19640
|
+
${a}${s}`;
|
|
19641
|
+
}
|
|
19642
|
+
case "cancel": {
|
|
19643
|
+
const r = l ? ` ${styleText2(["strikethrough", "dim"], l)}` : "", s = n ? styleText2("gray", S_BAR) : "";
|
|
19644
|
+
return `${i.join(`
|
|
19645
|
+
`)}
|
|
19646
|
+
${s}${r}`;
|
|
19647
|
+
}
|
|
19648
|
+
default: {
|
|
19649
|
+
const r = this.state === "error" ? "yellow" : "cyan", s = n ? `${styleText2(r, S_BAR)} ` : "", a = n ? styleText2(r, S_BAR_END) : "";
|
|
19650
|
+
let u = "";
|
|
19651
|
+
if (this.isNavigating || p) {
|
|
19652
|
+
const d = p ? m : l;
|
|
19653
|
+
u = d !== "" ? ` ${styleText2("dim", d)}` : "";
|
|
19654
|
+
} else
|
|
19655
|
+
u = ` ${this.userInputWithCursor}`;
|
|
19656
|
+
const V = this.filteredOptions.length !== o.length ? styleText2("dim", ` (${this.filteredOptions.length} match${this.filteredOptions.length === 1 ? "" : "es"})`) : "", y = this.filteredOptions.length === 0 && l ? [`${s}${styleText2("yellow", "No matches found")}`] : [], b = this.state === "error" ? [`${s}${styleText2("yellow", this.error)}`] : [];
|
|
19657
|
+
n && i.push(`${s.trimEnd()}`), i.push(`${s}${styleText2("dim", "Search:")}${u}${V}`, ...y, ...b);
|
|
19658
|
+
const v = [
|
|
19659
|
+
`${styleText2("dim", "↑/↓")} to select`,
|
|
19660
|
+
`${styleText2("dim", "Enter:")} confirm`,
|
|
19661
|
+
`${styleText2("dim", "Type:")} to search`
|
|
19662
|
+
], g = [`${s}${v.join(" • ")}`, a], O = this.filteredOptions.length === 0 ? [] : limitOptions({
|
|
19663
|
+
cursor: this.cursor,
|
|
19664
|
+
options: this.filteredOptions,
|
|
19665
|
+
columnPadding: n ? 3 : 0,
|
|
19666
|
+
rowPadding: i.length + g.length,
|
|
19667
|
+
style: (d, f) => $(d, d.disabled ? "disabled" : f ? "active" : "inactive"),
|
|
19668
|
+
maxItems: t.maxItems,
|
|
19669
|
+
output: t.output
|
|
19670
|
+
});
|
|
19671
|
+
return [
|
|
19672
|
+
...i,
|
|
19673
|
+
...O.map((d) => `${s}${d}`),
|
|
19674
|
+
...g
|
|
19675
|
+
].join(`
|
|
19676
|
+
`);
|
|
19677
|
+
}
|
|
19678
|
+
}
|
|
19679
|
+
}
|
|
19680
|
+
}).prompt();
|
|
18773
19681
|
var confirm = (i) => {
|
|
18774
19682
|
const a = i.active ?? "Yes", s = i.inactive ?? "No";
|
|
18775
19683
|
return new r({
|
|
@@ -19200,6 +20108,7 @@ function createStyledClack() {
|
|
|
19200
20108
|
cancel,
|
|
19201
20109
|
confirm,
|
|
19202
20110
|
password,
|
|
20111
|
+
autocomplete,
|
|
19203
20112
|
select,
|
|
19204
20113
|
isCancel,
|
|
19205
20114
|
spinner,
|
|
@@ -19459,11 +20368,50 @@ function createPassword({
|
|
|
19459
20368
|
};
|
|
19460
20369
|
}
|
|
19461
20370
|
|
|
20371
|
+
// src/core/textSearch.ts
|
|
20372
|
+
function normalizeForSearch(value) {
|
|
20373
|
+
return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
|
|
20374
|
+
}
|
|
20375
|
+
function matchesSearchTerm(search, fields) {
|
|
20376
|
+
const needle = normalizeForSearch(search);
|
|
20377
|
+
if (!needle)
|
|
20378
|
+
return true;
|
|
20379
|
+
return fields.some((field) => field !== undefined && normalizeForSearch(field).includes(needle));
|
|
20380
|
+
}
|
|
20381
|
+
function createSearchIndex(items, fieldsOf) {
|
|
20382
|
+
const indexed = new Map;
|
|
20383
|
+
for (const item of items) {
|
|
20384
|
+
indexed.set(item, fieldsOf(item).flatMap((field) => field === undefined ? [] : [normalizeForSearch(field)]));
|
|
20385
|
+
}
|
|
20386
|
+
let lastSearch;
|
|
20387
|
+
let needle = "";
|
|
20388
|
+
return (search, item) => {
|
|
20389
|
+
if (search !== lastSearch) {
|
|
20390
|
+
lastSearch = search;
|
|
20391
|
+
needle = normalizeForSearch(search);
|
|
20392
|
+
}
|
|
20393
|
+
if (!needle)
|
|
20394
|
+
return true;
|
|
20395
|
+
const fields = indexed.get(item);
|
|
20396
|
+
return fields ? fields.some((field) => field.includes(needle)) : matchesSearchTerm(search, fieldsOf(item));
|
|
20397
|
+
};
|
|
20398
|
+
}
|
|
20399
|
+
|
|
19462
20400
|
// src/shell/ui/renderers/select.ts
|
|
20401
|
+
var searchThreshold = 8;
|
|
20402
|
+
var searchPlaceholder = "Type to filter";
|
|
20403
|
+
function searchableFields(option) {
|
|
20404
|
+
return [option.label, option.hint, option.value];
|
|
20405
|
+
}
|
|
19463
20406
|
function createSelect({ mode, clack }) {
|
|
19464
20407
|
return async (message, options) => {
|
|
19465
20408
|
assertHumanMode(mode, "select");
|
|
19466
|
-
const value = await clack.
|
|
20409
|
+
const value = options.length > searchThreshold ? await clack.autocomplete({
|
|
20410
|
+
message,
|
|
20411
|
+
options: [...options],
|
|
20412
|
+
placeholder: searchPlaceholder,
|
|
20413
|
+
filter: createSearchIndex([...options], searchableFields)
|
|
20414
|
+
}) : await clack.select({ message, options: [...options] });
|
|
19467
20415
|
if (clack.isCancel(value))
|
|
19468
20416
|
return { ok: false };
|
|
19469
20417
|
return { ok: true, value };
|
|
@@ -19511,12 +20459,14 @@ function createVerboseContext(outputMode, isVerbose) {
|
|
|
19511
20459
|
|
|
19512
20460
|
// src/shell/npmRegistry.ts
|
|
19513
20461
|
var registryUrl = "https://registry.npmjs.org";
|
|
19514
|
-
var
|
|
20462
|
+
var timeoutMs4 = 3000;
|
|
19515
20463
|
async function fetchLatestVersion(packageName, deps = {}) {
|
|
19516
20464
|
const fetchFn = deps.fetchFn ?? globalThis.fetch;
|
|
20465
|
+
const deadline = AbortSignal.timeout(timeoutMs4);
|
|
20466
|
+
const signal = deps.signal ? AbortSignal.any([deps.signal, deadline]) : deadline;
|
|
19517
20467
|
try {
|
|
19518
20468
|
const response = await fetchFn(`${registryUrl}/${packageName}/latest`, {
|
|
19519
|
-
signal
|
|
20469
|
+
signal
|
|
19520
20470
|
});
|
|
19521
20471
|
if (!response.ok)
|
|
19522
20472
|
return;
|
|
@@ -19531,7 +20481,7 @@ async function fetchLatestVersion(packageName, deps = {}) {
|
|
|
19531
20481
|
}
|
|
19532
20482
|
|
|
19533
20483
|
// src/domains/updateCheck/updateCheck.ts
|
|
19534
|
-
import { join as
|
|
20484
|
+
import { join as join13 } from "node:path";
|
|
19535
20485
|
|
|
19536
20486
|
// src/core/version.ts
|
|
19537
20487
|
function parseRelease(version) {
|
|
@@ -19565,19 +20515,21 @@ function startUpdateCheck(deps) {
|
|
|
19565
20515
|
if (deps.env["QAWOLF_NO_UPDATE_CHECK"]) {
|
|
19566
20516
|
return noopNotifier;
|
|
19567
20517
|
}
|
|
20518
|
+
const controller = new AbortController;
|
|
19568
20519
|
let latest;
|
|
19569
|
-
deps.fetchLatestVersion().then((version) => {
|
|
20520
|
+
deps.fetchLatestVersion(controller.signal).then((version) => {
|
|
19570
20521
|
latest = version;
|
|
19571
20522
|
}, () => {
|
|
19572
20523
|
return;
|
|
19573
20524
|
});
|
|
19574
20525
|
return {
|
|
19575
20526
|
async notifyIfOutdated() {
|
|
20527
|
+
controller.abort();
|
|
19576
20528
|
if (latest === undefined)
|
|
19577
20529
|
return;
|
|
19578
20530
|
if (!isNewerVersion(deps.currentVersion, latest))
|
|
19579
20531
|
return;
|
|
19580
|
-
const noticePath =
|
|
20532
|
+
const noticePath = join13(deps.configDir, noticeFile);
|
|
19581
20533
|
try {
|
|
19582
20534
|
const notified = (await deps.fs.readFile(noticePath)).trim();
|
|
19583
20535
|
if (notified === latest)
|
|
@@ -19599,7 +20551,7 @@ function startUpdateCheck(deps) {
|
|
|
19599
20551
|
// package.json
|
|
19600
20552
|
var package_default = {
|
|
19601
20553
|
name: "@qawolf/cli",
|
|
19602
|
-
version: "1.
|
|
20554
|
+
version: "1.26.0",
|
|
19603
20555
|
description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
19604
20556
|
keywords: [
|
|
19605
20557
|
"automation",
|
|
@@ -19670,7 +20622,7 @@ var package_default = {
|
|
|
19670
20622
|
"@clack/prompts": "1.5.1",
|
|
19671
20623
|
"@napi-rs/keyring": "1.3.0",
|
|
19672
20624
|
"@oxc-node/core": "0.1.0",
|
|
19673
|
-
"@qawolf/api-contracts": "0.
|
|
20625
|
+
"@qawolf/api-contracts": "0.52.0",
|
|
19674
20626
|
"@qawolf/emails": "1.1.1",
|
|
19675
20627
|
"@qawolf/flow-targets": "1.0.0",
|
|
19676
20628
|
"@qawolf/flows": "0.1.4",
|
|
@@ -19736,7 +20688,7 @@ function buildBaseContext(command, signals) {
|
|
|
19736
20688
|
currentVersion: package_default.version,
|
|
19737
20689
|
configDir: getConfigDir(),
|
|
19738
20690
|
fs,
|
|
19739
|
-
fetchLatestVersion: () => fetchLatestVersion(package_default.name),
|
|
20691
|
+
fetchLatestVersion: (signal) => fetchLatestVersion(package_default.name, { signal }),
|
|
19740
20692
|
renderNotice: (body, title) => ui.note(body, title)
|
|
19741
20693
|
});
|
|
19742
20694
|
return {
|
|
@@ -19789,7 +20741,12 @@ function withAuthContext(signals, fn, deps = {}) {
|
|
|
19789
20741
|
await updateNotifier.notifyIfOutdated();
|
|
19790
20742
|
return;
|
|
19791
20743
|
}
|
|
19792
|
-
const platformClient = (deps.createPlatform ?? createPlatformClient)(resolved.key, {
|
|
20744
|
+
const platformClient = (deps.createPlatform ?? createPlatformClient)(resolved.key, {
|
|
20745
|
+
baseUrl: apiBaseUrl,
|
|
20746
|
+
fetch: globalThis.fetch,
|
|
20747
|
+
logger: ctx.log("trpc"),
|
|
20748
|
+
workspaceId: resolved.workspaceId
|
|
20749
|
+
});
|
|
19793
20750
|
try {
|
|
19794
20751
|
const result = await fn({
|
|
19795
20752
|
...ctx,
|
|
@@ -19810,28 +20767,15 @@ function withAuthContext(signals, fn, deps = {}) {
|
|
|
19810
20767
|
};
|
|
19811
20768
|
}
|
|
19812
20769
|
|
|
19813
|
-
// src/commands/auth/
|
|
19814
|
-
async function
|
|
19815
|
-
if (ctx.ui.mode !== "human") {
|
|
19816
|
-
ctx.ui.error(authMessages.login.nonInteractive);
|
|
19817
|
-
return { error: "non-interactive" };
|
|
19818
|
-
}
|
|
19819
|
-
const existing = await resolveApiKey(ctx.configDir, ctx.fs);
|
|
19820
|
-
if (existing) {
|
|
19821
|
-
const reauth = await ctx.ui.confirm(authMessages.login.reAuthPrompt);
|
|
19822
|
-
if (!reauth.ok || !reauth.value) {
|
|
19823
|
-
ctx.ui.info(authMessages.alreadyConfigured);
|
|
19824
|
-
return;
|
|
19825
|
-
}
|
|
19826
|
-
}
|
|
19827
|
-
ctx.ui.gap();
|
|
19828
|
-
ctx.ui.intro(authMessages.title);
|
|
20770
|
+
// src/commands/auth/loginApiKey.ts
|
|
20771
|
+
async function loginWithApiKey(ctx) {
|
|
19829
20772
|
const result = await ctx.ui.password(authMessages.promptApiKey, "Set QAWOLF_API_KEY to authenticate in non-interactive environments.");
|
|
19830
20773
|
if (!result.ok) {
|
|
19831
20774
|
ctx.ui.cancel(authMessages.cancelled);
|
|
19832
20775
|
return;
|
|
19833
20776
|
}
|
|
19834
|
-
|
|
20777
|
+
const apiKey = result.value.trim();
|
|
20778
|
+
if (!apiKey) {
|
|
19835
20779
|
ctx.ui.cancel(authMessages.cancelled);
|
|
19836
20780
|
return;
|
|
19837
20781
|
}
|
|
@@ -19840,7 +20784,7 @@ async function handleLogin(ctx) {
|
|
|
19840
20784
|
message: authMessages.verifying,
|
|
19841
20785
|
task: async () => {
|
|
19842
20786
|
const v = await validateApiKey({
|
|
19843
|
-
platformClient: createPlatformClient(
|
|
20787
|
+
platformClient: createPlatformClient(apiKey, {
|
|
19844
20788
|
baseUrl: ctx.apiBaseUrl,
|
|
19845
20789
|
fetch: globalThis.fetch
|
|
19846
20790
|
})
|
|
@@ -19851,7 +20795,7 @@ async function handleLogin(ctx) {
|
|
|
19851
20795
|
},
|
|
19852
20796
|
{
|
|
19853
20797
|
message: authMessages.storing,
|
|
19854
|
-
task: async () => saveApiKey(ctx.configDir,
|
|
20798
|
+
task: async () => saveApiKey(ctx.configDir, apiKey, ctx.fs)
|
|
19855
20799
|
}
|
|
19856
20800
|
], ([, saveResult]) => {
|
|
19857
20801
|
return saveResult.stored === "file" ? authMessages.storedFile : authMessages.storedKeychain;
|
|
@@ -19859,14 +20803,632 @@ async function handleLogin(ctx) {
|
|
|
19859
20803
|
ctx.ui.outro(authMessages.outroSuccess);
|
|
19860
20804
|
}
|
|
19861
20805
|
|
|
20806
|
+
// src/core/deviceAuth/pollState.ts
|
|
20807
|
+
var slowDownIncrementMs = 5000;
|
|
20808
|
+
var unreachableBackoffFactor = 2;
|
|
20809
|
+
function capToLifetime(delayMs, state, nowMs) {
|
|
20810
|
+
return Math.min(delayMs, Math.max(1, state.deadlineMs - nowMs));
|
|
20811
|
+
}
|
|
20812
|
+
function nextPollStep(state, response, nowMs) {
|
|
20813
|
+
if (response.kind === "tokens") {
|
|
20814
|
+
return { action: "done", tokens: response.tokens };
|
|
20815
|
+
}
|
|
20816
|
+
if (nowMs > state.deadlineMs) {
|
|
20817
|
+
return { action: "fail", reason: "timeout", detail: undefined };
|
|
20818
|
+
}
|
|
20819
|
+
switch (response.kind) {
|
|
20820
|
+
case "pending":
|
|
20821
|
+
return {
|
|
20822
|
+
action: "poll",
|
|
20823
|
+
delayMs: capToLifetime(state.intervalMs, state, nowMs),
|
|
20824
|
+
state
|
|
20825
|
+
};
|
|
20826
|
+
case "slow-down": {
|
|
20827
|
+
const slowed = {
|
|
20828
|
+
intervalMs: state.intervalMs + slowDownIncrementMs,
|
|
20829
|
+
deadlineMs: state.deadlineMs
|
|
20830
|
+
};
|
|
20831
|
+
return {
|
|
20832
|
+
action: "poll",
|
|
20833
|
+
delayMs: capToLifetime(slowed.intervalMs, slowed, nowMs),
|
|
20834
|
+
state: slowed
|
|
20835
|
+
};
|
|
20836
|
+
}
|
|
20837
|
+
case "denied":
|
|
20838
|
+
return { action: "fail", reason: "access-denied", detail: undefined };
|
|
20839
|
+
case "expired":
|
|
20840
|
+
return { action: "fail", reason: "expired", detail: undefined };
|
|
20841
|
+
case "unreachable": {
|
|
20842
|
+
const backedOff = {
|
|
20843
|
+
intervalMs: state.intervalMs * unreachableBackoffFactor,
|
|
20844
|
+
deadlineMs: state.deadlineMs
|
|
20845
|
+
};
|
|
20846
|
+
return {
|
|
20847
|
+
action: "poll",
|
|
20848
|
+
delayMs: capToLifetime(backedOff.intervalMs, backedOff, nowMs),
|
|
20849
|
+
state: backedOff
|
|
20850
|
+
};
|
|
20851
|
+
}
|
|
20852
|
+
case "error":
|
|
20853
|
+
return { action: "fail", reason: "network", detail: response.detail };
|
|
20854
|
+
}
|
|
20855
|
+
}
|
|
20856
|
+
|
|
20857
|
+
// src/domains/auth/deviceLogin.ts
|
|
20858
|
+
var cancelled = {
|
|
20859
|
+
ok: false,
|
|
20860
|
+
reason: "cancelled",
|
|
20861
|
+
detail: undefined
|
|
20862
|
+
};
|
|
20863
|
+
async function deviceLogin(deps) {
|
|
20864
|
+
if (deps.isCancelled())
|
|
20865
|
+
return cancelled;
|
|
20866
|
+
const authorization = await deps.requestAuthorization();
|
|
20867
|
+
if (!authorization.ok) {
|
|
20868
|
+
return { ok: false, reason: "unavailable", detail: authorization.error };
|
|
20869
|
+
}
|
|
20870
|
+
await deps.onPrompt(authorization.value);
|
|
20871
|
+
let state = {
|
|
20872
|
+
intervalMs: authorization.value.intervalSec * 1000,
|
|
20873
|
+
deadlineMs: deps.now() + authorization.value.expiresInSec * 1000
|
|
20874
|
+
};
|
|
20875
|
+
for (;; ) {
|
|
20876
|
+
if (deps.isCancelled())
|
|
20877
|
+
return cancelled;
|
|
20878
|
+
const response = await deps.pollToken(authorization.value.deviceCode);
|
|
20879
|
+
const step = nextPollStep(state, response, deps.now());
|
|
20880
|
+
if (step.action === "done")
|
|
20881
|
+
return { ok: true, tokens: step.tokens };
|
|
20882
|
+
if (step.action === "fail") {
|
|
20883
|
+
return { ok: false, reason: step.reason, detail: step.detail };
|
|
20884
|
+
}
|
|
20885
|
+
state = step.state;
|
|
20886
|
+
await deps.sleep(step.delayMs);
|
|
20887
|
+
}
|
|
20888
|
+
}
|
|
20889
|
+
|
|
20890
|
+
// src/shell/openBrowser.ts
|
|
20891
|
+
var launchTimeoutMs = 2000;
|
|
20892
|
+
function launcher(url, platform) {
|
|
20893
|
+
if (platform === "darwin")
|
|
20894
|
+
return { cmd: "open", args: [url] };
|
|
20895
|
+
if (platform === "win32") {
|
|
20896
|
+
return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
|
|
20897
|
+
}
|
|
20898
|
+
return { cmd: "xdg-open", args: [url] };
|
|
20899
|
+
}
|
|
20900
|
+
async function openBrowser(url, deps) {
|
|
20901
|
+
let parsed;
|
|
20902
|
+
try {
|
|
20903
|
+
parsed = new URL(url);
|
|
20904
|
+
} catch {
|
|
20905
|
+
return false;
|
|
20906
|
+
}
|
|
20907
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
20908
|
+
return false;
|
|
20909
|
+
}
|
|
20910
|
+
const { cmd, args } = launcher(url, deps.platform);
|
|
20911
|
+
const launched = deps.spawn(cmd, args, { platform: deps.platform }).then((result) => result.exitCode === 0).catch(() => false);
|
|
20912
|
+
return Promise.race([launched, deps.sleep(launchTimeoutMs).then(() => true)]);
|
|
20913
|
+
}
|
|
20914
|
+
|
|
20915
|
+
// src/shell/spawn.ts
|
|
20916
|
+
import { spawn } from "node:child_process";
|
|
20917
|
+
|
|
20918
|
+
// src/core/cmdEscape.ts
|
|
20919
|
+
var metaChars = /([()\][%!^"`<>&|;, *?])/g;
|
|
20920
|
+
function escapeCommand(command) {
|
|
20921
|
+
return command.replace(metaChars, "^$1");
|
|
20922
|
+
}
|
|
20923
|
+
function escapeArgument(arg) {
|
|
20924
|
+
const escaped = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"").replace(/(?=(\\+?)?)\1$/, "$1$1");
|
|
20925
|
+
return `"${escaped}"`.replace(metaChars, "^$1").replace(metaChars, "^$1");
|
|
20926
|
+
}
|
|
20927
|
+
|
|
20928
|
+
// src/shell/spawn.ts
|
|
20929
|
+
function buildSpawnCommand(cmd, args, platform, env) {
|
|
20930
|
+
const options = {};
|
|
20931
|
+
if (env)
|
|
20932
|
+
options.env = env;
|
|
20933
|
+
if (platform !== "win32" || !/\.(cmd|bat)$/i.test(cmd)) {
|
|
20934
|
+
return { cmd, args, options };
|
|
20935
|
+
}
|
|
20936
|
+
const line = [escapeCommand(cmd), ...args.map(escapeArgument)].join(" ");
|
|
20937
|
+
return {
|
|
20938
|
+
cmd: process.env["ComSpec"] ?? "cmd.exe",
|
|
20939
|
+
args: ["/d", "/s", "/c", `"${line}"`],
|
|
20940
|
+
options: { ...options, windowsVerbatimArguments: true }
|
|
20941
|
+
};
|
|
20942
|
+
}
|
|
20943
|
+
var defaultSpawn = (cmd, args, opts) => new Promise((resolve) => {
|
|
20944
|
+
const env = opts.env ? { ...process.env, ...opts.env } : undefined;
|
|
20945
|
+
const built = buildSpawnCommand(cmd, args, opts.platform, env);
|
|
20946
|
+
const child = spawn(built.cmd, built.args, built.options);
|
|
20947
|
+
let stdout = "";
|
|
20948
|
+
let stderr = "";
|
|
20949
|
+
child.stdout?.on("data", (chunk) => {
|
|
20950
|
+
stdout += String(chunk);
|
|
20951
|
+
});
|
|
20952
|
+
child.stderr?.on("data", (chunk) => {
|
|
20953
|
+
stderr += String(chunk);
|
|
20954
|
+
});
|
|
20955
|
+
if (opts?.stdin !== undefined) {
|
|
20956
|
+
child.stdin?.write(opts.stdin);
|
|
20957
|
+
child.stdin?.end();
|
|
20958
|
+
}
|
|
20959
|
+
child.on("error", (err) => resolve({ exitCode: -1, stdout, stderr: stderr || err.message }));
|
|
20960
|
+
child.on("close", (code) => resolve({ exitCode: code ?? -1, stdout, stderr }));
|
|
20961
|
+
});
|
|
20962
|
+
|
|
20963
|
+
// src/shell/platform/getAuthConfig.ts
|
|
20964
|
+
var timeoutMs5 = 1e4;
|
|
20965
|
+
var authConfigBody = object({
|
|
20966
|
+
workOsClientId: string2().trim().min(1)
|
|
20967
|
+
});
|
|
20968
|
+
async function getAuthConfig(deps) {
|
|
20969
|
+
let response;
|
|
20970
|
+
try {
|
|
20971
|
+
response = await deps.fetch(`${deps.baseUrl}/api/v0/auth/config`, {
|
|
20972
|
+
signal: AbortSignal.timeout(timeoutMs5)
|
|
20973
|
+
});
|
|
20974
|
+
} catch (err) {
|
|
20975
|
+
return { kind: "unreachable", detail: errorMessage(err) };
|
|
20976
|
+
}
|
|
20977
|
+
if (response.status === 404)
|
|
20978
|
+
return { kind: "unconfigured" };
|
|
20979
|
+
if (!response.ok) {
|
|
20980
|
+
return { kind: "unreachable", detail: `HTTP ${response.status}` };
|
|
20981
|
+
}
|
|
20982
|
+
let json;
|
|
20983
|
+
try {
|
|
20984
|
+
json = await response.json();
|
|
20985
|
+
} catch (err) {
|
|
20986
|
+
return { kind: "unreachable", detail: errorMessage(err) };
|
|
20987
|
+
}
|
|
20988
|
+
const parsed = authConfigBody.safeParse(json);
|
|
20989
|
+
return parsed.success ? { kind: "configured", clientId: parsed.data.workOsClientId } : { kind: "unconfigured" };
|
|
20990
|
+
}
|
|
20991
|
+
|
|
20992
|
+
// src/shell/workos/pollDeviceToken.ts
|
|
20993
|
+
var deviceCodeGrantType = "urn:ietf:params:oauth:grant-type:device_code";
|
|
20994
|
+
async function pollDeviceToken(deviceCode, deps) {
|
|
20995
|
+
const outcome = await sendWorkosRequest(`${deps.baseUrl}/user_management/authenticate`, {
|
|
20996
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
20997
|
+
body: new URLSearchParams({
|
|
20998
|
+
grant_type: deviceCodeGrantType,
|
|
20999
|
+
device_code: deviceCode,
|
|
21000
|
+
client_id: deps.clientId
|
|
21001
|
+
}).toString()
|
|
21002
|
+
}, deps.fetch);
|
|
21003
|
+
if (outcome.kind === "failure") {
|
|
21004
|
+
return outcome.retryable ? { kind: "unreachable", detail: outcome.detail } : { kind: "error", detail: outcome.detail };
|
|
21005
|
+
}
|
|
21006
|
+
if (outcome.kind === "oauth-error") {
|
|
21007
|
+
switch (outcome.code) {
|
|
21008
|
+
case "authorization_pending":
|
|
21009
|
+
return { kind: "pending" };
|
|
21010
|
+
case "slow_down":
|
|
21011
|
+
return { kind: "slow-down" };
|
|
21012
|
+
case "access_denied":
|
|
21013
|
+
return { kind: "denied" };
|
|
21014
|
+
case "expired_token":
|
|
21015
|
+
case "invalid_grant":
|
|
21016
|
+
return { kind: "expired" };
|
|
21017
|
+
default:
|
|
21018
|
+
return {
|
|
21019
|
+
kind: "error",
|
|
21020
|
+
detail: outcome.description ?? outcome.code
|
|
21021
|
+
};
|
|
21022
|
+
}
|
|
21023
|
+
}
|
|
21024
|
+
const parsed = deviceTokenBody.safeParse(outcome.json);
|
|
21025
|
+
if (!parsed.success) {
|
|
21026
|
+
return { kind: "error", detail: unexpectedResponse };
|
|
21027
|
+
}
|
|
21028
|
+
return {
|
|
21029
|
+
kind: "tokens",
|
|
21030
|
+
tokens: {
|
|
21031
|
+
accessToken: parsed.data.access_token,
|
|
21032
|
+
refreshToken: parsed.data.refresh_token,
|
|
21033
|
+
expiresAt: readAccessTokenExpiry(parsed.data.access_token),
|
|
21034
|
+
email: parsed.data.user.email,
|
|
21035
|
+
organizationId: parsed.data.organization_id
|
|
21036
|
+
}
|
|
21037
|
+
};
|
|
21038
|
+
}
|
|
21039
|
+
|
|
21040
|
+
// src/shell/workos/requestDeviceAuthorization.ts
|
|
21041
|
+
async function requestDeviceAuthorization(deps) {
|
|
21042
|
+
const outcome = await sendWorkosRequest(`${deps.baseUrl}/user_management/authorize/device`, {
|
|
21043
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
21044
|
+
body: new URLSearchParams({ client_id: deps.clientId }).toString()
|
|
21045
|
+
}, deps.fetch);
|
|
21046
|
+
if (outcome.kind === "failure") {
|
|
21047
|
+
return { ok: false, error: outcome.detail, retryable: outcome.retryable };
|
|
21048
|
+
}
|
|
21049
|
+
if (outcome.kind === "oauth-error") {
|
|
21050
|
+
return {
|
|
21051
|
+
ok: false,
|
|
21052
|
+
error: outcome.description ?? outcome.code,
|
|
21053
|
+
retryable: false
|
|
21054
|
+
};
|
|
21055
|
+
}
|
|
21056
|
+
const parsed = deviceAuthorizationBody.safeParse(outcome.json);
|
|
21057
|
+
if (!parsed.success) {
|
|
21058
|
+
return { ok: false, error: unexpectedResponse, retryable: false };
|
|
21059
|
+
}
|
|
21060
|
+
return {
|
|
21061
|
+
ok: true,
|
|
21062
|
+
value: {
|
|
21063
|
+
deviceCode: parsed.data.device_code,
|
|
21064
|
+
userCode: parsed.data.user_code,
|
|
21065
|
+
verificationUri: parsed.data.verification_uri,
|
|
21066
|
+
verificationUriComplete: parsed.data.verification_uri_complete,
|
|
21067
|
+
expiresInSec: parsed.data.expires_in,
|
|
21068
|
+
intervalSec: parsed.data.interval ?? defaultIntervalSec
|
|
21069
|
+
}
|
|
21070
|
+
};
|
|
21071
|
+
}
|
|
21072
|
+
|
|
21073
|
+
// src/domains/auth/nameMatching.ts
|
|
21074
|
+
function matches(candidate, wanted) {
|
|
21075
|
+
return candidate.toLowerCase() === wanted.trim().toLowerCase();
|
|
21076
|
+
}
|
|
21077
|
+
function findOrganization(organizations, wanted) {
|
|
21078
|
+
return organizations.find((organization) => matches(organization.name, wanted) || matches(organization.id, wanted) || matches(organization.workOsOrganizationId, wanted));
|
|
21079
|
+
}
|
|
21080
|
+
function findWorkspace(workspaces, wanted) {
|
|
21081
|
+
return workspaces.find((workspace) => matches(workspace.name, wanted) || matches(workspace.id, wanted) || workspace.slug !== undefined && matches(workspace.slug, wanted));
|
|
21082
|
+
}
|
|
21083
|
+
function findWorkspaceAcross(organizations, wanted) {
|
|
21084
|
+
const hits = organizations.flatMap((organization) => {
|
|
21085
|
+
const workspace = findWorkspace(organization.workspaces, wanted);
|
|
21086
|
+
return workspace ? [{ organization, workspace }] : [];
|
|
21087
|
+
});
|
|
21088
|
+
const [only] = hits;
|
|
21089
|
+
if (!only)
|
|
21090
|
+
return { kind: "none" };
|
|
21091
|
+
if (hits.length > 1) {
|
|
21092
|
+
return {
|
|
21093
|
+
kind: "ambiguous",
|
|
21094
|
+
organizations: hits.map((h) => h.organization)
|
|
21095
|
+
};
|
|
21096
|
+
}
|
|
21097
|
+
return {
|
|
21098
|
+
kind: "found",
|
|
21099
|
+
organization: only.organization,
|
|
21100
|
+
workspace: only.workspace
|
|
21101
|
+
};
|
|
21102
|
+
}
|
|
21103
|
+
function nameList(items) {
|
|
21104
|
+
return items.map((item) => item.slug ?? item.name).join(", ");
|
|
21105
|
+
}
|
|
21106
|
+
|
|
21107
|
+
// src/domains/auth/selectWorkspace.ts
|
|
21108
|
+
async function selectWorkspace(deps) {
|
|
21109
|
+
const { organizations } = deps;
|
|
21110
|
+
if (organizations.length === 0)
|
|
21111
|
+
return { outcome: "none" };
|
|
21112
|
+
let organization;
|
|
21113
|
+
if (deps.preferredOrganization) {
|
|
21114
|
+
organization = findOrganization(organizations, deps.preferredOrganization);
|
|
21115
|
+
if (!organization) {
|
|
21116
|
+
return {
|
|
21117
|
+
outcome: "failed",
|
|
21118
|
+
error: `No organization matches '${deps.preferredOrganization}'. Available: ${nameList(organizations)}.`
|
|
21119
|
+
};
|
|
21120
|
+
}
|
|
21121
|
+
} else if (organizations.length === 1) {
|
|
21122
|
+
organization = organizations[0];
|
|
21123
|
+
} else if (deps.preferredWorkspace) {
|
|
21124
|
+
const match = findWorkspaceAcross(organizations, deps.preferredWorkspace);
|
|
21125
|
+
if (match.kind === "none") {
|
|
21126
|
+
return {
|
|
21127
|
+
outcome: "failed",
|
|
21128
|
+
error: `No workspace matches '${deps.preferredWorkspace}'. Available: ${nameList(organizations.flatMap((o) => o.workspaces))}.`
|
|
21129
|
+
};
|
|
21130
|
+
}
|
|
21131
|
+
if (match.kind === "ambiguous") {
|
|
21132
|
+
return {
|
|
21133
|
+
outcome: "failed",
|
|
21134
|
+
error: `More than one organization has a workspace matching '${deps.preferredWorkspace}': ${nameList(match.organizations)}. Set QAWOLF_ORGANIZATION to choose between them.`
|
|
21135
|
+
};
|
|
21136
|
+
}
|
|
21137
|
+
organization = match.organization;
|
|
21138
|
+
} else {
|
|
21139
|
+
organization = await deps.chooseOrganization(organizations);
|
|
21140
|
+
if (!organization)
|
|
21141
|
+
return { outcome: "cancelled" };
|
|
21142
|
+
}
|
|
21143
|
+
if (!organization)
|
|
21144
|
+
throw Error("organization was not settled");
|
|
21145
|
+
const { workspaces } = organization;
|
|
21146
|
+
if (workspaces.length === 0) {
|
|
21147
|
+
return {
|
|
21148
|
+
outcome: "failed",
|
|
21149
|
+
error: `Organization '${organization.name}' has no workspaces.`
|
|
21150
|
+
};
|
|
21151
|
+
}
|
|
21152
|
+
let workspace;
|
|
21153
|
+
if (deps.preferredWorkspace) {
|
|
21154
|
+
workspace = findWorkspace(workspaces, deps.preferredWorkspace);
|
|
21155
|
+
if (!workspace) {
|
|
21156
|
+
return {
|
|
21157
|
+
outcome: "failed",
|
|
21158
|
+
error: `No workspace matches '${deps.preferredWorkspace}' in ${organization.name}. Available: ${nameList(workspaces)}.`
|
|
21159
|
+
};
|
|
21160
|
+
}
|
|
21161
|
+
} else if (workspaces.length === 1) {
|
|
21162
|
+
workspace = workspaces[0];
|
|
21163
|
+
} else {
|
|
21164
|
+
workspace = await deps.chooseWorkspace(workspaces);
|
|
21165
|
+
if (!workspace)
|
|
21166
|
+
return { outcome: "cancelled" };
|
|
21167
|
+
}
|
|
21168
|
+
if (!workspace)
|
|
21169
|
+
throw Error("workspace was not settled");
|
|
21170
|
+
try {
|
|
21171
|
+
await deps.saveWorkspace(workspace.id);
|
|
21172
|
+
} catch (err) {
|
|
21173
|
+
return {
|
|
21174
|
+
outcome: "failed",
|
|
21175
|
+
error: authMessages.workspace.saveFailed(errorMessage(err))
|
|
21176
|
+
};
|
|
21177
|
+
}
|
|
21178
|
+
return { outcome: "selected", organization, workspace };
|
|
21179
|
+
}
|
|
21180
|
+
|
|
21181
|
+
// src/domains/auth/store/saveWorkspaceId.ts
|
|
21182
|
+
import { Entry as Entry6 } from "@napi-rs/keyring";
|
|
21183
|
+
function makeSaveWorkspaceIdDeps(configDir, fs) {
|
|
21184
|
+
return {
|
|
21185
|
+
loadTokens: () => loadTokens(configDir, { EntryClass: Entry6, fs }),
|
|
21186
|
+
saveTokens: (session) => saveTokens(configDir, session, fs)
|
|
21187
|
+
};
|
|
21188
|
+
}
|
|
21189
|
+
async function saveWorkspaceId(session, workspaceId, deps) {
|
|
21190
|
+
const current = await deps.loadTokens();
|
|
21191
|
+
if (!current.found || !isSameSession(current.tokens, session)) {
|
|
21192
|
+
throw Error(authMessages.workspace.sessionChanged);
|
|
21193
|
+
}
|
|
21194
|
+
return deps.saveTokens({ ...current.tokens, workspaceId });
|
|
21195
|
+
}
|
|
21196
|
+
function isSameSession(stored, captured) {
|
|
21197
|
+
return stored.email === captured.email && stored.organizationId === captured.organizationId;
|
|
21198
|
+
}
|
|
21199
|
+
|
|
21200
|
+
// src/commands/auth/chooseWorkspace.ts
|
|
21201
|
+
async function chooseWorkspace(ctx, args) {
|
|
21202
|
+
const client = createPlatformClient(args.session.accessToken, {
|
|
21203
|
+
baseUrl: ctx.apiBaseUrl,
|
|
21204
|
+
fetch: globalThis.fetch,
|
|
21205
|
+
logger: ctx.log("trpc")
|
|
21206
|
+
});
|
|
21207
|
+
const [identity, discovered] = await Promise.all([
|
|
21208
|
+
client.getIdentity(),
|
|
21209
|
+
client.getAccessibleOrganizations()
|
|
21210
|
+
]);
|
|
21211
|
+
let organizations;
|
|
21212
|
+
if (discovered.ok) {
|
|
21213
|
+
organizations = discovered.value;
|
|
21214
|
+
} else if (identity.ok) {
|
|
21215
|
+
organizations = "user" in identity.value ? identity.value.organizations : [];
|
|
21216
|
+
} else {
|
|
21217
|
+
return { outcome: "failed", error: identity.error };
|
|
21218
|
+
}
|
|
21219
|
+
return selectWorkspace({
|
|
21220
|
+
organizations,
|
|
21221
|
+
preferredOrganization: args.env["QAWOLF_ORGANIZATION"]?.trim(),
|
|
21222
|
+
preferredWorkspace: args.env["QAWOLF_WORKSPACE"]?.trim(),
|
|
21223
|
+
chooseOrganization: async (organizations) => {
|
|
21224
|
+
if (ctx.ui.mode !== "human")
|
|
21225
|
+
return;
|
|
21226
|
+
const picked = await ctx.ui.select(authMessages.workspace.chooseOrganization, organizations.map((organization) => ({
|
|
21227
|
+
value: organization.workOsOrganizationId,
|
|
21228
|
+
label: organization.name,
|
|
21229
|
+
hint: authMessages.workspace.workspaceCount(organization.workspaces.length)
|
|
21230
|
+
})));
|
|
21231
|
+
if (!picked.ok)
|
|
21232
|
+
return;
|
|
21233
|
+
return organizations.find((organization) => organization.workOsOrganizationId === picked.value);
|
|
21234
|
+
},
|
|
21235
|
+
chooseWorkspace: async (workspaces) => {
|
|
21236
|
+
if (ctx.ui.mode !== "human")
|
|
21237
|
+
return;
|
|
21238
|
+
const picked = await ctx.ui.select(authMessages.workspace.choose, workspaces.map((workspace) => ({
|
|
21239
|
+
value: workspace.id,
|
|
21240
|
+
label: workspace.name,
|
|
21241
|
+
...workspace.slug ? { hint: workspace.slug } : {}
|
|
21242
|
+
})));
|
|
21243
|
+
if (!picked.ok)
|
|
21244
|
+
return;
|
|
21245
|
+
return workspaces.find((workspace) => workspace.id === picked.value);
|
|
21246
|
+
},
|
|
21247
|
+
saveWorkspace: (workspaceId) => saveWorkspaceId(args.session, workspaceId, makeSaveWorkspaceIdDeps(ctx.configDir, ctx.fs))
|
|
21248
|
+
});
|
|
21249
|
+
}
|
|
21250
|
+
function reportWorkspace(ctx, result) {
|
|
21251
|
+
switch (result.outcome) {
|
|
21252
|
+
case "selected":
|
|
21253
|
+
ctx.ui.info(authMessages.workspace.working(result.organization.name, result.workspace.name));
|
|
21254
|
+
return;
|
|
21255
|
+
case "none":
|
|
21256
|
+
ctx.ui.info(authMessages.workspace.none);
|
|
21257
|
+
return;
|
|
21258
|
+
case "cancelled":
|
|
21259
|
+
if (ctx.ui.mode !== "human") {
|
|
21260
|
+
return { error: authMessages.workspace.nonInteractive };
|
|
21261
|
+
}
|
|
21262
|
+
ctx.ui.info(authMessages.workspace.cancelled);
|
|
21263
|
+
return;
|
|
21264
|
+
case "failed":
|
|
21265
|
+
return { error: result.error };
|
|
21266
|
+
}
|
|
21267
|
+
}
|
|
21268
|
+
|
|
21269
|
+
// src/commands/auth/loginDevice.ts
|
|
21270
|
+
async function loginWithDevice(ctx, deps = {}) {
|
|
21271
|
+
const authConfig = await getAuthConfig({
|
|
21272
|
+
baseUrl: ctx.apiBaseUrl,
|
|
21273
|
+
fetch: globalThis.fetch
|
|
21274
|
+
});
|
|
21275
|
+
if (authConfig.kind === "unreachable") {
|
|
21276
|
+
ctx.log("auth").debug(`auth config unreachable: ${authConfig.detail}`);
|
|
21277
|
+
return {
|
|
21278
|
+
error: authMessages.device.configUnreachable,
|
|
21279
|
+
errorBody: authConfig.detail
|
|
21280
|
+
};
|
|
21281
|
+
}
|
|
21282
|
+
const config = resolveWorkosConfig(authConfig.kind === "configured" ? authConfig.clientId : undefined);
|
|
21283
|
+
if (!config.configured) {
|
|
21284
|
+
return { error: authMessages.device.unavailable };
|
|
21285
|
+
}
|
|
21286
|
+
const workos = {
|
|
21287
|
+
fetch: globalThis.fetch,
|
|
21288
|
+
baseUrl: config.baseUrl,
|
|
21289
|
+
clientId: config.clientId
|
|
21290
|
+
};
|
|
21291
|
+
let cancelled = false;
|
|
21292
|
+
const unregister = ctx.signals.register(() => {
|
|
21293
|
+
cancelled = true;
|
|
21294
|
+
});
|
|
21295
|
+
const showCode = async (authorization) => {
|
|
21296
|
+
const url = authorization.verificationUriComplete ?? authorization.verificationUri;
|
|
21297
|
+
ctx.ui.note([
|
|
21298
|
+
authMessages.device.confirmCode(authorization.userCode),
|
|
21299
|
+
authMessages.device.visitUrl(url),
|
|
21300
|
+
url === authorization.verificationUri ? undefined : authMessages.device.visitUrlPlain(authorization.verificationUri)
|
|
21301
|
+
].filter((line) => Boolean(line)).join(`
|
|
21302
|
+
`), authMessages.title);
|
|
21303
|
+
const opened = await openBrowser(url, {
|
|
21304
|
+
sleep,
|
|
21305
|
+
spawn: defaultSpawn,
|
|
21306
|
+
platform: deps.platform ?? process.platform
|
|
21307
|
+
});
|
|
21308
|
+
if (!opened)
|
|
21309
|
+
ctx.ui.info(authMessages.device.openFailed(url));
|
|
21310
|
+
ctx.ui.step(authMessages.device.waiting);
|
|
21311
|
+
};
|
|
21312
|
+
try {
|
|
21313
|
+
const result = await deviceLogin({
|
|
21314
|
+
requestAuthorization: () => requestDeviceAuthorization(workos),
|
|
21315
|
+
pollToken: (deviceCode) => pollDeviceToken(deviceCode, workos),
|
|
21316
|
+
onPrompt: showCode,
|
|
21317
|
+
sleep,
|
|
21318
|
+
now: () => Date.now(),
|
|
21319
|
+
isCancelled: () => cancelled
|
|
21320
|
+
});
|
|
21321
|
+
if (!result.ok) {
|
|
21322
|
+
return {
|
|
21323
|
+
error: authMessages.device.failed[result.reason],
|
|
21324
|
+
...result.detail ? { errorBody: result.detail } : {}
|
|
21325
|
+
};
|
|
21326
|
+
}
|
|
21327
|
+
const session = {
|
|
21328
|
+
...result.tokens,
|
|
21329
|
+
workspaceId: undefined,
|
|
21330
|
+
clientId: config.clientId
|
|
21331
|
+
};
|
|
21332
|
+
await saveTokens(ctx.configDir, session, ctx.fs);
|
|
21333
|
+
const workspace = await chooseWorkspace(ctx, {
|
|
21334
|
+
session,
|
|
21335
|
+
env: deps.env ?? process.env
|
|
21336
|
+
});
|
|
21337
|
+
const failure = reportWorkspace(ctx, workspace);
|
|
21338
|
+
if (failure)
|
|
21339
|
+
return failure;
|
|
21340
|
+
ctx.ui.outro(authMessages.device.signedIn(result.tokens.email));
|
|
21341
|
+
return;
|
|
21342
|
+
} finally {
|
|
21343
|
+
unregister();
|
|
21344
|
+
}
|
|
21345
|
+
}
|
|
21346
|
+
|
|
21347
|
+
// src/commands/auth/login.ts
|
|
21348
|
+
var browserMethod = "browser";
|
|
21349
|
+
async function handleLogin(ctx, deps = {}) {
|
|
21350
|
+
if (ctx.ui.mode !== "human") {
|
|
21351
|
+
ctx.ui.error(authMessages.login.nonInteractive);
|
|
21352
|
+
return { error: "non-interactive" };
|
|
21353
|
+
}
|
|
21354
|
+
const resolveApiKey2 = deps.resolveApiKey ?? resolveApiKey;
|
|
21355
|
+
const existing = await resolveApiKey2(ctx.configDir, ctx.fs);
|
|
21356
|
+
if (existing) {
|
|
21357
|
+
const reauth = await ctx.ui.confirm(authMessages.login.reAuthPrompt);
|
|
21358
|
+
if (!reauth.ok || !reauth.value) {
|
|
21359
|
+
ctx.ui.info(authMessages.alreadyConfigured);
|
|
21360
|
+
return;
|
|
21361
|
+
}
|
|
21362
|
+
}
|
|
21363
|
+
ctx.ui.gap();
|
|
21364
|
+
ctx.ui.intro(authMessages.title);
|
|
21365
|
+
const method = await ctx.ui.select(authMessages.login.chooseMethod, [
|
|
21366
|
+
{
|
|
21367
|
+
value: browserMethod,
|
|
21368
|
+
label: authMessages.login.methodBrowser,
|
|
21369
|
+
hint: authMessages.login.methodBrowserHint
|
|
21370
|
+
},
|
|
21371
|
+
{
|
|
21372
|
+
value: "api-key",
|
|
21373
|
+
label: authMessages.login.methodApiKey,
|
|
21374
|
+
hint: authMessages.login.methodApiKeyHint
|
|
21375
|
+
}
|
|
21376
|
+
]);
|
|
21377
|
+
if (!method.ok) {
|
|
21378
|
+
ctx.ui.cancel(authMessages.cancelled);
|
|
21379
|
+
return;
|
|
21380
|
+
}
|
|
21381
|
+
if (method.value !== browserMethod) {
|
|
21382
|
+
return (deps.loginWithApiKey ?? loginWithApiKey)(ctx);
|
|
21383
|
+
}
|
|
21384
|
+
if (existing && existing.source !== "browser") {
|
|
21385
|
+
ctx.ui.warn(existing.source === "env" ? authMessages.login.apiKeyPrecedence.env : authMessages.login.apiKeyPrecedence.stored);
|
|
21386
|
+
}
|
|
21387
|
+
return (deps.loginWithDevice ?? loginWithDevice)(ctx);
|
|
21388
|
+
}
|
|
21389
|
+
|
|
21390
|
+
// src/domains/auth/store/deleteTokens.ts
|
|
21391
|
+
import { join as join14 } from "node:path";
|
|
21392
|
+
import { Entry as Entry7 } from "@napi-rs/keyring";
|
|
21393
|
+
function deleteFromKeychain2() {
|
|
21394
|
+
try {
|
|
21395
|
+
new Entry7(service, tokensAccount).deletePassword();
|
|
21396
|
+
return "deleted";
|
|
21397
|
+
} catch {
|
|
21398
|
+
return "unavailable";
|
|
21399
|
+
}
|
|
21400
|
+
}
|
|
21401
|
+
async function deleteFromFile2(configDir, fs) {
|
|
21402
|
+
try {
|
|
21403
|
+
await fs.unlink(join14(configDir, tokensFile));
|
|
21404
|
+
return "deleted";
|
|
21405
|
+
} catch (err) {
|
|
21406
|
+
if (isNoEntError(err))
|
|
21407
|
+
return "not-found";
|
|
21408
|
+
throw err;
|
|
21409
|
+
}
|
|
21410
|
+
}
|
|
21411
|
+
async function deleteTokens(configDir, fs) {
|
|
21412
|
+
const [keychain, file] = await Promise.all([
|
|
21413
|
+
Promise.resolve(deleteFromKeychain2()),
|
|
21414
|
+
deleteFromFile2(configDir, fs)
|
|
21415
|
+
]);
|
|
21416
|
+
return { keychain, file };
|
|
21417
|
+
}
|
|
21418
|
+
|
|
19862
21419
|
// src/commands/auth/logout.ts
|
|
19863
|
-
async function handleLogout(ctx) {
|
|
19864
|
-
const
|
|
19865
|
-
|
|
21420
|
+
async function handleLogout(ctx, deps = {}) {
|
|
21421
|
+
const hasStoredCredentials2 = deps.hasStoredCredentials ?? hasStoredCredentials;
|
|
21422
|
+
const deleteApiKey2 = deps.deleteApiKey ?? deleteApiKey;
|
|
21423
|
+
const deleteTokens2 = deps.deleteTokens ?? deleteTokens;
|
|
21424
|
+
const env = deps.env ?? process.env;
|
|
21425
|
+
const envKey = env["QAWOLF_API_KEY"]?.trim();
|
|
21426
|
+
const stored = await hasStoredCredentials2(ctx.configDir, ctx.fs);
|
|
21427
|
+
if (!envKey && !stored) {
|
|
19866
21428
|
ctx.ui.info(authMessages.logout.notAuthenticated);
|
|
19867
21429
|
return;
|
|
19868
21430
|
}
|
|
19869
|
-
if (
|
|
21431
|
+
if (envKey) {
|
|
19870
21432
|
ctx.ui.warn(authMessages.logout.envVarWarning);
|
|
19871
21433
|
}
|
|
19872
21434
|
if (ctx.ui.mode === "human") {
|
|
@@ -19881,7 +21443,12 @@ async function handleLogout(ctx) {
|
|
|
19881
21443
|
await ctx.ui.withProgress([
|
|
19882
21444
|
{
|
|
19883
21445
|
message: authMessages.logout.deleting,
|
|
19884
|
-
task: () =>
|
|
21446
|
+
task: async () => {
|
|
21447
|
+
await Promise.all([
|
|
21448
|
+
deleteApiKey2(ctx.configDir, ctx.fs),
|
|
21449
|
+
deleteTokens2(ctx.configDir, ctx.fs)
|
|
21450
|
+
]);
|
|
21451
|
+
}
|
|
19885
21452
|
}
|
|
19886
21453
|
], () => authMessages.logout.credentialsRemoved);
|
|
19887
21454
|
if (ctx.ui.mode === "human") {
|
|
@@ -19891,6 +21458,76 @@ async function handleLogout(ctx) {
|
|
|
19891
21458
|
}
|
|
19892
21459
|
}
|
|
19893
21460
|
|
|
21461
|
+
// src/domains/auth/refreshStoredSession.ts
|
|
21462
|
+
import { Entry as Entry8 } from "@napi-rs/keyring";
|
|
21463
|
+
function makeDefaultDeps2(fs) {
|
|
21464
|
+
return {
|
|
21465
|
+
loadTokens: (configDir) => loadTokens(configDir, { EntryClass: Entry8, fs }),
|
|
21466
|
+
resolveOauth: (configDir) => resolveOauthToken(configDir, makeOauthDeps(fs))
|
|
21467
|
+
};
|
|
21468
|
+
}
|
|
21469
|
+
async function refreshStoredSession(configDir, fs, deps) {
|
|
21470
|
+
const resolvedDeps = deps ?? makeDefaultDeps2(fs);
|
|
21471
|
+
const before = await resolvedDeps.loadTokens(configDir);
|
|
21472
|
+
if (!before.found)
|
|
21473
|
+
return { kind: "not-signed-in" };
|
|
21474
|
+
const renewed = await resolvedDeps.resolveOauth(configDir);
|
|
21475
|
+
if (!renewed)
|
|
21476
|
+
return { kind: "refresh-failed" };
|
|
21477
|
+
const after = await resolvedDeps.loadTokens(configDir);
|
|
21478
|
+
if (!after.found)
|
|
21479
|
+
return { kind: "refresh-failed" };
|
|
21480
|
+
if (after.tokens.accessToken !== renewed.key) {
|
|
21481
|
+
return { kind: "refresh-failed" };
|
|
21482
|
+
}
|
|
21483
|
+
return { kind: "session", session: after.tokens };
|
|
21484
|
+
}
|
|
21485
|
+
|
|
21486
|
+
// src/commands/auth/switchWorkspace.ts
|
|
21487
|
+
async function handleSwitchWorkspace(ctx, deps = {}) {
|
|
21488
|
+
const env = deps.env ?? process.env;
|
|
21489
|
+
const refreshStoredSession2 = deps.refreshStoredSession ?? refreshStoredSession;
|
|
21490
|
+
const preferred = env["QAWOLF_WORKSPACE"]?.trim() || env["QAWOLF_ORGANIZATION"]?.trim();
|
|
21491
|
+
if (ctx.ui.mode !== "human" && !preferred) {
|
|
21492
|
+
return { error: authMessages.workspace.nonInteractive };
|
|
21493
|
+
}
|
|
21494
|
+
const stored = await refreshStoredSession2(ctx.configDir, ctx.fs);
|
|
21495
|
+
if (stored.kind === "not-signed-in") {
|
|
21496
|
+
return { error: authMessages.workspace.notSignedIn };
|
|
21497
|
+
}
|
|
21498
|
+
if (stored.kind === "refresh-failed") {
|
|
21499
|
+
return { error: authMessages.workspace.sessionExpired };
|
|
21500
|
+
}
|
|
21501
|
+
ctx.ui.gap();
|
|
21502
|
+
ctx.ui.intro(authMessages.title);
|
|
21503
|
+
const result = await chooseWorkspace(ctx, {
|
|
21504
|
+
session: stored.session,
|
|
21505
|
+
env
|
|
21506
|
+
});
|
|
21507
|
+
const failure = reportWorkspace(ctx, result);
|
|
21508
|
+
if (failure)
|
|
21509
|
+
return failure;
|
|
21510
|
+
ctx.ui.outro(authMessages.outroReady);
|
|
21511
|
+
return;
|
|
21512
|
+
}
|
|
21513
|
+
|
|
21514
|
+
// src/commands/auth/whoamiTeam.ts
|
|
21515
|
+
function reportTeamIdentity(ctx, value, source) {
|
|
21516
|
+
const { team } = value;
|
|
21517
|
+
const teamUrl = team.slug ? new URL("/" + encodeURIComponent(team.slug), ctx.apiBaseUrl).toString() : undefined;
|
|
21518
|
+
if (ctx.ui.mode === "human") {
|
|
21519
|
+
ctx.ui.note(authMessages.whoami.teamNote({ team, teamUrl, source }), authMessages.whoamiAuthenticated);
|
|
21520
|
+
ctx.ui.outro(authMessages.outroReady);
|
|
21521
|
+
} else {
|
|
21522
|
+
ctx.ui.output({
|
|
21523
|
+
authenticated: true,
|
|
21524
|
+
source,
|
|
21525
|
+
team,
|
|
21526
|
+
teamUrl
|
|
21527
|
+
}, authMessages.whoami.authenticatedAs(team.name, source));
|
|
21528
|
+
}
|
|
21529
|
+
}
|
|
21530
|
+
|
|
19894
21531
|
// src/commands/auth/whoami.ts
|
|
19895
21532
|
async function handleWhoami(ctx, deps = {}) {
|
|
19896
21533
|
ctx.ui.gap();
|
|
@@ -19932,11 +21569,19 @@ async function handleWhoami(ctx, deps = {}) {
|
|
|
19932
21569
|
const { value } = identity;
|
|
19933
21570
|
if ("user" in value) {
|
|
19934
21571
|
const { organization, user } = value;
|
|
21572
|
+
const found = value.organizations.flatMap((candidate) => candidate.workspaces.map((workspace) => ({
|
|
21573
|
+
organization: candidate.name,
|
|
21574
|
+
workspace: workspace.name,
|
|
21575
|
+
id: workspace.id
|
|
21576
|
+
}))).find((entry) => entry.id === resolved.workspaceId);
|
|
21577
|
+
const activeWorkspace = resolved.workspaceId ? authMessages.whoami.activeWorkspace(resolved.workspaceId, found) : undefined;
|
|
19935
21578
|
if (ctx.ui.mode === "human") {
|
|
19936
21579
|
ctx.ui.note(authMessages.whoami.userNote({
|
|
19937
21580
|
organization,
|
|
19938
21581
|
source: resolved.source,
|
|
19939
|
-
user
|
|
21582
|
+
user,
|
|
21583
|
+
activeWorkspace,
|
|
21584
|
+
organizations: value.organizations
|
|
19940
21585
|
}), authMessages.whoamiAuthenticated);
|
|
19941
21586
|
ctx.ui.outro(authMessages.outroReady);
|
|
19942
21587
|
} else {
|
|
@@ -19944,7 +21589,9 @@ async function handleWhoami(ctx, deps = {}) {
|
|
|
19944
21589
|
authenticated: true,
|
|
19945
21590
|
organization,
|
|
19946
21591
|
source: resolved.source,
|
|
19947
|
-
user
|
|
21592
|
+
user,
|
|
21593
|
+
workspaceId: resolved.workspaceId,
|
|
21594
|
+
organizations: value.organizations
|
|
19948
21595
|
}, authMessages.whoami.authenticatedAs(user.email, resolved.source));
|
|
19949
21596
|
}
|
|
19950
21597
|
return;
|
|
@@ -19966,26 +21613,15 @@ async function handleWhoami(ctx, deps = {}) {
|
|
|
19966
21613
|
}
|
|
19967
21614
|
return;
|
|
19968
21615
|
}
|
|
19969
|
-
|
|
19970
|
-
const teamUrl = team.slug ? new URL("/" + encodeURIComponent(team.slug), ctx.apiBaseUrl).toString() : undefined;
|
|
19971
|
-
if (ctx.ui.mode === "human") {
|
|
19972
|
-
ctx.ui.note(authMessages.whoami.teamNote({ team, teamUrl, source: resolved.source }), authMessages.whoamiAuthenticated);
|
|
19973
|
-
ctx.ui.outro(authMessages.outroReady);
|
|
19974
|
-
} else {
|
|
19975
|
-
ctx.ui.output({
|
|
19976
|
-
authenticated: true,
|
|
19977
|
-
source: resolved.source,
|
|
19978
|
-
team,
|
|
19979
|
-
teamUrl
|
|
19980
|
-
}, authMessages.whoami.authenticatedAs(team.name, resolved.source));
|
|
19981
|
-
}
|
|
21616
|
+
reportTeamIdentity(ctx, value, resolved.source);
|
|
19982
21617
|
}
|
|
19983
21618
|
|
|
19984
21619
|
// src/commands/auth/index.ts
|
|
19985
21620
|
function registerAuthCommand(program, signals) {
|
|
19986
21621
|
const auth = program.command("auth").description("Manage authentication with QA Wolf");
|
|
19987
|
-
declareCommandKind(auth.command("login"), "local").description("Authenticate with
|
|
21622
|
+
declareCommandKind(auth.command("login"), "local").description("Authenticate with QA Wolf in a browser or with an API key").action(withContext(signals, handleLogin));
|
|
19988
21623
|
declareCommandKind(auth.command("logout"), "local").description("Remove stored credentials").action(withContext(signals, handleLogout));
|
|
21624
|
+
declareCommandKind(auth.command("switch"), "local").description("Choose which workspace to work in").action(withContext(signals, handleSwitchWorkspace));
|
|
19989
21625
|
declareCommandKind(auth.command("whoami"), "read").description("Show authentication status").action(withContext(signals, handleWhoami));
|
|
19990
21626
|
}
|
|
19991
21627
|
|
|
@@ -20035,11 +21671,11 @@ async function* batchMap(items, fn, size) {
|
|
|
20035
21671
|
}
|
|
20036
21672
|
|
|
20037
21673
|
// src/core/nodeModulesBins.ts
|
|
20038
|
-
import { join as
|
|
21674
|
+
import { join as join15 } from "node:path";
|
|
20039
21675
|
function nodeModulesBinCandidates(envDir, name, platform) {
|
|
20040
|
-
const binDir =
|
|
21676
|
+
const binDir = join15(envDir, "node_modules", ".bin");
|
|
20041
21677
|
const names = platform === "win32" ? [`${name}.cmd`, `${name}.exe`] : [name];
|
|
20042
|
-
return names.map((shim) =>
|
|
21678
|
+
return names.map((shim) => join15(binDir, shim));
|
|
20043
21679
|
}
|
|
20044
21680
|
function appiumCliCandidates(envDir, platform) {
|
|
20045
21681
|
return nodeModulesBinCandidates(envDir, "appium", platform);
|
|
@@ -20096,21 +21732,21 @@ async function checkUiautomator2(spawn, appiumBin, platform) {
|
|
|
20096
21732
|
}
|
|
20097
21733
|
|
|
20098
21734
|
// src/core/androidBins.ts
|
|
20099
|
-
import { join as
|
|
21735
|
+
import { join as join16 } from "node:path";
|
|
20100
21736
|
function withExeSuffix(name, platform) {
|
|
20101
21737
|
return platform === "win32" ? `${name}.exe` : name;
|
|
20102
21738
|
}
|
|
20103
21739
|
function emulatorBin(home, platform) {
|
|
20104
21740
|
const name = withExeSuffix("emulator", platform);
|
|
20105
|
-
return home ?
|
|
21741
|
+
return home ? join16(home, "emulator", name) : name;
|
|
20106
21742
|
}
|
|
20107
21743
|
function adbBin(home, platform) {
|
|
20108
21744
|
const name = withExeSuffix("adb", platform);
|
|
20109
|
-
return home ?
|
|
21745
|
+
return home ? join16(home, "platform-tools", name) : name;
|
|
20110
21746
|
}
|
|
20111
21747
|
function cmdlineToolsBin(home, name, platform) {
|
|
20112
21748
|
const file = platform === "win32" ? `${name}.bat` : name;
|
|
20113
|
-
return
|
|
21749
|
+
return join16(home, "cmdline-tools", "latest", "bin", file);
|
|
20114
21750
|
}
|
|
20115
21751
|
function sdkManagerBin(home, platform) {
|
|
20116
21752
|
return cmdlineToolsBin(home, "sdkmanager", platform);
|
|
@@ -20407,9 +22043,9 @@ async function checkNpmRegistry(deps) {
|
|
|
20407
22043
|
}
|
|
20408
22044
|
|
|
20409
22045
|
// src/core/playwrightCli.ts
|
|
20410
|
-
import { join as
|
|
22046
|
+
import { join as join17 } from "node:path";
|
|
20411
22047
|
function playwrightCliJsPath(envDir) {
|
|
20412
|
-
return
|
|
22048
|
+
return join17(envDir, "node_modules", "playwright", "cli.js");
|
|
20413
22049
|
}
|
|
20414
22050
|
function playwrightCliInvocation(args) {
|
|
20415
22051
|
return {
|
|
@@ -20559,11 +22195,11 @@ function renderResults(ui, results) {
|
|
|
20559
22195
|
}
|
|
20560
22196
|
|
|
20561
22197
|
// src/domains/flows/ensureDeps.ts
|
|
20562
|
-
import { dirname as dirname4, join as
|
|
22198
|
+
import { dirname as dirname4, join as join18 } from "node:path";
|
|
20563
22199
|
function findEnvDir(flowPath, fs) {
|
|
20564
22200
|
let dir = dirname4(flowPath);
|
|
20565
22201
|
while (true) {
|
|
20566
|
-
if (fs.existsSync(
|
|
22202
|
+
if (fs.existsSync(join18(dir, "package.json")))
|
|
20567
22203
|
return dir;
|
|
20568
22204
|
const parent = dirname4(dir);
|
|
20569
22205
|
if (parent === dir)
|
|
@@ -20589,7 +22225,7 @@ function resolveProjectDirSafe(files, fs) {
|
|
|
20589
22225
|
}
|
|
20590
22226
|
|
|
20591
22227
|
// src/domains/flows/expand.ts
|
|
20592
|
-
import { join as
|
|
22228
|
+
import { join as join19, resolve as resolve3 } from "node:path";
|
|
20593
22229
|
|
|
20594
22230
|
// node_modules/tinyglobby/dist/index.mjs
|
|
20595
22231
|
import { readdir, readdirSync, realpath, realpathSync, stat, statSync } from "fs";
|
|
@@ -21578,11 +23214,11 @@ function makePeekFlowMeta(fs) {
|
|
|
21578
23214
|
};
|
|
21579
23215
|
}
|
|
21580
23216
|
async function resolveGlobRoots(cwd, fs) {
|
|
21581
|
-
const qawolfPath =
|
|
23217
|
+
const qawolfPath = join19(cwd, ".qawolf");
|
|
21582
23218
|
let envDirs = [];
|
|
21583
23219
|
try {
|
|
21584
23220
|
const entries = await fs.readdirWithTypes(qawolfPath);
|
|
21585
|
-
envDirs = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
23221
|
+
envDirs = entries.filter((e) => e.isDirectory()).map((e) => join19(qawolfPath, e.name));
|
|
21586
23222
|
} catch {}
|
|
21587
23223
|
return [cwd, ...envDirs];
|
|
21588
23224
|
}
|
|
@@ -21608,11 +23244,11 @@ async function expandPatterns(patterns, cwd, logger, fs) {
|
|
|
21608
23244
|
}
|
|
21609
23245
|
|
|
21610
23246
|
// src/domains/runtimeEnv/clearRuntimeEnv.ts
|
|
21611
|
-
import { join as
|
|
23247
|
+
import { join as join21 } from "node:path";
|
|
21612
23248
|
|
|
21613
23249
|
// src/domains/runtimeEnv/managedEnvDir.ts
|
|
21614
23250
|
import { createHash } from "node:crypto";
|
|
21615
|
-
import { join as
|
|
23251
|
+
import { join as join20, resolve as resolve4 } from "node:path";
|
|
21616
23252
|
|
|
21617
23253
|
// src/domains/runtimeEnv/pinnedPackages.ts
|
|
21618
23254
|
var pinnedPackages = [
|
|
@@ -21643,19 +23279,19 @@ function managedEnvBaseDir() {
|
|
|
21643
23279
|
const override = process.env["QAWOLF_RUNTIME_DIR"]?.trim();
|
|
21644
23280
|
if (override)
|
|
21645
23281
|
return resolve4(override);
|
|
21646
|
-
return
|
|
23282
|
+
return join20(getDataDir(), "runtime");
|
|
21647
23283
|
}
|
|
21648
23284
|
function runStagingRoot() {
|
|
21649
23285
|
return `${managedEnvBaseDir()}-runs`;
|
|
21650
23286
|
}
|
|
21651
23287
|
function managedEnvDir() {
|
|
21652
|
-
return
|
|
23288
|
+
return join20(managedEnvBaseDir(), managedEnvHash());
|
|
21653
23289
|
}
|
|
21654
23290
|
async function scaffoldManagedEnv(dir, fs) {
|
|
21655
23291
|
await fs.mkdir(dir, { recursive: true });
|
|
21656
23292
|
const dependencies = Object.fromEntries(pinnedPackages.map(({ name, version }) => [name, version]));
|
|
21657
|
-
await fs.writeFile(
|
|
21658
|
-
await fs.writeFile(
|
|
23293
|
+
await fs.writeFile(join20(dir, "package.json"), JSON.stringify({ name: "qawolf-runtime", private: true, dependencies }, undefined, 2));
|
|
23294
|
+
await fs.writeFile(join20(dir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
|
|
21659
23295
|
`);
|
|
21660
23296
|
}
|
|
21661
23297
|
|
|
@@ -21677,67 +23313,19 @@ async function isManagedRuntimeBase(dir, fs) {
|
|
|
21677
23313
|
for (const entry of entries) {
|
|
21678
23314
|
if (!entry.isDirectory())
|
|
21679
23315
|
return false;
|
|
21680
|
-
if (!await hasRuntimeSentinel(
|
|
23316
|
+
if (!await hasRuntimeSentinel(join21(dir, entry.name), fs))
|
|
21681
23317
|
return false;
|
|
21682
23318
|
}
|
|
21683
23319
|
return true;
|
|
21684
23320
|
}
|
|
21685
23321
|
async function hasRuntimeSentinel(hashDir, fs) {
|
|
21686
23322
|
try {
|
|
21687
|
-
const pkg = JSON.parse(await fs.readFile(
|
|
23323
|
+
const pkg = JSON.parse(await fs.readFile(join21(hashDir, "package.json")));
|
|
21688
23324
|
return pkg.name === runtimeSentinel;
|
|
21689
23325
|
} catch {
|
|
21690
23326
|
return false;
|
|
21691
23327
|
}
|
|
21692
23328
|
}
|
|
21693
|
-
// src/shell/spawn.ts
|
|
21694
|
-
import { spawn } from "node:child_process";
|
|
21695
|
-
|
|
21696
|
-
// src/core/cmdEscape.ts
|
|
21697
|
-
var metaChars = /([()\][%!^"`<>&|;, *?])/g;
|
|
21698
|
-
function escapeCommand(command) {
|
|
21699
|
-
return command.replace(metaChars, "^$1");
|
|
21700
|
-
}
|
|
21701
|
-
function escapeArgument(arg) {
|
|
21702
|
-
const escaped = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"").replace(/(?=(\\+?)?)\1$/, "$1$1");
|
|
21703
|
-
return `"${escaped}"`.replace(metaChars, "^$1").replace(metaChars, "^$1");
|
|
21704
|
-
}
|
|
21705
|
-
|
|
21706
|
-
// src/shell/spawn.ts
|
|
21707
|
-
function buildSpawnCommand(cmd, args, platform, env) {
|
|
21708
|
-
const options = {};
|
|
21709
|
-
if (env)
|
|
21710
|
-
options.env = env;
|
|
21711
|
-
if (platform !== "win32" || !/\.(cmd|bat)$/i.test(cmd)) {
|
|
21712
|
-
return { cmd, args, options };
|
|
21713
|
-
}
|
|
21714
|
-
const line = [escapeCommand(cmd), ...args.map(escapeArgument)].join(" ");
|
|
21715
|
-
return {
|
|
21716
|
-
cmd: process.env["ComSpec"] ?? "cmd.exe",
|
|
21717
|
-
args: ["/d", "/s", "/c", `"${line}"`],
|
|
21718
|
-
options: { ...options, windowsVerbatimArguments: true }
|
|
21719
|
-
};
|
|
21720
|
-
}
|
|
21721
|
-
var defaultSpawn = (cmd, args, opts) => new Promise((resolve) => {
|
|
21722
|
-
const env = opts.env ? { ...process.env, ...opts.env } : undefined;
|
|
21723
|
-
const built = buildSpawnCommand(cmd, args, opts.platform, env);
|
|
21724
|
-
const child = spawn(built.cmd, built.args, built.options);
|
|
21725
|
-
let stdout = "";
|
|
21726
|
-
let stderr = "";
|
|
21727
|
-
child.stdout?.on("data", (chunk) => {
|
|
21728
|
-
stdout += String(chunk);
|
|
21729
|
-
});
|
|
21730
|
-
child.stderr?.on("data", (chunk) => {
|
|
21731
|
-
stderr += String(chunk);
|
|
21732
|
-
});
|
|
21733
|
-
if (opts?.stdin !== undefined) {
|
|
21734
|
-
child.stdin?.write(opts.stdin);
|
|
21735
|
-
child.stdin?.end();
|
|
21736
|
-
}
|
|
21737
|
-
child.on("error", (err) => resolve({ exitCode: -1, stdout, stderr: stderr || err.message }));
|
|
21738
|
-
child.on("close", (code) => resolve({ exitCode: code ?? -1, stdout, stderr }));
|
|
21739
|
-
});
|
|
21740
|
-
|
|
21741
23329
|
// src/domains/runtimeEnv/npmInstall.ts
|
|
21742
23330
|
function buildNpmInstallSpawn(cwd, platform) {
|
|
21743
23331
|
const built = buildSpawnCommand(resolveNpmCommand(platform), ["install", "--legacy-peer-deps"], platform, undefined);
|
|
@@ -21758,10 +23346,10 @@ function spawnNpmInstall(cwd, spawn2 = spawn) {
|
|
|
21758
23346
|
}
|
|
21759
23347
|
|
|
21760
23348
|
// src/domains/runtimeEnv/resolvePinned.ts
|
|
21761
|
-
import { join as
|
|
23349
|
+
import { join as join22 } from "node:path";
|
|
21762
23350
|
function readInstalledVersion(dir, pkgName, fs) {
|
|
21763
23351
|
try {
|
|
21764
|
-
const pkgPath =
|
|
23352
|
+
const pkgPath = join22(dir, "node_modules", ...pkgName.split("/"), "package.json");
|
|
21765
23353
|
const raw = JSON.parse(fs.readFileSync(pkgPath));
|
|
21766
23354
|
const { version } = raw;
|
|
21767
23355
|
return typeof version === "string" ? version : undefined;
|
|
@@ -21809,10 +23397,10 @@ function allPinnedResolved(dir, fs, platform) {
|
|
|
21809
23397
|
}
|
|
21810
23398
|
|
|
21811
23399
|
// src/domains/runtimeEnv/shimDeps.ts
|
|
21812
|
-
import { join as
|
|
23400
|
+
import { join as join24 } from "node:path";
|
|
21813
23401
|
|
|
21814
23402
|
// src/shell/resolveExport.ts
|
|
21815
|
-
import { join as
|
|
23403
|
+
import { join as join23 } from "node:path";
|
|
21816
23404
|
function pickPath(entry, preference) {
|
|
21817
23405
|
if (typeof entry === "string")
|
|
21818
23406
|
return entry;
|
|
@@ -21844,28 +23432,28 @@ function resolveFromEnvDir(envDir, specifier, preference = "esm", fs = makeDefau
|
|
|
21844
23432
|
const pkgName = parts.slice(0, pkgPartCount).join("/");
|
|
21845
23433
|
const rest = parts.slice(pkgPartCount);
|
|
21846
23434
|
const subpath = rest.length > 0 ? "./" + rest.join("/") : ".";
|
|
21847
|
-
const pkgDir =
|
|
23435
|
+
const pkgDir = join23(envDir, "node_modules", ...pkgName.split("/"));
|
|
21848
23436
|
let pkg;
|
|
21849
23437
|
try {
|
|
21850
|
-
pkg = JSON.parse(fs.readFileSync(
|
|
23438
|
+
pkg = JSON.parse(fs.readFileSync(join23(pkgDir, "package.json")));
|
|
21851
23439
|
} catch (err) {
|
|
21852
23440
|
if (isNoEntError(err))
|
|
21853
|
-
throw new Error(`Package '${pkgName}' not found in ${
|
|
23441
|
+
throw new Error(`Package '${pkgName}' not found in ${join23(envDir, "node_modules")}`, { cause: err });
|
|
21854
23442
|
throw err;
|
|
21855
23443
|
}
|
|
21856
23444
|
const exportsMap = normalizeExports(pkg.exports);
|
|
21857
23445
|
const entry = exportsMap?.[subpath];
|
|
21858
23446
|
const relative = pickPath(entry, preference) ?? (subpath === "." ? pkg.module ?? pkg.main : undefined);
|
|
21859
23447
|
if (!relative) {
|
|
21860
|
-
throw new Error(`No entry for "${subpath}" in exports of ${
|
|
23448
|
+
throw new Error(`No entry for "${subpath}" in exports of ${join23(pkgDir, "package.json")}`);
|
|
21861
23449
|
}
|
|
21862
|
-
return
|
|
23450
|
+
return join23(pkgDir, relative);
|
|
21863
23451
|
}
|
|
21864
23452
|
|
|
21865
23453
|
// src/domains/runtimeEnv/shimDeps.ts
|
|
21866
23454
|
function readShimMarker(shimDir, fs) {
|
|
21867
23455
|
try {
|
|
21868
|
-
const pkg = JSON.parse(fs.readFileSync(
|
|
23456
|
+
const pkg = JSON.parse(fs.readFileSync(join24(shimDir, "package.json")));
|
|
21869
23457
|
if (pkg._qawolf_format) {
|
|
21870
23458
|
return {
|
|
21871
23459
|
_qawolf_version: pkg._qawolf_version ?? "",
|
|
@@ -21881,11 +23469,11 @@ function resolveBunBuild(bunBuild) {
|
|
|
21881
23469
|
return bunBuild === false ? undefined : { build: bunBuild };
|
|
21882
23470
|
}
|
|
21883
23471
|
async function removeStaleShims(flowsDir, flowsDeps, fs) {
|
|
21884
|
-
const shimsDir =
|
|
23472
|
+
const shimsDir = join24(flowsDir, "node_modules");
|
|
21885
23473
|
if (!fs.existsSync(shimsDir))
|
|
21886
23474
|
return;
|
|
21887
23475
|
for (const dep of flowsDeps) {
|
|
21888
|
-
const shimDepDir =
|
|
23476
|
+
const shimDepDir = join24(shimsDir, ...dep.split("/"));
|
|
21889
23477
|
if (readShimMarker(shimDepDir, fs)) {
|
|
21890
23478
|
await fs.rm(shimDepDir, { recursive: true, force: true });
|
|
21891
23479
|
}
|
|
@@ -21894,17 +23482,17 @@ async function removeStaleShims(flowsDir, flowsDeps, fs) {
|
|
|
21894
23482
|
async function buildDepShim(args) {
|
|
21895
23483
|
const { envDir, flowsDir, dep, fs, build } = args;
|
|
21896
23484
|
const depParts = dep.split("/");
|
|
21897
|
-
const depDir =
|
|
23485
|
+
const depDir = join24(envDir, "node_modules", ...depParts);
|
|
21898
23486
|
if (!fs.existsSync(depDir))
|
|
21899
23487
|
return;
|
|
21900
23488
|
let depVersion;
|
|
21901
23489
|
try {
|
|
21902
|
-
const pkg = JSON.parse(fs.readFileSync(
|
|
23490
|
+
const pkg = JSON.parse(fs.readFileSync(join24(depDir, "package.json")));
|
|
21903
23491
|
depVersion = pkg.version ?? "unknown";
|
|
21904
23492
|
} catch {
|
|
21905
23493
|
depVersion = "unknown";
|
|
21906
23494
|
}
|
|
21907
|
-
const shimDir =
|
|
23495
|
+
const shimDir = join24(flowsDir, "node_modules", ...depParts);
|
|
21908
23496
|
if (fs.existsSync(shimDir)) {
|
|
21909
23497
|
const marker = readShimMarker(shimDir, fs);
|
|
21910
23498
|
if (!marker)
|
|
@@ -21931,20 +23519,20 @@ async function buildDepShim(args) {
|
|
|
21931
23519
|
}
|
|
21932
23520
|
const shimCode = await output.text();
|
|
21933
23521
|
await fs.mkdir(shimDir, { recursive: true });
|
|
21934
|
-
await fs.writeFile(
|
|
23522
|
+
await fs.writeFile(join24(shimDir, "package.json"), JSON.stringify({
|
|
21935
23523
|
name: dep,
|
|
21936
23524
|
_qawolf_version: depVersion,
|
|
21937
23525
|
_qawolf_format: "bun-build-v1"
|
|
21938
23526
|
}));
|
|
21939
|
-
await fs.writeFile(
|
|
23527
|
+
await fs.writeFile(join24(shimDir, "index.js"), shimCode);
|
|
21940
23528
|
}
|
|
21941
23529
|
async function shimFlowsDeps(envDir, fs = makeDefaultFs(), bunBuild) {
|
|
21942
|
-
const flowsDir =
|
|
23530
|
+
const flowsDir = join24(envDir, "node_modules", "@qawolf", "flows");
|
|
21943
23531
|
if (!fs.existsSync(flowsDir))
|
|
21944
23532
|
return;
|
|
21945
23533
|
let flowsDeps;
|
|
21946
23534
|
try {
|
|
21947
|
-
const flowsPkg = JSON.parse(fs.readFileSync(
|
|
23535
|
+
const flowsPkg = JSON.parse(fs.readFileSync(join24(flowsDir, "package.json")));
|
|
21948
23536
|
flowsDeps = Object.keys(flowsPkg.dependencies ?? {});
|
|
21949
23537
|
} catch {
|
|
21950
23538
|
return;
|
|
@@ -22125,11 +23713,11 @@ function collectValue(value, previous) {
|
|
|
22125
23713
|
}
|
|
22126
23714
|
|
|
22127
23715
|
// src/shell/manifest/pulledEnv.ts
|
|
22128
|
-
import { join as
|
|
23716
|
+
import { join as join26 } from "node:path";
|
|
22129
23717
|
|
|
22130
23718
|
// src/shell/manifest/io.ts
|
|
22131
23719
|
import { createHash as createHash2 } from "node:crypto";
|
|
22132
|
-
import { join as
|
|
23720
|
+
import { join as join25 } from "node:path";
|
|
22133
23721
|
var manifestFilename = ".manifest.json";
|
|
22134
23722
|
var flowEntrySchema = object({
|
|
22135
23723
|
path: string2(),
|
|
@@ -22151,7 +23739,7 @@ var manifestSchema = object({
|
|
|
22151
23739
|
async function readManifest(envDir, fs = makeDefaultFs()) {
|
|
22152
23740
|
let raw;
|
|
22153
23741
|
try {
|
|
22154
|
-
raw = await fs.readFile(
|
|
23742
|
+
raw = await fs.readFile(join25(envDir, manifestFilename));
|
|
22155
23743
|
} catch (err) {
|
|
22156
23744
|
if (isNoEntError(err))
|
|
22157
23745
|
return "missing";
|
|
@@ -22185,7 +23773,7 @@ async function readManifest(envDir, fs = makeDefaultFs()) {
|
|
|
22185
23773
|
}
|
|
22186
23774
|
async function writeManifest(envDir, manifest, fs = makeDefaultFs()) {
|
|
22187
23775
|
const body = JSON.stringify(manifest, undefined, 2);
|
|
22188
|
-
await fs.writeFile(
|
|
23776
|
+
await fs.writeFile(join25(envDir, manifestFilename), `${body}
|
|
22189
23777
|
`);
|
|
22190
23778
|
}
|
|
22191
23779
|
function hashFile(absPath, fs = makeDefaultFs()) {
|
|
@@ -22211,7 +23799,7 @@ async function findPulledEnv(ref, cwd, fs = makeDefaultFs()) {
|
|
|
22211
23799
|
return;
|
|
22212
23800
|
}
|
|
22213
23801
|
async function listPulledEnvDirs(cwd, fs = makeDefaultFs()) {
|
|
22214
|
-
const root =
|
|
23802
|
+
const root = join26(cwd, ".qawolf");
|
|
22215
23803
|
let entries;
|
|
22216
23804
|
try {
|
|
22217
23805
|
entries = await fs.readdirWithTypes(root);
|
|
@@ -22224,7 +23812,7 @@ async function listPulledEnvDirs(cwd, fs = makeDefaultFs()) {
|
|
|
22224
23812
|
for (const entry of entries) {
|
|
22225
23813
|
if (!entry.isDirectory())
|
|
22226
23814
|
continue;
|
|
22227
|
-
const envDir =
|
|
23815
|
+
const envDir = join26(root, entry.name);
|
|
22228
23816
|
const manifest = await readManifest(envDir, fs);
|
|
22229
23817
|
if (typeof manifest === "string")
|
|
22230
23818
|
continue;
|
|
@@ -22278,9 +23866,6 @@ async function readEnvLabels(files, readEnvLabel) {
|
|
|
22278
23866
|
}
|
|
22279
23867
|
|
|
22280
23868
|
// src/core/suggestNearMiss.ts
|
|
22281
|
-
function normalize3(value) {
|
|
22282
|
-
return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
|
|
22283
|
-
}
|
|
22284
23869
|
function editDistance(a, b) {
|
|
22285
23870
|
let row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
22286
23871
|
for (let i = 1;i <= a.length; i += 1) {
|
|
@@ -22302,14 +23887,14 @@ function suggestNearMiss(input, candidates) {
|
|
|
22302
23887
|
if (candidates.includes(input))
|
|
22303
23888
|
return;
|
|
22304
23889
|
const sorted = [...candidates].sort();
|
|
22305
|
-
const normalizedInput =
|
|
22306
|
-
const presentationMatch = sorted.find((candidate) =>
|
|
23890
|
+
const normalizedInput = normalizeForSearch(input);
|
|
23891
|
+
const presentationMatch = sorted.find((candidate) => normalizeForSearch(candidate) === normalizedInput);
|
|
22307
23892
|
if (presentationMatch !== undefined)
|
|
22308
23893
|
return presentationMatch;
|
|
22309
23894
|
let best;
|
|
22310
23895
|
let bestDistance = Number.POSITIVE_INFINITY;
|
|
22311
23896
|
for (const candidate of sorted) {
|
|
22312
|
-
const distance = editDistance(normalizedInput,
|
|
23897
|
+
const distance = editDistance(normalizedInput, normalizeForSearch(candidate));
|
|
22313
23898
|
if (distance < bestDistance) {
|
|
22314
23899
|
bestDistance = distance;
|
|
22315
23900
|
best = candidate;
|
|
@@ -22619,7 +24204,7 @@ async function flowsListRemote(ctx, pattern, options) {
|
|
|
22619
24204
|
}
|
|
22620
24205
|
|
|
22621
24206
|
// src/domains/flows/pull/handler.ts
|
|
22622
|
-
import { dirname as dirname8, join as
|
|
24207
|
+
import { dirname as dirname8, join as join31, resolve as resolve8 } from "node:path";
|
|
22623
24208
|
|
|
22624
24209
|
// src/domains/flows/pull/fetchPhase.ts
|
|
22625
24210
|
async function fetchTags(ctx, envId) {
|
|
@@ -22763,7 +24348,7 @@ async function checkSafety(args) {
|
|
|
22763
24348
|
}
|
|
22764
24349
|
|
|
22765
24350
|
// src/domains/flows/pull/reportPull.ts
|
|
22766
|
-
import { join as
|
|
24351
|
+
import { join as join27 } from "node:path";
|
|
22767
24352
|
function reportPullResult(ui, args) {
|
|
22768
24353
|
if (ui.mode !== "json")
|
|
22769
24354
|
return;
|
|
@@ -22778,12 +24363,12 @@ function reportPullResult(ui, args) {
|
|
|
22778
24363
|
assetDownloadedCount: args.assets.downloadedCount,
|
|
22779
24364
|
assetReusedCount: args.assets.reusedCount,
|
|
22780
24365
|
assetSkippedCount: args.assets.skippedCount,
|
|
22781
|
-
manifestPath:
|
|
24366
|
+
manifestPath: join27(args.stage.envDir, manifestFilename)
|
|
22782
24367
|
}, "");
|
|
22783
24368
|
}
|
|
22784
24369
|
|
|
22785
24370
|
// src/domains/flows/pull/bundle.ts
|
|
22786
|
-
import { join as
|
|
24371
|
+
import { join as join28, relative as relative6 } from "node:path";
|
|
22787
24372
|
async function flattenSingleWrapper(dir, fs = makeDefaultFs()) {
|
|
22788
24373
|
const entries = await fs.readdir(dir);
|
|
22789
24374
|
if (entries.length !== 1)
|
|
@@ -22791,12 +24376,12 @@ async function flattenSingleWrapper(dir, fs = makeDefaultFs()) {
|
|
|
22791
24376
|
const innerName = entries[0];
|
|
22792
24377
|
if (!innerName)
|
|
22793
24378
|
return;
|
|
22794
|
-
const inner =
|
|
24379
|
+
const inner = join28(dir, innerName);
|
|
22795
24380
|
const innerStat = await fs.stat(inner);
|
|
22796
24381
|
if (!innerStat.isDirectory())
|
|
22797
24382
|
return;
|
|
22798
24383
|
for (const e of await fs.readdir(inner)) {
|
|
22799
|
-
await fs.rename(
|
|
24384
|
+
await fs.rename(join28(inner, e), join28(dir, e));
|
|
22800
24385
|
}
|
|
22801
24386
|
const leftovers = await fs.readdir(inner);
|
|
22802
24387
|
if (leftovers.length > 0) {
|
|
@@ -22815,7 +24400,7 @@ async function buildManifest(args, fs = makeDefaultFs()) {
|
|
|
22815
24400
|
const flowPaths = await walkForFlows(args.bundleDir, fs);
|
|
22816
24401
|
const flows = await Promise.all(flowPaths.map(async (rel) => ({
|
|
22817
24402
|
path: toPosix(rel),
|
|
22818
|
-
contentHash: await hashFile(
|
|
24403
|
+
contentHash: await hashFile(join28(args.bundleDir, rel), fs),
|
|
22819
24404
|
tags: args.tags?.byPath.get(toPosix(rel))
|
|
22820
24405
|
})));
|
|
22821
24406
|
return {
|
|
@@ -22839,7 +24424,7 @@ async function walkForFlows(root, fs) {
|
|
|
22839
24424
|
async function walk(current, root, out, fs) {
|
|
22840
24425
|
const entries = await fs.readdirWithTypes(current);
|
|
22841
24426
|
for (const e of entries) {
|
|
22842
|
-
const abs =
|
|
24427
|
+
const abs = join28(current, e.name);
|
|
22843
24428
|
if (e.isDirectory()) {
|
|
22844
24429
|
await walk(abs, root, out, fs);
|
|
22845
24430
|
} else if (e.isFile() && flowExtensions.some((ext) => e.name.endsWith(ext))) {
|
|
@@ -22852,11 +24437,11 @@ async function sampleQawolfCommittedAt(bundleDir, fs = makeDefaultFs()) {
|
|
|
22852
24437
|
const sample = flowPaths[0];
|
|
22853
24438
|
if (!sample)
|
|
22854
24439
|
return;
|
|
22855
|
-
return (await fs.stat(
|
|
24440
|
+
return (await fs.stat(join28(bundleDir, sample))).mtime.toISOString();
|
|
22856
24441
|
}
|
|
22857
24442
|
|
|
22858
24443
|
// src/domains/flows/pull/applyTeamStorageRewrite.ts
|
|
22859
|
-
import { join as
|
|
24444
|
+
import { join as join29, relative as relative7 } from "node:path";
|
|
22860
24445
|
|
|
22861
24446
|
// src/domains/flows/pull/rewriteTeamStorage.ts
|
|
22862
24447
|
var runnerMountPrefix = "/home/wolf/team-storage/";
|
|
@@ -22892,7 +24477,7 @@ function isFlowFile(name) {
|
|
|
22892
24477
|
async function walk2(dir, out, fs) {
|
|
22893
24478
|
const entries = await fs.readdirWithTypes(dir);
|
|
22894
24479
|
for (const e of entries) {
|
|
22895
|
-
const abs =
|
|
24480
|
+
const abs = join29(dir, e.name);
|
|
22896
24481
|
if (e.isDirectory()) {
|
|
22897
24482
|
await walk2(abs, out, fs);
|
|
22898
24483
|
} else if (e.isFile() && isSourceFile(e.name)) {
|
|
@@ -22920,7 +24505,7 @@ async function applyTeamStorageRewrite(rootDir, fs = makeDefaultFs()) {
|
|
|
22920
24505
|
}
|
|
22921
24506
|
|
|
22922
24507
|
// src/domains/flows/pull/envVars.ts
|
|
22923
|
-
import { join as
|
|
24508
|
+
import { join as join30 } from "node:path";
|
|
22924
24509
|
|
|
22925
24510
|
// src/core/dotenv.ts
|
|
22926
24511
|
var bareKeyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -22985,7 +24570,7 @@ function unquote(escaped) {
|
|
|
22985
24570
|
async function writeEnvFile(envDir, vars, fs = makeDefaultFs()) {
|
|
22986
24571
|
if (Object.keys(vars).length === 0)
|
|
22987
24572
|
return;
|
|
22988
|
-
await fs.writeFile(
|
|
24573
|
+
await fs.writeFile(join30(envDir, ".env"), serializeDotenv(vars), {
|
|
22989
24574
|
mode: 384
|
|
22990
24575
|
});
|
|
22991
24576
|
}
|
|
@@ -23049,11 +24634,11 @@ var Ae = Symbol("flush");
|
|
|
23049
24634
|
var As = Symbol("flushChunk");
|
|
23050
24635
|
var z = Symbol("encoding");
|
|
23051
24636
|
var Mt = Symbol("decoder");
|
|
23052
|
-
var
|
|
24637
|
+
var g2 = Symbol("flowing");
|
|
23053
24638
|
var Qt = Symbol("paused");
|
|
23054
24639
|
var Bt = Symbol("resume");
|
|
23055
24640
|
var b = Symbol("buffer");
|
|
23056
|
-
var
|
|
24641
|
+
var N2 = Symbol("pipes");
|
|
23057
24642
|
var _ = Symbol("bufferLength");
|
|
23058
24643
|
var bi = Symbol("bufferPush");
|
|
23059
24644
|
var Ie = Symbol("bufferShift");
|
|
@@ -23101,9 +24686,9 @@ var Li = class extends Fe {
|
|
|
23101
24686
|
var Xr = (s) => !!s.objectMode;
|
|
23102
24687
|
var qr = (s) => !s.objectMode && !!s.encoding && s.encoding !== "buffer";
|
|
23103
24688
|
var A2 = class extends Di {
|
|
23104
|
-
[
|
|
24689
|
+
[g2] = false;
|
|
23105
24690
|
[Qt] = false;
|
|
23106
|
-
[
|
|
24691
|
+
[N2] = [];
|
|
23107
24692
|
[b] = [];
|
|
23108
24693
|
[L];
|
|
23109
24694
|
[z];
|
|
@@ -23126,7 +24711,7 @@ var A2 = class extends Di {
|
|
|
23126
24711
|
let e = t[0] || {};
|
|
23127
24712
|
if (super(), e.objectMode && typeof e.encoding == "string")
|
|
23128
24713
|
throw new TypeError("Encoding and objectMode may not be used together");
|
|
23129
|
-
Xr(e) ? (this[L] = true, this[z] = null) : qr(e) ? (this[z] = e.encoding, this[L] = false) : (this[L] = false, this[z] = null), this[Z] = !!e.async, this[Mt] = this[z] ? new Hr(this[z]) : null, e && e.debugExposeBuffer === true && Object.defineProperty(this, "buffer", { get: () => this[b] }), e && e.debugExposePipes === true && Object.defineProperty(this, "pipes", { get: () => this[
|
|
24714
|
+
Xr(e) ? (this[L] = true, this[z] = null) : qr(e) ? (this[z] = e.encoding, this[L] = false) : (this[L] = false, this[z] = null), this[Z] = !!e.async, this[Mt] = this[z] ? new Hr(this[z]) : null, e && e.debugExposeBuffer === true && Object.defineProperty(this, "buffer", { get: () => this[b] }), e && e.debugExposePipes === true && Object.defineProperty(this, "pipes", { get: () => this[N2] });
|
|
23130
24715
|
let { signal: i } = e;
|
|
23131
24716
|
i && (this[Jt] = i, i.aborted ? this[xi]() : i.addEventListener("abort", () => this[xi]()));
|
|
23132
24717
|
}
|
|
@@ -23178,7 +24763,7 @@ var A2 = class extends Di {
|
|
|
23178
24763
|
else if (typeof t != "string")
|
|
23179
24764
|
throw new Error("Non-contiguous data written to non-objectMode stream");
|
|
23180
24765
|
}
|
|
23181
|
-
return this[L] ? (this[
|
|
24766
|
+
return this[L] ? (this[g2] && this[_] !== 0 && this[Ae](true), this[g2] ? this.emit("data", t) : this[bi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g2]) : t.length ? (typeof t == "string" && !(e === this[z] && !this[Mt]?.lastNeed) && (t = Buffer.from(t, e)), Buffer.isBuffer(t) && this[z] && (t = this[Mt].write(t)), this[g2] && this[_] !== 0 && this[Ae](true), this[g2] ? this.emit("data", t) : this[bi](t), this[_] !== 0 && this.emit("readable"), i && r(i), this[g2]) : (this[_] !== 0 && this.emit("readable"), i && r(i), this[g2]);
|
|
23182
24767
|
}
|
|
23183
24768
|
read(t) {
|
|
23184
24769
|
if (this[S])
|
|
@@ -23199,22 +24784,22 @@ var A2 = class extends Di {
|
|
|
23199
24784
|
return this.emit("data", e), !this[b].length && !this[Q] && this.emit("drain"), e;
|
|
23200
24785
|
}
|
|
23201
24786
|
end(t, e, i) {
|
|
23202
|
-
return typeof t == "function" && (i = t, t = undefined), typeof e == "function" && (i = e, e = "utf8"), t !== undefined && this.write(t, e), i && this.once("end", i), this[Q] = true, this.writable = false, (this[
|
|
24787
|
+
return typeof t == "function" && (i = t, t = undefined), typeof e == "function" && (i = e, e = "utf8"), t !== undefined && this.write(t, e), i && this.once("end", i), this[Q] = true, this.writable = false, (this[g2] || !this[Qt]) && this[J](), this;
|
|
23203
24788
|
}
|
|
23204
24789
|
[Bt]() {
|
|
23205
|
-
this[S] || (!this[Rt] && !this[
|
|
24790
|
+
this[S] || (!this[Rt] && !this[N2].length && (this[C2] = true), this[Qt] = false, this[g2] = true, this.emit("resume"), this[b].length ? this[Ae]() : this[Q] ? this[J]() : this.emit("drain"));
|
|
23206
24791
|
}
|
|
23207
24792
|
resume() {
|
|
23208
24793
|
return this[Bt]();
|
|
23209
24794
|
}
|
|
23210
24795
|
pause() {
|
|
23211
|
-
this[
|
|
24796
|
+
this[g2] = false, this[Qt] = true, this[C2] = false;
|
|
23212
24797
|
}
|
|
23213
24798
|
get destroyed() {
|
|
23214
24799
|
return this[S];
|
|
23215
24800
|
}
|
|
23216
24801
|
get flowing() {
|
|
23217
|
-
return this[
|
|
24802
|
+
return this[g2];
|
|
23218
24803
|
}
|
|
23219
24804
|
get paused() {
|
|
23220
24805
|
return this[Qt];
|
|
@@ -23232,18 +24817,18 @@ var A2 = class extends Di {
|
|
|
23232
24817
|
!t && !this[b].length && !this[Q] && this.emit("drain");
|
|
23233
24818
|
}
|
|
23234
24819
|
[As](t) {
|
|
23235
|
-
return this.emit("data", t), this[
|
|
24820
|
+
return this.emit("data", t), this[g2];
|
|
23236
24821
|
}
|
|
23237
24822
|
pipe(t, e) {
|
|
23238
24823
|
if (this[S])
|
|
23239
24824
|
return t;
|
|
23240
24825
|
this[C2] = false;
|
|
23241
24826
|
let i = this[nt];
|
|
23242
|
-
return e = e || {}, t === Ds.stdout || t === Ds.stderr ? e.end = false : e.end = e.end !== false, e.proxyErrors = !!e.proxyErrors, i ? e.end && t.end() : (this[
|
|
24827
|
+
return e = e || {}, t === Ds.stdout || t === Ds.stderr ? e.end = false : e.end = e.end !== false, e.proxyErrors = !!e.proxyErrors, i ? e.end && t.end() : (this[N2].push(e.proxyErrors ? new Li(this, t, e) : new Fe(this, t, e)), this[Z] ? jt(() => this[Bt]()) : this[Bt]()), t;
|
|
23243
24828
|
}
|
|
23244
24829
|
unpipe(t) {
|
|
23245
|
-
let e = this[
|
|
23246
|
-
e && (this[
|
|
24830
|
+
let e = this[N2].find((i) => i.dest === t);
|
|
24831
|
+
e && (this[N2].length === 1 ? (this[g2] && this[Rt] === 0 && (this[g2] = false), this[N2] = []) : this[N2].splice(this[N2].indexOf(e), 1), e.unpipe());
|
|
23247
24832
|
}
|
|
23248
24833
|
addListener(t, e) {
|
|
23249
24834
|
return this.on(t, e);
|
|
@@ -23251,7 +24836,7 @@ var A2 = class extends Di {
|
|
|
23251
24836
|
on(t, e) {
|
|
23252
24837
|
let i = super.on(t, e);
|
|
23253
24838
|
if (t === "data")
|
|
23254
|
-
this[C2] = false, this[Rt]++, !this[
|
|
24839
|
+
this[C2] = false, this[Rt]++, !this[N2].length && !this[g2] && this[Bt]();
|
|
23255
24840
|
else if (t === "readable" && this[_] !== 0)
|
|
23256
24841
|
super.emit("readable");
|
|
23257
24842
|
else if (Kr(t) && this[nt])
|
|
@@ -23267,11 +24852,11 @@ var A2 = class extends Di {
|
|
|
23267
24852
|
}
|
|
23268
24853
|
off(t, e) {
|
|
23269
24854
|
let i = super.off(t, e);
|
|
23270
|
-
return t === "data" && (this[Rt] = this.listeners("data").length, this[Rt] === 0 && !this[C2] && !this[
|
|
24855
|
+
return t === "data" && (this[Rt] = this.listeners("data").length, this[Rt] === 0 && !this[C2] && !this[N2].length && (this[g2] = false)), i;
|
|
23271
24856
|
}
|
|
23272
24857
|
removeAllListeners(t) {
|
|
23273
24858
|
let e = super.removeAllListeners(t);
|
|
23274
|
-
return (t === "data" || t === undefined) && (this[Rt] = 0, !this[C2] && !this[
|
|
24859
|
+
return (t === "data" || t === undefined) && (this[Rt] = 0, !this[C2] && !this[N2].length && (this[g2] = false)), e;
|
|
23275
24860
|
}
|
|
23276
24861
|
get emittedEnd() {
|
|
23277
24862
|
return this[nt];
|
|
@@ -23307,7 +24892,7 @@ var A2 = class extends Di {
|
|
|
23307
24892
|
return this[J](), r;
|
|
23308
24893
|
}
|
|
23309
24894
|
[Oi](t) {
|
|
23310
|
-
for (let i of this[
|
|
24895
|
+
for (let i of this[N2])
|
|
23311
24896
|
i.dest.write(t) === false && this.pause();
|
|
23312
24897
|
let e = this[C2] ? false : super.emit("data", t);
|
|
23313
24898
|
return this[J](), e;
|
|
@@ -23319,12 +24904,12 @@ var A2 = class extends Di {
|
|
|
23319
24904
|
if (this[Mt]) {
|
|
23320
24905
|
let e = this[Mt].end();
|
|
23321
24906
|
if (e) {
|
|
23322
|
-
for (let i of this[
|
|
24907
|
+
for (let i of this[N2])
|
|
23323
24908
|
i.dest.write(e);
|
|
23324
24909
|
this[C2] || super.emit("data", e);
|
|
23325
24910
|
}
|
|
23326
24911
|
}
|
|
23327
|
-
for (let e of this[
|
|
24912
|
+
for (let e of this[N2])
|
|
23328
24913
|
e.end();
|
|
23329
24914
|
let t = super.emit("end");
|
|
23330
24915
|
return this.removeAllListeners("end"), t;
|
|
@@ -23400,7 +24985,7 @@ var Jr = I.writev;
|
|
|
23400
24985
|
var ht = Symbol("_autoClose");
|
|
23401
24986
|
var H = Symbol("_close");
|
|
23402
24987
|
var te = Symbol("_ended");
|
|
23403
|
-
var
|
|
24988
|
+
var m3 = Symbol("_fd");
|
|
23404
24989
|
var Ni = Symbol("_finished");
|
|
23405
24990
|
var tt = Symbol("_flags");
|
|
23406
24991
|
var Ai = Symbol("_flush");
|
|
@@ -23427,7 +25012,7 @@ var Me = Symbol("_defaultFlag");
|
|
|
23427
25012
|
var bt = Symbol("_errored");
|
|
23428
25013
|
var _t = class extends A2 {
|
|
23429
25014
|
[bt] = false;
|
|
23430
|
-
[
|
|
25015
|
+
[m3];
|
|
23431
25016
|
[U2];
|
|
23432
25017
|
[Ci];
|
|
23433
25018
|
[j] = false;
|
|
@@ -23437,10 +25022,10 @@ var _t = class extends A2 {
|
|
|
23437
25022
|
constructor(t, e) {
|
|
23438
25023
|
if (e = e || {}, super(e), this.readable = true, this.writable = false, typeof t != "string")
|
|
23439
25024
|
throw new TypeError("path must be a string");
|
|
23440
|
-
this[bt] = false, this[
|
|
25025
|
+
this[bt] = false, this[m3] = typeof e.fd == "number" ? e.fd : undefined, this[U2] = t, this[Ci] = e.readSize || 16 * 1024 * 1024, this[j] = false, this[Fi] = typeof e.size == "number" ? e.size : 1 / 0, this[ee] = this[Fi], this[ht] = typeof e.autoClose == "boolean" ? e.autoClose : true, typeof this[m3] == "number" ? this[zt]() : this[at]();
|
|
23441
25026
|
}
|
|
23442
25027
|
get fd() {
|
|
23443
|
-
return this[
|
|
25028
|
+
return this[m3];
|
|
23444
25029
|
}
|
|
23445
25030
|
get path() {
|
|
23446
25031
|
return this[U2];
|
|
@@ -23455,7 +25040,7 @@ var _t = class extends A2 {
|
|
|
23455
25040
|
I.open(this[U2], "r", (t, e) => this[Ht](t, e));
|
|
23456
25041
|
}
|
|
23457
25042
|
[Ht](t, e) {
|
|
23458
|
-
t ? this[Ut](t) : (this[
|
|
25043
|
+
t ? this[Ut](t) : (this[m3] = e, this.emit("open", e), this[zt]());
|
|
23459
25044
|
}
|
|
23460
25045
|
[vi]() {
|
|
23461
25046
|
return Buffer.allocUnsafe(Math.min(this[Ci], this[ee]));
|
|
@@ -23466,16 +25051,16 @@ var _t = class extends A2 {
|
|
|
23466
25051
|
let t = this[vi]();
|
|
23467
25052
|
if (t.length === 0)
|
|
23468
25053
|
return process.nextTick(() => this[Ii](null, 0, t));
|
|
23469
|
-
I.read(this[
|
|
25054
|
+
I.read(this[m3], t, 0, t.length, null, (e, i, r) => this[Ii](e, i, r));
|
|
23470
25055
|
}
|
|
23471
25056
|
}
|
|
23472
25057
|
[Ii](t, e, i) {
|
|
23473
25058
|
this[j] = false, t ? this[Ut](t) : this[ki](e, i) && this[zt]();
|
|
23474
25059
|
}
|
|
23475
25060
|
[H]() {
|
|
23476
|
-
if (this[ht] && typeof this[
|
|
23477
|
-
let t = this[
|
|
23478
|
-
this[
|
|
25061
|
+
if (this[ht] && typeof this[m3] == "number") {
|
|
25062
|
+
let t = this[m3];
|
|
25063
|
+
this[m3] = undefined, I.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
|
|
23479
25064
|
}
|
|
23480
25065
|
}
|
|
23481
25066
|
[Ut](t) {
|
|
@@ -23491,7 +25076,7 @@ var _t = class extends A2 {
|
|
|
23491
25076
|
case "finish":
|
|
23492
25077
|
return false;
|
|
23493
25078
|
case "drain":
|
|
23494
|
-
return typeof this[
|
|
25079
|
+
return typeof this[m3] == "number" && this[zt](), false;
|
|
23495
25080
|
case "error":
|
|
23496
25081
|
return this[bt] ? false : (this[bt] = true, super.emit(t, ...e));
|
|
23497
25082
|
default:
|
|
@@ -23514,7 +25099,7 @@ var Be = class extends _t {
|
|
|
23514
25099
|
if (!this[j]) {
|
|
23515
25100
|
this[j] = true;
|
|
23516
25101
|
do {
|
|
23517
|
-
let e = this[vi](), i = e.length === 0 ? 0 : I.readSync(this[
|
|
25102
|
+
let e = this[vi](), i = e.length === 0 ? 0 : I.readSync(this[m3], e, 0, e.length, null);
|
|
23518
25103
|
if (!this[ki](i, e))
|
|
23519
25104
|
break;
|
|
23520
25105
|
} while (true);
|
|
@@ -23526,9 +25111,9 @@ var Be = class extends _t {
|
|
|
23526
25111
|
}
|
|
23527
25112
|
}
|
|
23528
25113
|
[H]() {
|
|
23529
|
-
if (this[ht] && typeof this[
|
|
23530
|
-
let t = this[
|
|
23531
|
-
this[
|
|
25114
|
+
if (this[ht] && typeof this[m3] == "number") {
|
|
25115
|
+
let t = this[m3];
|
|
25116
|
+
this[m3] = undefined, I.closeSync(t), this.emit("close");
|
|
23532
25117
|
}
|
|
23533
25118
|
}
|
|
23534
25119
|
};
|
|
@@ -23543,15 +25128,15 @@ var et = class extends Qr {
|
|
|
23543
25128
|
[U2];
|
|
23544
25129
|
[ie];
|
|
23545
25130
|
[ht];
|
|
23546
|
-
[
|
|
25131
|
+
[m3];
|
|
23547
25132
|
[Me];
|
|
23548
25133
|
[tt];
|
|
23549
25134
|
[Ni] = false;
|
|
23550
25135
|
[ot];
|
|
23551
25136
|
constructor(t, e) {
|
|
23552
|
-
e = e || {}, super(e), this[U2] = t, this[
|
|
25137
|
+
e = e || {}, super(e), this[U2] = t, this[m3] = typeof e.fd == "number" ? e.fd : undefined, this[ie] = e.mode === undefined ? 438 : e.mode, this[ot] = typeof e.start == "number" ? e.start : undefined, this[ht] = typeof e.autoClose == "boolean" ? e.autoClose : true;
|
|
23553
25138
|
let i = this[ot] !== undefined ? "r+" : "w";
|
|
23554
|
-
this[Me] = e.flags === undefined, this[tt] = e.flags === undefined ? i : e.flags, this[
|
|
25139
|
+
this[Me] = e.flags === undefined, this[tt] = e.flags === undefined ? i : e.flags, this[m3] === undefined && this[at]();
|
|
23555
25140
|
}
|
|
23556
25141
|
emit(t, ...e) {
|
|
23557
25142
|
if (t === "error") {
|
|
@@ -23562,7 +25147,7 @@ var et = class extends Qr {
|
|
|
23562
25147
|
return super.emit(t, ...e);
|
|
23563
25148
|
}
|
|
23564
25149
|
get fd() {
|
|
23565
|
-
return this[
|
|
25150
|
+
return this[m3];
|
|
23566
25151
|
}
|
|
23567
25152
|
get path() {
|
|
23568
25153
|
return this[U2];
|
|
@@ -23574,16 +25159,16 @@ var et = class extends Qr {
|
|
|
23574
25159
|
I.open(this[U2], this[tt], this[ie], (t, e) => this[Ht](t, e));
|
|
23575
25160
|
}
|
|
23576
25161
|
[Ht](t, e) {
|
|
23577
|
-
this[Me] && this[tt] === "r+" && t && t.code === "ENOENT" ? (this[tt] = "w", this[at]()) : t ? this[Ut](t) : (this[
|
|
25162
|
+
this[Me] && this[tt] === "r+" && t && t.code === "ENOENT" ? (this[tt] = "w", this[at]()) : t ? this[Ut](t) : (this[m3] = e, this.emit("open", e), this[gt] || this[Ai]());
|
|
23578
25163
|
}
|
|
23579
25164
|
end(t, e) {
|
|
23580
|
-
return t && this.write(t, e), this[te] = true, !this[gt] && !this[Y].length && typeof this[
|
|
25165
|
+
return t && this.write(t, e), this[te] = true, !this[gt] && !this[Y].length && typeof this[m3] == "number" && this[Pt](null, 0), this;
|
|
23581
25166
|
}
|
|
23582
25167
|
write(t, e) {
|
|
23583
|
-
return typeof t == "string" && (t = Buffer.from(t, e)), this[te] ? (this.emit("error", new Error("write() after end()")), false) : this[
|
|
25168
|
+
return typeof t == "string" && (t = Buffer.from(t, e)), this[te] ? (this.emit("error", new Error("write() after end()")), false) : this[m3] === undefined || this[gt] || this[Y].length ? (this[Y].push(t), this[ke] = true, false) : (this[gt] = true, this[ve](t), true);
|
|
23584
25169
|
}
|
|
23585
25170
|
[ve](t) {
|
|
23586
|
-
I.write(this[
|
|
25171
|
+
I.write(this[m3], t, 0, t.length, this[ot], (e, i) => this[Pt](e, i));
|
|
23587
25172
|
}
|
|
23588
25173
|
[Pt](t, e) {
|
|
23589
25174
|
t ? this[Ut](t) : (this[ot] !== undefined && typeof e == "number" && (this[ot] += e), this[Y].length ? this[Ai]() : (this[gt] = false, this[te] && !this[Ni] ? (this[Ni] = true, this[H](), this.emit("finish")) : this[ke] && (this[ke] = false, this.emit("drain"))));
|
|
@@ -23595,13 +25180,13 @@ var et = class extends Qr {
|
|
|
23595
25180
|
this[ve](this[Y].pop());
|
|
23596
25181
|
else {
|
|
23597
25182
|
let t = this[Y];
|
|
23598
|
-
this[Y] = [], Jr(this[
|
|
25183
|
+
this[Y] = [], Jr(this[m3], t, this[ot], (e, i) => this[Pt](e, i));
|
|
23599
25184
|
}
|
|
23600
25185
|
}
|
|
23601
25186
|
[H]() {
|
|
23602
|
-
if (this[ht] && typeof this[
|
|
23603
|
-
let t = this[
|
|
23604
|
-
this[
|
|
25187
|
+
if (this[ht] && typeof this[m3] == "number") {
|
|
25188
|
+
let t = this[m3];
|
|
25189
|
+
this[m3] = undefined, I.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
|
|
23605
25190
|
}
|
|
23606
25191
|
}
|
|
23607
25192
|
};
|
|
@@ -23621,15 +25206,15 @@ var Wt = class extends et {
|
|
|
23621
25206
|
this[Ht](null, t);
|
|
23622
25207
|
}
|
|
23623
25208
|
[H]() {
|
|
23624
|
-
if (this[ht] && typeof this[
|
|
23625
|
-
let t = this[
|
|
23626
|
-
this[
|
|
25209
|
+
if (this[ht] && typeof this[m3] == "number") {
|
|
25210
|
+
let t = this[m3];
|
|
25211
|
+
this[m3] = undefined, I.closeSync(t), this.emit("close");
|
|
23627
25212
|
}
|
|
23628
25213
|
}
|
|
23629
25214
|
[ve](t) {
|
|
23630
25215
|
let e = true;
|
|
23631
25216
|
try {
|
|
23632
|
-
this[Pt](null, I.writeSync(this[
|
|
25217
|
+
this[Pt](null, I.writeSync(this[m3], t, 0, t.length, this[ot])), e = false;
|
|
23633
25218
|
} finally {
|
|
23634
25219
|
if (e)
|
|
23635
25220
|
try {
|
|
@@ -25607,7 +27192,7 @@ var Dr = Symbol("onEntry");
|
|
|
25607
27192
|
var _s = Symbol("checkFs");
|
|
25608
27193
|
var Nr = Symbol("checkFs2");
|
|
25609
27194
|
var Os = Symbol("isReusable");
|
|
25610
|
-
var
|
|
27195
|
+
var P2 = Symbol("makeFs");
|
|
25611
27196
|
var Ts = Symbol("file");
|
|
25612
27197
|
var xs = Symbol("directory");
|
|
25613
27198
|
var gi = Symbol("link");
|
|
@@ -25895,23 +27480,23 @@ var Xt = class extends rt {
|
|
|
25895
27480
|
return;
|
|
25896
27481
|
}
|
|
25897
27482
|
if (h || this[Os](t, a))
|
|
25898
|
-
return this[
|
|
27483
|
+
return this[P2](null, t, i);
|
|
25899
27484
|
if (a.isDirectory()) {
|
|
25900
27485
|
if (t.type === "Directory") {
|
|
25901
|
-
let l = this.chmod && t.mode && (a.mode & 4095) !== t.mode, c = (d) => this[
|
|
27486
|
+
let l = this.chmod && t.mode && (a.mode & 4095) !== t.mode, c = (d) => this[P2](d ?? null, t, i);
|
|
25902
27487
|
return l ? u3.chmod(String(t.absolute), Number(t.mode), c) : c();
|
|
25903
27488
|
}
|
|
25904
27489
|
if (t.absolute !== this.cwd)
|
|
25905
|
-
return u3.rmdir(String(t.absolute), (l) => this[
|
|
27490
|
+
return u3.rmdir(String(t.absolute), (l) => this[P2](l ?? null, t, i));
|
|
25906
27491
|
}
|
|
25907
27492
|
if (t.absolute === this.cwd)
|
|
25908
|
-
return this[
|
|
25909
|
-
mo(String(t.absolute), (l) => this[
|
|
27493
|
+
return this[P2](null, t, i);
|
|
27494
|
+
mo(String(t.absolute), (l) => this[P2](l ?? null, t, i));
|
|
25910
27495
|
});
|
|
25911
27496
|
};
|
|
25912
27497
|
this[Oe] ? n() : r();
|
|
25913
27498
|
}
|
|
25914
|
-
[
|
|
27499
|
+
[P2](t, e, i) {
|
|
25915
27500
|
if (t) {
|
|
25916
27501
|
this[O](t, e), i();
|
|
25917
27502
|
return;
|
|
@@ -25945,8 +27530,8 @@ var ye = (s) => {
|
|
|
25945
27530
|
};
|
|
25946
27531
|
var xe = class extends Xt {
|
|
25947
27532
|
sync = true;
|
|
25948
|
-
[
|
|
25949
|
-
return super[
|
|
27533
|
+
[P2](t, e) {
|
|
27534
|
+
return super[P2](t, e, () => {});
|
|
25950
27535
|
}
|
|
25951
27536
|
[_s](t) {
|
|
25952
27537
|
if (!this[Oe]) {
|
|
@@ -25967,19 +27552,19 @@ var xe = class extends Xt {
|
|
|
25967
27552
|
if (i && (this.keep || this.newer && i.mtime > (t.mtime ?? i.mtime)))
|
|
25968
27553
|
return this[Ls](t);
|
|
25969
27554
|
if (e || this[Os](t, i))
|
|
25970
|
-
return this[
|
|
27555
|
+
return this[P2](null, t);
|
|
25971
27556
|
if (i.isDirectory()) {
|
|
25972
27557
|
if (t.type === "Directory") {
|
|
25973
27558
|
let o = this.chmod && t.mode && (i.mode & 4095) !== t.mode, [h] = o ? ye(() => {
|
|
25974
27559
|
u3.chmodSync(String(t.absolute), Number(t.mode));
|
|
25975
27560
|
}) : [];
|
|
25976
|
-
return this[
|
|
27561
|
+
return this[P2](h, t);
|
|
25977
27562
|
}
|
|
25978
27563
|
let [n] = ye(() => u3.rmdirSync(String(t.absolute)));
|
|
25979
|
-
this[
|
|
27564
|
+
this[P2](n, t);
|
|
25980
27565
|
}
|
|
25981
27566
|
let [r] = t.absolute === this.cwd ? [] : ye(() => po(String(t.absolute)));
|
|
25982
|
-
this[
|
|
27567
|
+
this[P2](r, t);
|
|
25983
27568
|
}
|
|
25984
27569
|
[Ts](t, e) {
|
|
25985
27570
|
let i = typeof t.mode == "number" ? t.mode & 4095 : this.fmode, r = (h) => {
|
|
@@ -26427,8 +28012,8 @@ async function handleFlowsPull(ctx, opts, deps) {
|
|
|
26427
28012
|
ctx.ui.error(validation.error);
|
|
26428
28013
|
return { error: validation.error };
|
|
26429
28014
|
}
|
|
26430
|
-
const destAbs = resolve8(opts.out ??
|
|
26431
|
-
const assetsAbs =
|
|
28015
|
+
const destAbs = resolve8(opts.out ?? join31(".qawolf", opts.env));
|
|
28016
|
+
const assetsAbs = join31(dirname8(destAbs), "assets");
|
|
26432
28017
|
await resolvedDeps.fs.mkdir(assetsAbs, { recursive: true });
|
|
26433
28018
|
const yes = opts.yes ?? false;
|
|
26434
28019
|
let archive;
|
|
@@ -27303,15 +28888,15 @@ async function defaultRunWebFlowDeps(envDir = process.cwd(), signals) {
|
|
|
27303
28888
|
|
|
27304
28889
|
// src/domains/runtimeEnv/prepareRunDir.ts
|
|
27305
28890
|
import { createHash as createHash4 } from "node:crypto";
|
|
27306
|
-
import { join as
|
|
28891
|
+
import { join as join39, resolve as resolve9 } from "node:path";
|
|
27307
28892
|
|
|
27308
28893
|
// src/domains/runtimeEnv/execSubpathImports.ts
|
|
27309
|
-
import { join as
|
|
28894
|
+
import { join as join32 } from "node:path";
|
|
27310
28895
|
var flowSubpathImports = { "#playwright": "playwright" };
|
|
27311
28896
|
var packageJsonSchema = looseObject({ imports: record(string2(), unknown()).catch({}) }).catch({ imports: {} });
|
|
27312
28897
|
async function writeExecSubpathImports(args) {
|
|
27313
28898
|
const { execDir, fs } = args;
|
|
27314
|
-
const pkgPath =
|
|
28899
|
+
const pkgPath = join32(execDir, "package.json");
|
|
27315
28900
|
const pkg = packageJsonSchema.parse(await readPackageJson(pkgPath, fs));
|
|
27316
28901
|
const merged = { ...pkg, imports: { ...pkg.imports, ...flowSubpathImports } };
|
|
27317
28902
|
await fs.writeFile(pkgPath, JSON.stringify(merged, undefined, 2));
|
|
@@ -27333,10 +28918,10 @@ async function readPackageJson(pkgPath, fs) {
|
|
|
27333
28918
|
}
|
|
27334
28919
|
|
|
27335
28920
|
// src/domains/runtimeEnv/innerHop.ts
|
|
27336
|
-
import { join as
|
|
28921
|
+
import { join as join34 } from "node:path";
|
|
27337
28922
|
|
|
27338
28923
|
// src/domains/runtimeEnv/linkPinnedPackages.ts
|
|
27339
|
-
import { dirname as dirname9, join as
|
|
28924
|
+
import { dirname as dirname9, join as join33 } from "node:path";
|
|
27340
28925
|
|
|
27341
28926
|
// src/domains/runtimeEnv/symlinkDir.ts
|
|
27342
28927
|
import { symlink } from "node:fs/promises";
|
|
@@ -27348,16 +28933,16 @@ async function createDirSymlink(source, target) {
|
|
|
27348
28933
|
// src/domains/runtimeEnv/linkPinnedPackages.ts
|
|
27349
28934
|
async function linkPinnedPackages(args) {
|
|
27350
28935
|
const { depsRoot, nodeModulesDir, fs } = args;
|
|
27351
|
-
const managedModules =
|
|
28936
|
+
const managedModules = join33(depsRoot, "node_modules");
|
|
27352
28937
|
await fs.mkdir(nodeModulesDir, { recursive: true });
|
|
27353
28938
|
for (const { name } of pinnedPackages) {
|
|
27354
28939
|
const segments = name.split("/");
|
|
27355
|
-
const target =
|
|
28940
|
+
const target = join33(nodeModulesDir, ...segments);
|
|
27356
28941
|
if (segments.length > 1) {
|
|
27357
28942
|
await fs.mkdir(dirname9(target), { recursive: true });
|
|
27358
28943
|
}
|
|
27359
28944
|
await fs.rm(target, { recursive: true, force: true });
|
|
27360
|
-
await createDirSymlink(
|
|
28945
|
+
await createDirSymlink(join33(managedModules, ...segments), target);
|
|
27361
28946
|
}
|
|
27362
28947
|
}
|
|
27363
28948
|
|
|
@@ -27366,18 +28951,18 @@ async function populateInnerHop(args) {
|
|
|
27366
28951
|
const { depsRoot, execDir, fs } = args;
|
|
27367
28952
|
await linkPinnedPackages({
|
|
27368
28953
|
depsRoot,
|
|
27369
|
-
nodeModulesDir:
|
|
28954
|
+
nodeModulesDir: join34(execDir, "node_modules"),
|
|
27370
28955
|
fs
|
|
27371
28956
|
});
|
|
27372
28957
|
}
|
|
27373
28958
|
|
|
27374
28959
|
// src/domains/runtimeEnv/outerHop.ts
|
|
27375
28960
|
import { lstat as lstat2 } from "node:fs/promises";
|
|
27376
|
-
import { dirname as dirname11, join as
|
|
28961
|
+
import { dirname as dirname11, join as join36 } from "node:path";
|
|
27377
28962
|
|
|
27378
28963
|
// src/domains/runtimeEnv/carryOverPackages.ts
|
|
27379
28964
|
import { lstat } from "node:fs/promises";
|
|
27380
|
-
import { dirname as dirname10, join as
|
|
28965
|
+
import { dirname as dirname10, join as join35 } from "node:path";
|
|
27381
28966
|
|
|
27382
28967
|
// src/domains/runtimeEnv/carryOverPlan.ts
|
|
27383
28968
|
var executorPackageNames = new Set(pinnedPackages.map((p) => p.name));
|
|
@@ -27399,12 +28984,12 @@ async function carryOverPackages(args) {
|
|
|
27399
28984
|
const linked = [];
|
|
27400
28985
|
for (const name of names) {
|
|
27401
28986
|
const segments = name.split("/");
|
|
27402
|
-
const target =
|
|
28987
|
+
const target = join35(nodeModulesDir, ...segments);
|
|
27403
28988
|
if (await pathOccupied(target))
|
|
27404
28989
|
continue;
|
|
27405
28990
|
if (segments.length > 1)
|
|
27406
28991
|
await fs.mkdir(dirname10(target), { recursive: true });
|
|
27407
|
-
await createDirSymlink(
|
|
28992
|
+
await createDirSymlink(join35(projectModulesDir, ...segments), target);
|
|
27408
28993
|
linked.push(name);
|
|
27409
28994
|
}
|
|
27410
28995
|
return linked;
|
|
@@ -27427,18 +29012,18 @@ async function listPackageNames(dir, fs) {
|
|
|
27427
29012
|
const names = [];
|
|
27428
29013
|
for (const entry of entries) {
|
|
27429
29014
|
if (!entry.startsWith("@")) {
|
|
27430
|
-
if (fs.existsSync(
|
|
29015
|
+
if (fs.existsSync(join35(dir, entry, "package.json")))
|
|
27431
29016
|
names.push(entry);
|
|
27432
29017
|
continue;
|
|
27433
29018
|
}
|
|
27434
29019
|
let scoped;
|
|
27435
29020
|
try {
|
|
27436
|
-
scoped = await fs.readdir(
|
|
29021
|
+
scoped = await fs.readdir(join35(dir, entry));
|
|
27437
29022
|
} catch {
|
|
27438
29023
|
continue;
|
|
27439
29024
|
}
|
|
27440
29025
|
for (const inner of scoped) {
|
|
27441
|
-
if (fs.existsSync(
|
|
29026
|
+
if (fs.existsSync(join35(dir, entry, inner, "package.json"))) {
|
|
27442
29027
|
names.push(`${entry}/${inner}`);
|
|
27443
29028
|
}
|
|
27444
29029
|
}
|
|
@@ -27456,7 +29041,7 @@ async function populateOuterHop(args) {
|
|
|
27456
29041
|
const depNames = Object.keys(installableDeps);
|
|
27457
29042
|
const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs);
|
|
27458
29043
|
if (satisfying !== undefined) {
|
|
27459
|
-
await createDirSymlink(satisfying,
|
|
29044
|
+
await createDirSymlink(satisfying, join36(runDir, "node_modules"));
|
|
27460
29045
|
return { mode: "symlink", nodeModulesDir: satisfying };
|
|
27461
29046
|
}
|
|
27462
29047
|
if (depNames.length === 0)
|
|
@@ -27465,12 +29050,12 @@ async function populateOuterHop(args) {
|
|
|
27465
29050
|
await installOuterDeps(runDir, installableDeps, fs, args.install ?? spawnNpmInstall);
|
|
27466
29051
|
await linkPinnedPackages({
|
|
27467
29052
|
depsRoot: args.depsRoot,
|
|
27468
|
-
nodeModulesDir:
|
|
29053
|
+
nodeModulesDir: join36(runDir, "node_modules"),
|
|
27469
29054
|
fs
|
|
27470
29055
|
});
|
|
27471
29056
|
const carriedOver = await carryOverPackages({
|
|
27472
|
-
projectModulesDir:
|
|
27473
|
-
nodeModulesDir:
|
|
29057
|
+
projectModulesDir: join36(projectDir, "node_modules"),
|
|
29058
|
+
nodeModulesDir: join36(runDir, "node_modules"),
|
|
27474
29059
|
fs
|
|
27475
29060
|
});
|
|
27476
29061
|
return { mode: "install", depCount: depNames.length, rejected, carriedOver };
|
|
@@ -27479,10 +29064,10 @@ async function findSatisfyingNodeModulesDir(startDir, depNames, fs) {
|
|
|
27479
29064
|
const rejected = [];
|
|
27480
29065
|
let dir = startDir;
|
|
27481
29066
|
while (true) {
|
|
27482
|
-
const candidate =
|
|
29067
|
+
const candidate = join36(dir, "node_modules");
|
|
27483
29068
|
const stats = await lstatSafe(candidate);
|
|
27484
29069
|
if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
27485
|
-
const missing = depNames.filter((name) => !fs.existsSync(
|
|
29070
|
+
const missing = depNames.filter((name) => !fs.existsSync(join36(candidate, name, "package.json")));
|
|
27486
29071
|
if (missing.length === 0)
|
|
27487
29072
|
return { satisfying: candidate, rejected };
|
|
27488
29073
|
rejected.push({ dir: candidate, missing });
|
|
@@ -27503,7 +29088,7 @@ async function lstatSafe(path) {
|
|
|
27503
29088
|
async function readInstallableDeps(projectDir, fs) {
|
|
27504
29089
|
let content;
|
|
27505
29090
|
try {
|
|
27506
|
-
content = await fs.readFile(
|
|
29091
|
+
content = await fs.readFile(join36(projectDir, "package.json"));
|
|
27507
29092
|
} catch {
|
|
27508
29093
|
return {};
|
|
27509
29094
|
}
|
|
@@ -27521,8 +29106,8 @@ async function readInstallableDeps(projectDir, fs) {
|
|
|
27521
29106
|
return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames2.has(name)).filter((entry) => typeof entry[1] === "string"));
|
|
27522
29107
|
}
|
|
27523
29108
|
async function installOuterDeps(runDir, deps, fs, install) {
|
|
27524
|
-
await fs.writeFile(
|
|
27525
|
-
await fs.writeFile(
|
|
29109
|
+
await fs.writeFile(join36(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
|
|
29110
|
+
await fs.writeFile(join36(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
|
|
27526
29111
|
`);
|
|
27527
29112
|
const result = await install(runDir);
|
|
27528
29113
|
if (result.exitCode !== 0) {
|
|
@@ -27532,14 +29117,14 @@ async function installOuterDeps(runDir, deps, fs, install) {
|
|
|
27532
29117
|
|
|
27533
29118
|
// src/domains/runtimeEnv/stageFlowFiles.ts
|
|
27534
29119
|
import { createHash as createHash3 } from "node:crypto";
|
|
27535
|
-
import { basename as basename6, dirname as dirname12, join as
|
|
29120
|
+
import { basename as basename6, dirname as dirname12, join as join38, sep as sep4 } from "node:path";
|
|
27536
29121
|
|
|
27537
29122
|
// src/shell/copyDir.ts
|
|
27538
29123
|
import { cp, readdir as readdir2 } from "node:fs/promises";
|
|
27539
|
-
import { basename as basename5, join as
|
|
29124
|
+
import { basename as basename5, join as join37 } from "node:path";
|
|
27540
29125
|
async function copyDirExcluding(source, destination, excludedNames) {
|
|
27541
29126
|
const entries = await readdir2(source, { withFileTypes: true });
|
|
27542
|
-
await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(
|
|
29127
|
+
await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join37(source, entry.name), join37(destination, entry.name), {
|
|
27543
29128
|
recursive: true,
|
|
27544
29129
|
filter: (path) => !excludedNames.has(basename5(path))
|
|
27545
29130
|
})));
|
|
@@ -27556,14 +29141,14 @@ async function stageFlowFiles(args) {
|
|
|
27556
29141
|
if (files.length > 1) {
|
|
27557
29142
|
return Promise.all(files.map((f) => stageOneFile(f, execDir, fs)));
|
|
27558
29143
|
}
|
|
27559
|
-
await Promise.all(files.map((f) => fs.copyFile(f,
|
|
27560
|
-
return files.map((f) =>
|
|
29144
|
+
await Promise.all(files.map((f) => fs.copyFile(f, join38(execDir, basename6(f)))));
|
|
29145
|
+
return files.map((f) => join38(execDir, basename6(f)));
|
|
27561
29146
|
}
|
|
27562
29147
|
async function stageOneFile(file, execDir, fs) {
|
|
27563
29148
|
const dirHash = createHash3("sha256").update(dirname12(file)).digest("hex").slice(0, 8);
|
|
27564
|
-
const subDir =
|
|
29149
|
+
const subDir = join38(execDir, dirHash);
|
|
27565
29150
|
await fs.mkdir(subDir, { recursive: true });
|
|
27566
|
-
const dest =
|
|
29151
|
+
const dest = join38(subDir, basename6(file));
|
|
27567
29152
|
await fs.copyFile(file, dest);
|
|
27568
29153
|
return dest;
|
|
27569
29154
|
}
|
|
@@ -27571,7 +29156,7 @@ function remapPath(file, projectDir, execDir) {
|
|
|
27571
29156
|
if (file === projectDir)
|
|
27572
29157
|
return execDir;
|
|
27573
29158
|
if (file.startsWith(projectDir + sep4)) {
|
|
27574
|
-
return
|
|
29159
|
+
return join38(execDir, file.slice(projectDir.length + 1));
|
|
27575
29160
|
}
|
|
27576
29161
|
return;
|
|
27577
29162
|
}
|
|
@@ -27581,10 +29166,10 @@ async function prepareRunDir(args) {
|
|
|
27581
29166
|
const { files, projectDir, depsRoot, runRoot } = args;
|
|
27582
29167
|
const fs = args.fs ?? makeDefaultFs();
|
|
27583
29168
|
const runId = buildRunId(projectDir, files);
|
|
27584
|
-
const runDir =
|
|
29169
|
+
const runDir = join39(runRoot, runId);
|
|
27585
29170
|
await fs.rm(runDir, { recursive: true, force: true });
|
|
27586
29171
|
await fs.mkdir(runDir, { recursive: true });
|
|
27587
|
-
const execDir =
|
|
29172
|
+
const execDir = join39(runDir, "exec");
|
|
27588
29173
|
await fs.mkdir(execDir, { recursive: true });
|
|
27589
29174
|
await populateInnerHop({ depsRoot, execDir, fs });
|
|
27590
29175
|
const stagedFiles = await stageFlowFiles({ files, projectDir, execDir, fs });
|
|
@@ -27615,17 +29200,17 @@ function buildRunId(projectDir, files) {
|
|
|
27615
29200
|
}
|
|
27616
29201
|
|
|
27617
29202
|
// src/shell/testkit.ts
|
|
27618
|
-
import { join as
|
|
29203
|
+
import { join as join40 } from "node:path";
|
|
27619
29204
|
function notAvailableLocally(name) {
|
|
27620
29205
|
throw new Error(runnerMessages.notAvailableLocally(name));
|
|
27621
29206
|
}
|
|
27622
29207
|
async function loadSdkDeps(cwd) {
|
|
27623
|
-
const testkitDir =
|
|
29208
|
+
const testkitDir = join40(cwd, "node_modules", "@qawolf", "testkit");
|
|
27624
29209
|
try {
|
|
27625
29210
|
const clientMod = await importFromPath(resolveFromEnvDir(cwd, "@qawolf/testkit/client"));
|
|
27626
29211
|
if (typeof clientMod.createTestkitClient !== "function")
|
|
27627
29212
|
throw new Error("createTestkitClient is not a function");
|
|
27628
|
-
const scopeMod = await importFromPath(
|
|
29213
|
+
const scopeMod = await importFromPath(join40(testkitDir, "dist", "clientScope.js"));
|
|
27629
29214
|
if (typeof scopeMod.configureTestkitClient !== "function")
|
|
27630
29215
|
throw new Error("configureTestkitClient is not a function");
|
|
27631
29216
|
return {
|
|
@@ -27683,9 +29268,9 @@ async function registerLazyEmailsClient(getInbox, cwd, deps) {
|
|
|
27683
29268
|
}
|
|
27684
29269
|
|
|
27685
29270
|
// src/commands/flows/envVarDeps.ts
|
|
27686
|
-
import { join as
|
|
29271
|
+
import { join as join41 } from "node:path";
|
|
27687
29272
|
function envFilePath(envDir) {
|
|
27688
|
-
return
|
|
29273
|
+
return join41(envDir, ".env");
|
|
27689
29274
|
}
|
|
27690
29275
|
async function readEnv(envDir, fs) {
|
|
27691
29276
|
try {
|
|
@@ -27809,7 +29394,7 @@ async function selectRunByTag(args) {
|
|
|
27809
29394
|
}
|
|
27810
29395
|
|
|
27811
29396
|
// src/commands/flows/runStagedFlows.ts
|
|
27812
|
-
import { join as
|
|
29397
|
+
import { join as join45 } from "node:path";
|
|
27813
29398
|
|
|
27814
29399
|
// src/shell/appium/createAppiumServer.ts
|
|
27815
29400
|
import { existsSync as existsSync2 } from "node:fs";
|
|
@@ -29115,14 +30700,14 @@ async function runWebFlow({
|
|
|
29115
30700
|
|
|
29116
30701
|
// src/shell/embeddedWorkerCli.ts
|
|
29117
30702
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
29118
|
-
import { basename as basename7, join as
|
|
30703
|
+
import { basename as basename7, join as join42 } from "node:path";
|
|
29119
30704
|
var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
|
|
29120
30705
|
function extractEmbeddedWorkerCli() {
|
|
29121
30706
|
const assetPath = process.env[embeddedCliPathEnv];
|
|
29122
30707
|
if (assetPath === undefined || assetPath === "")
|
|
29123
30708
|
return;
|
|
29124
|
-
const dir =
|
|
29125
|
-
const dest =
|
|
30709
|
+
const dir = join42(getDataDir(), "worker");
|
|
30710
|
+
const dest = join42(dir, basename7(assetPath));
|
|
29126
30711
|
if (existsSync3(dest))
|
|
29127
30712
|
return dest;
|
|
29128
30713
|
mkdirSync3(dir, { recursive: true });
|
|
@@ -29360,7 +30945,7 @@ ${styleText3("dim", "─".repeat(width))}
|
|
|
29360
30945
|
};
|
|
29361
30946
|
}
|
|
29362
30947
|
// src/shell/reporter/createJUnitReporter.ts
|
|
29363
|
-
import { join as
|
|
30948
|
+
import { join as join43 } from "node:path";
|
|
29364
30949
|
|
|
29365
30950
|
// src/shell/reporter/junitXml.ts
|
|
29366
30951
|
function escapeXml(str) {
|
|
@@ -29399,7 +30984,7 @@ ${suites}`;
|
|
|
29399
30984
|
|
|
29400
30985
|
// src/shell/reporter/createJUnitReporter.ts
|
|
29401
30986
|
function resolveJUnitOutputPath(junit, outputDir) {
|
|
29402
|
-
return typeof junit === "string" && junit.trim() !== "" ? junit :
|
|
30987
|
+
return typeof junit === "string" && junit.trim() !== "" ? junit : join43(outputDir, "junit-report.xml");
|
|
29403
30988
|
}
|
|
29404
30989
|
function createJUnitReporter(deps) {
|
|
29405
30990
|
const flows = [];
|
|
@@ -29490,11 +31075,11 @@ function buildFlowsRunDeps(args) {
|
|
|
29490
31075
|
|
|
29491
31076
|
// src/commands/flows/loadEnvFile.ts
|
|
29492
31077
|
import { readFile } from "node:fs/promises";
|
|
29493
|
-
import { join as
|
|
31078
|
+
import { join as join44 } from "node:path";
|
|
29494
31079
|
async function loadEnvFile(envDir) {
|
|
29495
31080
|
let content;
|
|
29496
31081
|
try {
|
|
29497
|
-
content = await readFile(
|
|
31082
|
+
content = await readFile(join44(envDir, ".env"), "utf8");
|
|
29498
31083
|
} catch (err) {
|
|
29499
31084
|
if (isNoEntError(err))
|
|
29500
31085
|
return;
|
|
@@ -29539,7 +31124,7 @@ async function runStagedFlows(args) {
|
|
|
29539
31124
|
for (const candidate of staged.outerHop.rejected) {
|
|
29540
31125
|
log.debug(runnerMessages.outerHopCandidateRejected(candidate.dir, candidate.missing));
|
|
29541
31126
|
}
|
|
29542
|
-
const ownModules = projectDir === undefined ? undefined :
|
|
31127
|
+
const ownModules = projectDir === undefined ? undefined : join45(projectDir, "node_modules");
|
|
29543
31128
|
const own = staged.outerHop.rejected.find((c) => c.dir === ownModules);
|
|
29544
31129
|
if (own !== undefined) {
|
|
29545
31130
|
ctx.ui.warn(runnerMessages.outerHopFallbackNotice(own.dir, own.missing));
|
|
@@ -29574,7 +31159,7 @@ async function runStagedFlows(args) {
|
|
|
29574
31159
|
}
|
|
29575
31160
|
|
|
29576
31161
|
// src/commands/flows/runDefaults.ts
|
|
29577
|
-
function
|
|
31162
|
+
function makeDefaultDeps3(fs) {
|
|
29578
31163
|
return {
|
|
29579
31164
|
expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs),
|
|
29580
31165
|
resolveDepsRoot: (args) => resolveDepsRoot({ ...args, fs }),
|
|
@@ -29586,7 +31171,7 @@ function makeDefaultDeps2(fs) {
|
|
|
29586
31171
|
};
|
|
29587
31172
|
}
|
|
29588
31173
|
async function handleFlowsRun(ctx, pattern, flags, deps, selectors = { tags: [] }, allEnvs = false) {
|
|
29589
|
-
const resolvedDeps = deps ??
|
|
31174
|
+
const resolvedDeps = deps ?? makeDefaultDeps3(ctx.fs);
|
|
29590
31175
|
const cwd = process.cwd();
|
|
29591
31176
|
const expandedFiles = await resolvedDeps.expandPatterns(buildPatternArgs(pattern), cwd, ctx.log("flows"));
|
|
29592
31177
|
ctx.log("flows").debug(`discovered ${pluralize(expandedFiles.length, "flow")}`);
|
|
@@ -29635,7 +31220,7 @@ async function chooseEnv(ctx, files, allEnvs) {
|
|
|
29635
31220
|
}
|
|
29636
31221
|
|
|
29637
31222
|
// src/commands/flows/hybridRunDefaults.ts
|
|
29638
|
-
import { join as
|
|
31223
|
+
import { join as join46, resolve as resolve10 } from "node:path";
|
|
29639
31224
|
|
|
29640
31225
|
// src/core/selectFlowFiles.ts
|
|
29641
31226
|
function selectFlowFiles(args) {
|
|
@@ -29740,7 +31325,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, options = {}) {
|
|
|
29740
31325
|
if (validation !== "ok") {
|
|
29741
31326
|
return { error: validation.error, exitCode: 2 };
|
|
29742
31327
|
}
|
|
29743
|
-
const envDir = resolve10(
|
|
31328
|
+
const envDir = resolve10(join46(".qawolf", flags.env));
|
|
29744
31329
|
const patternArgs = buildPatternArgs(pattern);
|
|
29745
31330
|
const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
|
|
29746
31331
|
let files = await globFlows();
|
|
@@ -29936,12 +31521,12 @@ function registerFlowsCommand(program, signals, deps = { withResolvedEnv }) {
|
|
|
29936
31521
|
}
|
|
29937
31522
|
|
|
29938
31523
|
// src/domains/init/init.ts
|
|
29939
|
-
import { dirname as dirname14, join as
|
|
31524
|
+
import { dirname as dirname14, join as join48, relative as relative9 } from "node:path";
|
|
29940
31525
|
|
|
29941
31526
|
// src/domains/init/ensurePackageJson.ts
|
|
29942
|
-
import { join as
|
|
31527
|
+
import { join as join47 } from "node:path";
|
|
29943
31528
|
async function ensurePackageJson(ctx, yes, deps) {
|
|
29944
|
-
const pkgPath =
|
|
31529
|
+
const pkgPath = join47(deps.cwd, "package.json");
|
|
29945
31530
|
if (!await deps.fs.pathExists(pkgPath)) {
|
|
29946
31531
|
const confirmed = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
|
|
29947
31532
|
if (!confirmed.ok || !confirmed.value) {
|
|
@@ -30067,9 +31652,9 @@ function makeDefaultInitDeps() {
|
|
|
30067
31652
|
async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
|
|
30068
31653
|
ctx.ui.gap();
|
|
30069
31654
|
ctx.ui.intro(initMessages.title);
|
|
30070
|
-
const configPath =
|
|
30071
|
-
const flowPath =
|
|
30072
|
-
const gitignorePath =
|
|
31655
|
+
const configPath = join48(deps.cwd, "qawolf.config.ts");
|
|
31656
|
+
const flowPath = join48(deps.cwd, "src", "flows", "example.flow.ts");
|
|
31657
|
+
const gitignorePath = join48(deps.cwd, ".qawolf", ".gitignore");
|
|
30073
31658
|
await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
|
|
30074
31659
|
await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
|
|
30075
31660
|
await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
|
|
@@ -30107,7 +31692,7 @@ Examples:
|
|
|
30107
31692
|
import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
|
|
30108
31693
|
|
|
30109
31694
|
// src/domains/install/android/avd.ts
|
|
30110
|
-
import { join as
|
|
31695
|
+
import { join as join49 } from "node:path";
|
|
30111
31696
|
async function installAvds(ctx, specs, deps) {
|
|
30112
31697
|
const sdkManagerPath = sdkManagerBin(deps.androidHome, deps.platform);
|
|
30113
31698
|
const avdManagerPath = avdManagerBin(deps.androidHome, deps.platform);
|
|
@@ -30117,7 +31702,7 @@ async function installAvds(ctx, specs, deps) {
|
|
|
30117
31702
|
if (versionResult.exitCode !== 0) {
|
|
30118
31703
|
throw new Error(installMessages.android.sdkmanagerNotFound(sdkManagerPath));
|
|
30119
31704
|
}
|
|
30120
|
-
const licenseFile =
|
|
31705
|
+
const licenseFile = join49(deps.androidHome, "licenses", "android-sdk-license");
|
|
30121
31706
|
if (deps.checkExists(licenseFile)) {
|
|
30122
31707
|
ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
|
|
30123
31708
|
} else {
|
|
@@ -30132,7 +31717,7 @@ async function installAvds(ctx, specs, deps) {
|
|
|
30132
31717
|
for (const spec of specs) {
|
|
30133
31718
|
if (installedImages.has(spec.systemImage))
|
|
30134
31719
|
continue;
|
|
30135
|
-
const systemImg =
|
|
31720
|
+
const systemImg = join49(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
|
|
30136
31721
|
if (deps.checkExists(systemImg)) {
|
|
30137
31722
|
ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
|
|
30138
31723
|
installedImages.add(spec.systemImage);
|
|
@@ -30430,42 +32015,6 @@ Examples:
|
|
|
30430
32015
|
});
|
|
30431
32016
|
}
|
|
30432
32017
|
|
|
30433
|
-
// src/core/publicApi/flagKind.ts
|
|
30434
|
-
function flagKind(schema) {
|
|
30435
|
-
if (schema.type === "string")
|
|
30436
|
-
return { ok: true, kind: "string" };
|
|
30437
|
-
if (schema.type === "number" || schema.type === "integer") {
|
|
30438
|
-
return { ok: true, kind: "number" };
|
|
30439
|
-
}
|
|
30440
|
-
if (schema.type === "boolean")
|
|
30441
|
-
return { ok: true, kind: "boolean" };
|
|
30442
|
-
if (schema.type === "array") {
|
|
30443
|
-
if (schema.items?.type !== "string") {
|
|
30444
|
-
return {
|
|
30445
|
-
ok: false,
|
|
30446
|
-
reason: "only string arrays can be expressed as flags"
|
|
30447
|
-
};
|
|
30448
|
-
}
|
|
30449
|
-
return { ok: true, kind: "string-array" };
|
|
30450
|
-
}
|
|
30451
|
-
if (schema.type === "object") {
|
|
30452
|
-
if (schema.properties) {
|
|
30453
|
-
return {
|
|
30454
|
-
ok: false,
|
|
30455
|
-
reason: "nested objects cannot be expressed as flags"
|
|
30456
|
-
};
|
|
30457
|
-
}
|
|
30458
|
-
if (typeof schema.additionalProperties === "object" && schema.additionalProperties.type === "string") {
|
|
30459
|
-
return { ok: true, kind: "key-value-record" };
|
|
30460
|
-
}
|
|
30461
|
-
return { ok: false, reason: "nested objects cannot be expressed as flags" };
|
|
30462
|
-
}
|
|
30463
|
-
return {
|
|
30464
|
-
ok: false,
|
|
30465
|
-
reason: `type ${schema.type ?? "unknown"} cannot be expressed as a flag`
|
|
30466
|
-
};
|
|
30467
|
-
}
|
|
30468
|
-
|
|
30469
32018
|
// src/core/publicApi/flagNames.ts
|
|
30470
32019
|
var kebabCase = (segment) => segment.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
|
|
30471
32020
|
var capitalize = (segment) => `${segment.slice(0, 1).toUpperCase()}${segment.slice(1)}`;
|
|
@@ -30484,108 +32033,6 @@ function flagUsage(path, kind) {
|
|
|
30484
32033
|
return `${name} <value>`;
|
|
30485
32034
|
}
|
|
30486
32035
|
|
|
30487
|
-
// src/core/publicApi/objectShape.ts
|
|
30488
|
-
var asObjectShape = (schema) => schema.type === "object" && schema.properties ? { properties: schema.properties, required: new Set(schema.required) } : undefined;
|
|
30489
|
-
function mergeIntersection(members) {
|
|
30490
|
-
const shape = { properties: {}, required: new Set };
|
|
30491
|
-
for (const member of members) {
|
|
30492
|
-
const memberShape = asObjectShape(member);
|
|
30493
|
-
if (!memberShape) {
|
|
30494
|
-
return {
|
|
30495
|
-
ok: false,
|
|
30496
|
-
field: "",
|
|
30497
|
-
reason: "intersection members must be object schemas"
|
|
30498
|
-
};
|
|
30499
|
-
}
|
|
30500
|
-
for (const [field, fieldSchema] of Object.entries(memberShape.properties)) {
|
|
30501
|
-
if (field in shape.properties) {
|
|
30502
|
-
return {
|
|
30503
|
-
ok: false,
|
|
30504
|
-
field,
|
|
30505
|
-
reason: "field appears in multiple intersection members"
|
|
30506
|
-
};
|
|
30507
|
-
}
|
|
30508
|
-
shape.properties[field] = fieldSchema;
|
|
30509
|
-
if (memberShape.required.has(field))
|
|
30510
|
-
shape.required.add(field);
|
|
30511
|
-
}
|
|
30512
|
-
}
|
|
30513
|
-
return { ok: true, shape };
|
|
30514
|
-
}
|
|
30515
|
-
function mergeUnion(branches) {
|
|
30516
|
-
const shapes = [];
|
|
30517
|
-
for (const branch of branches) {
|
|
30518
|
-
const branchShape = asObjectShape(branch);
|
|
30519
|
-
if (!branchShape) {
|
|
30520
|
-
return {
|
|
30521
|
-
ok: false,
|
|
30522
|
-
field: "",
|
|
30523
|
-
reason: "union branches must be object schemas"
|
|
30524
|
-
};
|
|
30525
|
-
}
|
|
30526
|
-
shapes.push(branchShape);
|
|
30527
|
-
}
|
|
30528
|
-
const shape = { properties: {}, required: new Set };
|
|
30529
|
-
for (const branchShape of shapes) {
|
|
30530
|
-
for (const [field, fieldSchema] of Object.entries(branchShape.properties)) {
|
|
30531
|
-
const existing = shape.properties[field];
|
|
30532
|
-
if (!existing) {
|
|
30533
|
-
shape.properties[field] = { ...fieldSchema };
|
|
30534
|
-
continue;
|
|
30535
|
-
}
|
|
30536
|
-
const kinds = [flagKind(existing), flagKind(fieldSchema)];
|
|
30537
|
-
if (kinds[0]?.ok && kinds[1]?.ok) {
|
|
30538
|
-
if (kinds[0].kind !== kinds[1].kind) {
|
|
30539
|
-
return {
|
|
30540
|
-
ok: false,
|
|
30541
|
-
field,
|
|
30542
|
-
reason: "field has conflicting types across union branches"
|
|
30543
|
-
};
|
|
30544
|
-
}
|
|
30545
|
-
} else if (JSON.stringify({ ...existing, description: undefined }) !== JSON.stringify({ ...fieldSchema, description: undefined })) {
|
|
30546
|
-
return {
|
|
30547
|
-
ok: false,
|
|
30548
|
-
field,
|
|
30549
|
-
reason: "field has conflicting object schemas across union branches"
|
|
30550
|
-
};
|
|
30551
|
-
}
|
|
30552
|
-
if (!existing.description && fieldSchema.description) {
|
|
30553
|
-
existing.description = fieldSchema.description;
|
|
30554
|
-
}
|
|
30555
|
-
}
|
|
30556
|
-
}
|
|
30557
|
-
const discriminator = Object.keys(shape.properties).find((field) => {
|
|
30558
|
-
const values = shapes.map((branch) => branch.properties[field]?.const);
|
|
30559
|
-
return values.every((value) => typeof value === "string") && new Set(values).size === shapes.length && shapes.every((branch) => branch.required.has(field));
|
|
30560
|
-
});
|
|
30561
|
-
for (const [field, fieldSchema] of Object.entries(shape.properties)) {
|
|
30562
|
-
const occurrences = shapes.filter((branch) => (field in branch.properties));
|
|
30563
|
-
if (occurrences.length === shapes.length && occurrences.every((branch) => branch.required.has(field))) {
|
|
30564
|
-
shape.required.add(field);
|
|
30565
|
-
}
|
|
30566
|
-
if (field === discriminator) {
|
|
30567
|
-
const values = shapes.map((branch) => branch.properties[field]?.const);
|
|
30568
|
-
fieldSchema.description = `One of: ${values.join(", ")}`;
|
|
30569
|
-
}
|
|
30570
|
-
}
|
|
30571
|
-
return { ok: true, shape };
|
|
30572
|
-
}
|
|
30573
|
-
function toObjectShape(schema) {
|
|
30574
|
-
if (schema.allOf)
|
|
30575
|
-
return mergeIntersection(schema.allOf);
|
|
30576
|
-
const branches = schema.oneOf ?? schema.anyOf;
|
|
30577
|
-
if (branches)
|
|
30578
|
-
return mergeUnion(branches);
|
|
30579
|
-
const shape = asObjectShape(schema);
|
|
30580
|
-
if (shape)
|
|
30581
|
-
return { ok: true, shape };
|
|
30582
|
-
return {
|
|
30583
|
-
ok: false,
|
|
30584
|
-
field: "",
|
|
30585
|
-
reason: "contract input must be an object schema"
|
|
30586
|
-
};
|
|
30587
|
-
}
|
|
30588
|
-
|
|
30589
32036
|
// src/core/publicApi/flagSpecs.ts
|
|
30590
32037
|
function describeFlag(schema) {
|
|
30591
32038
|
const description = schema.description ?? "";
|
|
@@ -31039,7 +32486,7 @@ function describeFailure(failureReason) {
|
|
|
31039
32486
|
}
|
|
31040
32487
|
|
|
31041
32488
|
// src/shell/interactiveRunner/collectRunFiles.ts
|
|
31042
|
-
import { join as
|
|
32489
|
+
import { join as join51 } from "node:path";
|
|
31043
32490
|
|
|
31044
32491
|
// src/core/interactiveRunner/getImports.ts
|
|
31045
32492
|
function getImports(options) {
|
|
@@ -31078,7 +32525,7 @@ function isPathAlias(importPath, paths) {
|
|
|
31078
32525
|
}
|
|
31079
32526
|
|
|
31080
32527
|
// src/core/interactiveRunner/resolveImportPath.ts
|
|
31081
|
-
import { dirname as dirname15, join as
|
|
32528
|
+
import { dirname as dirname15, join as join50, normalize as normalize3 } from "node:path/posix";
|
|
31082
32529
|
|
|
31083
32530
|
// src/core/interactiveRunner/tsconfigPaths.ts
|
|
31084
32531
|
function isRecord4(value) {
|
|
@@ -31131,7 +32578,7 @@ function candidatesForExplicitExtension(options) {
|
|
|
31131
32578
|
}
|
|
31132
32579
|
function resolveImportPath(options) {
|
|
31133
32580
|
const aliasTarget = resolvePathAlias(options.importPath, options.tsconfigPaths);
|
|
31134
|
-
const resolvedPath = aliasTarget === undefined ?
|
|
32581
|
+
const resolvedPath = aliasTarget === undefined ? normalize3(join50(dirname15(options.importingFilePath), options.importPath)) : normalize3(aliasTarget);
|
|
31135
32582
|
const hasExplicitExtension = supportedExtensions.some((extension) => options.importPath.endsWith(extension));
|
|
31136
32583
|
const candidates = hasExplicitExtension ? candidatesForExplicitExtension({
|
|
31137
32584
|
importPath: options.importPath,
|
|
@@ -31155,7 +32602,7 @@ function resolveImportPath(options) {
|
|
|
31155
32602
|
var tsconfigPath = "tsconfig.json";
|
|
31156
32603
|
async function collectRunFiles(options) {
|
|
31157
32604
|
const held = await listShippablePaths(options);
|
|
31158
|
-
const readFile = (path) => options.fs.readFile(
|
|
32605
|
+
const readFile = (path) => options.fs.readFile(join51(options.cwd, path));
|
|
31159
32606
|
const tsconfigContent = held.has(tsconfigPath) ? await readFile(tsconfigPath).catch(() => {
|
|
31160
32607
|
return;
|
|
31161
32608
|
}) : undefined;
|
|
@@ -31232,13 +32679,13 @@ async function walkImportGraph(options) {
|
|
|
31232
32679
|
}
|
|
31233
32680
|
|
|
31234
32681
|
// src/shell/interactiveRunner/makeRunnerId.ts
|
|
31235
|
-
import { randomUUID } from "node:crypto";
|
|
32682
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
31236
32683
|
function makeRunnerId() {
|
|
31237
|
-
return `cli-${
|
|
32684
|
+
return `cli-${randomUUID2().replaceAll("-", "").slice(0, 12)}`;
|
|
31238
32685
|
}
|
|
31239
32686
|
|
|
31240
32687
|
// src/shell/interactiveRunner/runFilesManifest.ts
|
|
31241
|
-
import { join as
|
|
32688
|
+
import { join as join52 } from "node:path";
|
|
31242
32689
|
var manifestFileName = "runner-files.json";
|
|
31243
32690
|
var manifestSchema2 = object({
|
|
31244
32691
|
files: array(object({ contentHash: string2(), path: string2() })),
|
|
@@ -31246,8 +32693,8 @@ var manifestSchema2 = object({
|
|
|
31246
32693
|
version: literal(1)
|
|
31247
32694
|
});
|
|
31248
32695
|
function makeRunFilesManifestStore(options) {
|
|
31249
|
-
const directory =
|
|
31250
|
-
const path =
|
|
32696
|
+
const directory = join52(options.cwd, qawolfDir);
|
|
32697
|
+
const path = join52(directory, manifestFileName);
|
|
31251
32698
|
let pendingWrites = 0;
|
|
31252
32699
|
return {
|
|
31253
32700
|
async read() {
|
|
@@ -31277,7 +32724,7 @@ function parseJson(text) {
|
|
|
31277
32724
|
}
|
|
31278
32725
|
|
|
31279
32726
|
// src/shell/interactiveRunner/runnerStore.ts
|
|
31280
|
-
import { join as
|
|
32727
|
+
import { join as join53 } from "node:path";
|
|
31281
32728
|
var storeFileName = "runner.json";
|
|
31282
32729
|
var runnersDirName = "runners";
|
|
31283
32730
|
var storedRunnerSchema = object({
|
|
@@ -31296,11 +32743,11 @@ function parseJson2(text) {
|
|
|
31296
32743
|
}
|
|
31297
32744
|
var pendingWrites = 0;
|
|
31298
32745
|
function makeRunnerStore(options) {
|
|
31299
|
-
const directory =
|
|
31300
|
-
const path =
|
|
31301
|
-
const runnersDir =
|
|
32746
|
+
const directory = join53(options.cwd, qawolfDir);
|
|
32747
|
+
const path = join53(directory, storeFileName);
|
|
32748
|
+
const runnersDir = join53(directory, runnersDirName);
|
|
31302
32749
|
const nextPendingPath = (target) => `${target}.${process.pid}.${++pendingWrites}.tmp`;
|
|
31303
|
-
const runnerPath = (runnerId) =>
|
|
32750
|
+
const runnerPath = (runnerId) => join53(runnersDir, `${encodeURIComponent(runnerId)}.json`);
|
|
31304
32751
|
const writeAtomically = async (target, contents) => {
|
|
31305
32752
|
const pendingPath = nextPendingPath(target);
|
|
31306
32753
|
await options.fs.writeFile(pendingPath, `${JSON.stringify(contents, undefined, 2)}
|
|
@@ -31337,7 +32784,7 @@ function makeRunnerStore(options) {
|
|
|
31337
32784
|
readDefaultRunnerId,
|
|
31338
32785
|
async readRunners() {
|
|
31339
32786
|
const runners = await Promise.all((await readRunnerFileNames()).map(async (name) => {
|
|
31340
|
-
const contents = await options.fs.readFile(
|
|
32787
|
+
const contents = await options.fs.readFile(join53(runnersDir, name)).catch(() => {
|
|
31341
32788
|
return;
|
|
31342
32789
|
});
|
|
31343
32790
|
if (contents === undefined)
|
|
@@ -31356,7 +32803,7 @@ function makeRunnerStore(options) {
|
|
|
31356
32803
|
await Promise.all(runnerIds.map((runnerId) => options.fs.rm(runnerPath(runnerId), { force: true })));
|
|
31357
32804
|
const names = await readRunnerFileNames();
|
|
31358
32805
|
await Promise.all(names.map(async (name) => {
|
|
31359
|
-
const runnerFile =
|
|
32806
|
+
const runnerFile = join53(runnersDir, name);
|
|
31360
32807
|
const contents = await options.fs.readFile(runnerFile).catch(() => {
|
|
31361
32808
|
return;
|
|
31362
32809
|
});
|
|
@@ -31440,7 +32887,7 @@ function registerRunnerHighlightSelectorCommand(runner, signals) {
|
|
|
31440
32887
|
}
|
|
31441
32888
|
|
|
31442
32889
|
// src/domains/interactiveRunner/importPackage.ts
|
|
31443
|
-
import { join as
|
|
32890
|
+
import { join as join54 } from "node:path";
|
|
31444
32891
|
|
|
31445
32892
|
// src/core/interactiveRunner/npmDependencies.ts
|
|
31446
32893
|
var sectionsInPrecedenceOrder = ["dependencies", "devDependencies"];
|
|
@@ -31481,7 +32928,7 @@ function readNpmDependencies(packageJsonContent) {
|
|
|
31481
32928
|
// src/domains/interactiveRunner/importPackage.ts
|
|
31482
32929
|
var defaultPackageVersion = "latest";
|
|
31483
32930
|
async function handleRunnerImportPackage(ctx, options, deps) {
|
|
31484
|
-
const content = await deps.readFile(
|
|
32931
|
+
const content = await deps.readFile(join54(deps.cwd, runPackageJsonPath)).catch(() => {
|
|
31485
32932
|
return;
|
|
31486
32933
|
});
|
|
31487
32934
|
if (content === undefined) {
|
|
@@ -33188,21 +34635,12 @@ function createProgram({
|
|
|
33188
34635
|
|
|
33189
34636
|
// src/main.ts
|
|
33190
34637
|
var signals = createSignalRegistry();
|
|
33191
|
-
var
|
|
33192
|
-
var onSignal = (sig) => () => {
|
|
33193
|
-
const code = sig === "SIGINT" ? 130 : 143;
|
|
33194
|
-
if (forced)
|
|
33195
|
-
process.exit(code);
|
|
33196
|
-
forced = true;
|
|
33197
|
-
signals.shutdown(sig).finally(() => {
|
|
33198
|
-
process.exit(code);
|
|
33199
|
-
});
|
|
33200
|
-
};
|
|
34638
|
+
var onSignal = createSignalExit({ shutdown: (sig) => signals.shutdown(sig) });
|
|
33201
34639
|
process.on("SIGINT", onSignal("SIGINT"));
|
|
33202
34640
|
process.on("SIGTERM", onSignal("SIGTERM"));
|
|
33203
34641
|
createProgram({ signals }).parseAsync().catch(() => {
|
|
33204
34642
|
if (process.exitCode === undefined)
|
|
33205
34643
|
process.exitCode = 1;
|
|
33206
|
-
}).finally(() =>
|
|
34644
|
+
}).finally(() => exitWhenIdle(typeof process.exitCode === "number" ? process.exitCode : 0));
|
|
33207
34645
|
|
|
33208
|
-
//# debugId=
|
|
34646
|
+
//# debugId=D197CB13AEBF087464756E2164756E21
|