@qawolf/cli 1.23.0 → 1.25.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 +2215 -640
- package/dist/runner-sdk.js +839 -139
- package/dist/types/runnerSdk/types.d.ts +6 -2
- package/package.json +2 -2
- package/skills/qawolf-cli/SKILL.md +11 -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",
|
|
@@ -8800,7 +8856,8 @@ var lifecycleMessages = {
|
|
|
8800
8856
|
|
|
8801
8857
|
// src/core/messages/interactiveRunner/list.ts
|
|
8802
8858
|
var listMessages = {
|
|
8803
|
-
noRunners: "
|
|
8859
|
+
noRunners: "Your team has no runner running. Launch one with qawolf runner launch.",
|
|
8860
|
+
noRunnersHere: "This directory has no runner running. Launch one with qawolf runner launch, or drop --here to see the whole team's runners.",
|
|
8804
8861
|
runnerCount: (count) => count === 1 ? "1 runner" : `${String(count)} runners`,
|
|
8805
8862
|
title: "Runners"
|
|
8806
8863
|
};
|
|
@@ -9160,43 +9217,30 @@ function resolveHostUrl(env) {
|
|
|
9160
9217
|
}
|
|
9161
9218
|
|
|
9162
9219
|
// src/domains/auth/resolve.ts
|
|
9163
|
-
import { Entry as
|
|
9164
|
-
|
|
9165
|
-
// src/domains/auth/store/delete.ts
|
|
9166
|
-
import { join as join2 } from "node:path";
|
|
9167
|
-
import { Entry } from "@napi-rs/keyring";
|
|
9168
|
-
|
|
9169
|
-
// src/domains/auth/store/constants.ts
|
|
9170
|
-
var service = "qawolf-cli";
|
|
9171
|
-
var account = "api-key";
|
|
9172
|
-
var credentialsFile = "credentials.json";
|
|
9220
|
+
import { Entry as Entry5 } from "@napi-rs/keyring";
|
|
9173
9221
|
|
|
9174
|
-
// src/
|
|
9175
|
-
function
|
|
9176
|
-
|
|
9177
|
-
|
|
9178
|
-
return
|
|
9179
|
-
|
|
9180
|
-
|
|
9181
|
-
|
|
9182
|
-
|
|
9183
|
-
async function deleteFromFile(configDir, fs) {
|
|
9222
|
+
// src/core/deviceAuth/tokenExpiry.ts
|
|
9223
|
+
function readAccessTokenExpiry(accessToken) {
|
|
9224
|
+
const segments = accessToken.split(".");
|
|
9225
|
+
if (segments.length !== 3)
|
|
9226
|
+
return;
|
|
9227
|
+
const [, payload] = segments;
|
|
9228
|
+
if (!payload)
|
|
9229
|
+
return;
|
|
9230
|
+
let claims;
|
|
9184
9231
|
try {
|
|
9185
|
-
|
|
9186
|
-
return "deleted";
|
|
9232
|
+
claims = JSON.parse(Buffer.from(payload, "base64url").toString("utf8"));
|
|
9187
9233
|
} catch {
|
|
9188
|
-
return
|
|
9234
|
+
return;
|
|
9189
9235
|
}
|
|
9236
|
+
if (typeof claims !== "object" || claims === null)
|
|
9237
|
+
return;
|
|
9238
|
+
const exp = claims.exp;
|
|
9239
|
+
if (typeof exp !== "number" || !Number.isFinite(exp))
|
|
9240
|
+
return;
|
|
9241
|
+
const expiresAtMs = exp * 1000;
|
|
9242
|
+
return Number.isFinite(expiresAtMs) ? expiresAtMs : undefined;
|
|
9190
9243
|
}
|
|
9191
|
-
async function deleteApiKey(configDir, fs) {
|
|
9192
|
-
const [keychain, file] = await Promise.all([
|
|
9193
|
-
Promise.resolve(deleteFromKeychain()),
|
|
9194
|
-
deleteFromFile(configDir, fs)
|
|
9195
|
-
]);
|
|
9196
|
-
return { keychain, file };
|
|
9197
|
-
}
|
|
9198
|
-
// src/domains/auth/store/load.ts
|
|
9199
|
-
import { join as join3 } from "node:path";
|
|
9200
9244
|
|
|
9201
9245
|
// node_modules/zod/v4/core/core.js
|
|
9202
9246
|
var _a;
|
|
@@ -13779,6 +13823,9 @@ var ZodEmail = /* @__PURE__ */ $constructor("ZodEmail", (inst, def) => {
|
|
|
13779
13823
|
$ZodEmail.init(inst, def);
|
|
13780
13824
|
ZodStringFormat.init(inst, def);
|
|
13781
13825
|
});
|
|
13826
|
+
function email2(params) {
|
|
13827
|
+
return _email(ZodEmail, params);
|
|
13828
|
+
}
|
|
13782
13829
|
var ZodGUID = /* @__PURE__ */ $constructor("ZodGUID", (inst, def) => {
|
|
13783
13830
|
$ZodGUID.init(inst, def);
|
|
13784
13831
|
ZodStringFormat.init(inst, def);
|
|
@@ -13842,6 +13889,9 @@ var ZodBase64 = /* @__PURE__ */ $constructor("ZodBase64", (inst, def) => {
|
|
|
13842
13889
|
$ZodBase64.init(inst, def);
|
|
13843
13890
|
ZodStringFormat.init(inst, def);
|
|
13844
13891
|
});
|
|
13892
|
+
function base642(params) {
|
|
13893
|
+
return _base64(ZodBase64, params);
|
|
13894
|
+
}
|
|
13845
13895
|
var ZodBase64URL = /* @__PURE__ */ $constructor("ZodBase64URL", (inst, def) => {
|
|
13846
13896
|
$ZodBase64URL.init(inst, def);
|
|
13847
13897
|
ZodStringFormat.init(inst, def);
|
|
@@ -14417,10 +14467,229 @@ function date4(params) {
|
|
|
14417
14467
|
|
|
14418
14468
|
// node_modules/zod/v4/classic/external.js
|
|
14419
14469
|
config(en_default());
|
|
14470
|
+
// src/shell/workos/types.ts
|
|
14471
|
+
var defaultWorkosBaseUrl = "https://api.workos.com";
|
|
14472
|
+
var defaultIntervalSec = 5;
|
|
14473
|
+
var deviceAuthorizationBody = object({
|
|
14474
|
+
device_code: string2().min(1),
|
|
14475
|
+
user_code: string2().min(1),
|
|
14476
|
+
verification_uri: string2().min(1),
|
|
14477
|
+
verification_uri_complete: string2().min(1).optional(),
|
|
14478
|
+
expires_in: number2().int().positive(),
|
|
14479
|
+
interval: number2().int().positive().optional()
|
|
14480
|
+
});
|
|
14481
|
+
var deviceTokenBody = object({
|
|
14482
|
+
access_token: string2().min(1),
|
|
14483
|
+
refresh_token: string2().min(1),
|
|
14484
|
+
user: object({ email: string2().min(1) }),
|
|
14485
|
+
organization_id: string2().min(1).optional()
|
|
14486
|
+
});
|
|
14487
|
+
var oauthErrorBody = object({
|
|
14488
|
+
error: string2().min(1),
|
|
14489
|
+
error_description: string2().min(1).optional()
|
|
14490
|
+
});
|
|
14491
|
+
var authenticationErrorBody = object({
|
|
14492
|
+
code: string2().min(1),
|
|
14493
|
+
message: string2().min(1).optional()
|
|
14494
|
+
});
|
|
14495
|
+
|
|
14496
|
+
// src/shell/workos/send.ts
|
|
14497
|
+
var timeoutMs = 15000;
|
|
14498
|
+
var unexpectedResponse = authErrorMessages.workos.unexpectedResponse;
|
|
14499
|
+
function isTransientStatus(status) {
|
|
14500
|
+
return status >= 500 || status === 429 || status === 408;
|
|
14501
|
+
}
|
|
14502
|
+
async function sendWorkosRequest(url, init, fetchFn) {
|
|
14503
|
+
let response;
|
|
14504
|
+
try {
|
|
14505
|
+
response = await fetchFn(url, {
|
|
14506
|
+
method: "POST",
|
|
14507
|
+
headers: init.headers,
|
|
14508
|
+
body: init.body,
|
|
14509
|
+
redirect: "manual",
|
|
14510
|
+
signal: AbortSignal.timeout(timeoutMs)
|
|
14511
|
+
});
|
|
14512
|
+
} catch (err) {
|
|
14513
|
+
return {
|
|
14514
|
+
kind: "failure",
|
|
14515
|
+
detail: authErrorMessages.workos.unreachable(errorMessage(err)),
|
|
14516
|
+
retryable: true
|
|
14517
|
+
};
|
|
14518
|
+
}
|
|
14519
|
+
if (response.status >= 300 && response.status < 400) {
|
|
14520
|
+
return {
|
|
14521
|
+
kind: "failure",
|
|
14522
|
+
detail: authErrorMessages.workos.redirected,
|
|
14523
|
+
retryable: false
|
|
14524
|
+
};
|
|
14525
|
+
}
|
|
14526
|
+
let json;
|
|
14527
|
+
try {
|
|
14528
|
+
json = await response.json();
|
|
14529
|
+
} catch {
|
|
14530
|
+
return {
|
|
14531
|
+
kind: "failure",
|
|
14532
|
+
detail: authErrorMessages.workos.unexpectedResponseWithStatus(response.status),
|
|
14533
|
+
retryable: response.ok || isTransientStatus(response.status)
|
|
14534
|
+
};
|
|
14535
|
+
}
|
|
14536
|
+
if (response.ok)
|
|
14537
|
+
return { kind: "json", json };
|
|
14538
|
+
if (isTransientStatus(response.status)) {
|
|
14539
|
+
return {
|
|
14540
|
+
kind: "failure",
|
|
14541
|
+
detail: authErrorMessages.workos.unexpectedResponseWithStatus(response.status),
|
|
14542
|
+
retryable: true
|
|
14543
|
+
};
|
|
14544
|
+
}
|
|
14545
|
+
const parsed = oauthErrorBody.safeParse(json);
|
|
14546
|
+
if (parsed.success) {
|
|
14547
|
+
return {
|
|
14548
|
+
kind: "oauth-error",
|
|
14549
|
+
code: parsed.data.error,
|
|
14550
|
+
description: parsed.data.error_description
|
|
14551
|
+
};
|
|
14552
|
+
}
|
|
14553
|
+
const authError = authenticationErrorBody.safeParse(json);
|
|
14554
|
+
if (authError.success) {
|
|
14555
|
+
return {
|
|
14556
|
+
kind: "oauth-error",
|
|
14557
|
+
code: authError.data.code,
|
|
14558
|
+
description: authError.data.message
|
|
14559
|
+
};
|
|
14560
|
+
}
|
|
14561
|
+
return {
|
|
14562
|
+
kind: "failure",
|
|
14563
|
+
detail: authErrorMessages.workos.unexpectedResponseWithStatus(response.status),
|
|
14564
|
+
retryable: false
|
|
14565
|
+
};
|
|
14566
|
+
}
|
|
14567
|
+
|
|
14568
|
+
// src/shell/workos/refreshAccessToken.ts
|
|
14569
|
+
async function refreshAccessToken(refreshToken, organizationId, deps) {
|
|
14570
|
+
const params = new URLSearchParams({
|
|
14571
|
+
grant_type: "refresh_token",
|
|
14572
|
+
refresh_token: refreshToken,
|
|
14573
|
+
client_id: deps.clientId
|
|
14574
|
+
});
|
|
14575
|
+
if (organizationId)
|
|
14576
|
+
params.set("organization_id", organizationId);
|
|
14577
|
+
const outcome = await sendWorkosRequest(`${deps.baseUrl}/user_management/authenticate`, {
|
|
14578
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
14579
|
+
body: params.toString()
|
|
14580
|
+
}, deps.fetch);
|
|
14581
|
+
if (outcome.kind === "failure") {
|
|
14582
|
+
return { ok: false, error: outcome.detail, retryable: outcome.retryable };
|
|
14583
|
+
}
|
|
14584
|
+
if (outcome.kind === "oauth-error") {
|
|
14585
|
+
return {
|
|
14586
|
+
ok: false,
|
|
14587
|
+
error: outcome.description ?? outcome.code,
|
|
14588
|
+
retryable: false
|
|
14589
|
+
};
|
|
14590
|
+
}
|
|
14591
|
+
const parsed = deviceTokenBody.safeParse(outcome.json);
|
|
14592
|
+
if (!parsed.success) {
|
|
14593
|
+
return { ok: false, error: unexpectedResponse, retryable: false };
|
|
14594
|
+
}
|
|
14595
|
+
return {
|
|
14596
|
+
ok: true,
|
|
14597
|
+
value: {
|
|
14598
|
+
accessToken: parsed.data.access_token,
|
|
14599
|
+
refreshToken: parsed.data.refresh_token,
|
|
14600
|
+
expiresAt: readAccessTokenExpiry(parsed.data.access_token),
|
|
14601
|
+
email: parsed.data.user.email,
|
|
14602
|
+
organizationId: parsed.data.organization_id
|
|
14603
|
+
}
|
|
14604
|
+
};
|
|
14605
|
+
}
|
|
14606
|
+
|
|
14607
|
+
// src/shell/workos/config.ts
|
|
14608
|
+
function resolveWorkosConfig(publishedClientId) {
|
|
14609
|
+
const clientId = publishedClientId?.trim();
|
|
14610
|
+
if (!clientId)
|
|
14611
|
+
return { configured: false };
|
|
14612
|
+
return { configured: true, clientId, baseUrl: defaultWorkosBaseUrl };
|
|
14613
|
+
}
|
|
14614
|
+
|
|
14615
|
+
// src/domains/auth/store/delete.ts
|
|
14616
|
+
import { join as join2 } from "node:path";
|
|
14617
|
+
import { Entry } from "@napi-rs/keyring";
|
|
14618
|
+
|
|
14619
|
+
// src/domains/auth/store/constants.ts
|
|
14620
|
+
var service = "qawolf-cli";
|
|
14621
|
+
var account = "api-key";
|
|
14622
|
+
var credentialsFile = "credentials.json";
|
|
14623
|
+
var tokensAccount = "oauth-tokens";
|
|
14624
|
+
var tokensFile = "tokens.json";
|
|
14625
|
+
|
|
14626
|
+
// src/domains/auth/store/delete.ts
|
|
14627
|
+
function deleteFromKeychain() {
|
|
14628
|
+
try {
|
|
14629
|
+
new Entry(service, account).deletePassword();
|
|
14630
|
+
return "deleted";
|
|
14631
|
+
} catch {
|
|
14632
|
+
return "unavailable";
|
|
14633
|
+
}
|
|
14634
|
+
}
|
|
14635
|
+
async function deleteFromFile(configDir, fs) {
|
|
14636
|
+
try {
|
|
14637
|
+
await fs.unlink(join2(configDir, credentialsFile));
|
|
14638
|
+
return "deleted";
|
|
14639
|
+
} catch (err) {
|
|
14640
|
+
if (isNoEntError(err))
|
|
14641
|
+
return "not-found";
|
|
14642
|
+
throw err;
|
|
14643
|
+
}
|
|
14644
|
+
}
|
|
14645
|
+
async function deleteApiKey(configDir, fs) {
|
|
14646
|
+
const [keychain, file] = await Promise.all([
|
|
14647
|
+
Promise.resolve(deleteFromKeychain()),
|
|
14648
|
+
deleteFromFile(configDir, fs)
|
|
14649
|
+
]);
|
|
14650
|
+
return { keychain, file };
|
|
14651
|
+
}
|
|
14652
|
+
// src/domains/auth/store/hasStoredCredentials.ts
|
|
14653
|
+
import { join as join3 } from "node:path";
|
|
14654
|
+
import { Entry as Entry2 } from "@napi-rs/keyring";
|
|
14655
|
+
async function fileExists(path, fs) {
|
|
14656
|
+
try {
|
|
14657
|
+
await fs.readFile(path);
|
|
14658
|
+
return true;
|
|
14659
|
+
} catch (err) {
|
|
14660
|
+
return !isNoEntError(err);
|
|
14661
|
+
}
|
|
14662
|
+
}
|
|
14663
|
+
function keychainHolds(entryAccount) {
|
|
14664
|
+
try {
|
|
14665
|
+
return Boolean(new Entry2(service, entryAccount).getPassword());
|
|
14666
|
+
} catch {
|
|
14667
|
+
return false;
|
|
14668
|
+
}
|
|
14669
|
+
}
|
|
14670
|
+
async function hasStoredCredentials(configDir, fs) {
|
|
14671
|
+
const [apiKeyFile, tokenFile] = await Promise.all([
|
|
14672
|
+
fileExists(join3(configDir, credentialsFile), fs),
|
|
14673
|
+
fileExists(join3(configDir, tokensFile), fs)
|
|
14674
|
+
]);
|
|
14675
|
+
return apiKeyFile || tokenFile || keychainHolds(account) || keychainHolds(tokensAccount);
|
|
14676
|
+
}
|
|
14677
|
+
// src/domains/auth/store/load.ts
|
|
14678
|
+
import { join as join4 } from "node:path";
|
|
14679
|
+
|
|
14420
14680
|
// src/domains/auth/store/types.ts
|
|
14421
14681
|
var credentialsFileSchema = object({
|
|
14422
14682
|
apiKey: string2().min(1)
|
|
14423
14683
|
});
|
|
14684
|
+
var oauthTokensSchema = object({
|
|
14685
|
+
accessToken: string2().min(1),
|
|
14686
|
+
refreshToken: string2().min(1),
|
|
14687
|
+
expiresAt: number2().int().optional(),
|
|
14688
|
+
email: string2().min(1),
|
|
14689
|
+
organizationId: string2().min(1).optional(),
|
|
14690
|
+
workspaceId: string2().min(1).optional(),
|
|
14691
|
+
clientId: string2().min(1).optional()
|
|
14692
|
+
});
|
|
14424
14693
|
|
|
14425
14694
|
// src/domains/auth/store/load.ts
|
|
14426
14695
|
async function loadApiKey(configDir, deps) {
|
|
@@ -14434,7 +14703,7 @@ async function loadApiKey(configDir, deps) {
|
|
|
14434
14703
|
errors.keychain = errorMessage(err);
|
|
14435
14704
|
}
|
|
14436
14705
|
try {
|
|
14437
|
-
const content = await deps.fs.readFile(
|
|
14706
|
+
const content = await deps.fs.readFile(join4(configDir, credentialsFile));
|
|
14438
14707
|
const parsed = credentialsFileSchema.safeParse(JSON.parse(content));
|
|
14439
14708
|
if (parsed.success) {
|
|
14440
14709
|
return { found: true, key: parsed.data.apiKey, source: "file" };
|
|
@@ -14446,16 +14715,16 @@ async function loadApiKey(configDir, deps) {
|
|
|
14446
14715
|
return { found: false, errors };
|
|
14447
14716
|
}
|
|
14448
14717
|
// src/domains/auth/store/save.ts
|
|
14449
|
-
import { join as
|
|
14450
|
-
import { Entry as
|
|
14718
|
+
import { join as join5 } from "node:path";
|
|
14719
|
+
import { Entry as Entry3 } from "@napi-rs/keyring";
|
|
14451
14720
|
async function saveToFile(configDir, key, fs) {
|
|
14452
14721
|
const payload = { apiKey: key };
|
|
14453
14722
|
await fs.mkdir(configDir, { recursive: true, mode: 448 });
|
|
14454
|
-
await fs.writeFile(
|
|
14723
|
+
await fs.writeFile(join5(configDir, credentialsFile), JSON.stringify(payload, undefined, 2), { mode: 384 });
|
|
14455
14724
|
}
|
|
14456
14725
|
async function saveApiKey(configDir, key, fs) {
|
|
14457
14726
|
try {
|
|
14458
|
-
const entry = new
|
|
14727
|
+
const entry = new Entry3(service, account);
|
|
14459
14728
|
entry.setPassword(key);
|
|
14460
14729
|
return { stored: "keychain" };
|
|
14461
14730
|
} catch (err) {
|
|
@@ -14463,10 +14732,159 @@ async function saveApiKey(configDir, key, fs) {
|
|
|
14463
14732
|
return { stored: "file", keychainError: errorMessage(err) };
|
|
14464
14733
|
}
|
|
14465
14734
|
}
|
|
14735
|
+
// src/domains/auth/store/loadTokens.ts
|
|
14736
|
+
import { join as join6 } from "node:path";
|
|
14737
|
+
var invalidPayload = "Invalid stored token format";
|
|
14738
|
+
function parseTokens(raw) {
|
|
14739
|
+
const parsed = oauthTokensSchema.safeParse(JSON.parse(raw));
|
|
14740
|
+
if (!parsed.success)
|
|
14741
|
+
return;
|
|
14742
|
+
return {
|
|
14743
|
+
accessToken: parsed.data.accessToken,
|
|
14744
|
+
refreshToken: parsed.data.refreshToken,
|
|
14745
|
+
expiresAt: parsed.data.expiresAt,
|
|
14746
|
+
email: parsed.data.email,
|
|
14747
|
+
organizationId: parsed.data.organizationId,
|
|
14748
|
+
workspaceId: parsed.data.workspaceId,
|
|
14749
|
+
clientId: parsed.data.clientId
|
|
14750
|
+
};
|
|
14751
|
+
}
|
|
14752
|
+
async function loadTokens(configDir, deps) {
|
|
14753
|
+
const errors = {};
|
|
14754
|
+
try {
|
|
14755
|
+
const raw = new deps.EntryClass(service, tokensAccount).getPassword();
|
|
14756
|
+
if (raw) {
|
|
14757
|
+
const tokens = parseTokens(raw);
|
|
14758
|
+
if (tokens)
|
|
14759
|
+
return { found: true, tokens, source: "keychain" };
|
|
14760
|
+
errors.keychain = invalidPayload;
|
|
14761
|
+
}
|
|
14762
|
+
} catch (err) {
|
|
14763
|
+
errors.keychain = errorMessage(err);
|
|
14764
|
+
}
|
|
14765
|
+
try {
|
|
14766
|
+
const raw = await deps.fs.readFile(join6(configDir, tokensFile));
|
|
14767
|
+
const tokens = parseTokens(raw);
|
|
14768
|
+
if (tokens)
|
|
14769
|
+
return { found: true, tokens, source: "file" };
|
|
14770
|
+
errors.file = invalidPayload;
|
|
14771
|
+
} catch (err) {
|
|
14772
|
+
errors.file = errorMessage(err);
|
|
14773
|
+
}
|
|
14774
|
+
return { found: false, errors };
|
|
14775
|
+
}
|
|
14776
|
+
|
|
14777
|
+
// src/domains/auth/store/saveTokens.ts
|
|
14778
|
+
import { randomUUID } from "node:crypto";
|
|
14779
|
+
import { join as join7 } from "node:path";
|
|
14780
|
+
import { Entry as Entry4 } from "@napi-rs/keyring";
|
|
14781
|
+
async function saveToFile2(configDir, tokens, fs) {
|
|
14782
|
+
await fs.mkdir(configDir, { recursive: true, mode: 448 });
|
|
14783
|
+
const target = join7(configDir, tokensFile);
|
|
14784
|
+
const staging = `${target}.${randomUUID()}.tmp`;
|
|
14785
|
+
try {
|
|
14786
|
+
await fs.writeFile(staging, JSON.stringify(tokens, undefined, 2), {
|
|
14787
|
+
mode: 384
|
|
14788
|
+
});
|
|
14789
|
+
await fs.rename(staging, target);
|
|
14790
|
+
} catch (err) {
|
|
14791
|
+
await fs.unlink(staging).catch(() => {});
|
|
14792
|
+
throw err;
|
|
14793
|
+
}
|
|
14794
|
+
}
|
|
14795
|
+
async function saveTokens(configDir, tokens, fs) {
|
|
14796
|
+
try {
|
|
14797
|
+
new Entry4(service, tokensAccount).setPassword(JSON.stringify(tokens));
|
|
14798
|
+
return { stored: "keychain" };
|
|
14799
|
+
} catch (err) {
|
|
14800
|
+
try {
|
|
14801
|
+
new Entry4(service, tokensAccount).deletePassword();
|
|
14802
|
+
} catch {}
|
|
14803
|
+
await saveToFile2(configDir, tokens, fs);
|
|
14804
|
+
return { stored: "file", keychainError: errorMessage(err) };
|
|
14805
|
+
}
|
|
14806
|
+
}
|
|
14807
|
+
|
|
14808
|
+
// src/domains/auth/resolveOauthToken.ts
|
|
14809
|
+
var expiryMarginMs = 30000;
|
|
14810
|
+
async function resolveOauthToken(configDir, deps) {
|
|
14811
|
+
const stored = await deps.loadTokens(configDir);
|
|
14812
|
+
if (!stored.found)
|
|
14813
|
+
return;
|
|
14814
|
+
const { tokens } = stored;
|
|
14815
|
+
const expiresAt = tokens.expiresAt;
|
|
14816
|
+
const isFresh = expiresAt !== undefined && expiresAt - expiryMarginMs > deps.now();
|
|
14817
|
+
if (isFresh) {
|
|
14818
|
+
return {
|
|
14819
|
+
key: tokens.accessToken,
|
|
14820
|
+
email: tokens.email,
|
|
14821
|
+
workspaceId: tokens.workspaceId
|
|
14822
|
+
};
|
|
14823
|
+
}
|
|
14824
|
+
const refreshed = await deps.refreshTokens({
|
|
14825
|
+
refreshToken: tokens.refreshToken,
|
|
14826
|
+
organizationId: tokens.organizationId,
|
|
14827
|
+
clientId: tokens.clientId
|
|
14828
|
+
});
|
|
14829
|
+
if (!refreshed.ok) {
|
|
14830
|
+
if (refreshed.retryable && expiresAt !== undefined && expiresAt > deps.now()) {
|
|
14831
|
+
return {
|
|
14832
|
+
key: tokens.accessToken,
|
|
14833
|
+
email: tokens.email,
|
|
14834
|
+
workspaceId: tokens.workspaceId
|
|
14835
|
+
};
|
|
14836
|
+
}
|
|
14837
|
+
const current = await deps.loadTokens(configDir);
|
|
14838
|
+
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()) {
|
|
14839
|
+
return {
|
|
14840
|
+
key: current.tokens.accessToken,
|
|
14841
|
+
email: current.tokens.email,
|
|
14842
|
+
workspaceId: current.tokens.workspaceId
|
|
14843
|
+
};
|
|
14844
|
+
}
|
|
14845
|
+
return;
|
|
14846
|
+
}
|
|
14847
|
+
try {
|
|
14848
|
+
await deps.saveTokens(configDir, {
|
|
14849
|
+
...refreshed.value,
|
|
14850
|
+
workspaceId: tokens.workspaceId,
|
|
14851
|
+
clientId: tokens.clientId
|
|
14852
|
+
});
|
|
14853
|
+
} catch {}
|
|
14854
|
+
return {
|
|
14855
|
+
key: refreshed.value.accessToken,
|
|
14856
|
+
email: refreshed.value.email,
|
|
14857
|
+
workspaceId: tokens.workspaceId
|
|
14858
|
+
};
|
|
14859
|
+
}
|
|
14860
|
+
|
|
14466
14861
|
// src/domains/auth/resolve.ts
|
|
14862
|
+
function makeOauthDeps(fs) {
|
|
14863
|
+
return {
|
|
14864
|
+
loadTokens: (configDir) => loadTokens(configDir, { EntryClass: Entry5, fs }),
|
|
14865
|
+
refreshTokens: async ({ refreshToken, organizationId, clientId }) => {
|
|
14866
|
+
const config = resolveWorkosConfig(clientId);
|
|
14867
|
+
if (!config.configured) {
|
|
14868
|
+
return {
|
|
14869
|
+
ok: false,
|
|
14870
|
+
error: authErrorMessages.workos.noClientForSession,
|
|
14871
|
+
retryable: false
|
|
14872
|
+
};
|
|
14873
|
+
}
|
|
14874
|
+
return refreshAccessToken(refreshToken, organizationId, {
|
|
14875
|
+
fetch: globalThis.fetch,
|
|
14876
|
+
baseUrl: config.baseUrl,
|
|
14877
|
+
clientId: config.clientId
|
|
14878
|
+
});
|
|
14879
|
+
},
|
|
14880
|
+
saveTokens: (configDir, tokens) => saveTokens(configDir, tokens, fs),
|
|
14881
|
+
now: () => Date.now()
|
|
14882
|
+
};
|
|
14883
|
+
}
|
|
14467
14884
|
function makeDefaultDeps(fs) {
|
|
14468
14885
|
return {
|
|
14469
|
-
loadApiKey: (configDir) => loadApiKey(configDir, { EntryClass:
|
|
14886
|
+
loadApiKey: (configDir) => loadApiKey(configDir, { EntryClass: Entry5, fs }),
|
|
14887
|
+
resolveOauth: (configDir) => resolveOauthToken(configDir, makeOauthDeps(fs)),
|
|
14470
14888
|
env: process.env
|
|
14471
14889
|
};
|
|
14472
14890
|
}
|
|
@@ -14474,11 +14892,19 @@ async function resolveApiKey(configDir, fs, deps) {
|
|
|
14474
14892
|
const resolvedDeps = deps ?? makeDefaultDeps(fs);
|
|
14475
14893
|
const envKey = resolvedDeps.env["QAWOLF_API_KEY"];
|
|
14476
14894
|
if (envKey?.trim()) {
|
|
14477
|
-
return { key: envKey.trim(), source: "env" };
|
|
14895
|
+
return { key: envKey.trim(), source: "env", workspaceId: undefined };
|
|
14478
14896
|
}
|
|
14479
14897
|
const stored = await resolvedDeps.loadApiKey(configDir);
|
|
14480
14898
|
if (stored.found) {
|
|
14481
|
-
return { key: stored.key, source: stored.source };
|
|
14899
|
+
return { key: stored.key, source: stored.source, workspaceId: undefined };
|
|
14900
|
+
}
|
|
14901
|
+
const oauth = await resolvedDeps.resolveOauth(configDir);
|
|
14902
|
+
if (oauth) {
|
|
14903
|
+
return {
|
|
14904
|
+
key: oauth.key,
|
|
14905
|
+
source: "browser",
|
|
14906
|
+
workspaceId: oauth.workspaceId
|
|
14907
|
+
};
|
|
14482
14908
|
}
|
|
14483
14909
|
return;
|
|
14484
14910
|
}
|
|
@@ -14500,9 +14926,9 @@ async function validateApiKey(deps) {
|
|
|
14500
14926
|
// src/shell/logger.ts
|
|
14501
14927
|
var import_pino = __toESM(require_pino(), 1);
|
|
14502
14928
|
import { mkdirSync as mkdirSync2, openSync } from "node:fs";
|
|
14503
|
-
import { dirname, join as
|
|
14929
|
+
import { dirname, join as join8 } from "node:path";
|
|
14504
14930
|
function defaultLogPath() {
|
|
14505
|
-
return
|
|
14931
|
+
return join8(envPaths("qawolf", { suffix: "" }).log, "cli.log");
|
|
14506
14932
|
}
|
|
14507
14933
|
function resolveStderrLevel(env, verbose) {
|
|
14508
14934
|
if (verbose)
|
|
@@ -14570,12 +14996,182 @@ function createLoggingSystem(opts) {
|
|
|
14570
14996
|
};
|
|
14571
14997
|
}
|
|
14572
14998
|
|
|
14573
|
-
// src/
|
|
14574
|
-
|
|
14575
|
-
|
|
14576
|
-
|
|
14577
|
-
|
|
14578
|
-
|
|
14999
|
+
// src/core/publicApi/flagKind.ts
|
|
15000
|
+
function flagKind(schema) {
|
|
15001
|
+
if (schema.type === "string")
|
|
15002
|
+
return { ok: true, kind: "string" };
|
|
15003
|
+
if (schema.type === "number" || schema.type === "integer") {
|
|
15004
|
+
return { ok: true, kind: "number" };
|
|
15005
|
+
}
|
|
15006
|
+
if (schema.type === "boolean")
|
|
15007
|
+
return { ok: true, kind: "boolean" };
|
|
15008
|
+
if (schema.type === "array") {
|
|
15009
|
+
if (schema.items?.type !== "string") {
|
|
15010
|
+
return {
|
|
15011
|
+
ok: false,
|
|
15012
|
+
reason: "only string arrays can be expressed as flags"
|
|
15013
|
+
};
|
|
15014
|
+
}
|
|
15015
|
+
return { ok: true, kind: "string-array" };
|
|
15016
|
+
}
|
|
15017
|
+
if (schema.type === "object") {
|
|
15018
|
+
if (schema.properties) {
|
|
15019
|
+
return {
|
|
15020
|
+
ok: false,
|
|
15021
|
+
reason: "nested objects cannot be expressed as flags"
|
|
15022
|
+
};
|
|
15023
|
+
}
|
|
15024
|
+
if (typeof schema.additionalProperties === "object" && schema.additionalProperties.type === "string") {
|
|
15025
|
+
return { ok: true, kind: "key-value-record" };
|
|
15026
|
+
}
|
|
15027
|
+
return { ok: false, reason: "nested objects cannot be expressed as flags" };
|
|
15028
|
+
}
|
|
15029
|
+
return {
|
|
15030
|
+
ok: false,
|
|
15031
|
+
reason: `type ${schema.type ?? "unknown"} cannot be expressed as a flag`
|
|
15032
|
+
};
|
|
15033
|
+
}
|
|
15034
|
+
|
|
15035
|
+
// src/core/publicApi/objectShape.ts
|
|
15036
|
+
var asObjectShape = (schema) => schema.type === "object" && schema.properties ? { properties: schema.properties, required: new Set(schema.required) } : undefined;
|
|
15037
|
+
function mergeIntersection(members) {
|
|
15038
|
+
const shape = { properties: {}, required: new Set };
|
|
15039
|
+
for (const member of members) {
|
|
15040
|
+
const memberShape = asObjectShape(member);
|
|
15041
|
+
if (!memberShape) {
|
|
15042
|
+
return {
|
|
15043
|
+
ok: false,
|
|
15044
|
+
field: "",
|
|
15045
|
+
reason: "intersection members must be object schemas"
|
|
15046
|
+
};
|
|
15047
|
+
}
|
|
15048
|
+
for (const [field, fieldSchema] of Object.entries(memberShape.properties)) {
|
|
15049
|
+
if (field in shape.properties) {
|
|
15050
|
+
return {
|
|
15051
|
+
ok: false,
|
|
15052
|
+
field,
|
|
15053
|
+
reason: "field appears in multiple intersection members"
|
|
15054
|
+
};
|
|
15055
|
+
}
|
|
15056
|
+
shape.properties[field] = fieldSchema;
|
|
15057
|
+
if (memberShape.required.has(field))
|
|
15058
|
+
shape.required.add(field);
|
|
15059
|
+
}
|
|
15060
|
+
}
|
|
15061
|
+
return { ok: true, shape };
|
|
15062
|
+
}
|
|
15063
|
+
function mergeUnion(branches) {
|
|
15064
|
+
const shapes = [];
|
|
15065
|
+
for (const branch of branches) {
|
|
15066
|
+
const branchShape = asObjectShape(branch);
|
|
15067
|
+
if (!branchShape) {
|
|
15068
|
+
return {
|
|
15069
|
+
ok: false,
|
|
15070
|
+
field: "",
|
|
15071
|
+
reason: "union branches must be object schemas"
|
|
15072
|
+
};
|
|
15073
|
+
}
|
|
15074
|
+
shapes.push(branchShape);
|
|
15075
|
+
}
|
|
15076
|
+
const shape = { properties: {}, required: new Set };
|
|
15077
|
+
for (const branchShape of shapes) {
|
|
15078
|
+
for (const [field, fieldSchema] of Object.entries(branchShape.properties)) {
|
|
15079
|
+
const existing = shape.properties[field];
|
|
15080
|
+
if (!existing) {
|
|
15081
|
+
shape.properties[field] = { ...fieldSchema };
|
|
15082
|
+
continue;
|
|
15083
|
+
}
|
|
15084
|
+
const kinds = [flagKind(existing), flagKind(fieldSchema)];
|
|
15085
|
+
if (kinds[0]?.ok && kinds[1]?.ok) {
|
|
15086
|
+
if (kinds[0].kind !== kinds[1].kind) {
|
|
15087
|
+
return {
|
|
15088
|
+
ok: false,
|
|
15089
|
+
field,
|
|
15090
|
+
reason: "field has conflicting types across union branches"
|
|
15091
|
+
};
|
|
15092
|
+
}
|
|
15093
|
+
} else if (JSON.stringify({ ...existing, description: undefined }) !== JSON.stringify({ ...fieldSchema, description: undefined })) {
|
|
15094
|
+
return {
|
|
15095
|
+
ok: false,
|
|
15096
|
+
field,
|
|
15097
|
+
reason: "field has conflicting object schemas across union branches"
|
|
15098
|
+
};
|
|
15099
|
+
}
|
|
15100
|
+
if (!existing.description && fieldSchema.description) {
|
|
15101
|
+
existing.description = fieldSchema.description;
|
|
15102
|
+
}
|
|
15103
|
+
}
|
|
15104
|
+
}
|
|
15105
|
+
const discriminator = Object.keys(shape.properties).find((field) => {
|
|
15106
|
+
const values = shapes.map((branch) => branch.properties[field]?.const);
|
|
15107
|
+
return values.every((value) => typeof value === "string") && new Set(values).size === shapes.length && shapes.every((branch) => branch.required.has(field));
|
|
15108
|
+
});
|
|
15109
|
+
for (const [field, fieldSchema] of Object.entries(shape.properties)) {
|
|
15110
|
+
const occurrences = shapes.filter((branch) => (field in branch.properties));
|
|
15111
|
+
if (occurrences.length === shapes.length && occurrences.every((branch) => branch.required.has(field))) {
|
|
15112
|
+
shape.required.add(field);
|
|
15113
|
+
}
|
|
15114
|
+
if (field === discriminator) {
|
|
15115
|
+
const values = shapes.map((branch) => branch.properties[field]?.const);
|
|
15116
|
+
fieldSchema.description = `One of: ${values.join(", ")}`;
|
|
15117
|
+
}
|
|
15118
|
+
}
|
|
15119
|
+
return { ok: true, shape };
|
|
15120
|
+
}
|
|
15121
|
+
function toObjectShape(schema) {
|
|
15122
|
+
if (schema.allOf)
|
|
15123
|
+
return mergeIntersection(schema.allOf);
|
|
15124
|
+
const branches = schema.oneOf ?? schema.anyOf;
|
|
15125
|
+
if (branches)
|
|
15126
|
+
return mergeUnion(branches);
|
|
15127
|
+
const shape = asObjectShape(schema);
|
|
15128
|
+
if (shape)
|
|
15129
|
+
return { ok: true, shape };
|
|
15130
|
+
return {
|
|
15131
|
+
ok: false,
|
|
15132
|
+
field: "",
|
|
15133
|
+
reason: "contract input must be an object schema"
|
|
15134
|
+
};
|
|
15135
|
+
}
|
|
15136
|
+
|
|
15137
|
+
// src/shell/platform/applyWorkspaceId.ts
|
|
15138
|
+
var acceptsWorkspace = new WeakMap;
|
|
15139
|
+
function takesWorkspaceId(inputSchema) {
|
|
15140
|
+
const cached = acceptsWorkspace.get(inputSchema);
|
|
15141
|
+
if (cached !== undefined)
|
|
15142
|
+
return cached;
|
|
15143
|
+
let accepted = false;
|
|
15144
|
+
try {
|
|
15145
|
+
const jsonSchema = toJSONSchema(inputSchema, {
|
|
15146
|
+
io: "input"
|
|
15147
|
+
});
|
|
15148
|
+
const shape = toObjectShape(jsonSchema);
|
|
15149
|
+
accepted = shape.ok && "workspaceId" in shape.shape.properties;
|
|
15150
|
+
} catch {
|
|
15151
|
+
accepted = false;
|
|
15152
|
+
}
|
|
15153
|
+
acceptsWorkspace.set(inputSchema, accepted);
|
|
15154
|
+
return accepted;
|
|
15155
|
+
}
|
|
15156
|
+
function applyWorkspaceId(inputSchema, input, workspaceId) {
|
|
15157
|
+
if (!workspaceId)
|
|
15158
|
+
return input;
|
|
15159
|
+
if (typeof input !== "object" || input === null)
|
|
15160
|
+
return input;
|
|
15161
|
+
const existing = input.workspaceId;
|
|
15162
|
+
if (existing !== undefined)
|
|
15163
|
+
return input;
|
|
15164
|
+
if (!takesWorkspaceId(inputSchema))
|
|
15165
|
+
return input;
|
|
15166
|
+
return { ...input, workspaceId };
|
|
15167
|
+
}
|
|
15168
|
+
|
|
15169
|
+
// src/shell/platform/parseErrorBody.ts
|
|
15170
|
+
var defaultMaxLength = 1024;
|
|
15171
|
+
function asRecord(value) {
|
|
15172
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : undefined;
|
|
15173
|
+
}
|
|
15174
|
+
function asString(value) {
|
|
14579
15175
|
return typeof value === "string" ? value : undefined;
|
|
14580
15176
|
}
|
|
14581
15177
|
function extractMessage(parsed) {
|
|
@@ -14736,7 +15332,7 @@ function callPublicApi(trpc, contract, input, options) {
|
|
|
14736
15332
|
}
|
|
14737
15333
|
function makeCallPublicApiMethod(trpc, deps, readBackoffMs) {
|
|
14738
15334
|
return async (contract, input, options) => requestWithRetry({
|
|
14739
|
-
call: () => callPublicApi(trpc, contract, input, options),
|
|
15335
|
+
call: () => callPublicApi(trpc, contract, applyWorkspaceId(contract.input, input, deps.workspaceId), options),
|
|
14740
15336
|
backoffMs: contract.kind === "read" ? readBackoffMs : [],
|
|
14741
15337
|
describe: (err) => describeRequestError(err, deps.baseUrl, contract.name),
|
|
14742
15338
|
sleep: deps.sleep
|
|
@@ -15639,7 +16235,7 @@ function summarize(error) {
|
|
|
15639
16235
|
// src/shell/platform/downloadBundle.ts
|
|
15640
16236
|
import { randomBytes } from "node:crypto";
|
|
15641
16237
|
import { tmpdir as tmpdir2 } from "node:os";
|
|
15642
|
-
import { join as
|
|
16238
|
+
import { join as join9 } from "node:path";
|
|
15643
16239
|
|
|
15644
16240
|
// src/shell/platform/fetchSignedUrl.ts
|
|
15645
16241
|
var defaultStallTimeoutMs = 30000;
|
|
@@ -15727,7 +16323,7 @@ async function fetchSignedUrl(args, deps = { fetch: globalThis.fetch }) {
|
|
|
15727
16323
|
|
|
15728
16324
|
// src/shell/platform/downloadBundle.ts
|
|
15729
16325
|
async function downloadBundle(signedUrl, deps) {
|
|
15730
|
-
const tmpArchive =
|
|
16326
|
+
const tmpArchive = join9(tmpdir2(), `qawolf-pull-${randomBytes(8).toString("hex")}.tar.gz`);
|
|
15731
16327
|
const result = await fetchSignedUrl({ url: signedUrl, dest: tmpArchive }, { fetch: deps.fetch, fs: deps.fs });
|
|
15732
16328
|
if (!result.ok) {
|
|
15733
16329
|
await deps.fs.unlink(tmpArchive).catch(() => {});
|
|
@@ -15751,6 +16347,44 @@ var agentReplySchema = object({
|
|
|
15751
16347
|
text: string2().describe("What the AI said.")
|
|
15752
16348
|
});
|
|
15753
16349
|
|
|
16350
|
+
// node_modules/@qawolf/api-contracts/dist/v1/resource.js
|
|
16351
|
+
function resource(fields, { urlFieldDescription = "Absolute URL." } = {}) {
|
|
16352
|
+
return object({
|
|
16353
|
+
...fields,
|
|
16354
|
+
url: url().describe(urlFieldDescription)
|
|
16355
|
+
});
|
|
16356
|
+
}
|
|
16357
|
+
|
|
16358
|
+
// node_modules/@qawolf/api-contracts/dist/v1/email/resource.js
|
|
16359
|
+
var emailDirectionSchema = _enum(["inbound", "outbound"]);
|
|
16360
|
+
var namedAddressSchema = object({
|
|
16361
|
+
address: string2().describe("The email address."),
|
|
16362
|
+
name: string2().optional().describe("The display name, when the message carried one.")
|
|
16363
|
+
});
|
|
16364
|
+
var emailUrlFieldDescription = "Absolute URL of the workspace. The inbox opens from the app header.";
|
|
16365
|
+
var makeEmailSummaryFields = () => ({
|
|
16366
|
+
createdAt: exports_iso.datetime({ offset: true }).describe("When QA Wolf received or sent the email."),
|
|
16367
|
+
direction: emailDirectionSchema.describe("inbound for mail the workspace received, outbound for mail it sent."),
|
|
16368
|
+
from: namedAddressSchema,
|
|
16369
|
+
id: string2().min(1).describe("The id of the email. Pass it to email.get to read the body."),
|
|
16370
|
+
subject: string2().optional(),
|
|
16371
|
+
to: array(namedAddressSchema)
|
|
16372
|
+
});
|
|
16373
|
+
var makeEmailResourceSchema = () => resource({
|
|
16374
|
+
...makeEmailSummaryFields(),
|
|
16375
|
+
attachments: array(object({
|
|
16376
|
+
fileName: string2(),
|
|
16377
|
+
index: number2().int().min(0).describe("The position of the attachment in the email, from 0. Pass it to email.getAttachment when file names repeat."),
|
|
16378
|
+
type: string2().optional().describe("The MIME type.")
|
|
16379
|
+
})).optional().describe("Attachment names, positions, and types. Read the content with email.getAttachment."),
|
|
16380
|
+
bcc: array(namedAddressSchema).optional(),
|
|
16381
|
+
cc: array(namedAddressSchema).optional(),
|
|
16382
|
+
html: string2().optional().describe("The HTML body."),
|
|
16383
|
+
links: array(string2()).describe("The links of the email, in order: HTML anchors, then images, then bare URLs of the plain text body. Follow one to confirm a sign-up or reset a password."),
|
|
16384
|
+
replyTo: array(namedAddressSchema).optional(),
|
|
16385
|
+
text: string2().optional().describe("The plain text body.")
|
|
16386
|
+
}, { urlFieldDescription: emailUrlFieldDescription });
|
|
16387
|
+
|
|
15754
16388
|
// node_modules/@qawolf/api-contracts/dist/v1/identity/organization.js
|
|
15755
16389
|
var identityOrganization = object({
|
|
15756
16390
|
id: string2(),
|
|
@@ -15793,6 +16427,10 @@ var identityResponse = union([
|
|
|
15793
16427
|
user: never().optional()
|
|
15794
16428
|
}).transform(({ organization }) => ({ organization }))
|
|
15795
16429
|
]);
|
|
16430
|
+
// node_modules/@qawolf/api-contracts/dist/v1/identity/organizations.js
|
|
16431
|
+
var identityOrganizationsResponse = object({
|
|
16432
|
+
organizations: array(identityOrganization)
|
|
16433
|
+
});
|
|
15796
16434
|
// node_modules/@qawolf/api-contracts/dist/v1/ids.js
|
|
15797
16435
|
var defaultIdSchemas = {
|
|
15798
16436
|
aiTask: string2().min(1).describe("The id of the AI task."),
|
|
@@ -15806,14 +16444,6 @@ var defaultIdSchemas = {
|
|
|
15806
16444
|
workspace: string2().min(1).describe("The id of the workspace.")
|
|
15807
16445
|
};
|
|
15808
16446
|
|
|
15809
|
-
// node_modules/@qawolf/api-contracts/dist/v1/resource.js
|
|
15810
|
-
function resource(fields, { urlFieldDescription = "Absolute URL." } = {}) {
|
|
15811
|
-
return object({
|
|
15812
|
-
...fields,
|
|
15813
|
-
url: url().describe(urlFieldDescription)
|
|
15814
|
-
});
|
|
15815
|
-
}
|
|
15816
|
-
|
|
15817
16447
|
// node_modules/@qawolf/api-contracts/dist/v1/run/flowSelection.js
|
|
15818
16448
|
var makeFlowSelectionSchema = (ids) => object({
|
|
15819
16449
|
flowIds: array(ids.flow).default([]),
|
|
@@ -16343,6 +16973,131 @@ var makeAutomateContract = (ids) => {
|
|
|
16343
16973
|
};
|
|
16344
16974
|
};
|
|
16345
16975
|
|
|
16976
|
+
// node_modules/@qawolf/api-contracts/dist/v1/pagination.js
|
|
16977
|
+
var makePaginationInputFields = (limits) => ({
|
|
16978
|
+
cursor: string2().min(1).optional().describe("Opaque cursor copied from the previous response's nextCursor. Omit to fetch the first page."),
|
|
16979
|
+
limit: number2().int().min(1).max(limits.maxLimit).default(limits.defaultLimit).describe(`Maximum rows per page. Defaults to ${limits.defaultLimit}, capped at ${limits.maxLimit}.`)
|
|
16980
|
+
});
|
|
16981
|
+
var nextCursorSchema = string2().min(1).optional().describe("Repeat the request with this value as cursor to fetch the next page. Absent when there are no further results.");
|
|
16982
|
+
|
|
16983
|
+
// node_modules/@qawolf/api-contracts/dist/v1/email/find.js
|
|
16984
|
+
var makeFindEmailsContract = (ids) => {
|
|
16985
|
+
const input = object({
|
|
16986
|
+
address: email2().optional().describe("Only emails delivered to this exact address (inbound) or sent from it (outbound). Mail to a plus-suffixed address such as inbox+abc@example.com is found by that full address, not by inbox@example.com."),
|
|
16987
|
+
createdAfter: exports_iso.datetime({ offset: true }).optional().describe("Only include emails received or sent after this instant, e.g. since a flow submitted a sign-up form."),
|
|
16988
|
+
direction: emailDirectionSchema.default("inbound").describe("inbound lists the mail the workspace received: its inbox. outbound lists the mail it sent."),
|
|
16989
|
+
workspaceId: ids.workspace.optional().describe("The workspace whose emails to list. Required when authenticating with an organization or user API key."),
|
|
16990
|
+
...makePaginationInputFields({ defaultLimit: 20, maxLimit: 50 })
|
|
16991
|
+
});
|
|
16992
|
+
const output = object({
|
|
16993
|
+
emails: array(resource(makeEmailSummaryFields(), {
|
|
16994
|
+
urlFieldDescription: emailUrlFieldDescription
|
|
16995
|
+
})).describe("The workspace's emails, newest first. Bodies are available via email.get."),
|
|
16996
|
+
nextCursor: nextCursorSchema
|
|
16997
|
+
});
|
|
16998
|
+
return {
|
|
16999
|
+
description: "List the workspace's inbox, or its sent mail, newest first. Read a message body with email.get.",
|
|
17000
|
+
input,
|
|
17001
|
+
kind: "read",
|
|
17002
|
+
name: "email.find",
|
|
17003
|
+
output
|
|
17004
|
+
};
|
|
17005
|
+
};
|
|
17006
|
+
|
|
17007
|
+
// node_modules/@qawolf/api-contracts/dist/v1/email/get.js
|
|
17008
|
+
var makeGetEmailContract = (ids) => {
|
|
17009
|
+
const input = object({
|
|
17010
|
+
id: string2().min(1).describe("The id of the email, from email.find."),
|
|
17011
|
+
workspaceId: ids.workspace.optional().describe("The workspace the email belongs to. Required when authenticating with an organization or user API key.")
|
|
17012
|
+
});
|
|
17013
|
+
const output = makeEmailResourceSchema();
|
|
17014
|
+
return {
|
|
17015
|
+
description: "Read one email of the workspace, with its plain text and HTML bodies. Use it to pull a sign-in code or a verification link out of a message.",
|
|
17016
|
+
input,
|
|
17017
|
+
kind: "read",
|
|
17018
|
+
name: "email.get",
|
|
17019
|
+
output
|
|
17020
|
+
};
|
|
17021
|
+
};
|
|
17022
|
+
|
|
17023
|
+
// node_modules/@qawolf/api-contracts/dist/v1/email/getAttachment.js
|
|
17024
|
+
var makeGetEmailAttachmentContract = (ids) => {
|
|
17025
|
+
const input = object({
|
|
17026
|
+
fileName: string2().min(1).optional().describe("The attachment's file name, as email.get lists it. The first attachment with that name is returned."),
|
|
17027
|
+
id: string2().min(1).describe("The id of the email, from email.find."),
|
|
17028
|
+
index: number2().int().min(0).optional().describe("The attachment's position in the email, from 0, as email.get lists it. Use it when file names repeat."),
|
|
17029
|
+
workspaceId: ids.workspace.optional().describe("The workspace the email belongs to. Required when authenticating with an organization or user API key.")
|
|
17030
|
+
}).refine((input) => input.fileName === undefined !== (input.index === undefined), { message: "Provide fileName or index, not both." });
|
|
17031
|
+
const output = resource({
|
|
17032
|
+
contentBase64: string2().describe("The file content, base64 encoded."),
|
|
17033
|
+
fileName: string2(),
|
|
17034
|
+
index: number2().int().min(0).describe("The position of the attachment in the email, from 0."),
|
|
17035
|
+
type: string2().optional().describe("The MIME type.")
|
|
17036
|
+
}, { urlFieldDescription: emailUrlFieldDescription });
|
|
17037
|
+
return {
|
|
17038
|
+
description: "Read one attachment of a workspace email as base64 content, by file name or by position. email.get lists both.",
|
|
17039
|
+
input,
|
|
17040
|
+
kind: "read",
|
|
17041
|
+
name: "email.getAttachment",
|
|
17042
|
+
output
|
|
17043
|
+
};
|
|
17044
|
+
};
|
|
17045
|
+
|
|
17046
|
+
// node_modules/@qawolf/api-contracts/dist/v1/email/listAddresses.js
|
|
17047
|
+
var makeListEmailAddressesContract = (ids) => {
|
|
17048
|
+
const input = object({
|
|
17049
|
+
workspaceId: ids.workspace.optional().describe("The workspace whose inbox addresses to list. Required when authenticating with an organization or user API key."),
|
|
17050
|
+
...makePaginationInputFields({ defaultLimit: 20, maxLimit: 50 })
|
|
17051
|
+
});
|
|
17052
|
+
const output = object({
|
|
17053
|
+
addresses: array(resource({
|
|
17054
|
+
address: string2().describe("The inbox address. Mail sent to any plus-suffixed form, such as inbox+abc@example.com, lands in the same inbox."),
|
|
17055
|
+
isDefault: boolean2().describe("Whether this is the workspace's default inbox address.")
|
|
17056
|
+
}, { urlFieldDescription: emailUrlFieldDescription })).describe("The workspace's permanent inbox addresses, alphabetical."),
|
|
17057
|
+
nextCursor: nextCursorSchema
|
|
17058
|
+
});
|
|
17059
|
+
return {
|
|
17060
|
+
description: "List the workspace's inbox addresses, alphabetical. A flow can sign up with a plus-suffixed form of any of them, and email.find reads what arrives.",
|
|
17061
|
+
input,
|
|
17062
|
+
kind: "read",
|
|
17063
|
+
name: "email.listAddresses",
|
|
17064
|
+
output
|
|
17065
|
+
};
|
|
17066
|
+
};
|
|
17067
|
+
|
|
17068
|
+
// node_modules/@qawolf/api-contracts/dist/v1/email/send.js
|
|
17069
|
+
var recipientsSchema = array(email2()).max(50);
|
|
17070
|
+
var maxAttachments = 10;
|
|
17071
|
+
var maxEmailAttachmentsBase64Length = 30 * 1024 * 1024;
|
|
17072
|
+
var attachmentsBase64Length = (attachments) => Object.values(attachments).reduce((total, content) => total + content.length, 0);
|
|
17073
|
+
var makeSendEmailContract = (ids) => {
|
|
17074
|
+
const input = object({
|
|
17075
|
+
attachments: record(string2().min(1), base642()).refine((attachments) => Object.keys(attachments).length <= maxAttachments, { message: `Send at most ${maxAttachments} attachments.` }).refine((attachments) => attachmentsBase64Length(attachments) <= maxEmailAttachmentsBase64Length, { message: "Send at most 30 MB of attachments." }).optional().describe("Files to attach, as file name to base64 content, at most 30 MB in total. The MIME type comes from the file name's extension."),
|
|
17076
|
+
bcc: recipientsSchema.optional(),
|
|
17077
|
+
cc: recipientsSchema.optional(),
|
|
17078
|
+
from: email2().optional().describe("The workspace inbox address to send from, or a plus-suffixed form of one. Defaults to the workspace's default inbox address. See email.listAddresses."),
|
|
17079
|
+
html: string2().optional().describe("The HTML body."),
|
|
17080
|
+
inReplyTo: string2().min(1).optional().describe("The id of an email of the workspace to reply to, from email.find. The reply threads under that message."),
|
|
17081
|
+
replyTo: recipientsSchema.optional().describe("Where replies should go instead of the from address."),
|
|
17082
|
+
subject: string2().min(1),
|
|
17083
|
+
text: string2().optional().describe("The plain text body."),
|
|
17084
|
+
to: recipientsSchema.min(1),
|
|
17085
|
+
workspaceId: ids.workspace.optional().describe("The workspace to send from. Required when authenticating with an organization or user API key.")
|
|
17086
|
+
}).refine((email) => email.html || email.text, {
|
|
17087
|
+
message: "Provide text, html, or both."
|
|
17088
|
+
});
|
|
17089
|
+
const output = resource(makeEmailSummaryFields(), {
|
|
17090
|
+
urlFieldDescription: emailUrlFieldDescription
|
|
17091
|
+
});
|
|
17092
|
+
return {
|
|
17093
|
+
description: "Send an email from one of the workspace's inbox addresses, for example to exercise a flow that reacts to incoming mail. Returns the sent email; read it back with email.get.",
|
|
17094
|
+
input,
|
|
17095
|
+
kind: "write",
|
|
17096
|
+
name: "email.send",
|
|
17097
|
+
output
|
|
17098
|
+
};
|
|
17099
|
+
};
|
|
17100
|
+
|
|
16346
17101
|
// node_modules/@qawolf/api-contracts/dist/v1/environment/deleteVariable.js
|
|
16347
17102
|
var makeDeleteEnvironmentVariableContract = (ids) => {
|
|
16348
17103
|
const input = object({
|
|
@@ -16361,13 +17116,6 @@ var makeDeleteEnvironmentVariableContract = (ids) => {
|
|
|
16361
17116
|
};
|
|
16362
17117
|
};
|
|
16363
17118
|
|
|
16364
|
-
// node_modules/@qawolf/api-contracts/dist/v1/pagination.js
|
|
16365
|
-
var makePaginationInputFields = (limits) => ({
|
|
16366
|
-
cursor: string2().min(1).optional().describe("Opaque cursor copied from the previous response's nextCursor. Omit to fetch the first page."),
|
|
16367
|
-
limit: number2().int().min(1).max(limits.maxLimit).default(limits.defaultLimit).describe(`Maximum rows per page. Defaults to ${limits.defaultLimit}, capped at ${limits.maxLimit}.`)
|
|
16368
|
-
});
|
|
16369
|
-
var nextCursorSchema = string2().min(1).optional().describe("Repeat the request with this value as cursor to fetch the next page. Absent when there are no further results.");
|
|
16370
|
-
|
|
16371
17119
|
// node_modules/@qawolf/api-contracts/dist/v1/environment/resource.js
|
|
16372
17120
|
var makeEnvironmentResourceSchema = (ids) => resource({
|
|
16373
17121
|
alias: string2().optional().describe("The environment's alias: a team-unique slug that team API keys can use in place of the id. Terminated environments have none."),
|
|
@@ -16539,15 +17287,27 @@ var makeCreateTagContract = (ids) => {
|
|
|
16539
17287
|
};
|
|
16540
17288
|
};
|
|
16541
17289
|
|
|
17290
|
+
// node_modules/@qawolf/api-contracts/dist/v1/flow/skippedFlows.js
|
|
17291
|
+
var makeSkippedFlowsSchema = (ids, { description, reason }) => array(object({
|
|
17292
|
+
flowId: ids.flow,
|
|
17293
|
+
name: string2().describe("The name of the flow."),
|
|
17294
|
+
reason
|
|
17295
|
+
})).describe(description);
|
|
17296
|
+
|
|
16542
17297
|
// node_modules/@qawolf/api-contracts/dist/v1/flow/addTag.js
|
|
16543
17298
|
var makeAddTagToFlowsContract = (ids) => {
|
|
16544
17299
|
const input = object({
|
|
16545
17300
|
flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows that receive the tag."),
|
|
16546
17301
|
tagName: string2().trim().min(1).describe("The name of an existing tag on the caller's team.")
|
|
16547
17302
|
});
|
|
16548
|
-
const output = makeTagResourceSchema()
|
|
17303
|
+
const output = makeTagResourceSchema().extend({
|
|
17304
|
+
skippedFlows: makeSkippedFlowsSchema(ids, {
|
|
17305
|
+
description: "Requested flows that already carried the tag, so nothing changed for them.",
|
|
17306
|
+
reason: _enum(["already-tagged"]).describe('Why the flow was skipped: "already-tagged" means the flow already carried the tag.')
|
|
17307
|
+
})
|
|
17308
|
+
});
|
|
16549
17309
|
return {
|
|
16550
|
-
description: "Assign an existing tag to the selected flows. Create tags with tag.create.",
|
|
17310
|
+
description: "Assign an existing tag to the selected flows. Create tags with tag.create. Flows that already carry the tag are reported in skippedFlows.",
|
|
16551
17311
|
input,
|
|
16552
17312
|
kind: "write",
|
|
16553
17313
|
name: "flow.addTag",
|
|
@@ -16587,6 +17347,27 @@ var makeListFlowsContract = (ids) => {
|
|
|
16587
17347
|
};
|
|
16588
17348
|
};
|
|
16589
17349
|
|
|
17350
|
+
// node_modules/@qawolf/api-contracts/dist/v1/flow/removeTag.js
|
|
17351
|
+
var makeRemoveTagFromFlowsContract = (ids) => {
|
|
17352
|
+
const input = object({
|
|
17353
|
+
flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows that lose the tag."),
|
|
17354
|
+
tagName: string2().trim().min(1).describe("The name of an existing tag on the caller's team.")
|
|
17355
|
+
});
|
|
17356
|
+
const output = makeTagResourceSchema().extend({
|
|
17357
|
+
skippedFlows: makeSkippedFlowsSchema(ids, {
|
|
17358
|
+
description: "Requested flows that did not carry the tag, so nothing changed for them.",
|
|
17359
|
+
reason: _enum(["not-tagged"]).describe('Why the flow was skipped: "not-tagged" means the flow did not carry the tag.')
|
|
17360
|
+
})
|
|
17361
|
+
});
|
|
17362
|
+
return {
|
|
17363
|
+
description: "Remove a tag from the selected flows. Succeeds whether or not each flow carried the tag; the flows that did not are reported in skippedFlows.",
|
|
17364
|
+
input,
|
|
17365
|
+
kind: "write",
|
|
17366
|
+
name: "flow.removeTag",
|
|
17367
|
+
output
|
|
17368
|
+
};
|
|
17369
|
+
};
|
|
17370
|
+
|
|
16590
17371
|
// node_modules/@qawolf/api-contracts/dist/v1/flow/update.js
|
|
16591
17372
|
var makeUpdateFlowContract = (ids) => {
|
|
16592
17373
|
const input = object({
|
|
@@ -16731,9 +17512,27 @@ var makeCreateIssueContract = (ids) => {
|
|
|
16731
17512
|
};
|
|
16732
17513
|
};
|
|
16733
17514
|
|
|
16734
|
-
// node_modules/@qawolf/api-contracts/dist/v1/issue/
|
|
16735
|
-
var
|
|
16736
|
-
const
|
|
17515
|
+
// node_modules/@qawolf/api-contracts/dist/v1/issue/removeFlows.js
|
|
17516
|
+
var makeRemoveFlowsFromIssueContract = (ids) => {
|
|
17517
|
+
const input = object({
|
|
17518
|
+
flowIds: array(ids.flow).min(1).max(300).refine((flowIds) => new Set(flowIds).size === flowIds.length, "flowIds must be unique").describe("The flows the coverage request no longer covers."),
|
|
17519
|
+
issueId: ids.issue
|
|
17520
|
+
});
|
|
17521
|
+
const output = object({
|
|
17522
|
+
issue: makeIssueResourceSchema(ids)
|
|
17523
|
+
});
|
|
17524
|
+
return {
|
|
17525
|
+
description: "Remove flows from a coverage request owned by the caller's team. " + "Flows the request does not cover are left alone. " + "Bug and maintenance reports link to flows through the runs that reproduce them, so their flows cannot be set directly.",
|
|
17526
|
+
input,
|
|
17527
|
+
kind: "write",
|
|
17528
|
+
name: "issue.removeFlows",
|
|
17529
|
+
output
|
|
17530
|
+
};
|
|
17531
|
+
};
|
|
17532
|
+
|
|
17533
|
+
// node_modules/@qawolf/api-contracts/dist/v1/issue/update.js
|
|
17534
|
+
var makeUpdateIssueContract = (ids) => {
|
|
17535
|
+
const descriptionSchema = string2().describe("The issue description as plain text; each line becomes a paragraph. Markdown is not parsed.");
|
|
16737
17536
|
const nameSchema = string2().trim().min(1).max(255);
|
|
16738
17537
|
const input = object({
|
|
16739
17538
|
description: descriptionSchema.optional(),
|
|
@@ -17057,6 +17856,22 @@ var makeTerminateRunnerContract = () => {
|
|
|
17057
17856
|
};
|
|
17058
17857
|
};
|
|
17059
17858
|
|
|
17859
|
+
// node_modules/@qawolf/api-contracts/dist/v1/runner/list.js
|
|
17860
|
+
var makeListRunnersContract = () => {
|
|
17861
|
+
const input = object({});
|
|
17862
|
+
const output = object({
|
|
17863
|
+
outcome: literal("success"),
|
|
17864
|
+
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.")
|
|
17865
|
+
});
|
|
17866
|
+
return {
|
|
17867
|
+
description: "List the runners running on the caller's team right now. Takes no arguments: the API key already names the team. 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.",
|
|
17868
|
+
input,
|
|
17869
|
+
kind: "read",
|
|
17870
|
+
name: "runner.list",
|
|
17871
|
+
output
|
|
17872
|
+
};
|
|
17873
|
+
};
|
|
17874
|
+
|
|
17060
17875
|
// node_modules/@qawolf/api-contracts/dist/v1/runner/promoteSnapshot.js
|
|
17061
17876
|
var maxSnapshotPathLength = 1024;
|
|
17062
17877
|
var makePromoteSnapshotOnRunnerContract = () => {
|
|
@@ -17252,6 +18067,13 @@ var makeContractsV1 = (ids) => {
|
|
|
17252
18067
|
send: makeAgentSendContract(resolvedIds)
|
|
17253
18068
|
},
|
|
17254
18069
|
automate: makeAutomateContract(resolvedIds),
|
|
18070
|
+
email: {
|
|
18071
|
+
find: makeFindEmailsContract(resolvedIds),
|
|
18072
|
+
get: makeGetEmailContract(resolvedIds),
|
|
18073
|
+
getAttachment: makeGetEmailAttachmentContract(resolvedIds),
|
|
18074
|
+
listAddresses: makeListEmailAddressesContract(resolvedIds),
|
|
18075
|
+
send: makeSendEmailContract(resolvedIds)
|
|
18076
|
+
},
|
|
17255
18077
|
environment: {
|
|
17256
18078
|
create: makeCreateEnvironmentContract(resolvedIds),
|
|
17257
18079
|
deleteVariable: makeDeleteEnvironmentVariableContract(resolvedIds),
|
|
@@ -17265,6 +18087,7 @@ var makeContractsV1 = (ids) => {
|
|
|
17265
18087
|
flow: {
|
|
17266
18088
|
addTag: makeAddTagToFlowsContract(resolvedIds),
|
|
17267
18089
|
list: makeListFlowsContract(resolvedIds),
|
|
18090
|
+
removeTag: makeRemoveTagFromFlowsContract(resolvedIds),
|
|
17268
18091
|
update: makeUpdateFlowContract(resolvedIds)
|
|
17269
18092
|
},
|
|
17270
18093
|
issue: {
|
|
@@ -17272,6 +18095,7 @@ var makeContractsV1 = (ids) => {
|
|
|
17272
18095
|
create: makeCreateIssueContract(resolvedIds),
|
|
17273
18096
|
find: makeFindIssuesContract(resolvedIds),
|
|
17274
18097
|
get: makeGetIssueContract(resolvedIds),
|
|
18098
|
+
removeFlows: makeRemoveFlowsFromIssueContract(resolvedIds),
|
|
17275
18099
|
update: makeUpdateIssueContract(resolvedIds)
|
|
17276
18100
|
},
|
|
17277
18101
|
run: {
|
|
@@ -17289,6 +18113,7 @@ var makeContractsV1 = (ids) => {
|
|
|
17289
18113
|
inspect: makeInspectOnRunnerContract(),
|
|
17290
18114
|
inspectMobile: makeInspectMobileOnRunnerContract(),
|
|
17291
18115
|
launch: makeLaunchRunnerContract(),
|
|
18116
|
+
list: makeListRunnersContract(),
|
|
17292
18117
|
performAction: makePerformActionOnRunnerContract(),
|
|
17293
18118
|
promoteSnapshot: makePromoteSnapshotOnRunnerContract(),
|
|
17294
18119
|
readJournal: makeReadRunnerJournalContract(),
|
|
@@ -17305,19 +18130,70 @@ var makeContractsV1 = (ids) => {
|
|
|
17305
18130
|
};
|
|
17306
18131
|
var publicContractsV1 = makeContractsV1(defaultIdSchemas);
|
|
17307
18132
|
|
|
18133
|
+
// src/shell/platform/organizations.ts
|
|
18134
|
+
function parseOrganizationsResponse(json) {
|
|
18135
|
+
const parsed = identityOrganizationsResponse.safeParse(json);
|
|
18136
|
+
return parsed.success ? parsed.data.organizations : undefined;
|
|
18137
|
+
}
|
|
18138
|
+
|
|
18139
|
+
// src/shell/platform/getAccessibleOrganizations.ts
|
|
18140
|
+
var timeoutMs2 = 1e4;
|
|
18141
|
+
async function getAccessibleOrganizations(apiKey, deps) {
|
|
18142
|
+
const url = `${deps.baseUrl}/api/v0/identity/organizations`;
|
|
18143
|
+
let response;
|
|
18144
|
+
try {
|
|
18145
|
+
response = await deps.fetch(url, {
|
|
18146
|
+
headers: { Authorization: `Bearer ${apiKey}` },
|
|
18147
|
+
signal: AbortSignal.timeout(timeoutMs2)
|
|
18148
|
+
});
|
|
18149
|
+
} catch (error) {
|
|
18150
|
+
if (isTimeoutError(error)) {
|
|
18151
|
+
return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs2 } };
|
|
18152
|
+
}
|
|
18153
|
+
return { ok: false, error: { kind: "network", cause: toError(error) } };
|
|
18154
|
+
}
|
|
18155
|
+
if (!response.ok) {
|
|
18156
|
+
const body = await response.text().catch(() => "");
|
|
18157
|
+
return {
|
|
18158
|
+
ok: false,
|
|
18159
|
+
error: { kind: "http", status: response.status, body }
|
|
18160
|
+
};
|
|
18161
|
+
}
|
|
18162
|
+
let json;
|
|
18163
|
+
try {
|
|
18164
|
+
json = await response.json();
|
|
18165
|
+
} catch (error) {
|
|
18166
|
+
if (isTimeoutError(error)) {
|
|
18167
|
+
return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs2 } };
|
|
18168
|
+
}
|
|
18169
|
+
json = undefined;
|
|
18170
|
+
}
|
|
18171
|
+
const organizations = parseOrganizationsResponse(json);
|
|
18172
|
+
if (!organizations) {
|
|
18173
|
+
return {
|
|
18174
|
+
ok: false,
|
|
18175
|
+
error: {
|
|
18176
|
+
kind: "parse",
|
|
18177
|
+
cause: Error("organizations response did not match the contract")
|
|
18178
|
+
}
|
|
18179
|
+
};
|
|
18180
|
+
}
|
|
18181
|
+
return { ok: true, data: organizations };
|
|
18182
|
+
}
|
|
18183
|
+
|
|
17308
18184
|
// src/shell/platform/getIdentity.ts
|
|
17309
|
-
var
|
|
18185
|
+
var timeoutMs3 = 1e4;
|
|
17310
18186
|
async function getIdentity(apiKey, deps) {
|
|
17311
18187
|
const url = `${deps.baseUrl}/api/v0/identity`;
|
|
17312
18188
|
let response;
|
|
17313
18189
|
try {
|
|
17314
18190
|
response = await deps.fetch(url, {
|
|
17315
18191
|
headers: { Authorization: `Bearer ${apiKey}` },
|
|
17316
|
-
signal: AbortSignal.timeout(
|
|
18192
|
+
signal: AbortSignal.timeout(timeoutMs3)
|
|
17317
18193
|
});
|
|
17318
18194
|
} catch (error) {
|
|
17319
18195
|
if (isTimeoutError(error)) {
|
|
17320
|
-
return { ok: false, error: { kind: "timeout", timeoutMs } };
|
|
18196
|
+
return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs3 } };
|
|
17321
18197
|
}
|
|
17322
18198
|
return {
|
|
17323
18199
|
ok: false,
|
|
@@ -17336,7 +18212,7 @@ async function getIdentity(apiKey, deps) {
|
|
|
17336
18212
|
json = await response.json();
|
|
17337
18213
|
} catch (error) {
|
|
17338
18214
|
if (isTimeoutError(error)) {
|
|
17339
|
-
return { ok: false, error: { kind: "timeout", timeoutMs } };
|
|
18215
|
+
return { ok: false, error: { kind: "timeout", timeoutMs: timeoutMs3 } };
|
|
17340
18216
|
}
|
|
17341
18217
|
json = undefined;
|
|
17342
18218
|
}
|
|
@@ -17347,6 +18223,24 @@ async function getIdentity(apiKey, deps) {
|
|
|
17347
18223
|
return { ok: true, data: parsed.data };
|
|
17348
18224
|
}
|
|
17349
18225
|
|
|
18226
|
+
// src/shell/platform/identityMethods.ts
|
|
18227
|
+
function createIdentityMethods(apiKey, deps, backoffMs) {
|
|
18228
|
+
return {
|
|
18229
|
+
getIdentity: () => requestWithRetry({
|
|
18230
|
+
call: () => getIdentity(apiKey, deps),
|
|
18231
|
+
backoffMs,
|
|
18232
|
+
describe: describeIdentityError,
|
|
18233
|
+
sleep: deps.sleep
|
|
18234
|
+
}),
|
|
18235
|
+
getAccessibleOrganizations: () => requestWithRetry({
|
|
18236
|
+
call: () => getAccessibleOrganizations(apiKey, deps),
|
|
18237
|
+
backoffMs,
|
|
18238
|
+
describe: describeIdentityError,
|
|
18239
|
+
sleep: deps.sleep
|
|
18240
|
+
})
|
|
18241
|
+
};
|
|
18242
|
+
}
|
|
18243
|
+
|
|
17350
18244
|
// src/shell/platform/types.ts
|
|
17351
18245
|
var environmentListItemSchema = object({
|
|
17352
18246
|
envId: string2(),
|
|
@@ -17474,7 +18368,7 @@ function isUnsafeSegment(segment) {
|
|
|
17474
18368
|
|
|
17475
18369
|
// src/shell/platform/teamStorageAssetSnapshot.ts
|
|
17476
18370
|
import { randomBytes as randomBytes2 } from "node:crypto";
|
|
17477
|
-
import { join as
|
|
18371
|
+
import { join as join10, sep as sep2 } from "node:path";
|
|
17478
18372
|
function mintAssetSnapshotPath(assetsAbs, label) {
|
|
17479
18373
|
return `${assetsAbs}.${label}-${randomBytes2(8).toString("hex")}`;
|
|
17480
18374
|
}
|
|
@@ -17524,9 +18418,9 @@ function expectedEntries(filePaths) {
|
|
|
17524
18418
|
return entries;
|
|
17525
18419
|
}
|
|
17526
18420
|
async function collectEntries(root, relDir, out, fs) {
|
|
17527
|
-
const current = relDir ?
|
|
18421
|
+
const current = relDir ? join10(root, relDir) : root;
|
|
17528
18422
|
for (const entry of await fs.readdirWithTypes(current)) {
|
|
17529
|
-
const relPath = relDir ?
|
|
18423
|
+
const relPath = relDir ? join10(relDir, entry.name) : entry.name;
|
|
17530
18424
|
out.add(relPath);
|
|
17531
18425
|
if (entry.isDirectory()) {
|
|
17532
18426
|
await collectEntries(root, relPath, out, fs);
|
|
@@ -17540,7 +18434,7 @@ function setsEqual(left, right) {
|
|
|
17540
18434
|
}
|
|
17541
18435
|
|
|
17542
18436
|
// src/shell/platform/teamStorageAssetReuse.ts
|
|
17543
|
-
import { join as
|
|
18437
|
+
import { join as join11 } from "node:path";
|
|
17544
18438
|
async function reusableAssetPaths(assetsAbs, safeFiles, manifest, fs) {
|
|
17545
18439
|
const reusable = new Set;
|
|
17546
18440
|
for (const { file, relativePath } of safeFiles) {
|
|
@@ -17556,20 +18450,20 @@ async function canReuseAsset(assetsAbs, relativePath, file, manifest, fs) {
|
|
|
17556
18450
|
const previous = manifest.get(relativePath);
|
|
17557
18451
|
if (previous?.etag !== file.etag)
|
|
17558
18452
|
return false;
|
|
17559
|
-
return fs.pathExists(
|
|
18453
|
+
return fs.pathExists(join11(assetsAbs, relativePath));
|
|
17560
18454
|
}
|
|
17561
18455
|
|
|
17562
18456
|
// src/shell/platform/writeAssetSnapshot.ts
|
|
17563
|
-
import { dirname as dirname3, join as
|
|
18457
|
+
import { dirname as dirname3, join as join12 } from "node:path";
|
|
17564
18458
|
async function writeAssetSnapshot(args) {
|
|
17565
18459
|
let downloadedCount = 0;
|
|
17566
18460
|
const total = args.safeFiles.filter(({ relativePath }) => !args.reusable.has(relativePath)).length;
|
|
17567
18461
|
await args.deps.fs.mkdir(args.tmpAssets, { recursive: true });
|
|
17568
18462
|
for (const { file, relativePath } of args.safeFiles) {
|
|
17569
|
-
const dest =
|
|
18463
|
+
const dest = join12(args.tmpAssets, relativePath);
|
|
17570
18464
|
await args.deps.fs.mkdir(dirname3(dest), { recursive: true });
|
|
17571
18465
|
if (args.reusable.has(relativePath)) {
|
|
17572
|
-
await args.deps.fs.copyFile(
|
|
18466
|
+
await args.deps.fs.copyFile(join12(args.assetsAbs, relativePath), dest);
|
|
17573
18467
|
continue;
|
|
17574
18468
|
}
|
|
17575
18469
|
args.deps.onProgress?.({ current: downloadedCount + 1, total });
|
|
@@ -17646,14 +18540,7 @@ function createPlatformClient(apiKey, deps) {
|
|
|
17646
18540
|
return { ok: true, value: { signedUrl: result.value.url } };
|
|
17647
18541
|
}
|
|
17648
18542
|
return {
|
|
17649
|
-
|
|
17650
|
-
return requestWithRetry({
|
|
17651
|
-
call: () => getIdentity(apiKey, deps),
|
|
17652
|
-
backoffMs: requestBackoffMs2,
|
|
17653
|
-
describe: describeIdentityError,
|
|
17654
|
-
sleep: deps.sleep
|
|
17655
|
-
});
|
|
17656
|
-
},
|
|
18543
|
+
...createIdentityMethods(apiKey, deps, requestBackoffMs2),
|
|
17657
18544
|
getFlowsBundleUrl: getFlowsBundleUrlImpl,
|
|
17658
18545
|
callPublicApi: makeCallPublicApiMethod(trpc, deps, requestBackoffMs2),
|
|
17659
18546
|
async getEnvVars(envId) {
|
|
@@ -18352,6 +19239,86 @@ class V {
|
|
|
18352
19239
|
}
|
|
18353
19240
|
}
|
|
18354
19241
|
}
|
|
19242
|
+
function p$1(l, e) {
|
|
19243
|
+
if (l === undefined || e.length === 0)
|
|
19244
|
+
return 0;
|
|
19245
|
+
const i = e.findIndex((s) => s.value === l);
|
|
19246
|
+
return i !== -1 ? i : 0;
|
|
19247
|
+
}
|
|
19248
|
+
function g(l, e) {
|
|
19249
|
+
return (e.label ?? String(e.value)).toLowerCase().includes(l.toLowerCase());
|
|
19250
|
+
}
|
|
19251
|
+
function m2(l, e) {
|
|
19252
|
+
if (e)
|
|
19253
|
+
return l ? e : e[0];
|
|
19254
|
+
}
|
|
19255
|
+
var T$1 = class T extends V {
|
|
19256
|
+
filteredOptions;
|
|
19257
|
+
multiple;
|
|
19258
|
+
isNavigating = false;
|
|
19259
|
+
selectedValues = [];
|
|
19260
|
+
focusedValue;
|
|
19261
|
+
#e = 0;
|
|
19262
|
+
#s = "";
|
|
19263
|
+
#t;
|
|
19264
|
+
#i;
|
|
19265
|
+
#n;
|
|
19266
|
+
get cursor() {
|
|
19267
|
+
return this.#e;
|
|
19268
|
+
}
|
|
19269
|
+
get userInputWithCursor() {
|
|
19270
|
+
if (!this.userInput)
|
|
19271
|
+
return styleText(["inverse", "hidden"], "_");
|
|
19272
|
+
if (this._cursor >= this.userInput.length)
|
|
19273
|
+
return `${this.userInput}█`;
|
|
19274
|
+
const e = this.userInput.slice(0, this._cursor), [t, ...i] = this.userInput.slice(this._cursor);
|
|
19275
|
+
return `${e}${styleText("inverse", t)}${i.join("")}`;
|
|
19276
|
+
}
|
|
19277
|
+
get options() {
|
|
19278
|
+
return typeof this.#i == "function" ? this.#i() : this.#i;
|
|
19279
|
+
}
|
|
19280
|
+
constructor(e) {
|
|
19281
|
+
super(e), this.#i = e.options, this.#n = e.placeholder;
|
|
19282
|
+
const t = this.options;
|
|
19283
|
+
this.filteredOptions = [...t], this.multiple = e.multiple === true, this.#t = typeof e.options == "function" ? e.filter : e.filter ?? g;
|
|
19284
|
+
let i;
|
|
19285
|
+
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)
|
|
19286
|
+
for (const s of i) {
|
|
19287
|
+
const n = t.findIndex((o) => o.value === s);
|
|
19288
|
+
n !== -1 && (this.toggleSelected(s), this.#e = n);
|
|
19289
|
+
}
|
|
19290
|
+
this.focusedValue = this.options[this.#e]?.value, this.on("key", (s, n) => this.#l(s, n)), this.on("userInput", (s) => this.#u(s));
|
|
19291
|
+
}
|
|
19292
|
+
_isActionKey(e, t) {
|
|
19293
|
+
return e === "\t" || this.multiple && this.isNavigating && t.name === "space" && e !== undefined && e !== "";
|
|
19294
|
+
}
|
|
19295
|
+
#l(e, t) {
|
|
19296
|
+
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));
|
|
19297
|
+
if (t.name === "tab" && o && f) {
|
|
19298
|
+
this.userInput === "\t" && this._clearUserInput(), this._setUserInput(u, true), this.isNavigating = false;
|
|
19299
|
+
return;
|
|
19300
|
+
}
|
|
19301
|
+
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);
|
|
19302
|
+
}
|
|
19303
|
+
deselectAll() {
|
|
19304
|
+
this.selectedValues = [];
|
|
19305
|
+
}
|
|
19306
|
+
toggleSelected(e) {
|
|
19307
|
+
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]);
|
|
19308
|
+
}
|
|
19309
|
+
#u(e) {
|
|
19310
|
+
if (e !== this.#s) {
|
|
19311
|
+
this.#s = e;
|
|
19312
|
+
const t = this.options;
|
|
19313
|
+
e && this.#t ? this.filteredOptions = t.filter((n) => this.#t?.(e, n)) : this.filteredOptions = [...t];
|
|
19314
|
+
const i = p$1(this.focusedValue, this.filteredOptions);
|
|
19315
|
+
this.#e = findCursor(i, 0, this.filteredOptions);
|
|
19316
|
+
const s = this.filteredOptions[this.#e];
|
|
19317
|
+
s && !s.disabled ? this.focusedValue = s.value : this.focusedValue = undefined, this.multiple || (this.focusedValue !== undefined ? this.toggleSelected(this.focusedValue) : this.deselectAll());
|
|
19318
|
+
}
|
|
19319
|
+
}
|
|
19320
|
+
};
|
|
19321
|
+
|
|
18355
19322
|
class r extends V {
|
|
18356
19323
|
get cursor() {
|
|
18357
19324
|
return this.value ? 0 : 1;
|
|
@@ -18538,6 +19505,89 @@ var limitOptions = ({
|
|
|
18538
19505
|
b.push(n);
|
|
18539
19506
|
return u && b.push(C), b;
|
|
18540
19507
|
};
|
|
19508
|
+
function P(t) {
|
|
19509
|
+
return t.label ?? String(t.value ?? "");
|
|
19510
|
+
}
|
|
19511
|
+
function E(t, c) {
|
|
19512
|
+
if (!t)
|
|
19513
|
+
return true;
|
|
19514
|
+
const n = (c.label ?? String(c.value ?? "")).toLowerCase(), i = (c.hint ?? "").toLowerCase(), l = String(c.value).toLowerCase(), o = t.toLowerCase();
|
|
19515
|
+
return n.includes(o) || i.includes(o) || l.includes(o);
|
|
19516
|
+
}
|
|
19517
|
+
function N(t, c) {
|
|
19518
|
+
const n = [];
|
|
19519
|
+
for (const i of c)
|
|
19520
|
+
t.includes(i.value) && n.push(i);
|
|
19521
|
+
return n;
|
|
19522
|
+
}
|
|
19523
|
+
var autocomplete = (t) => new T$1({
|
|
19524
|
+
options: t.options,
|
|
19525
|
+
initialValue: t.initialValue ? [t.initialValue] : undefined,
|
|
19526
|
+
initialUserInput: t.initialUserInput,
|
|
19527
|
+
placeholder: t.placeholder,
|
|
19528
|
+
filter: t.filter ?? ((n, i) => E(n, i)),
|
|
19529
|
+
signal: t.signal,
|
|
19530
|
+
input: t.input,
|
|
19531
|
+
output: t.output,
|
|
19532
|
+
validate: t.validate,
|
|
19533
|
+
render() {
|
|
19534
|
+
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) => {
|
|
19535
|
+
const a = P(r), u = r.hint && r.value === this.focusedValue ? styleText2("dim", ` (${r.hint})`) : "";
|
|
19536
|
+
switch (s) {
|
|
19537
|
+
case "active":
|
|
19538
|
+
return `${styleText2("green", S_RADIO_ACTIVE)} ${a}${u}`;
|
|
19539
|
+
case "inactive":
|
|
19540
|
+
return `${styleText2("dim", S_RADIO_INACTIVE)} ${styleText2("dim", a)}`;
|
|
19541
|
+
case "disabled":
|
|
19542
|
+
return `${styleText2("gray", S_RADIO_INACTIVE)} ${styleText2(["strikethrough", "gray"], a)}`;
|
|
19543
|
+
}
|
|
19544
|
+
};
|
|
19545
|
+
switch (this.state) {
|
|
19546
|
+
case "submit": {
|
|
19547
|
+
const r = N(this.selectedValues, o), s = r.length > 0 ? ` ${styleText2("dim", r.map(P).join(", "))}` : "", a = n ? styleText2("gray", S_BAR) : "";
|
|
19548
|
+
return `${i.join(`
|
|
19549
|
+
`)}
|
|
19550
|
+
${a}${s}`;
|
|
19551
|
+
}
|
|
19552
|
+
case "cancel": {
|
|
19553
|
+
const r = l ? ` ${styleText2(["strikethrough", "dim"], l)}` : "", s = n ? styleText2("gray", S_BAR) : "";
|
|
19554
|
+
return `${i.join(`
|
|
19555
|
+
`)}
|
|
19556
|
+
${s}${r}`;
|
|
19557
|
+
}
|
|
19558
|
+
default: {
|
|
19559
|
+
const r = this.state === "error" ? "yellow" : "cyan", s = n ? `${styleText2(r, S_BAR)} ` : "", a = n ? styleText2(r, S_BAR_END) : "";
|
|
19560
|
+
let u = "";
|
|
19561
|
+
if (this.isNavigating || p) {
|
|
19562
|
+
const d = p ? m : l;
|
|
19563
|
+
u = d !== "" ? ` ${styleText2("dim", d)}` : "";
|
|
19564
|
+
} else
|
|
19565
|
+
u = ` ${this.userInputWithCursor}`;
|
|
19566
|
+
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)}`] : [];
|
|
19567
|
+
n && i.push(`${s.trimEnd()}`), i.push(`${s}${styleText2("dim", "Search:")}${u}${V}`, ...y, ...b);
|
|
19568
|
+
const v = [
|
|
19569
|
+
`${styleText2("dim", "↑/↓")} to select`,
|
|
19570
|
+
`${styleText2("dim", "Enter:")} confirm`,
|
|
19571
|
+
`${styleText2("dim", "Type:")} to search`
|
|
19572
|
+
], g = [`${s}${v.join(" • ")}`, a], O = this.filteredOptions.length === 0 ? [] : limitOptions({
|
|
19573
|
+
cursor: this.cursor,
|
|
19574
|
+
options: this.filteredOptions,
|
|
19575
|
+
columnPadding: n ? 3 : 0,
|
|
19576
|
+
rowPadding: i.length + g.length,
|
|
19577
|
+
style: (d, f) => $(d, d.disabled ? "disabled" : f ? "active" : "inactive"),
|
|
19578
|
+
maxItems: t.maxItems,
|
|
19579
|
+
output: t.output
|
|
19580
|
+
});
|
|
19581
|
+
return [
|
|
19582
|
+
...i,
|
|
19583
|
+
...O.map((d) => `${s}${d}`),
|
|
19584
|
+
...g
|
|
19585
|
+
].join(`
|
|
19586
|
+
`);
|
|
19587
|
+
}
|
|
19588
|
+
}
|
|
19589
|
+
}
|
|
19590
|
+
}).prompt();
|
|
18541
19591
|
var confirm = (i) => {
|
|
18542
19592
|
const a = i.active ?? "Yes", s = i.inactive ?? "No";
|
|
18543
19593
|
return new r({
|
|
@@ -18968,6 +20018,7 @@ function createStyledClack() {
|
|
|
18968
20018
|
cancel,
|
|
18969
20019
|
confirm,
|
|
18970
20020
|
password,
|
|
20021
|
+
autocomplete,
|
|
18971
20022
|
select,
|
|
18972
20023
|
isCancel,
|
|
18973
20024
|
spinner,
|
|
@@ -19227,11 +20278,50 @@ function createPassword({
|
|
|
19227
20278
|
};
|
|
19228
20279
|
}
|
|
19229
20280
|
|
|
20281
|
+
// src/core/textSearch.ts
|
|
20282
|
+
function normalizeForSearch(value) {
|
|
20283
|
+
return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
|
|
20284
|
+
}
|
|
20285
|
+
function matchesSearchTerm(search, fields) {
|
|
20286
|
+
const needle = normalizeForSearch(search);
|
|
20287
|
+
if (!needle)
|
|
20288
|
+
return true;
|
|
20289
|
+
return fields.some((field) => field !== undefined && normalizeForSearch(field).includes(needle));
|
|
20290
|
+
}
|
|
20291
|
+
function createSearchIndex(items, fieldsOf) {
|
|
20292
|
+
const indexed = new Map;
|
|
20293
|
+
for (const item of items) {
|
|
20294
|
+
indexed.set(item, fieldsOf(item).flatMap((field) => field === undefined ? [] : [normalizeForSearch(field)]));
|
|
20295
|
+
}
|
|
20296
|
+
let lastSearch;
|
|
20297
|
+
let needle = "";
|
|
20298
|
+
return (search, item) => {
|
|
20299
|
+
if (search !== lastSearch) {
|
|
20300
|
+
lastSearch = search;
|
|
20301
|
+
needle = normalizeForSearch(search);
|
|
20302
|
+
}
|
|
20303
|
+
if (!needle)
|
|
20304
|
+
return true;
|
|
20305
|
+
const fields = indexed.get(item);
|
|
20306
|
+
return fields ? fields.some((field) => field.includes(needle)) : matchesSearchTerm(search, fieldsOf(item));
|
|
20307
|
+
};
|
|
20308
|
+
}
|
|
20309
|
+
|
|
19230
20310
|
// src/shell/ui/renderers/select.ts
|
|
20311
|
+
var searchThreshold = 8;
|
|
20312
|
+
var searchPlaceholder = "Type to filter";
|
|
20313
|
+
function searchableFields(option) {
|
|
20314
|
+
return [option.label, option.hint, option.value];
|
|
20315
|
+
}
|
|
19231
20316
|
function createSelect({ mode, clack }) {
|
|
19232
20317
|
return async (message, options) => {
|
|
19233
20318
|
assertHumanMode(mode, "select");
|
|
19234
|
-
const value = await clack.
|
|
20319
|
+
const value = options.length > searchThreshold ? await clack.autocomplete({
|
|
20320
|
+
message,
|
|
20321
|
+
options: [...options],
|
|
20322
|
+
placeholder: searchPlaceholder,
|
|
20323
|
+
filter: createSearchIndex([...options], searchableFields)
|
|
20324
|
+
}) : await clack.select({ message, options: [...options] });
|
|
19235
20325
|
if (clack.isCancel(value))
|
|
19236
20326
|
return { ok: false };
|
|
19237
20327
|
return { ok: true, value };
|
|
@@ -19279,12 +20369,12 @@ function createVerboseContext(outputMode, isVerbose) {
|
|
|
19279
20369
|
|
|
19280
20370
|
// src/shell/npmRegistry.ts
|
|
19281
20371
|
var registryUrl = "https://registry.npmjs.org";
|
|
19282
|
-
var
|
|
20372
|
+
var timeoutMs4 = 3000;
|
|
19283
20373
|
async function fetchLatestVersion(packageName, deps = {}) {
|
|
19284
20374
|
const fetchFn = deps.fetchFn ?? globalThis.fetch;
|
|
19285
20375
|
try {
|
|
19286
20376
|
const response = await fetchFn(`${registryUrl}/${packageName}/latest`, {
|
|
19287
|
-
signal: AbortSignal.timeout(
|
|
20377
|
+
signal: AbortSignal.timeout(timeoutMs4)
|
|
19288
20378
|
});
|
|
19289
20379
|
if (!response.ok)
|
|
19290
20380
|
return;
|
|
@@ -19299,7 +20389,7 @@ async function fetchLatestVersion(packageName, deps = {}) {
|
|
|
19299
20389
|
}
|
|
19300
20390
|
|
|
19301
20391
|
// src/domains/updateCheck/updateCheck.ts
|
|
19302
|
-
import { join as
|
|
20392
|
+
import { join as join13 } from "node:path";
|
|
19303
20393
|
|
|
19304
20394
|
// src/core/version.ts
|
|
19305
20395
|
function parseRelease(version) {
|
|
@@ -19345,7 +20435,7 @@ function startUpdateCheck(deps) {
|
|
|
19345
20435
|
return;
|
|
19346
20436
|
if (!isNewerVersion(deps.currentVersion, latest))
|
|
19347
20437
|
return;
|
|
19348
|
-
const noticePath =
|
|
20438
|
+
const noticePath = join13(deps.configDir, noticeFile);
|
|
19349
20439
|
try {
|
|
19350
20440
|
const notified = (await deps.fs.readFile(noticePath)).trim();
|
|
19351
20441
|
if (notified === latest)
|
|
@@ -19367,7 +20457,7 @@ function startUpdateCheck(deps) {
|
|
|
19367
20457
|
// package.json
|
|
19368
20458
|
var package_default = {
|
|
19369
20459
|
name: "@qawolf/cli",
|
|
19370
|
-
version: "1.
|
|
20460
|
+
version: "1.25.0",
|
|
19371
20461
|
description: "Run and manage QA Wolf flows from the terminal, CI, or an AI agent",
|
|
19372
20462
|
keywords: [
|
|
19373
20463
|
"automation",
|
|
@@ -19438,7 +20528,7 @@ var package_default = {
|
|
|
19438
20528
|
"@clack/prompts": "1.5.1",
|
|
19439
20529
|
"@napi-rs/keyring": "1.3.0",
|
|
19440
20530
|
"@oxc-node/core": "0.1.0",
|
|
19441
|
-
"@qawolf/api-contracts": "0.
|
|
20531
|
+
"@qawolf/api-contracts": "0.47.0",
|
|
19442
20532
|
"@qawolf/emails": "1.1.1",
|
|
19443
20533
|
"@qawolf/flow-targets": "1.0.0",
|
|
19444
20534
|
"@qawolf/flows": "0.1.4",
|
|
@@ -19557,7 +20647,12 @@ function withAuthContext(signals, fn, deps = {}) {
|
|
|
19557
20647
|
await updateNotifier.notifyIfOutdated();
|
|
19558
20648
|
return;
|
|
19559
20649
|
}
|
|
19560
|
-
const platformClient = (deps.createPlatform ?? createPlatformClient)(resolved.key, {
|
|
20650
|
+
const platformClient = (deps.createPlatform ?? createPlatformClient)(resolved.key, {
|
|
20651
|
+
baseUrl: apiBaseUrl,
|
|
20652
|
+
fetch: globalThis.fetch,
|
|
20653
|
+
logger: ctx.log("trpc"),
|
|
20654
|
+
workspaceId: resolved.workspaceId
|
|
20655
|
+
});
|
|
19561
20656
|
try {
|
|
19562
20657
|
const result = await fn({
|
|
19563
20658
|
...ctx,
|
|
@@ -19578,28 +20673,15 @@ function withAuthContext(signals, fn, deps = {}) {
|
|
|
19578
20673
|
};
|
|
19579
20674
|
}
|
|
19580
20675
|
|
|
19581
|
-
// src/commands/auth/
|
|
19582
|
-
async function
|
|
19583
|
-
if (ctx.ui.mode !== "human") {
|
|
19584
|
-
ctx.ui.error(authMessages.login.nonInteractive);
|
|
19585
|
-
return { error: "non-interactive" };
|
|
19586
|
-
}
|
|
19587
|
-
const existing = await resolveApiKey(ctx.configDir, ctx.fs);
|
|
19588
|
-
if (existing) {
|
|
19589
|
-
const reauth = await ctx.ui.confirm(authMessages.login.reAuthPrompt);
|
|
19590
|
-
if (!reauth.ok || !reauth.value) {
|
|
19591
|
-
ctx.ui.info(authMessages.alreadyConfigured);
|
|
19592
|
-
return;
|
|
19593
|
-
}
|
|
19594
|
-
}
|
|
19595
|
-
ctx.ui.gap();
|
|
19596
|
-
ctx.ui.intro(authMessages.title);
|
|
20676
|
+
// src/commands/auth/loginApiKey.ts
|
|
20677
|
+
async function loginWithApiKey(ctx) {
|
|
19597
20678
|
const result = await ctx.ui.password(authMessages.promptApiKey, "Set QAWOLF_API_KEY to authenticate in non-interactive environments.");
|
|
19598
20679
|
if (!result.ok) {
|
|
19599
20680
|
ctx.ui.cancel(authMessages.cancelled);
|
|
19600
20681
|
return;
|
|
19601
20682
|
}
|
|
19602
|
-
|
|
20683
|
+
const apiKey = result.value.trim();
|
|
20684
|
+
if (!apiKey) {
|
|
19603
20685
|
ctx.ui.cancel(authMessages.cancelled);
|
|
19604
20686
|
return;
|
|
19605
20687
|
}
|
|
@@ -19608,7 +20690,7 @@ async function handleLogin(ctx) {
|
|
|
19608
20690
|
message: authMessages.verifying,
|
|
19609
20691
|
task: async () => {
|
|
19610
20692
|
const v = await validateApiKey({
|
|
19611
|
-
platformClient: createPlatformClient(
|
|
20693
|
+
platformClient: createPlatformClient(apiKey, {
|
|
19612
20694
|
baseUrl: ctx.apiBaseUrl,
|
|
19613
20695
|
fetch: globalThis.fetch
|
|
19614
20696
|
})
|
|
@@ -19619,7 +20701,7 @@ async function handleLogin(ctx) {
|
|
|
19619
20701
|
},
|
|
19620
20702
|
{
|
|
19621
20703
|
message: authMessages.storing,
|
|
19622
|
-
task: async () => saveApiKey(ctx.configDir,
|
|
20704
|
+
task: async () => saveApiKey(ctx.configDir, apiKey, ctx.fs)
|
|
19623
20705
|
}
|
|
19624
20706
|
], ([, saveResult]) => {
|
|
19625
20707
|
return saveResult.stored === "file" ? authMessages.storedFile : authMessages.storedKeychain;
|
|
@@ -19627,14 +20709,632 @@ async function handleLogin(ctx) {
|
|
|
19627
20709
|
ctx.ui.outro(authMessages.outroSuccess);
|
|
19628
20710
|
}
|
|
19629
20711
|
|
|
20712
|
+
// src/core/deviceAuth/pollState.ts
|
|
20713
|
+
var slowDownIncrementMs = 5000;
|
|
20714
|
+
var unreachableBackoffFactor = 2;
|
|
20715
|
+
function capToLifetime(delayMs, state, nowMs) {
|
|
20716
|
+
return Math.min(delayMs, Math.max(1, state.deadlineMs - nowMs));
|
|
20717
|
+
}
|
|
20718
|
+
function nextPollStep(state, response, nowMs) {
|
|
20719
|
+
if (response.kind === "tokens") {
|
|
20720
|
+
return { action: "done", tokens: response.tokens };
|
|
20721
|
+
}
|
|
20722
|
+
if (nowMs > state.deadlineMs) {
|
|
20723
|
+
return { action: "fail", reason: "timeout", detail: undefined };
|
|
20724
|
+
}
|
|
20725
|
+
switch (response.kind) {
|
|
20726
|
+
case "pending":
|
|
20727
|
+
return {
|
|
20728
|
+
action: "poll",
|
|
20729
|
+
delayMs: capToLifetime(state.intervalMs, state, nowMs),
|
|
20730
|
+
state
|
|
20731
|
+
};
|
|
20732
|
+
case "slow-down": {
|
|
20733
|
+
const slowed = {
|
|
20734
|
+
intervalMs: state.intervalMs + slowDownIncrementMs,
|
|
20735
|
+
deadlineMs: state.deadlineMs
|
|
20736
|
+
};
|
|
20737
|
+
return {
|
|
20738
|
+
action: "poll",
|
|
20739
|
+
delayMs: capToLifetime(slowed.intervalMs, slowed, nowMs),
|
|
20740
|
+
state: slowed
|
|
20741
|
+
};
|
|
20742
|
+
}
|
|
20743
|
+
case "denied":
|
|
20744
|
+
return { action: "fail", reason: "access-denied", detail: undefined };
|
|
20745
|
+
case "expired":
|
|
20746
|
+
return { action: "fail", reason: "expired", detail: undefined };
|
|
20747
|
+
case "unreachable": {
|
|
20748
|
+
const backedOff = {
|
|
20749
|
+
intervalMs: state.intervalMs * unreachableBackoffFactor,
|
|
20750
|
+
deadlineMs: state.deadlineMs
|
|
20751
|
+
};
|
|
20752
|
+
return {
|
|
20753
|
+
action: "poll",
|
|
20754
|
+
delayMs: capToLifetime(backedOff.intervalMs, backedOff, nowMs),
|
|
20755
|
+
state: backedOff
|
|
20756
|
+
};
|
|
20757
|
+
}
|
|
20758
|
+
case "error":
|
|
20759
|
+
return { action: "fail", reason: "network", detail: response.detail };
|
|
20760
|
+
}
|
|
20761
|
+
}
|
|
20762
|
+
|
|
20763
|
+
// src/domains/auth/deviceLogin.ts
|
|
20764
|
+
var cancelled = {
|
|
20765
|
+
ok: false,
|
|
20766
|
+
reason: "cancelled",
|
|
20767
|
+
detail: undefined
|
|
20768
|
+
};
|
|
20769
|
+
async function deviceLogin(deps) {
|
|
20770
|
+
if (deps.isCancelled())
|
|
20771
|
+
return cancelled;
|
|
20772
|
+
const authorization = await deps.requestAuthorization();
|
|
20773
|
+
if (!authorization.ok) {
|
|
20774
|
+
return { ok: false, reason: "unavailable", detail: authorization.error };
|
|
20775
|
+
}
|
|
20776
|
+
await deps.onPrompt(authorization.value);
|
|
20777
|
+
let state = {
|
|
20778
|
+
intervalMs: authorization.value.intervalSec * 1000,
|
|
20779
|
+
deadlineMs: deps.now() + authorization.value.expiresInSec * 1000
|
|
20780
|
+
};
|
|
20781
|
+
for (;; ) {
|
|
20782
|
+
if (deps.isCancelled())
|
|
20783
|
+
return cancelled;
|
|
20784
|
+
const response = await deps.pollToken(authorization.value.deviceCode);
|
|
20785
|
+
const step = nextPollStep(state, response, deps.now());
|
|
20786
|
+
if (step.action === "done")
|
|
20787
|
+
return { ok: true, tokens: step.tokens };
|
|
20788
|
+
if (step.action === "fail") {
|
|
20789
|
+
return { ok: false, reason: step.reason, detail: step.detail };
|
|
20790
|
+
}
|
|
20791
|
+
state = step.state;
|
|
20792
|
+
await deps.sleep(step.delayMs);
|
|
20793
|
+
}
|
|
20794
|
+
}
|
|
20795
|
+
|
|
20796
|
+
// src/shell/openBrowser.ts
|
|
20797
|
+
var launchTimeoutMs = 2000;
|
|
20798
|
+
function launcher(url, platform) {
|
|
20799
|
+
if (platform === "darwin")
|
|
20800
|
+
return { cmd: "open", args: [url] };
|
|
20801
|
+
if (platform === "win32") {
|
|
20802
|
+
return { cmd: "rundll32", args: ["url.dll,FileProtocolHandler", url] };
|
|
20803
|
+
}
|
|
20804
|
+
return { cmd: "xdg-open", args: [url] };
|
|
20805
|
+
}
|
|
20806
|
+
async function openBrowser(url, deps) {
|
|
20807
|
+
let parsed;
|
|
20808
|
+
try {
|
|
20809
|
+
parsed = new URL(url);
|
|
20810
|
+
} catch {
|
|
20811
|
+
return false;
|
|
20812
|
+
}
|
|
20813
|
+
if (parsed.protocol !== "https:" && parsed.protocol !== "http:") {
|
|
20814
|
+
return false;
|
|
20815
|
+
}
|
|
20816
|
+
const { cmd, args } = launcher(url, deps.platform);
|
|
20817
|
+
const launched = deps.spawn(cmd, args, { platform: deps.platform }).then((result) => result.exitCode === 0).catch(() => false);
|
|
20818
|
+
return Promise.race([launched, deps.sleep(launchTimeoutMs).then(() => true)]);
|
|
20819
|
+
}
|
|
20820
|
+
|
|
20821
|
+
// src/shell/spawn.ts
|
|
20822
|
+
import { spawn } from "node:child_process";
|
|
20823
|
+
|
|
20824
|
+
// src/core/cmdEscape.ts
|
|
20825
|
+
var metaChars = /([()\][%!^"`<>&|;, *?])/g;
|
|
20826
|
+
function escapeCommand(command) {
|
|
20827
|
+
return command.replace(metaChars, "^$1");
|
|
20828
|
+
}
|
|
20829
|
+
function escapeArgument(arg) {
|
|
20830
|
+
const escaped = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"").replace(/(?=(\\+?)?)\1$/, "$1$1");
|
|
20831
|
+
return `"${escaped}"`.replace(metaChars, "^$1").replace(metaChars, "^$1");
|
|
20832
|
+
}
|
|
20833
|
+
|
|
20834
|
+
// src/shell/spawn.ts
|
|
20835
|
+
function buildSpawnCommand(cmd, args, platform, env) {
|
|
20836
|
+
const options = {};
|
|
20837
|
+
if (env)
|
|
20838
|
+
options.env = env;
|
|
20839
|
+
if (platform !== "win32" || !/\.(cmd|bat)$/i.test(cmd)) {
|
|
20840
|
+
return { cmd, args, options };
|
|
20841
|
+
}
|
|
20842
|
+
const line = [escapeCommand(cmd), ...args.map(escapeArgument)].join(" ");
|
|
20843
|
+
return {
|
|
20844
|
+
cmd: process.env["ComSpec"] ?? "cmd.exe",
|
|
20845
|
+
args: ["/d", "/s", "/c", `"${line}"`],
|
|
20846
|
+
options: { ...options, windowsVerbatimArguments: true }
|
|
20847
|
+
};
|
|
20848
|
+
}
|
|
20849
|
+
var defaultSpawn = (cmd, args, opts) => new Promise((resolve) => {
|
|
20850
|
+
const env = opts.env ? { ...process.env, ...opts.env } : undefined;
|
|
20851
|
+
const built = buildSpawnCommand(cmd, args, opts.platform, env);
|
|
20852
|
+
const child = spawn(built.cmd, built.args, built.options);
|
|
20853
|
+
let stdout = "";
|
|
20854
|
+
let stderr = "";
|
|
20855
|
+
child.stdout?.on("data", (chunk) => {
|
|
20856
|
+
stdout += String(chunk);
|
|
20857
|
+
});
|
|
20858
|
+
child.stderr?.on("data", (chunk) => {
|
|
20859
|
+
stderr += String(chunk);
|
|
20860
|
+
});
|
|
20861
|
+
if (opts?.stdin !== undefined) {
|
|
20862
|
+
child.stdin?.write(opts.stdin);
|
|
20863
|
+
child.stdin?.end();
|
|
20864
|
+
}
|
|
20865
|
+
child.on("error", (err) => resolve({ exitCode: -1, stdout, stderr: stderr || err.message }));
|
|
20866
|
+
child.on("close", (code) => resolve({ exitCode: code ?? -1, stdout, stderr }));
|
|
20867
|
+
});
|
|
20868
|
+
|
|
20869
|
+
// src/shell/platform/getAuthConfig.ts
|
|
20870
|
+
var timeoutMs5 = 1e4;
|
|
20871
|
+
var authConfigBody = object({
|
|
20872
|
+
workOsClientId: string2().trim().min(1)
|
|
20873
|
+
});
|
|
20874
|
+
async function getAuthConfig(deps) {
|
|
20875
|
+
let response;
|
|
20876
|
+
try {
|
|
20877
|
+
response = await deps.fetch(`${deps.baseUrl}/api/v0/auth/config`, {
|
|
20878
|
+
signal: AbortSignal.timeout(timeoutMs5)
|
|
20879
|
+
});
|
|
20880
|
+
} catch (err) {
|
|
20881
|
+
return { kind: "unreachable", detail: errorMessage(err) };
|
|
20882
|
+
}
|
|
20883
|
+
if (response.status === 404)
|
|
20884
|
+
return { kind: "unconfigured" };
|
|
20885
|
+
if (!response.ok) {
|
|
20886
|
+
return { kind: "unreachable", detail: `HTTP ${response.status}` };
|
|
20887
|
+
}
|
|
20888
|
+
let json;
|
|
20889
|
+
try {
|
|
20890
|
+
json = await response.json();
|
|
20891
|
+
} catch (err) {
|
|
20892
|
+
return { kind: "unreachable", detail: errorMessage(err) };
|
|
20893
|
+
}
|
|
20894
|
+
const parsed = authConfigBody.safeParse(json);
|
|
20895
|
+
return parsed.success ? { kind: "configured", clientId: parsed.data.workOsClientId } : { kind: "unconfigured" };
|
|
20896
|
+
}
|
|
20897
|
+
|
|
20898
|
+
// src/shell/workos/pollDeviceToken.ts
|
|
20899
|
+
var deviceCodeGrantType = "urn:ietf:params:oauth:grant-type:device_code";
|
|
20900
|
+
async function pollDeviceToken(deviceCode, deps) {
|
|
20901
|
+
const outcome = await sendWorkosRequest(`${deps.baseUrl}/user_management/authenticate`, {
|
|
20902
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
20903
|
+
body: new URLSearchParams({
|
|
20904
|
+
grant_type: deviceCodeGrantType,
|
|
20905
|
+
device_code: deviceCode,
|
|
20906
|
+
client_id: deps.clientId
|
|
20907
|
+
}).toString()
|
|
20908
|
+
}, deps.fetch);
|
|
20909
|
+
if (outcome.kind === "failure") {
|
|
20910
|
+
return outcome.retryable ? { kind: "unreachable", detail: outcome.detail } : { kind: "error", detail: outcome.detail };
|
|
20911
|
+
}
|
|
20912
|
+
if (outcome.kind === "oauth-error") {
|
|
20913
|
+
switch (outcome.code) {
|
|
20914
|
+
case "authorization_pending":
|
|
20915
|
+
return { kind: "pending" };
|
|
20916
|
+
case "slow_down":
|
|
20917
|
+
return { kind: "slow-down" };
|
|
20918
|
+
case "access_denied":
|
|
20919
|
+
return { kind: "denied" };
|
|
20920
|
+
case "expired_token":
|
|
20921
|
+
case "invalid_grant":
|
|
20922
|
+
return { kind: "expired" };
|
|
20923
|
+
default:
|
|
20924
|
+
return {
|
|
20925
|
+
kind: "error",
|
|
20926
|
+
detail: outcome.description ?? outcome.code
|
|
20927
|
+
};
|
|
20928
|
+
}
|
|
20929
|
+
}
|
|
20930
|
+
const parsed = deviceTokenBody.safeParse(outcome.json);
|
|
20931
|
+
if (!parsed.success) {
|
|
20932
|
+
return { kind: "error", detail: unexpectedResponse };
|
|
20933
|
+
}
|
|
20934
|
+
return {
|
|
20935
|
+
kind: "tokens",
|
|
20936
|
+
tokens: {
|
|
20937
|
+
accessToken: parsed.data.access_token,
|
|
20938
|
+
refreshToken: parsed.data.refresh_token,
|
|
20939
|
+
expiresAt: readAccessTokenExpiry(parsed.data.access_token),
|
|
20940
|
+
email: parsed.data.user.email,
|
|
20941
|
+
organizationId: parsed.data.organization_id
|
|
20942
|
+
}
|
|
20943
|
+
};
|
|
20944
|
+
}
|
|
20945
|
+
|
|
20946
|
+
// src/shell/workos/requestDeviceAuthorization.ts
|
|
20947
|
+
async function requestDeviceAuthorization(deps) {
|
|
20948
|
+
const outcome = await sendWorkosRequest(`${deps.baseUrl}/user_management/authorize/device`, {
|
|
20949
|
+
headers: { "content-type": "application/x-www-form-urlencoded" },
|
|
20950
|
+
body: new URLSearchParams({ client_id: deps.clientId }).toString()
|
|
20951
|
+
}, deps.fetch);
|
|
20952
|
+
if (outcome.kind === "failure") {
|
|
20953
|
+
return { ok: false, error: outcome.detail, retryable: outcome.retryable };
|
|
20954
|
+
}
|
|
20955
|
+
if (outcome.kind === "oauth-error") {
|
|
20956
|
+
return {
|
|
20957
|
+
ok: false,
|
|
20958
|
+
error: outcome.description ?? outcome.code,
|
|
20959
|
+
retryable: false
|
|
20960
|
+
};
|
|
20961
|
+
}
|
|
20962
|
+
const parsed = deviceAuthorizationBody.safeParse(outcome.json);
|
|
20963
|
+
if (!parsed.success) {
|
|
20964
|
+
return { ok: false, error: unexpectedResponse, retryable: false };
|
|
20965
|
+
}
|
|
20966
|
+
return {
|
|
20967
|
+
ok: true,
|
|
20968
|
+
value: {
|
|
20969
|
+
deviceCode: parsed.data.device_code,
|
|
20970
|
+
userCode: parsed.data.user_code,
|
|
20971
|
+
verificationUri: parsed.data.verification_uri,
|
|
20972
|
+
verificationUriComplete: parsed.data.verification_uri_complete,
|
|
20973
|
+
expiresInSec: parsed.data.expires_in,
|
|
20974
|
+
intervalSec: parsed.data.interval ?? defaultIntervalSec
|
|
20975
|
+
}
|
|
20976
|
+
};
|
|
20977
|
+
}
|
|
20978
|
+
|
|
20979
|
+
// src/domains/auth/nameMatching.ts
|
|
20980
|
+
function matches(candidate, wanted) {
|
|
20981
|
+
return candidate.toLowerCase() === wanted.trim().toLowerCase();
|
|
20982
|
+
}
|
|
20983
|
+
function findOrganization(organizations, wanted) {
|
|
20984
|
+
return organizations.find((organization) => matches(organization.name, wanted) || matches(organization.id, wanted) || matches(organization.workOsOrganizationId, wanted));
|
|
20985
|
+
}
|
|
20986
|
+
function findWorkspace(workspaces, wanted) {
|
|
20987
|
+
return workspaces.find((workspace) => matches(workspace.name, wanted) || matches(workspace.id, wanted) || workspace.slug !== undefined && matches(workspace.slug, wanted));
|
|
20988
|
+
}
|
|
20989
|
+
function findWorkspaceAcross(organizations, wanted) {
|
|
20990
|
+
const hits = organizations.flatMap((organization) => {
|
|
20991
|
+
const workspace = findWorkspace(organization.workspaces, wanted);
|
|
20992
|
+
return workspace ? [{ organization, workspace }] : [];
|
|
20993
|
+
});
|
|
20994
|
+
const [only] = hits;
|
|
20995
|
+
if (!only)
|
|
20996
|
+
return { kind: "none" };
|
|
20997
|
+
if (hits.length > 1) {
|
|
20998
|
+
return {
|
|
20999
|
+
kind: "ambiguous",
|
|
21000
|
+
organizations: hits.map((h) => h.organization)
|
|
21001
|
+
};
|
|
21002
|
+
}
|
|
21003
|
+
return {
|
|
21004
|
+
kind: "found",
|
|
21005
|
+
organization: only.organization,
|
|
21006
|
+
workspace: only.workspace
|
|
21007
|
+
};
|
|
21008
|
+
}
|
|
21009
|
+
function nameList(items) {
|
|
21010
|
+
return items.map((item) => item.slug ?? item.name).join(", ");
|
|
21011
|
+
}
|
|
21012
|
+
|
|
21013
|
+
// src/domains/auth/selectWorkspace.ts
|
|
21014
|
+
async function selectWorkspace(deps) {
|
|
21015
|
+
const { organizations } = deps;
|
|
21016
|
+
if (organizations.length === 0)
|
|
21017
|
+
return { outcome: "none" };
|
|
21018
|
+
let organization;
|
|
21019
|
+
if (deps.preferredOrganization) {
|
|
21020
|
+
organization = findOrganization(organizations, deps.preferredOrganization);
|
|
21021
|
+
if (!organization) {
|
|
21022
|
+
return {
|
|
21023
|
+
outcome: "failed",
|
|
21024
|
+
error: `No organization matches '${deps.preferredOrganization}'. Available: ${nameList(organizations)}.`
|
|
21025
|
+
};
|
|
21026
|
+
}
|
|
21027
|
+
} else if (organizations.length === 1) {
|
|
21028
|
+
organization = organizations[0];
|
|
21029
|
+
} else if (deps.preferredWorkspace) {
|
|
21030
|
+
const match = findWorkspaceAcross(organizations, deps.preferredWorkspace);
|
|
21031
|
+
if (match.kind === "none") {
|
|
21032
|
+
return {
|
|
21033
|
+
outcome: "failed",
|
|
21034
|
+
error: `No workspace matches '${deps.preferredWorkspace}'. Available: ${nameList(organizations.flatMap((o) => o.workspaces))}.`
|
|
21035
|
+
};
|
|
21036
|
+
}
|
|
21037
|
+
if (match.kind === "ambiguous") {
|
|
21038
|
+
return {
|
|
21039
|
+
outcome: "failed",
|
|
21040
|
+
error: `More than one organization has a workspace matching '${deps.preferredWorkspace}': ${nameList(match.organizations)}. Set QAWOLF_ORGANIZATION to choose between them.`
|
|
21041
|
+
};
|
|
21042
|
+
}
|
|
21043
|
+
organization = match.organization;
|
|
21044
|
+
} else {
|
|
21045
|
+
organization = await deps.chooseOrganization(organizations);
|
|
21046
|
+
if (!organization)
|
|
21047
|
+
return { outcome: "cancelled" };
|
|
21048
|
+
}
|
|
21049
|
+
if (!organization)
|
|
21050
|
+
throw Error("organization was not settled");
|
|
21051
|
+
const { workspaces } = organization;
|
|
21052
|
+
if (workspaces.length === 0) {
|
|
21053
|
+
return {
|
|
21054
|
+
outcome: "failed",
|
|
21055
|
+
error: `Organization '${organization.name}' has no workspaces.`
|
|
21056
|
+
};
|
|
21057
|
+
}
|
|
21058
|
+
let workspace;
|
|
21059
|
+
if (deps.preferredWorkspace) {
|
|
21060
|
+
workspace = findWorkspace(workspaces, deps.preferredWorkspace);
|
|
21061
|
+
if (!workspace) {
|
|
21062
|
+
return {
|
|
21063
|
+
outcome: "failed",
|
|
21064
|
+
error: `No workspace matches '${deps.preferredWorkspace}' in ${organization.name}. Available: ${nameList(workspaces)}.`
|
|
21065
|
+
};
|
|
21066
|
+
}
|
|
21067
|
+
} else if (workspaces.length === 1) {
|
|
21068
|
+
workspace = workspaces[0];
|
|
21069
|
+
} else {
|
|
21070
|
+
workspace = await deps.chooseWorkspace(workspaces);
|
|
21071
|
+
if (!workspace)
|
|
21072
|
+
return { outcome: "cancelled" };
|
|
21073
|
+
}
|
|
21074
|
+
if (!workspace)
|
|
21075
|
+
throw Error("workspace was not settled");
|
|
21076
|
+
try {
|
|
21077
|
+
await deps.saveWorkspace(workspace.id);
|
|
21078
|
+
} catch (err) {
|
|
21079
|
+
return {
|
|
21080
|
+
outcome: "failed",
|
|
21081
|
+
error: authMessages.workspace.saveFailed(errorMessage(err))
|
|
21082
|
+
};
|
|
21083
|
+
}
|
|
21084
|
+
return { outcome: "selected", organization, workspace };
|
|
21085
|
+
}
|
|
21086
|
+
|
|
21087
|
+
// src/domains/auth/store/saveWorkspaceId.ts
|
|
21088
|
+
import { Entry as Entry6 } from "@napi-rs/keyring";
|
|
21089
|
+
function makeSaveWorkspaceIdDeps(configDir, fs) {
|
|
21090
|
+
return {
|
|
21091
|
+
loadTokens: () => loadTokens(configDir, { EntryClass: Entry6, fs }),
|
|
21092
|
+
saveTokens: (session) => saveTokens(configDir, session, fs)
|
|
21093
|
+
};
|
|
21094
|
+
}
|
|
21095
|
+
async function saveWorkspaceId(session, workspaceId, deps) {
|
|
21096
|
+
const current = await deps.loadTokens();
|
|
21097
|
+
if (!current.found || !isSameSession(current.tokens, session)) {
|
|
21098
|
+
throw Error(authMessages.workspace.sessionChanged);
|
|
21099
|
+
}
|
|
21100
|
+
return deps.saveTokens({ ...current.tokens, workspaceId });
|
|
21101
|
+
}
|
|
21102
|
+
function isSameSession(stored, captured) {
|
|
21103
|
+
return stored.email === captured.email && stored.organizationId === captured.organizationId;
|
|
21104
|
+
}
|
|
21105
|
+
|
|
21106
|
+
// src/commands/auth/chooseWorkspace.ts
|
|
21107
|
+
async function chooseWorkspace(ctx, args) {
|
|
21108
|
+
const client = createPlatformClient(args.session.accessToken, {
|
|
21109
|
+
baseUrl: ctx.apiBaseUrl,
|
|
21110
|
+
fetch: globalThis.fetch,
|
|
21111
|
+
logger: ctx.log("trpc")
|
|
21112
|
+
});
|
|
21113
|
+
const [identity, discovered] = await Promise.all([
|
|
21114
|
+
client.getIdentity(),
|
|
21115
|
+
client.getAccessibleOrganizations()
|
|
21116
|
+
]);
|
|
21117
|
+
let organizations;
|
|
21118
|
+
if (discovered.ok) {
|
|
21119
|
+
organizations = discovered.value;
|
|
21120
|
+
} else if (identity.ok) {
|
|
21121
|
+
organizations = "user" in identity.value ? identity.value.organizations : [];
|
|
21122
|
+
} else {
|
|
21123
|
+
return { outcome: "failed", error: identity.error };
|
|
21124
|
+
}
|
|
21125
|
+
return selectWorkspace({
|
|
21126
|
+
organizations,
|
|
21127
|
+
preferredOrganization: args.env["QAWOLF_ORGANIZATION"]?.trim(),
|
|
21128
|
+
preferredWorkspace: args.env["QAWOLF_WORKSPACE"]?.trim(),
|
|
21129
|
+
chooseOrganization: async (organizations) => {
|
|
21130
|
+
if (ctx.ui.mode !== "human")
|
|
21131
|
+
return;
|
|
21132
|
+
const picked = await ctx.ui.select(authMessages.workspace.chooseOrganization, organizations.map((organization) => ({
|
|
21133
|
+
value: organization.workOsOrganizationId,
|
|
21134
|
+
label: organization.name,
|
|
21135
|
+
hint: authMessages.workspace.workspaceCount(organization.workspaces.length)
|
|
21136
|
+
})));
|
|
21137
|
+
if (!picked.ok)
|
|
21138
|
+
return;
|
|
21139
|
+
return organizations.find((organization) => organization.workOsOrganizationId === picked.value);
|
|
21140
|
+
},
|
|
21141
|
+
chooseWorkspace: async (workspaces) => {
|
|
21142
|
+
if (ctx.ui.mode !== "human")
|
|
21143
|
+
return;
|
|
21144
|
+
const picked = await ctx.ui.select(authMessages.workspace.choose, workspaces.map((workspace) => ({
|
|
21145
|
+
value: workspace.id,
|
|
21146
|
+
label: workspace.name,
|
|
21147
|
+
...workspace.slug ? { hint: workspace.slug } : {}
|
|
21148
|
+
})));
|
|
21149
|
+
if (!picked.ok)
|
|
21150
|
+
return;
|
|
21151
|
+
return workspaces.find((workspace) => workspace.id === picked.value);
|
|
21152
|
+
},
|
|
21153
|
+
saveWorkspace: (workspaceId) => saveWorkspaceId(args.session, workspaceId, makeSaveWorkspaceIdDeps(ctx.configDir, ctx.fs))
|
|
21154
|
+
});
|
|
21155
|
+
}
|
|
21156
|
+
function reportWorkspace(ctx, result) {
|
|
21157
|
+
switch (result.outcome) {
|
|
21158
|
+
case "selected":
|
|
21159
|
+
ctx.ui.info(authMessages.workspace.working(result.organization.name, result.workspace.name));
|
|
21160
|
+
return;
|
|
21161
|
+
case "none":
|
|
21162
|
+
ctx.ui.info(authMessages.workspace.none);
|
|
21163
|
+
return;
|
|
21164
|
+
case "cancelled":
|
|
21165
|
+
if (ctx.ui.mode !== "human") {
|
|
21166
|
+
return { error: authMessages.workspace.nonInteractive };
|
|
21167
|
+
}
|
|
21168
|
+
ctx.ui.info(authMessages.workspace.cancelled);
|
|
21169
|
+
return;
|
|
21170
|
+
case "failed":
|
|
21171
|
+
return { error: result.error };
|
|
21172
|
+
}
|
|
21173
|
+
}
|
|
21174
|
+
|
|
21175
|
+
// src/commands/auth/loginDevice.ts
|
|
21176
|
+
async function loginWithDevice(ctx, deps = {}) {
|
|
21177
|
+
const authConfig = await getAuthConfig({
|
|
21178
|
+
baseUrl: ctx.apiBaseUrl,
|
|
21179
|
+
fetch: globalThis.fetch
|
|
21180
|
+
});
|
|
21181
|
+
if (authConfig.kind === "unreachable") {
|
|
21182
|
+
ctx.log("auth").debug(`auth config unreachable: ${authConfig.detail}`);
|
|
21183
|
+
return {
|
|
21184
|
+
error: authMessages.device.configUnreachable,
|
|
21185
|
+
errorBody: authConfig.detail
|
|
21186
|
+
};
|
|
21187
|
+
}
|
|
21188
|
+
const config = resolveWorkosConfig(authConfig.kind === "configured" ? authConfig.clientId : undefined);
|
|
21189
|
+
if (!config.configured) {
|
|
21190
|
+
return { error: authMessages.device.unavailable };
|
|
21191
|
+
}
|
|
21192
|
+
const workos = {
|
|
21193
|
+
fetch: globalThis.fetch,
|
|
21194
|
+
baseUrl: config.baseUrl,
|
|
21195
|
+
clientId: config.clientId
|
|
21196
|
+
};
|
|
21197
|
+
let cancelled = false;
|
|
21198
|
+
const unregister = ctx.signals.register(() => {
|
|
21199
|
+
cancelled = true;
|
|
21200
|
+
});
|
|
21201
|
+
const showCode = async (authorization) => {
|
|
21202
|
+
const url = authorization.verificationUriComplete ?? authorization.verificationUri;
|
|
21203
|
+
ctx.ui.note([
|
|
21204
|
+
authMessages.device.confirmCode(authorization.userCode),
|
|
21205
|
+
authMessages.device.visitUrl(url),
|
|
21206
|
+
url === authorization.verificationUri ? undefined : authMessages.device.visitUrlPlain(authorization.verificationUri)
|
|
21207
|
+
].filter((line) => Boolean(line)).join(`
|
|
21208
|
+
`), authMessages.title);
|
|
21209
|
+
const opened = await openBrowser(url, {
|
|
21210
|
+
sleep,
|
|
21211
|
+
spawn: defaultSpawn,
|
|
21212
|
+
platform: deps.platform ?? process.platform
|
|
21213
|
+
});
|
|
21214
|
+
if (!opened)
|
|
21215
|
+
ctx.ui.info(authMessages.device.openFailed(url));
|
|
21216
|
+
ctx.ui.step(authMessages.device.waiting);
|
|
21217
|
+
};
|
|
21218
|
+
try {
|
|
21219
|
+
const result = await deviceLogin({
|
|
21220
|
+
requestAuthorization: () => requestDeviceAuthorization(workos),
|
|
21221
|
+
pollToken: (deviceCode) => pollDeviceToken(deviceCode, workos),
|
|
21222
|
+
onPrompt: showCode,
|
|
21223
|
+
sleep,
|
|
21224
|
+
now: () => Date.now(),
|
|
21225
|
+
isCancelled: () => cancelled
|
|
21226
|
+
});
|
|
21227
|
+
if (!result.ok) {
|
|
21228
|
+
return {
|
|
21229
|
+
error: authMessages.device.failed[result.reason],
|
|
21230
|
+
...result.detail ? { errorBody: result.detail } : {}
|
|
21231
|
+
};
|
|
21232
|
+
}
|
|
21233
|
+
const session = {
|
|
21234
|
+
...result.tokens,
|
|
21235
|
+
workspaceId: undefined,
|
|
21236
|
+
clientId: config.clientId
|
|
21237
|
+
};
|
|
21238
|
+
await saveTokens(ctx.configDir, session, ctx.fs);
|
|
21239
|
+
const workspace = await chooseWorkspace(ctx, {
|
|
21240
|
+
session,
|
|
21241
|
+
env: deps.env ?? process.env
|
|
21242
|
+
});
|
|
21243
|
+
const failure = reportWorkspace(ctx, workspace);
|
|
21244
|
+
if (failure)
|
|
21245
|
+
return failure;
|
|
21246
|
+
ctx.ui.outro(authMessages.device.signedIn(result.tokens.email));
|
|
21247
|
+
return;
|
|
21248
|
+
} finally {
|
|
21249
|
+
unregister();
|
|
21250
|
+
}
|
|
21251
|
+
}
|
|
21252
|
+
|
|
21253
|
+
// src/commands/auth/login.ts
|
|
21254
|
+
var browserMethod = "browser";
|
|
21255
|
+
async function handleLogin(ctx, deps = {}) {
|
|
21256
|
+
if (ctx.ui.mode !== "human") {
|
|
21257
|
+
ctx.ui.error(authMessages.login.nonInteractive);
|
|
21258
|
+
return { error: "non-interactive" };
|
|
21259
|
+
}
|
|
21260
|
+
const resolveApiKey2 = deps.resolveApiKey ?? resolveApiKey;
|
|
21261
|
+
const existing = await resolveApiKey2(ctx.configDir, ctx.fs);
|
|
21262
|
+
if (existing) {
|
|
21263
|
+
const reauth = await ctx.ui.confirm(authMessages.login.reAuthPrompt);
|
|
21264
|
+
if (!reauth.ok || !reauth.value) {
|
|
21265
|
+
ctx.ui.info(authMessages.alreadyConfigured);
|
|
21266
|
+
return;
|
|
21267
|
+
}
|
|
21268
|
+
}
|
|
21269
|
+
ctx.ui.gap();
|
|
21270
|
+
ctx.ui.intro(authMessages.title);
|
|
21271
|
+
const method = await ctx.ui.select(authMessages.login.chooseMethod, [
|
|
21272
|
+
{
|
|
21273
|
+
value: browserMethod,
|
|
21274
|
+
label: authMessages.login.methodBrowser,
|
|
21275
|
+
hint: authMessages.login.methodBrowserHint
|
|
21276
|
+
},
|
|
21277
|
+
{
|
|
21278
|
+
value: "api-key",
|
|
21279
|
+
label: authMessages.login.methodApiKey,
|
|
21280
|
+
hint: authMessages.login.methodApiKeyHint
|
|
21281
|
+
}
|
|
21282
|
+
]);
|
|
21283
|
+
if (!method.ok) {
|
|
21284
|
+
ctx.ui.cancel(authMessages.cancelled);
|
|
21285
|
+
return;
|
|
21286
|
+
}
|
|
21287
|
+
if (method.value !== browserMethod) {
|
|
21288
|
+
return (deps.loginWithApiKey ?? loginWithApiKey)(ctx);
|
|
21289
|
+
}
|
|
21290
|
+
if (existing && existing.source !== "browser") {
|
|
21291
|
+
ctx.ui.warn(existing.source === "env" ? authMessages.login.apiKeyPrecedence.env : authMessages.login.apiKeyPrecedence.stored);
|
|
21292
|
+
}
|
|
21293
|
+
return (deps.loginWithDevice ?? loginWithDevice)(ctx);
|
|
21294
|
+
}
|
|
21295
|
+
|
|
21296
|
+
// src/domains/auth/store/deleteTokens.ts
|
|
21297
|
+
import { join as join14 } from "node:path";
|
|
21298
|
+
import { Entry as Entry7 } from "@napi-rs/keyring";
|
|
21299
|
+
function deleteFromKeychain2() {
|
|
21300
|
+
try {
|
|
21301
|
+
new Entry7(service, tokensAccount).deletePassword();
|
|
21302
|
+
return "deleted";
|
|
21303
|
+
} catch {
|
|
21304
|
+
return "unavailable";
|
|
21305
|
+
}
|
|
21306
|
+
}
|
|
21307
|
+
async function deleteFromFile2(configDir, fs) {
|
|
21308
|
+
try {
|
|
21309
|
+
await fs.unlink(join14(configDir, tokensFile));
|
|
21310
|
+
return "deleted";
|
|
21311
|
+
} catch (err) {
|
|
21312
|
+
if (isNoEntError(err))
|
|
21313
|
+
return "not-found";
|
|
21314
|
+
throw err;
|
|
21315
|
+
}
|
|
21316
|
+
}
|
|
21317
|
+
async function deleteTokens(configDir, fs) {
|
|
21318
|
+
const [keychain, file] = await Promise.all([
|
|
21319
|
+
Promise.resolve(deleteFromKeychain2()),
|
|
21320
|
+
deleteFromFile2(configDir, fs)
|
|
21321
|
+
]);
|
|
21322
|
+
return { keychain, file };
|
|
21323
|
+
}
|
|
21324
|
+
|
|
19630
21325
|
// src/commands/auth/logout.ts
|
|
19631
|
-
async function handleLogout(ctx) {
|
|
19632
|
-
const
|
|
19633
|
-
|
|
21326
|
+
async function handleLogout(ctx, deps = {}) {
|
|
21327
|
+
const hasStoredCredentials2 = deps.hasStoredCredentials ?? hasStoredCredentials;
|
|
21328
|
+
const deleteApiKey2 = deps.deleteApiKey ?? deleteApiKey;
|
|
21329
|
+
const deleteTokens2 = deps.deleteTokens ?? deleteTokens;
|
|
21330
|
+
const env = deps.env ?? process.env;
|
|
21331
|
+
const envKey = env["QAWOLF_API_KEY"]?.trim();
|
|
21332
|
+
const stored = await hasStoredCredentials2(ctx.configDir, ctx.fs);
|
|
21333
|
+
if (!envKey && !stored) {
|
|
19634
21334
|
ctx.ui.info(authMessages.logout.notAuthenticated);
|
|
19635
21335
|
return;
|
|
19636
21336
|
}
|
|
19637
|
-
if (
|
|
21337
|
+
if (envKey) {
|
|
19638
21338
|
ctx.ui.warn(authMessages.logout.envVarWarning);
|
|
19639
21339
|
}
|
|
19640
21340
|
if (ctx.ui.mode === "human") {
|
|
@@ -19649,13 +21349,88 @@ async function handleLogout(ctx) {
|
|
|
19649
21349
|
await ctx.ui.withProgress([
|
|
19650
21350
|
{
|
|
19651
21351
|
message: authMessages.logout.deleting,
|
|
19652
|
-
task: () =>
|
|
21352
|
+
task: async () => {
|
|
21353
|
+
await Promise.all([
|
|
21354
|
+
deleteApiKey2(ctx.configDir, ctx.fs),
|
|
21355
|
+
deleteTokens2(ctx.configDir, ctx.fs)
|
|
21356
|
+
]);
|
|
21357
|
+
}
|
|
19653
21358
|
}
|
|
19654
21359
|
], () => authMessages.logout.credentialsRemoved);
|
|
19655
21360
|
if (ctx.ui.mode === "human") {
|
|
19656
|
-
ctx.ui.outro(authMessages.logout.success);
|
|
21361
|
+
ctx.ui.outro(authMessages.logout.success);
|
|
21362
|
+
} else {
|
|
21363
|
+
ctx.ui.output({ loggedOut: true }, authMessages.logout.success);
|
|
21364
|
+
}
|
|
21365
|
+
}
|
|
21366
|
+
|
|
21367
|
+
// src/domains/auth/refreshStoredSession.ts
|
|
21368
|
+
import { Entry as Entry8 } from "@napi-rs/keyring";
|
|
21369
|
+
function makeDefaultDeps2(fs) {
|
|
21370
|
+
return {
|
|
21371
|
+
loadTokens: (configDir) => loadTokens(configDir, { EntryClass: Entry8, fs }),
|
|
21372
|
+
resolveOauth: (configDir) => resolveOauthToken(configDir, makeOauthDeps(fs))
|
|
21373
|
+
};
|
|
21374
|
+
}
|
|
21375
|
+
async function refreshStoredSession(configDir, fs, deps) {
|
|
21376
|
+
const resolvedDeps = deps ?? makeDefaultDeps2(fs);
|
|
21377
|
+
const before = await resolvedDeps.loadTokens(configDir);
|
|
21378
|
+
if (!before.found)
|
|
21379
|
+
return { kind: "not-signed-in" };
|
|
21380
|
+
const renewed = await resolvedDeps.resolveOauth(configDir);
|
|
21381
|
+
if (!renewed)
|
|
21382
|
+
return { kind: "refresh-failed" };
|
|
21383
|
+
const after = await resolvedDeps.loadTokens(configDir);
|
|
21384
|
+
if (!after.found)
|
|
21385
|
+
return { kind: "refresh-failed" };
|
|
21386
|
+
if (after.tokens.accessToken !== renewed.key) {
|
|
21387
|
+
return { kind: "refresh-failed" };
|
|
21388
|
+
}
|
|
21389
|
+
return { kind: "session", session: after.tokens };
|
|
21390
|
+
}
|
|
21391
|
+
|
|
21392
|
+
// src/commands/auth/switchWorkspace.ts
|
|
21393
|
+
async function handleSwitchWorkspace(ctx, deps = {}) {
|
|
21394
|
+
const env = deps.env ?? process.env;
|
|
21395
|
+
const refreshStoredSession2 = deps.refreshStoredSession ?? refreshStoredSession;
|
|
21396
|
+
const preferred = env["QAWOLF_WORKSPACE"]?.trim() || env["QAWOLF_ORGANIZATION"]?.trim();
|
|
21397
|
+
if (ctx.ui.mode !== "human" && !preferred) {
|
|
21398
|
+
return { error: authMessages.workspace.nonInteractive };
|
|
21399
|
+
}
|
|
21400
|
+
const stored = await refreshStoredSession2(ctx.configDir, ctx.fs);
|
|
21401
|
+
if (stored.kind === "not-signed-in") {
|
|
21402
|
+
return { error: authMessages.workspace.notSignedIn };
|
|
21403
|
+
}
|
|
21404
|
+
if (stored.kind === "refresh-failed") {
|
|
21405
|
+
return { error: authMessages.workspace.sessionExpired };
|
|
21406
|
+
}
|
|
21407
|
+
ctx.ui.gap();
|
|
21408
|
+
ctx.ui.intro(authMessages.title);
|
|
21409
|
+
const result = await chooseWorkspace(ctx, {
|
|
21410
|
+
session: stored.session,
|
|
21411
|
+
env
|
|
21412
|
+
});
|
|
21413
|
+
const failure = reportWorkspace(ctx, result);
|
|
21414
|
+
if (failure)
|
|
21415
|
+
return failure;
|
|
21416
|
+
ctx.ui.outro(authMessages.outroReady);
|
|
21417
|
+
return;
|
|
21418
|
+
}
|
|
21419
|
+
|
|
21420
|
+
// src/commands/auth/whoamiTeam.ts
|
|
21421
|
+
function reportTeamIdentity(ctx, value, source) {
|
|
21422
|
+
const { team } = value;
|
|
21423
|
+
const teamUrl = team.slug ? new URL("/" + encodeURIComponent(team.slug), ctx.apiBaseUrl).toString() : undefined;
|
|
21424
|
+
if (ctx.ui.mode === "human") {
|
|
21425
|
+
ctx.ui.note(authMessages.whoami.teamNote({ team, teamUrl, source }), authMessages.whoamiAuthenticated);
|
|
21426
|
+
ctx.ui.outro(authMessages.outroReady);
|
|
19657
21427
|
} else {
|
|
19658
|
-
ctx.ui.output({
|
|
21428
|
+
ctx.ui.output({
|
|
21429
|
+
authenticated: true,
|
|
21430
|
+
source,
|
|
21431
|
+
team,
|
|
21432
|
+
teamUrl
|
|
21433
|
+
}, authMessages.whoami.authenticatedAs(team.name, source));
|
|
19659
21434
|
}
|
|
19660
21435
|
}
|
|
19661
21436
|
|
|
@@ -19700,11 +21475,19 @@ async function handleWhoami(ctx, deps = {}) {
|
|
|
19700
21475
|
const { value } = identity;
|
|
19701
21476
|
if ("user" in value) {
|
|
19702
21477
|
const { organization, user } = value;
|
|
21478
|
+
const found = value.organizations.flatMap((candidate) => candidate.workspaces.map((workspace) => ({
|
|
21479
|
+
organization: candidate.name,
|
|
21480
|
+
workspace: workspace.name,
|
|
21481
|
+
id: workspace.id
|
|
21482
|
+
}))).find((entry) => entry.id === resolved.workspaceId);
|
|
21483
|
+
const activeWorkspace = resolved.workspaceId ? authMessages.whoami.activeWorkspace(resolved.workspaceId, found) : undefined;
|
|
19703
21484
|
if (ctx.ui.mode === "human") {
|
|
19704
21485
|
ctx.ui.note(authMessages.whoami.userNote({
|
|
19705
21486
|
organization,
|
|
19706
21487
|
source: resolved.source,
|
|
19707
|
-
user
|
|
21488
|
+
user,
|
|
21489
|
+
activeWorkspace,
|
|
21490
|
+
organizations: value.organizations
|
|
19708
21491
|
}), authMessages.whoamiAuthenticated);
|
|
19709
21492
|
ctx.ui.outro(authMessages.outroReady);
|
|
19710
21493
|
} else {
|
|
@@ -19712,7 +21495,9 @@ async function handleWhoami(ctx, deps = {}) {
|
|
|
19712
21495
|
authenticated: true,
|
|
19713
21496
|
organization,
|
|
19714
21497
|
source: resolved.source,
|
|
19715
|
-
user
|
|
21498
|
+
user,
|
|
21499
|
+
workspaceId: resolved.workspaceId,
|
|
21500
|
+
organizations: value.organizations
|
|
19716
21501
|
}, authMessages.whoami.authenticatedAs(user.email, resolved.source));
|
|
19717
21502
|
}
|
|
19718
21503
|
return;
|
|
@@ -19734,26 +21519,15 @@ async function handleWhoami(ctx, deps = {}) {
|
|
|
19734
21519
|
}
|
|
19735
21520
|
return;
|
|
19736
21521
|
}
|
|
19737
|
-
|
|
19738
|
-
const teamUrl = team.slug ? new URL("/" + encodeURIComponent(team.slug), ctx.apiBaseUrl).toString() : undefined;
|
|
19739
|
-
if (ctx.ui.mode === "human") {
|
|
19740
|
-
ctx.ui.note(authMessages.whoami.teamNote({ team, teamUrl, source: resolved.source }), authMessages.whoamiAuthenticated);
|
|
19741
|
-
ctx.ui.outro(authMessages.outroReady);
|
|
19742
|
-
} else {
|
|
19743
|
-
ctx.ui.output({
|
|
19744
|
-
authenticated: true,
|
|
19745
|
-
source: resolved.source,
|
|
19746
|
-
team,
|
|
19747
|
-
teamUrl
|
|
19748
|
-
}, authMessages.whoami.authenticatedAs(team.name, resolved.source));
|
|
19749
|
-
}
|
|
21522
|
+
reportTeamIdentity(ctx, value, resolved.source);
|
|
19750
21523
|
}
|
|
19751
21524
|
|
|
19752
21525
|
// src/commands/auth/index.ts
|
|
19753
21526
|
function registerAuthCommand(program, signals) {
|
|
19754
21527
|
const auth = program.command("auth").description("Manage authentication with QA Wolf");
|
|
19755
|
-
declareCommandKind(auth.command("login"), "local").description("Authenticate with
|
|
21528
|
+
declareCommandKind(auth.command("login"), "local").description("Authenticate with QA Wolf in a browser or with an API key").action(withContext(signals, handleLogin));
|
|
19756
21529
|
declareCommandKind(auth.command("logout"), "local").description("Remove stored credentials").action(withContext(signals, handleLogout));
|
|
21530
|
+
declareCommandKind(auth.command("switch"), "local").description("Choose which workspace to work in").action(withContext(signals, handleSwitchWorkspace));
|
|
19757
21531
|
declareCommandKind(auth.command("whoami"), "read").description("Show authentication status").action(withContext(signals, handleWhoami));
|
|
19758
21532
|
}
|
|
19759
21533
|
|
|
@@ -19803,11 +21577,11 @@ async function* batchMap(items, fn, size) {
|
|
|
19803
21577
|
}
|
|
19804
21578
|
|
|
19805
21579
|
// src/core/nodeModulesBins.ts
|
|
19806
|
-
import { join as
|
|
21580
|
+
import { join as join15 } from "node:path";
|
|
19807
21581
|
function nodeModulesBinCandidates(envDir, name, platform) {
|
|
19808
|
-
const binDir =
|
|
21582
|
+
const binDir = join15(envDir, "node_modules", ".bin");
|
|
19809
21583
|
const names = platform === "win32" ? [`${name}.cmd`, `${name}.exe`] : [name];
|
|
19810
|
-
return names.map((shim) =>
|
|
21584
|
+
return names.map((shim) => join15(binDir, shim));
|
|
19811
21585
|
}
|
|
19812
21586
|
function appiumCliCandidates(envDir, platform) {
|
|
19813
21587
|
return nodeModulesBinCandidates(envDir, "appium", platform);
|
|
@@ -19864,21 +21638,21 @@ async function checkUiautomator2(spawn, appiumBin, platform) {
|
|
|
19864
21638
|
}
|
|
19865
21639
|
|
|
19866
21640
|
// src/core/androidBins.ts
|
|
19867
|
-
import { join as
|
|
21641
|
+
import { join as join16 } from "node:path";
|
|
19868
21642
|
function withExeSuffix(name, platform) {
|
|
19869
21643
|
return platform === "win32" ? `${name}.exe` : name;
|
|
19870
21644
|
}
|
|
19871
21645
|
function emulatorBin(home, platform) {
|
|
19872
21646
|
const name = withExeSuffix("emulator", platform);
|
|
19873
|
-
return home ?
|
|
21647
|
+
return home ? join16(home, "emulator", name) : name;
|
|
19874
21648
|
}
|
|
19875
21649
|
function adbBin(home, platform) {
|
|
19876
21650
|
const name = withExeSuffix("adb", platform);
|
|
19877
|
-
return home ?
|
|
21651
|
+
return home ? join16(home, "platform-tools", name) : name;
|
|
19878
21652
|
}
|
|
19879
21653
|
function cmdlineToolsBin(home, name, platform) {
|
|
19880
21654
|
const file = platform === "win32" ? `${name}.bat` : name;
|
|
19881
|
-
return
|
|
21655
|
+
return join16(home, "cmdline-tools", "latest", "bin", file);
|
|
19882
21656
|
}
|
|
19883
21657
|
function sdkManagerBin(home, platform) {
|
|
19884
21658
|
return cmdlineToolsBin(home, "sdkmanager", platform);
|
|
@@ -20175,9 +21949,9 @@ async function checkNpmRegistry(deps) {
|
|
|
20175
21949
|
}
|
|
20176
21950
|
|
|
20177
21951
|
// src/core/playwrightCli.ts
|
|
20178
|
-
import { join as
|
|
21952
|
+
import { join as join17 } from "node:path";
|
|
20179
21953
|
function playwrightCliJsPath(envDir) {
|
|
20180
|
-
return
|
|
21954
|
+
return join17(envDir, "node_modules", "playwright", "cli.js");
|
|
20181
21955
|
}
|
|
20182
21956
|
function playwrightCliInvocation(args) {
|
|
20183
21957
|
return {
|
|
@@ -20327,11 +22101,11 @@ function renderResults(ui, results) {
|
|
|
20327
22101
|
}
|
|
20328
22102
|
|
|
20329
22103
|
// src/domains/flows/ensureDeps.ts
|
|
20330
|
-
import { dirname as dirname4, join as
|
|
22104
|
+
import { dirname as dirname4, join as join18 } from "node:path";
|
|
20331
22105
|
function findEnvDir(flowPath, fs) {
|
|
20332
22106
|
let dir = dirname4(flowPath);
|
|
20333
22107
|
while (true) {
|
|
20334
|
-
if (fs.existsSync(
|
|
22108
|
+
if (fs.existsSync(join18(dir, "package.json")))
|
|
20335
22109
|
return dir;
|
|
20336
22110
|
const parent = dirname4(dir);
|
|
20337
22111
|
if (parent === dir)
|
|
@@ -20357,7 +22131,7 @@ function resolveProjectDirSafe(files, fs) {
|
|
|
20357
22131
|
}
|
|
20358
22132
|
|
|
20359
22133
|
// src/domains/flows/expand.ts
|
|
20360
|
-
import { join as
|
|
22134
|
+
import { join as join19, resolve as resolve3 } from "node:path";
|
|
20361
22135
|
|
|
20362
22136
|
// node_modules/tinyglobby/dist/index.mjs
|
|
20363
22137
|
import { readdir, readdirSync, realpath, realpathSync, stat, statSync } from "fs";
|
|
@@ -21346,11 +23120,11 @@ function makePeekFlowMeta(fs) {
|
|
|
21346
23120
|
};
|
|
21347
23121
|
}
|
|
21348
23122
|
async function resolveGlobRoots(cwd, fs) {
|
|
21349
|
-
const qawolfPath =
|
|
23123
|
+
const qawolfPath = join19(cwd, ".qawolf");
|
|
21350
23124
|
let envDirs = [];
|
|
21351
23125
|
try {
|
|
21352
23126
|
const entries = await fs.readdirWithTypes(qawolfPath);
|
|
21353
|
-
envDirs = entries.filter((e) => e.isDirectory()).map((e) =>
|
|
23127
|
+
envDirs = entries.filter((e) => e.isDirectory()).map((e) => join19(qawolfPath, e.name));
|
|
21354
23128
|
} catch {}
|
|
21355
23129
|
return [cwd, ...envDirs];
|
|
21356
23130
|
}
|
|
@@ -21376,11 +23150,11 @@ async function expandPatterns(patterns, cwd, logger, fs) {
|
|
|
21376
23150
|
}
|
|
21377
23151
|
|
|
21378
23152
|
// src/domains/runtimeEnv/clearRuntimeEnv.ts
|
|
21379
|
-
import { join as
|
|
23153
|
+
import { join as join21 } from "node:path";
|
|
21380
23154
|
|
|
21381
23155
|
// src/domains/runtimeEnv/managedEnvDir.ts
|
|
21382
23156
|
import { createHash } from "node:crypto";
|
|
21383
|
-
import { join as
|
|
23157
|
+
import { join as join20, resolve as resolve4 } from "node:path";
|
|
21384
23158
|
|
|
21385
23159
|
// src/domains/runtimeEnv/pinnedPackages.ts
|
|
21386
23160
|
var pinnedPackages = [
|
|
@@ -21411,19 +23185,19 @@ function managedEnvBaseDir() {
|
|
|
21411
23185
|
const override = process.env["QAWOLF_RUNTIME_DIR"]?.trim();
|
|
21412
23186
|
if (override)
|
|
21413
23187
|
return resolve4(override);
|
|
21414
|
-
return
|
|
23188
|
+
return join20(getDataDir(), "runtime");
|
|
21415
23189
|
}
|
|
21416
23190
|
function runStagingRoot() {
|
|
21417
23191
|
return `${managedEnvBaseDir()}-runs`;
|
|
21418
23192
|
}
|
|
21419
23193
|
function managedEnvDir() {
|
|
21420
|
-
return
|
|
23194
|
+
return join20(managedEnvBaseDir(), managedEnvHash());
|
|
21421
23195
|
}
|
|
21422
23196
|
async function scaffoldManagedEnv(dir, fs) {
|
|
21423
23197
|
await fs.mkdir(dir, { recursive: true });
|
|
21424
23198
|
const dependencies = Object.fromEntries(pinnedPackages.map(({ name, version }) => [name, version]));
|
|
21425
|
-
await fs.writeFile(
|
|
21426
|
-
await fs.writeFile(
|
|
23199
|
+
await fs.writeFile(join20(dir, "package.json"), JSON.stringify({ name: "qawolf-runtime", private: true, dependencies }, undefined, 2));
|
|
23200
|
+
await fs.writeFile(join20(dir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
|
|
21427
23201
|
`);
|
|
21428
23202
|
}
|
|
21429
23203
|
|
|
@@ -21445,67 +23219,19 @@ async function isManagedRuntimeBase(dir, fs) {
|
|
|
21445
23219
|
for (const entry of entries) {
|
|
21446
23220
|
if (!entry.isDirectory())
|
|
21447
23221
|
return false;
|
|
21448
|
-
if (!await hasRuntimeSentinel(
|
|
23222
|
+
if (!await hasRuntimeSentinel(join21(dir, entry.name), fs))
|
|
21449
23223
|
return false;
|
|
21450
23224
|
}
|
|
21451
23225
|
return true;
|
|
21452
23226
|
}
|
|
21453
23227
|
async function hasRuntimeSentinel(hashDir, fs) {
|
|
21454
23228
|
try {
|
|
21455
|
-
const pkg = JSON.parse(await fs.readFile(
|
|
23229
|
+
const pkg = JSON.parse(await fs.readFile(join21(hashDir, "package.json")));
|
|
21456
23230
|
return pkg.name === runtimeSentinel;
|
|
21457
23231
|
} catch {
|
|
21458
23232
|
return false;
|
|
21459
23233
|
}
|
|
21460
23234
|
}
|
|
21461
|
-
// src/shell/spawn.ts
|
|
21462
|
-
import { spawn } from "node:child_process";
|
|
21463
|
-
|
|
21464
|
-
// src/core/cmdEscape.ts
|
|
21465
|
-
var metaChars = /([()\][%!^"`<>&|;, *?])/g;
|
|
21466
|
-
function escapeCommand(command) {
|
|
21467
|
-
return command.replace(metaChars, "^$1");
|
|
21468
|
-
}
|
|
21469
|
-
function escapeArgument(arg) {
|
|
21470
|
-
const escaped = arg.replace(/(?=(\\+?)?)\1"/g, "$1$1\\\"").replace(/(?=(\\+?)?)\1$/, "$1$1");
|
|
21471
|
-
return `"${escaped}"`.replace(metaChars, "^$1").replace(metaChars, "^$1");
|
|
21472
|
-
}
|
|
21473
|
-
|
|
21474
|
-
// src/shell/spawn.ts
|
|
21475
|
-
function buildSpawnCommand(cmd, args, platform, env) {
|
|
21476
|
-
const options = {};
|
|
21477
|
-
if (env)
|
|
21478
|
-
options.env = env;
|
|
21479
|
-
if (platform !== "win32" || !/\.(cmd|bat)$/i.test(cmd)) {
|
|
21480
|
-
return { cmd, args, options };
|
|
21481
|
-
}
|
|
21482
|
-
const line = [escapeCommand(cmd), ...args.map(escapeArgument)].join(" ");
|
|
21483
|
-
return {
|
|
21484
|
-
cmd: process.env["ComSpec"] ?? "cmd.exe",
|
|
21485
|
-
args: ["/d", "/s", "/c", `"${line}"`],
|
|
21486
|
-
options: { ...options, windowsVerbatimArguments: true }
|
|
21487
|
-
};
|
|
21488
|
-
}
|
|
21489
|
-
var defaultSpawn = (cmd, args, opts) => new Promise((resolve) => {
|
|
21490
|
-
const env = opts.env ? { ...process.env, ...opts.env } : undefined;
|
|
21491
|
-
const built = buildSpawnCommand(cmd, args, opts.platform, env);
|
|
21492
|
-
const child = spawn(built.cmd, built.args, built.options);
|
|
21493
|
-
let stdout = "";
|
|
21494
|
-
let stderr = "";
|
|
21495
|
-
child.stdout?.on("data", (chunk) => {
|
|
21496
|
-
stdout += String(chunk);
|
|
21497
|
-
});
|
|
21498
|
-
child.stderr?.on("data", (chunk) => {
|
|
21499
|
-
stderr += String(chunk);
|
|
21500
|
-
});
|
|
21501
|
-
if (opts?.stdin !== undefined) {
|
|
21502
|
-
child.stdin?.write(opts.stdin);
|
|
21503
|
-
child.stdin?.end();
|
|
21504
|
-
}
|
|
21505
|
-
child.on("error", (err) => resolve({ exitCode: -1, stdout, stderr: stderr || err.message }));
|
|
21506
|
-
child.on("close", (code) => resolve({ exitCode: code ?? -1, stdout, stderr }));
|
|
21507
|
-
});
|
|
21508
|
-
|
|
21509
23235
|
// src/domains/runtimeEnv/npmInstall.ts
|
|
21510
23236
|
function buildNpmInstallSpawn(cwd, platform) {
|
|
21511
23237
|
const built = buildSpawnCommand(resolveNpmCommand(platform), ["install", "--legacy-peer-deps"], platform, undefined);
|
|
@@ -21526,10 +23252,10 @@ function spawnNpmInstall(cwd, spawn2 = spawn) {
|
|
|
21526
23252
|
}
|
|
21527
23253
|
|
|
21528
23254
|
// src/domains/runtimeEnv/resolvePinned.ts
|
|
21529
|
-
import { join as
|
|
23255
|
+
import { join as join22 } from "node:path";
|
|
21530
23256
|
function readInstalledVersion(dir, pkgName, fs) {
|
|
21531
23257
|
try {
|
|
21532
|
-
const pkgPath =
|
|
23258
|
+
const pkgPath = join22(dir, "node_modules", ...pkgName.split("/"), "package.json");
|
|
21533
23259
|
const raw = JSON.parse(fs.readFileSync(pkgPath));
|
|
21534
23260
|
const { version } = raw;
|
|
21535
23261
|
return typeof version === "string" ? version : undefined;
|
|
@@ -21577,10 +23303,10 @@ function allPinnedResolved(dir, fs, platform) {
|
|
|
21577
23303
|
}
|
|
21578
23304
|
|
|
21579
23305
|
// src/domains/runtimeEnv/shimDeps.ts
|
|
21580
|
-
import { join as
|
|
23306
|
+
import { join as join24 } from "node:path";
|
|
21581
23307
|
|
|
21582
23308
|
// src/shell/resolveExport.ts
|
|
21583
|
-
import { join as
|
|
23309
|
+
import { join as join23 } from "node:path";
|
|
21584
23310
|
function pickPath(entry, preference) {
|
|
21585
23311
|
if (typeof entry === "string")
|
|
21586
23312
|
return entry;
|
|
@@ -21612,28 +23338,28 @@ function resolveFromEnvDir(envDir, specifier, preference = "esm", fs = makeDefau
|
|
|
21612
23338
|
const pkgName = parts.slice(0, pkgPartCount).join("/");
|
|
21613
23339
|
const rest = parts.slice(pkgPartCount);
|
|
21614
23340
|
const subpath = rest.length > 0 ? "./" + rest.join("/") : ".";
|
|
21615
|
-
const pkgDir =
|
|
23341
|
+
const pkgDir = join23(envDir, "node_modules", ...pkgName.split("/"));
|
|
21616
23342
|
let pkg;
|
|
21617
23343
|
try {
|
|
21618
|
-
pkg = JSON.parse(fs.readFileSync(
|
|
23344
|
+
pkg = JSON.parse(fs.readFileSync(join23(pkgDir, "package.json")));
|
|
21619
23345
|
} catch (err) {
|
|
21620
23346
|
if (isNoEntError(err))
|
|
21621
|
-
throw new Error(`Package '${pkgName}' not found in ${
|
|
23347
|
+
throw new Error(`Package '${pkgName}' not found in ${join23(envDir, "node_modules")}`, { cause: err });
|
|
21622
23348
|
throw err;
|
|
21623
23349
|
}
|
|
21624
23350
|
const exportsMap = normalizeExports(pkg.exports);
|
|
21625
23351
|
const entry = exportsMap?.[subpath];
|
|
21626
23352
|
const relative = pickPath(entry, preference) ?? (subpath === "." ? pkg.module ?? pkg.main : undefined);
|
|
21627
23353
|
if (!relative) {
|
|
21628
|
-
throw new Error(`No entry for "${subpath}" in exports of ${
|
|
23354
|
+
throw new Error(`No entry for "${subpath}" in exports of ${join23(pkgDir, "package.json")}`);
|
|
21629
23355
|
}
|
|
21630
|
-
return
|
|
23356
|
+
return join23(pkgDir, relative);
|
|
21631
23357
|
}
|
|
21632
23358
|
|
|
21633
23359
|
// src/domains/runtimeEnv/shimDeps.ts
|
|
21634
23360
|
function readShimMarker(shimDir, fs) {
|
|
21635
23361
|
try {
|
|
21636
|
-
const pkg = JSON.parse(fs.readFileSync(
|
|
23362
|
+
const pkg = JSON.parse(fs.readFileSync(join24(shimDir, "package.json")));
|
|
21637
23363
|
if (pkg._qawolf_format) {
|
|
21638
23364
|
return {
|
|
21639
23365
|
_qawolf_version: pkg._qawolf_version ?? "",
|
|
@@ -21649,11 +23375,11 @@ function resolveBunBuild(bunBuild) {
|
|
|
21649
23375
|
return bunBuild === false ? undefined : { build: bunBuild };
|
|
21650
23376
|
}
|
|
21651
23377
|
async function removeStaleShims(flowsDir, flowsDeps, fs) {
|
|
21652
|
-
const shimsDir =
|
|
23378
|
+
const shimsDir = join24(flowsDir, "node_modules");
|
|
21653
23379
|
if (!fs.existsSync(shimsDir))
|
|
21654
23380
|
return;
|
|
21655
23381
|
for (const dep of flowsDeps) {
|
|
21656
|
-
const shimDepDir =
|
|
23382
|
+
const shimDepDir = join24(shimsDir, ...dep.split("/"));
|
|
21657
23383
|
if (readShimMarker(shimDepDir, fs)) {
|
|
21658
23384
|
await fs.rm(shimDepDir, { recursive: true, force: true });
|
|
21659
23385
|
}
|
|
@@ -21662,17 +23388,17 @@ async function removeStaleShims(flowsDir, flowsDeps, fs) {
|
|
|
21662
23388
|
async function buildDepShim(args) {
|
|
21663
23389
|
const { envDir, flowsDir, dep, fs, build } = args;
|
|
21664
23390
|
const depParts = dep.split("/");
|
|
21665
|
-
const depDir =
|
|
23391
|
+
const depDir = join24(envDir, "node_modules", ...depParts);
|
|
21666
23392
|
if (!fs.existsSync(depDir))
|
|
21667
23393
|
return;
|
|
21668
23394
|
let depVersion;
|
|
21669
23395
|
try {
|
|
21670
|
-
const pkg = JSON.parse(fs.readFileSync(
|
|
23396
|
+
const pkg = JSON.parse(fs.readFileSync(join24(depDir, "package.json")));
|
|
21671
23397
|
depVersion = pkg.version ?? "unknown";
|
|
21672
23398
|
} catch {
|
|
21673
23399
|
depVersion = "unknown";
|
|
21674
23400
|
}
|
|
21675
|
-
const shimDir =
|
|
23401
|
+
const shimDir = join24(flowsDir, "node_modules", ...depParts);
|
|
21676
23402
|
if (fs.existsSync(shimDir)) {
|
|
21677
23403
|
const marker = readShimMarker(shimDir, fs);
|
|
21678
23404
|
if (!marker)
|
|
@@ -21699,20 +23425,20 @@ async function buildDepShim(args) {
|
|
|
21699
23425
|
}
|
|
21700
23426
|
const shimCode = await output.text();
|
|
21701
23427
|
await fs.mkdir(shimDir, { recursive: true });
|
|
21702
|
-
await fs.writeFile(
|
|
23428
|
+
await fs.writeFile(join24(shimDir, "package.json"), JSON.stringify({
|
|
21703
23429
|
name: dep,
|
|
21704
23430
|
_qawolf_version: depVersion,
|
|
21705
23431
|
_qawolf_format: "bun-build-v1"
|
|
21706
23432
|
}));
|
|
21707
|
-
await fs.writeFile(
|
|
23433
|
+
await fs.writeFile(join24(shimDir, "index.js"), shimCode);
|
|
21708
23434
|
}
|
|
21709
23435
|
async function shimFlowsDeps(envDir, fs = makeDefaultFs(), bunBuild) {
|
|
21710
|
-
const flowsDir =
|
|
23436
|
+
const flowsDir = join24(envDir, "node_modules", "@qawolf", "flows");
|
|
21711
23437
|
if (!fs.existsSync(flowsDir))
|
|
21712
23438
|
return;
|
|
21713
23439
|
let flowsDeps;
|
|
21714
23440
|
try {
|
|
21715
|
-
const flowsPkg = JSON.parse(fs.readFileSync(
|
|
23441
|
+
const flowsPkg = JSON.parse(fs.readFileSync(join24(flowsDir, "package.json")));
|
|
21716
23442
|
flowsDeps = Object.keys(flowsPkg.dependencies ?? {});
|
|
21717
23443
|
} catch {
|
|
21718
23444
|
return;
|
|
@@ -21893,11 +23619,11 @@ function collectValue(value, previous) {
|
|
|
21893
23619
|
}
|
|
21894
23620
|
|
|
21895
23621
|
// src/shell/manifest/pulledEnv.ts
|
|
21896
|
-
import { join as
|
|
23622
|
+
import { join as join26 } from "node:path";
|
|
21897
23623
|
|
|
21898
23624
|
// src/shell/manifest/io.ts
|
|
21899
23625
|
import { createHash as createHash2 } from "node:crypto";
|
|
21900
|
-
import { join as
|
|
23626
|
+
import { join as join25 } from "node:path";
|
|
21901
23627
|
var manifestFilename = ".manifest.json";
|
|
21902
23628
|
var flowEntrySchema = object({
|
|
21903
23629
|
path: string2(),
|
|
@@ -21919,7 +23645,7 @@ var manifestSchema = object({
|
|
|
21919
23645
|
async function readManifest(envDir, fs = makeDefaultFs()) {
|
|
21920
23646
|
let raw;
|
|
21921
23647
|
try {
|
|
21922
|
-
raw = await fs.readFile(
|
|
23648
|
+
raw = await fs.readFile(join25(envDir, manifestFilename));
|
|
21923
23649
|
} catch (err) {
|
|
21924
23650
|
if (isNoEntError(err))
|
|
21925
23651
|
return "missing";
|
|
@@ -21953,7 +23679,7 @@ async function readManifest(envDir, fs = makeDefaultFs()) {
|
|
|
21953
23679
|
}
|
|
21954
23680
|
async function writeManifest(envDir, manifest, fs = makeDefaultFs()) {
|
|
21955
23681
|
const body = JSON.stringify(manifest, undefined, 2);
|
|
21956
|
-
await fs.writeFile(
|
|
23682
|
+
await fs.writeFile(join25(envDir, manifestFilename), `${body}
|
|
21957
23683
|
`);
|
|
21958
23684
|
}
|
|
21959
23685
|
function hashFile(absPath, fs = makeDefaultFs()) {
|
|
@@ -21979,7 +23705,7 @@ async function findPulledEnv(ref, cwd, fs = makeDefaultFs()) {
|
|
|
21979
23705
|
return;
|
|
21980
23706
|
}
|
|
21981
23707
|
async function listPulledEnvDirs(cwd, fs = makeDefaultFs()) {
|
|
21982
|
-
const root =
|
|
23708
|
+
const root = join26(cwd, ".qawolf");
|
|
21983
23709
|
let entries;
|
|
21984
23710
|
try {
|
|
21985
23711
|
entries = await fs.readdirWithTypes(root);
|
|
@@ -21992,7 +23718,7 @@ async function listPulledEnvDirs(cwd, fs = makeDefaultFs()) {
|
|
|
21992
23718
|
for (const entry of entries) {
|
|
21993
23719
|
if (!entry.isDirectory())
|
|
21994
23720
|
continue;
|
|
21995
|
-
const envDir =
|
|
23721
|
+
const envDir = join26(root, entry.name);
|
|
21996
23722
|
const manifest = await readManifest(envDir, fs);
|
|
21997
23723
|
if (typeof manifest === "string")
|
|
21998
23724
|
continue;
|
|
@@ -22046,9 +23772,6 @@ async function readEnvLabels(files, readEnvLabel) {
|
|
|
22046
23772
|
}
|
|
22047
23773
|
|
|
22048
23774
|
// src/core/suggestNearMiss.ts
|
|
22049
|
-
function normalize3(value) {
|
|
22050
|
-
return value.toLowerCase().replace(/[-_\s]+/g, " ").trim();
|
|
22051
|
-
}
|
|
22052
23775
|
function editDistance(a, b) {
|
|
22053
23776
|
let row = Array.from({ length: b.length + 1 }, (_, j) => j);
|
|
22054
23777
|
for (let i = 1;i <= a.length; i += 1) {
|
|
@@ -22070,14 +23793,14 @@ function suggestNearMiss(input, candidates) {
|
|
|
22070
23793
|
if (candidates.includes(input))
|
|
22071
23794
|
return;
|
|
22072
23795
|
const sorted = [...candidates].sort();
|
|
22073
|
-
const normalizedInput =
|
|
22074
|
-
const presentationMatch = sorted.find((candidate) =>
|
|
23796
|
+
const normalizedInput = normalizeForSearch(input);
|
|
23797
|
+
const presentationMatch = sorted.find((candidate) => normalizeForSearch(candidate) === normalizedInput);
|
|
22075
23798
|
if (presentationMatch !== undefined)
|
|
22076
23799
|
return presentationMatch;
|
|
22077
23800
|
let best;
|
|
22078
23801
|
let bestDistance = Number.POSITIVE_INFINITY;
|
|
22079
23802
|
for (const candidate of sorted) {
|
|
22080
|
-
const distance = editDistance(normalizedInput,
|
|
23803
|
+
const distance = editDistance(normalizedInput, normalizeForSearch(candidate));
|
|
22081
23804
|
if (distance < bestDistance) {
|
|
22082
23805
|
bestDistance = distance;
|
|
22083
23806
|
best = candidate;
|
|
@@ -22387,7 +24110,7 @@ async function flowsListRemote(ctx, pattern, options) {
|
|
|
22387
24110
|
}
|
|
22388
24111
|
|
|
22389
24112
|
// src/domains/flows/pull/handler.ts
|
|
22390
|
-
import { dirname as dirname8, join as
|
|
24113
|
+
import { dirname as dirname8, join as join31, resolve as resolve8 } from "node:path";
|
|
22391
24114
|
|
|
22392
24115
|
// src/domains/flows/pull/fetchPhase.ts
|
|
22393
24116
|
async function fetchTags(ctx, envId) {
|
|
@@ -22531,7 +24254,7 @@ async function checkSafety(args) {
|
|
|
22531
24254
|
}
|
|
22532
24255
|
|
|
22533
24256
|
// src/domains/flows/pull/reportPull.ts
|
|
22534
|
-
import { join as
|
|
24257
|
+
import { join as join27 } from "node:path";
|
|
22535
24258
|
function reportPullResult(ui, args) {
|
|
22536
24259
|
if (ui.mode !== "json")
|
|
22537
24260
|
return;
|
|
@@ -22546,12 +24269,12 @@ function reportPullResult(ui, args) {
|
|
|
22546
24269
|
assetDownloadedCount: args.assets.downloadedCount,
|
|
22547
24270
|
assetReusedCount: args.assets.reusedCount,
|
|
22548
24271
|
assetSkippedCount: args.assets.skippedCount,
|
|
22549
|
-
manifestPath:
|
|
24272
|
+
manifestPath: join27(args.stage.envDir, manifestFilename)
|
|
22550
24273
|
}, "");
|
|
22551
24274
|
}
|
|
22552
24275
|
|
|
22553
24276
|
// src/domains/flows/pull/bundle.ts
|
|
22554
|
-
import { join as
|
|
24277
|
+
import { join as join28, relative as relative6 } from "node:path";
|
|
22555
24278
|
async function flattenSingleWrapper(dir, fs = makeDefaultFs()) {
|
|
22556
24279
|
const entries = await fs.readdir(dir);
|
|
22557
24280
|
if (entries.length !== 1)
|
|
@@ -22559,12 +24282,12 @@ async function flattenSingleWrapper(dir, fs = makeDefaultFs()) {
|
|
|
22559
24282
|
const innerName = entries[0];
|
|
22560
24283
|
if (!innerName)
|
|
22561
24284
|
return;
|
|
22562
|
-
const inner =
|
|
24285
|
+
const inner = join28(dir, innerName);
|
|
22563
24286
|
const innerStat = await fs.stat(inner);
|
|
22564
24287
|
if (!innerStat.isDirectory())
|
|
22565
24288
|
return;
|
|
22566
24289
|
for (const e of await fs.readdir(inner)) {
|
|
22567
|
-
await fs.rename(
|
|
24290
|
+
await fs.rename(join28(inner, e), join28(dir, e));
|
|
22568
24291
|
}
|
|
22569
24292
|
const leftovers = await fs.readdir(inner);
|
|
22570
24293
|
if (leftovers.length > 0) {
|
|
@@ -22583,7 +24306,7 @@ async function buildManifest(args, fs = makeDefaultFs()) {
|
|
|
22583
24306
|
const flowPaths = await walkForFlows(args.bundleDir, fs);
|
|
22584
24307
|
const flows = await Promise.all(flowPaths.map(async (rel) => ({
|
|
22585
24308
|
path: toPosix(rel),
|
|
22586
|
-
contentHash: await hashFile(
|
|
24309
|
+
contentHash: await hashFile(join28(args.bundleDir, rel), fs),
|
|
22587
24310
|
tags: args.tags?.byPath.get(toPosix(rel))
|
|
22588
24311
|
})));
|
|
22589
24312
|
return {
|
|
@@ -22607,7 +24330,7 @@ async function walkForFlows(root, fs) {
|
|
|
22607
24330
|
async function walk(current, root, out, fs) {
|
|
22608
24331
|
const entries = await fs.readdirWithTypes(current);
|
|
22609
24332
|
for (const e of entries) {
|
|
22610
|
-
const abs =
|
|
24333
|
+
const abs = join28(current, e.name);
|
|
22611
24334
|
if (e.isDirectory()) {
|
|
22612
24335
|
await walk(abs, root, out, fs);
|
|
22613
24336
|
} else if (e.isFile() && flowExtensions.some((ext) => e.name.endsWith(ext))) {
|
|
@@ -22620,11 +24343,11 @@ async function sampleQawolfCommittedAt(bundleDir, fs = makeDefaultFs()) {
|
|
|
22620
24343
|
const sample = flowPaths[0];
|
|
22621
24344
|
if (!sample)
|
|
22622
24345
|
return;
|
|
22623
|
-
return (await fs.stat(
|
|
24346
|
+
return (await fs.stat(join28(bundleDir, sample))).mtime.toISOString();
|
|
22624
24347
|
}
|
|
22625
24348
|
|
|
22626
24349
|
// src/domains/flows/pull/applyTeamStorageRewrite.ts
|
|
22627
|
-
import { join as
|
|
24350
|
+
import { join as join29, relative as relative7 } from "node:path";
|
|
22628
24351
|
|
|
22629
24352
|
// src/domains/flows/pull/rewriteTeamStorage.ts
|
|
22630
24353
|
var runnerMountPrefix = "/home/wolf/team-storage/";
|
|
@@ -22660,7 +24383,7 @@ function isFlowFile(name) {
|
|
|
22660
24383
|
async function walk2(dir, out, fs) {
|
|
22661
24384
|
const entries = await fs.readdirWithTypes(dir);
|
|
22662
24385
|
for (const e of entries) {
|
|
22663
|
-
const abs =
|
|
24386
|
+
const abs = join29(dir, e.name);
|
|
22664
24387
|
if (e.isDirectory()) {
|
|
22665
24388
|
await walk2(abs, out, fs);
|
|
22666
24389
|
} else if (e.isFile() && isSourceFile(e.name)) {
|
|
@@ -22688,7 +24411,7 @@ async function applyTeamStorageRewrite(rootDir, fs = makeDefaultFs()) {
|
|
|
22688
24411
|
}
|
|
22689
24412
|
|
|
22690
24413
|
// src/domains/flows/pull/envVars.ts
|
|
22691
|
-
import { join as
|
|
24414
|
+
import { join as join30 } from "node:path";
|
|
22692
24415
|
|
|
22693
24416
|
// src/core/dotenv.ts
|
|
22694
24417
|
var bareKeyPattern = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
@@ -22753,7 +24476,7 @@ function unquote(escaped) {
|
|
|
22753
24476
|
async function writeEnvFile(envDir, vars, fs = makeDefaultFs()) {
|
|
22754
24477
|
if (Object.keys(vars).length === 0)
|
|
22755
24478
|
return;
|
|
22756
|
-
await fs.writeFile(
|
|
24479
|
+
await fs.writeFile(join30(envDir, ".env"), serializeDotenv(vars), {
|
|
22757
24480
|
mode: 384
|
|
22758
24481
|
});
|
|
22759
24482
|
}
|
|
@@ -22817,11 +24540,11 @@ var Ae = Symbol("flush");
|
|
|
22817
24540
|
var As = Symbol("flushChunk");
|
|
22818
24541
|
var z = Symbol("encoding");
|
|
22819
24542
|
var Mt = Symbol("decoder");
|
|
22820
|
-
var
|
|
24543
|
+
var g2 = Symbol("flowing");
|
|
22821
24544
|
var Qt = Symbol("paused");
|
|
22822
24545
|
var Bt = Symbol("resume");
|
|
22823
24546
|
var b = Symbol("buffer");
|
|
22824
|
-
var
|
|
24547
|
+
var N2 = Symbol("pipes");
|
|
22825
24548
|
var _ = Symbol("bufferLength");
|
|
22826
24549
|
var bi = Symbol("bufferPush");
|
|
22827
24550
|
var Ie = Symbol("bufferShift");
|
|
@@ -22869,9 +24592,9 @@ var Li = class extends Fe {
|
|
|
22869
24592
|
var Xr = (s) => !!s.objectMode;
|
|
22870
24593
|
var qr = (s) => !s.objectMode && !!s.encoding && s.encoding !== "buffer";
|
|
22871
24594
|
var A2 = class extends Di {
|
|
22872
|
-
[
|
|
24595
|
+
[g2] = false;
|
|
22873
24596
|
[Qt] = false;
|
|
22874
|
-
[
|
|
24597
|
+
[N2] = [];
|
|
22875
24598
|
[b] = [];
|
|
22876
24599
|
[L];
|
|
22877
24600
|
[z];
|
|
@@ -22894,7 +24617,7 @@ var A2 = class extends Di {
|
|
|
22894
24617
|
let e = t[0] || {};
|
|
22895
24618
|
if (super(), e.objectMode && typeof e.encoding == "string")
|
|
22896
24619
|
throw new TypeError("Encoding and objectMode may not be used together");
|
|
22897
|
-
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[
|
|
24620
|
+
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] });
|
|
22898
24621
|
let { signal: i } = e;
|
|
22899
24622
|
i && (this[Jt] = i, i.aborted ? this[xi]() : i.addEventListener("abort", () => this[xi]()));
|
|
22900
24623
|
}
|
|
@@ -22946,7 +24669,7 @@ var A2 = class extends Di {
|
|
|
22946
24669
|
else if (typeof t != "string")
|
|
22947
24670
|
throw new Error("Non-contiguous data written to non-objectMode stream");
|
|
22948
24671
|
}
|
|
22949
|
-
return this[L] ? (this[
|
|
24672
|
+
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]);
|
|
22950
24673
|
}
|
|
22951
24674
|
read(t) {
|
|
22952
24675
|
if (this[S])
|
|
@@ -22967,22 +24690,22 @@ var A2 = class extends Di {
|
|
|
22967
24690
|
return this.emit("data", e), !this[b].length && !this[Q] && this.emit("drain"), e;
|
|
22968
24691
|
}
|
|
22969
24692
|
end(t, e, i) {
|
|
22970
|
-
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[
|
|
24693
|
+
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;
|
|
22971
24694
|
}
|
|
22972
24695
|
[Bt]() {
|
|
22973
|
-
this[S] || (!this[Rt] && !this[
|
|
24696
|
+
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"));
|
|
22974
24697
|
}
|
|
22975
24698
|
resume() {
|
|
22976
24699
|
return this[Bt]();
|
|
22977
24700
|
}
|
|
22978
24701
|
pause() {
|
|
22979
|
-
this[
|
|
24702
|
+
this[g2] = false, this[Qt] = true, this[C2] = false;
|
|
22980
24703
|
}
|
|
22981
24704
|
get destroyed() {
|
|
22982
24705
|
return this[S];
|
|
22983
24706
|
}
|
|
22984
24707
|
get flowing() {
|
|
22985
|
-
return this[
|
|
24708
|
+
return this[g2];
|
|
22986
24709
|
}
|
|
22987
24710
|
get paused() {
|
|
22988
24711
|
return this[Qt];
|
|
@@ -23000,18 +24723,18 @@ var A2 = class extends Di {
|
|
|
23000
24723
|
!t && !this[b].length && !this[Q] && this.emit("drain");
|
|
23001
24724
|
}
|
|
23002
24725
|
[As](t) {
|
|
23003
|
-
return this.emit("data", t), this[
|
|
24726
|
+
return this.emit("data", t), this[g2];
|
|
23004
24727
|
}
|
|
23005
24728
|
pipe(t, e) {
|
|
23006
24729
|
if (this[S])
|
|
23007
24730
|
return t;
|
|
23008
24731
|
this[C2] = false;
|
|
23009
24732
|
let i = this[nt];
|
|
23010
|
-
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[
|
|
24733
|
+
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;
|
|
23011
24734
|
}
|
|
23012
24735
|
unpipe(t) {
|
|
23013
|
-
let e = this[
|
|
23014
|
-
e && (this[
|
|
24736
|
+
let e = this[N2].find((i) => i.dest === t);
|
|
24737
|
+
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());
|
|
23015
24738
|
}
|
|
23016
24739
|
addListener(t, e) {
|
|
23017
24740
|
return this.on(t, e);
|
|
@@ -23019,7 +24742,7 @@ var A2 = class extends Di {
|
|
|
23019
24742
|
on(t, e) {
|
|
23020
24743
|
let i = super.on(t, e);
|
|
23021
24744
|
if (t === "data")
|
|
23022
|
-
this[C2] = false, this[Rt]++, !this[
|
|
24745
|
+
this[C2] = false, this[Rt]++, !this[N2].length && !this[g2] && this[Bt]();
|
|
23023
24746
|
else if (t === "readable" && this[_] !== 0)
|
|
23024
24747
|
super.emit("readable");
|
|
23025
24748
|
else if (Kr(t) && this[nt])
|
|
@@ -23035,11 +24758,11 @@ var A2 = class extends Di {
|
|
|
23035
24758
|
}
|
|
23036
24759
|
off(t, e) {
|
|
23037
24760
|
let i = super.off(t, e);
|
|
23038
|
-
return t === "data" && (this[Rt] = this.listeners("data").length, this[Rt] === 0 && !this[C2] && !this[
|
|
24761
|
+
return t === "data" && (this[Rt] = this.listeners("data").length, this[Rt] === 0 && !this[C2] && !this[N2].length && (this[g2] = false)), i;
|
|
23039
24762
|
}
|
|
23040
24763
|
removeAllListeners(t) {
|
|
23041
24764
|
let e = super.removeAllListeners(t);
|
|
23042
|
-
return (t === "data" || t === undefined) && (this[Rt] = 0, !this[C2] && !this[
|
|
24765
|
+
return (t === "data" || t === undefined) && (this[Rt] = 0, !this[C2] && !this[N2].length && (this[g2] = false)), e;
|
|
23043
24766
|
}
|
|
23044
24767
|
get emittedEnd() {
|
|
23045
24768
|
return this[nt];
|
|
@@ -23075,7 +24798,7 @@ var A2 = class extends Di {
|
|
|
23075
24798
|
return this[J](), r;
|
|
23076
24799
|
}
|
|
23077
24800
|
[Oi](t) {
|
|
23078
|
-
for (let i of this[
|
|
24801
|
+
for (let i of this[N2])
|
|
23079
24802
|
i.dest.write(t) === false && this.pause();
|
|
23080
24803
|
let e = this[C2] ? false : super.emit("data", t);
|
|
23081
24804
|
return this[J](), e;
|
|
@@ -23087,12 +24810,12 @@ var A2 = class extends Di {
|
|
|
23087
24810
|
if (this[Mt]) {
|
|
23088
24811
|
let e = this[Mt].end();
|
|
23089
24812
|
if (e) {
|
|
23090
|
-
for (let i of this[
|
|
24813
|
+
for (let i of this[N2])
|
|
23091
24814
|
i.dest.write(e);
|
|
23092
24815
|
this[C2] || super.emit("data", e);
|
|
23093
24816
|
}
|
|
23094
24817
|
}
|
|
23095
|
-
for (let e of this[
|
|
24818
|
+
for (let e of this[N2])
|
|
23096
24819
|
e.end();
|
|
23097
24820
|
let t = super.emit("end");
|
|
23098
24821
|
return this.removeAllListeners("end"), t;
|
|
@@ -23168,7 +24891,7 @@ var Jr = I.writev;
|
|
|
23168
24891
|
var ht = Symbol("_autoClose");
|
|
23169
24892
|
var H = Symbol("_close");
|
|
23170
24893
|
var te = Symbol("_ended");
|
|
23171
|
-
var
|
|
24894
|
+
var m3 = Symbol("_fd");
|
|
23172
24895
|
var Ni = Symbol("_finished");
|
|
23173
24896
|
var tt = Symbol("_flags");
|
|
23174
24897
|
var Ai = Symbol("_flush");
|
|
@@ -23195,7 +24918,7 @@ var Me = Symbol("_defaultFlag");
|
|
|
23195
24918
|
var bt = Symbol("_errored");
|
|
23196
24919
|
var _t = class extends A2 {
|
|
23197
24920
|
[bt] = false;
|
|
23198
|
-
[
|
|
24921
|
+
[m3];
|
|
23199
24922
|
[U2];
|
|
23200
24923
|
[Ci];
|
|
23201
24924
|
[j] = false;
|
|
@@ -23205,10 +24928,10 @@ var _t = class extends A2 {
|
|
|
23205
24928
|
constructor(t, e) {
|
|
23206
24929
|
if (e = e || {}, super(e), this.readable = true, this.writable = false, typeof t != "string")
|
|
23207
24930
|
throw new TypeError("path must be a string");
|
|
23208
|
-
this[bt] = false, this[
|
|
24931
|
+
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]();
|
|
23209
24932
|
}
|
|
23210
24933
|
get fd() {
|
|
23211
|
-
return this[
|
|
24934
|
+
return this[m3];
|
|
23212
24935
|
}
|
|
23213
24936
|
get path() {
|
|
23214
24937
|
return this[U2];
|
|
@@ -23223,7 +24946,7 @@ var _t = class extends A2 {
|
|
|
23223
24946
|
I.open(this[U2], "r", (t, e) => this[Ht](t, e));
|
|
23224
24947
|
}
|
|
23225
24948
|
[Ht](t, e) {
|
|
23226
|
-
t ? this[Ut](t) : (this[
|
|
24949
|
+
t ? this[Ut](t) : (this[m3] = e, this.emit("open", e), this[zt]());
|
|
23227
24950
|
}
|
|
23228
24951
|
[vi]() {
|
|
23229
24952
|
return Buffer.allocUnsafe(Math.min(this[Ci], this[ee]));
|
|
@@ -23234,16 +24957,16 @@ var _t = class extends A2 {
|
|
|
23234
24957
|
let t = this[vi]();
|
|
23235
24958
|
if (t.length === 0)
|
|
23236
24959
|
return process.nextTick(() => this[Ii](null, 0, t));
|
|
23237
|
-
I.read(this[
|
|
24960
|
+
I.read(this[m3], t, 0, t.length, null, (e, i, r) => this[Ii](e, i, r));
|
|
23238
24961
|
}
|
|
23239
24962
|
}
|
|
23240
24963
|
[Ii](t, e, i) {
|
|
23241
24964
|
this[j] = false, t ? this[Ut](t) : this[ki](e, i) && this[zt]();
|
|
23242
24965
|
}
|
|
23243
24966
|
[H]() {
|
|
23244
|
-
if (this[ht] && typeof this[
|
|
23245
|
-
let t = this[
|
|
23246
|
-
this[
|
|
24967
|
+
if (this[ht] && typeof this[m3] == "number") {
|
|
24968
|
+
let t = this[m3];
|
|
24969
|
+
this[m3] = undefined, I.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
|
|
23247
24970
|
}
|
|
23248
24971
|
}
|
|
23249
24972
|
[Ut](t) {
|
|
@@ -23259,7 +24982,7 @@ var _t = class extends A2 {
|
|
|
23259
24982
|
case "finish":
|
|
23260
24983
|
return false;
|
|
23261
24984
|
case "drain":
|
|
23262
|
-
return typeof this[
|
|
24985
|
+
return typeof this[m3] == "number" && this[zt](), false;
|
|
23263
24986
|
case "error":
|
|
23264
24987
|
return this[bt] ? false : (this[bt] = true, super.emit(t, ...e));
|
|
23265
24988
|
default:
|
|
@@ -23282,7 +25005,7 @@ var Be = class extends _t {
|
|
|
23282
25005
|
if (!this[j]) {
|
|
23283
25006
|
this[j] = true;
|
|
23284
25007
|
do {
|
|
23285
|
-
let e = this[vi](), i = e.length === 0 ? 0 : I.readSync(this[
|
|
25008
|
+
let e = this[vi](), i = e.length === 0 ? 0 : I.readSync(this[m3], e, 0, e.length, null);
|
|
23286
25009
|
if (!this[ki](i, e))
|
|
23287
25010
|
break;
|
|
23288
25011
|
} while (true);
|
|
@@ -23294,9 +25017,9 @@ var Be = class extends _t {
|
|
|
23294
25017
|
}
|
|
23295
25018
|
}
|
|
23296
25019
|
[H]() {
|
|
23297
|
-
if (this[ht] && typeof this[
|
|
23298
|
-
let t = this[
|
|
23299
|
-
this[
|
|
25020
|
+
if (this[ht] && typeof this[m3] == "number") {
|
|
25021
|
+
let t = this[m3];
|
|
25022
|
+
this[m3] = undefined, I.closeSync(t), this.emit("close");
|
|
23300
25023
|
}
|
|
23301
25024
|
}
|
|
23302
25025
|
};
|
|
@@ -23311,15 +25034,15 @@ var et = class extends Qr {
|
|
|
23311
25034
|
[U2];
|
|
23312
25035
|
[ie];
|
|
23313
25036
|
[ht];
|
|
23314
|
-
[
|
|
25037
|
+
[m3];
|
|
23315
25038
|
[Me];
|
|
23316
25039
|
[tt];
|
|
23317
25040
|
[Ni] = false;
|
|
23318
25041
|
[ot];
|
|
23319
25042
|
constructor(t, e) {
|
|
23320
|
-
e = e || {}, super(e), this[U2] = t, this[
|
|
25043
|
+
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;
|
|
23321
25044
|
let i = this[ot] !== undefined ? "r+" : "w";
|
|
23322
|
-
this[Me] = e.flags === undefined, this[tt] = e.flags === undefined ? i : e.flags, this[
|
|
25045
|
+
this[Me] = e.flags === undefined, this[tt] = e.flags === undefined ? i : e.flags, this[m3] === undefined && this[at]();
|
|
23323
25046
|
}
|
|
23324
25047
|
emit(t, ...e) {
|
|
23325
25048
|
if (t === "error") {
|
|
@@ -23330,7 +25053,7 @@ var et = class extends Qr {
|
|
|
23330
25053
|
return super.emit(t, ...e);
|
|
23331
25054
|
}
|
|
23332
25055
|
get fd() {
|
|
23333
|
-
return this[
|
|
25056
|
+
return this[m3];
|
|
23334
25057
|
}
|
|
23335
25058
|
get path() {
|
|
23336
25059
|
return this[U2];
|
|
@@ -23342,16 +25065,16 @@ var et = class extends Qr {
|
|
|
23342
25065
|
I.open(this[U2], this[tt], this[ie], (t, e) => this[Ht](t, e));
|
|
23343
25066
|
}
|
|
23344
25067
|
[Ht](t, e) {
|
|
23345
|
-
this[Me] && this[tt] === "r+" && t && t.code === "ENOENT" ? (this[tt] = "w", this[at]()) : t ? this[Ut](t) : (this[
|
|
25068
|
+
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]());
|
|
23346
25069
|
}
|
|
23347
25070
|
end(t, e) {
|
|
23348
|
-
return t && this.write(t, e), this[te] = true, !this[gt] && !this[Y].length && typeof this[
|
|
25071
|
+
return t && this.write(t, e), this[te] = true, !this[gt] && !this[Y].length && typeof this[m3] == "number" && this[Pt](null, 0), this;
|
|
23349
25072
|
}
|
|
23350
25073
|
write(t, e) {
|
|
23351
|
-
return typeof t == "string" && (t = Buffer.from(t, e)), this[te] ? (this.emit("error", new Error("write() after end()")), false) : this[
|
|
25074
|
+
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);
|
|
23352
25075
|
}
|
|
23353
25076
|
[ve](t) {
|
|
23354
|
-
I.write(this[
|
|
25077
|
+
I.write(this[m3], t, 0, t.length, this[ot], (e, i) => this[Pt](e, i));
|
|
23355
25078
|
}
|
|
23356
25079
|
[Pt](t, e) {
|
|
23357
25080
|
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"))));
|
|
@@ -23363,13 +25086,13 @@ var et = class extends Qr {
|
|
|
23363
25086
|
this[ve](this[Y].pop());
|
|
23364
25087
|
else {
|
|
23365
25088
|
let t = this[Y];
|
|
23366
|
-
this[Y] = [], Jr(this[
|
|
25089
|
+
this[Y] = [], Jr(this[m3], t, this[ot], (e, i) => this[Pt](e, i));
|
|
23367
25090
|
}
|
|
23368
25091
|
}
|
|
23369
25092
|
[H]() {
|
|
23370
|
-
if (this[ht] && typeof this[
|
|
23371
|
-
let t = this[
|
|
23372
|
-
this[
|
|
25093
|
+
if (this[ht] && typeof this[m3] == "number") {
|
|
25094
|
+
let t = this[m3];
|
|
25095
|
+
this[m3] = undefined, I.close(t, (e) => e ? this.emit("error", e) : this.emit("close"));
|
|
23373
25096
|
}
|
|
23374
25097
|
}
|
|
23375
25098
|
};
|
|
@@ -23389,15 +25112,15 @@ var Wt = class extends et {
|
|
|
23389
25112
|
this[Ht](null, t);
|
|
23390
25113
|
}
|
|
23391
25114
|
[H]() {
|
|
23392
|
-
if (this[ht] && typeof this[
|
|
23393
|
-
let t = this[
|
|
23394
|
-
this[
|
|
25115
|
+
if (this[ht] && typeof this[m3] == "number") {
|
|
25116
|
+
let t = this[m3];
|
|
25117
|
+
this[m3] = undefined, I.closeSync(t), this.emit("close");
|
|
23395
25118
|
}
|
|
23396
25119
|
}
|
|
23397
25120
|
[ve](t) {
|
|
23398
25121
|
let e = true;
|
|
23399
25122
|
try {
|
|
23400
|
-
this[Pt](null, I.writeSync(this[
|
|
25123
|
+
this[Pt](null, I.writeSync(this[m3], t, 0, t.length, this[ot])), e = false;
|
|
23401
25124
|
} finally {
|
|
23402
25125
|
if (e)
|
|
23403
25126
|
try {
|
|
@@ -25375,7 +27098,7 @@ var Dr = Symbol("onEntry");
|
|
|
25375
27098
|
var _s = Symbol("checkFs");
|
|
25376
27099
|
var Nr = Symbol("checkFs2");
|
|
25377
27100
|
var Os = Symbol("isReusable");
|
|
25378
|
-
var
|
|
27101
|
+
var P2 = Symbol("makeFs");
|
|
25379
27102
|
var Ts = Symbol("file");
|
|
25380
27103
|
var xs = Symbol("directory");
|
|
25381
27104
|
var gi = Symbol("link");
|
|
@@ -25663,23 +27386,23 @@ var Xt = class extends rt {
|
|
|
25663
27386
|
return;
|
|
25664
27387
|
}
|
|
25665
27388
|
if (h || this[Os](t, a))
|
|
25666
|
-
return this[
|
|
27389
|
+
return this[P2](null, t, i);
|
|
25667
27390
|
if (a.isDirectory()) {
|
|
25668
27391
|
if (t.type === "Directory") {
|
|
25669
|
-
let l = this.chmod && t.mode && (a.mode & 4095) !== t.mode, c = (d) => this[
|
|
27392
|
+
let l = this.chmod && t.mode && (a.mode & 4095) !== t.mode, c = (d) => this[P2](d ?? null, t, i);
|
|
25670
27393
|
return l ? u3.chmod(String(t.absolute), Number(t.mode), c) : c();
|
|
25671
27394
|
}
|
|
25672
27395
|
if (t.absolute !== this.cwd)
|
|
25673
|
-
return u3.rmdir(String(t.absolute), (l) => this[
|
|
27396
|
+
return u3.rmdir(String(t.absolute), (l) => this[P2](l ?? null, t, i));
|
|
25674
27397
|
}
|
|
25675
27398
|
if (t.absolute === this.cwd)
|
|
25676
|
-
return this[
|
|
25677
|
-
mo(String(t.absolute), (l) => this[
|
|
27399
|
+
return this[P2](null, t, i);
|
|
27400
|
+
mo(String(t.absolute), (l) => this[P2](l ?? null, t, i));
|
|
25678
27401
|
});
|
|
25679
27402
|
};
|
|
25680
27403
|
this[Oe] ? n() : r();
|
|
25681
27404
|
}
|
|
25682
|
-
[
|
|
27405
|
+
[P2](t, e, i) {
|
|
25683
27406
|
if (t) {
|
|
25684
27407
|
this[O](t, e), i();
|
|
25685
27408
|
return;
|
|
@@ -25713,8 +27436,8 @@ var ye = (s) => {
|
|
|
25713
27436
|
};
|
|
25714
27437
|
var xe = class extends Xt {
|
|
25715
27438
|
sync = true;
|
|
25716
|
-
[
|
|
25717
|
-
return super[
|
|
27439
|
+
[P2](t, e) {
|
|
27440
|
+
return super[P2](t, e, () => {});
|
|
25718
27441
|
}
|
|
25719
27442
|
[_s](t) {
|
|
25720
27443
|
if (!this[Oe]) {
|
|
@@ -25735,19 +27458,19 @@ var xe = class extends Xt {
|
|
|
25735
27458
|
if (i && (this.keep || this.newer && i.mtime > (t.mtime ?? i.mtime)))
|
|
25736
27459
|
return this[Ls](t);
|
|
25737
27460
|
if (e || this[Os](t, i))
|
|
25738
|
-
return this[
|
|
27461
|
+
return this[P2](null, t);
|
|
25739
27462
|
if (i.isDirectory()) {
|
|
25740
27463
|
if (t.type === "Directory") {
|
|
25741
27464
|
let o = this.chmod && t.mode && (i.mode & 4095) !== t.mode, [h] = o ? ye(() => {
|
|
25742
27465
|
u3.chmodSync(String(t.absolute), Number(t.mode));
|
|
25743
27466
|
}) : [];
|
|
25744
|
-
return this[
|
|
27467
|
+
return this[P2](h, t);
|
|
25745
27468
|
}
|
|
25746
27469
|
let [n] = ye(() => u3.rmdirSync(String(t.absolute)));
|
|
25747
|
-
this[
|
|
27470
|
+
this[P2](n, t);
|
|
25748
27471
|
}
|
|
25749
27472
|
let [r] = t.absolute === this.cwd ? [] : ye(() => po(String(t.absolute)));
|
|
25750
|
-
this[
|
|
27473
|
+
this[P2](r, t);
|
|
25751
27474
|
}
|
|
25752
27475
|
[Ts](t, e) {
|
|
25753
27476
|
let i = typeof t.mode == "number" ? t.mode & 4095 : this.fmode, r = (h) => {
|
|
@@ -26195,8 +27918,8 @@ async function handleFlowsPull(ctx, opts, deps) {
|
|
|
26195
27918
|
ctx.ui.error(validation.error);
|
|
26196
27919
|
return { error: validation.error };
|
|
26197
27920
|
}
|
|
26198
|
-
const destAbs = resolve8(opts.out ??
|
|
26199
|
-
const assetsAbs =
|
|
27921
|
+
const destAbs = resolve8(opts.out ?? join31(".qawolf", opts.env));
|
|
27922
|
+
const assetsAbs = join31(dirname8(destAbs), "assets");
|
|
26200
27923
|
await resolvedDeps.fs.mkdir(assetsAbs, { recursive: true });
|
|
26201
27924
|
const yes = opts.yes ?? false;
|
|
26202
27925
|
let archive;
|
|
@@ -27071,15 +28794,15 @@ async function defaultRunWebFlowDeps(envDir = process.cwd(), signals) {
|
|
|
27071
28794
|
|
|
27072
28795
|
// src/domains/runtimeEnv/prepareRunDir.ts
|
|
27073
28796
|
import { createHash as createHash4 } from "node:crypto";
|
|
27074
|
-
import { join as
|
|
28797
|
+
import { join as join39, resolve as resolve9 } from "node:path";
|
|
27075
28798
|
|
|
27076
28799
|
// src/domains/runtimeEnv/execSubpathImports.ts
|
|
27077
|
-
import { join as
|
|
28800
|
+
import { join as join32 } from "node:path";
|
|
27078
28801
|
var flowSubpathImports = { "#playwright": "playwright" };
|
|
27079
28802
|
var packageJsonSchema = looseObject({ imports: record(string2(), unknown()).catch({}) }).catch({ imports: {} });
|
|
27080
28803
|
async function writeExecSubpathImports(args) {
|
|
27081
28804
|
const { execDir, fs } = args;
|
|
27082
|
-
const pkgPath =
|
|
28805
|
+
const pkgPath = join32(execDir, "package.json");
|
|
27083
28806
|
const pkg = packageJsonSchema.parse(await readPackageJson(pkgPath, fs));
|
|
27084
28807
|
const merged = { ...pkg, imports: { ...pkg.imports, ...flowSubpathImports } };
|
|
27085
28808
|
await fs.writeFile(pkgPath, JSON.stringify(merged, undefined, 2));
|
|
@@ -27101,10 +28824,10 @@ async function readPackageJson(pkgPath, fs) {
|
|
|
27101
28824
|
}
|
|
27102
28825
|
|
|
27103
28826
|
// src/domains/runtimeEnv/innerHop.ts
|
|
27104
|
-
import { join as
|
|
28827
|
+
import { join as join34 } from "node:path";
|
|
27105
28828
|
|
|
27106
28829
|
// src/domains/runtimeEnv/linkPinnedPackages.ts
|
|
27107
|
-
import { dirname as dirname9, join as
|
|
28830
|
+
import { dirname as dirname9, join as join33 } from "node:path";
|
|
27108
28831
|
|
|
27109
28832
|
// src/domains/runtimeEnv/symlinkDir.ts
|
|
27110
28833
|
import { symlink } from "node:fs/promises";
|
|
@@ -27116,16 +28839,16 @@ async function createDirSymlink(source, target) {
|
|
|
27116
28839
|
// src/domains/runtimeEnv/linkPinnedPackages.ts
|
|
27117
28840
|
async function linkPinnedPackages(args) {
|
|
27118
28841
|
const { depsRoot, nodeModulesDir, fs } = args;
|
|
27119
|
-
const managedModules =
|
|
28842
|
+
const managedModules = join33(depsRoot, "node_modules");
|
|
27120
28843
|
await fs.mkdir(nodeModulesDir, { recursive: true });
|
|
27121
28844
|
for (const { name } of pinnedPackages) {
|
|
27122
28845
|
const segments = name.split("/");
|
|
27123
|
-
const target =
|
|
28846
|
+
const target = join33(nodeModulesDir, ...segments);
|
|
27124
28847
|
if (segments.length > 1) {
|
|
27125
28848
|
await fs.mkdir(dirname9(target), { recursive: true });
|
|
27126
28849
|
}
|
|
27127
28850
|
await fs.rm(target, { recursive: true, force: true });
|
|
27128
|
-
await createDirSymlink(
|
|
28851
|
+
await createDirSymlink(join33(managedModules, ...segments), target);
|
|
27129
28852
|
}
|
|
27130
28853
|
}
|
|
27131
28854
|
|
|
@@ -27134,18 +28857,18 @@ async function populateInnerHop(args) {
|
|
|
27134
28857
|
const { depsRoot, execDir, fs } = args;
|
|
27135
28858
|
await linkPinnedPackages({
|
|
27136
28859
|
depsRoot,
|
|
27137
|
-
nodeModulesDir:
|
|
28860
|
+
nodeModulesDir: join34(execDir, "node_modules"),
|
|
27138
28861
|
fs
|
|
27139
28862
|
});
|
|
27140
28863
|
}
|
|
27141
28864
|
|
|
27142
28865
|
// src/domains/runtimeEnv/outerHop.ts
|
|
27143
28866
|
import { lstat as lstat2 } from "node:fs/promises";
|
|
27144
|
-
import { dirname as dirname11, join as
|
|
28867
|
+
import { dirname as dirname11, join as join36 } from "node:path";
|
|
27145
28868
|
|
|
27146
28869
|
// src/domains/runtimeEnv/carryOverPackages.ts
|
|
27147
28870
|
import { lstat } from "node:fs/promises";
|
|
27148
|
-
import { dirname as dirname10, join as
|
|
28871
|
+
import { dirname as dirname10, join as join35 } from "node:path";
|
|
27149
28872
|
|
|
27150
28873
|
// src/domains/runtimeEnv/carryOverPlan.ts
|
|
27151
28874
|
var executorPackageNames = new Set(pinnedPackages.map((p) => p.name));
|
|
@@ -27167,12 +28890,12 @@ async function carryOverPackages(args) {
|
|
|
27167
28890
|
const linked = [];
|
|
27168
28891
|
for (const name of names) {
|
|
27169
28892
|
const segments = name.split("/");
|
|
27170
|
-
const target =
|
|
28893
|
+
const target = join35(nodeModulesDir, ...segments);
|
|
27171
28894
|
if (await pathOccupied(target))
|
|
27172
28895
|
continue;
|
|
27173
28896
|
if (segments.length > 1)
|
|
27174
28897
|
await fs.mkdir(dirname10(target), { recursive: true });
|
|
27175
|
-
await createDirSymlink(
|
|
28898
|
+
await createDirSymlink(join35(projectModulesDir, ...segments), target);
|
|
27176
28899
|
linked.push(name);
|
|
27177
28900
|
}
|
|
27178
28901
|
return linked;
|
|
@@ -27195,18 +28918,18 @@ async function listPackageNames(dir, fs) {
|
|
|
27195
28918
|
const names = [];
|
|
27196
28919
|
for (const entry of entries) {
|
|
27197
28920
|
if (!entry.startsWith("@")) {
|
|
27198
|
-
if (fs.existsSync(
|
|
28921
|
+
if (fs.existsSync(join35(dir, entry, "package.json")))
|
|
27199
28922
|
names.push(entry);
|
|
27200
28923
|
continue;
|
|
27201
28924
|
}
|
|
27202
28925
|
let scoped;
|
|
27203
28926
|
try {
|
|
27204
|
-
scoped = await fs.readdir(
|
|
28927
|
+
scoped = await fs.readdir(join35(dir, entry));
|
|
27205
28928
|
} catch {
|
|
27206
28929
|
continue;
|
|
27207
28930
|
}
|
|
27208
28931
|
for (const inner of scoped) {
|
|
27209
|
-
if (fs.existsSync(
|
|
28932
|
+
if (fs.existsSync(join35(dir, entry, inner, "package.json"))) {
|
|
27210
28933
|
names.push(`${entry}/${inner}`);
|
|
27211
28934
|
}
|
|
27212
28935
|
}
|
|
@@ -27224,7 +28947,7 @@ async function populateOuterHop(args) {
|
|
|
27224
28947
|
const depNames = Object.keys(installableDeps);
|
|
27225
28948
|
const { satisfying, rejected } = await findSatisfyingNodeModulesDir(projectDir, depNames, fs);
|
|
27226
28949
|
if (satisfying !== undefined) {
|
|
27227
|
-
await createDirSymlink(satisfying,
|
|
28950
|
+
await createDirSymlink(satisfying, join36(runDir, "node_modules"));
|
|
27228
28951
|
return { mode: "symlink", nodeModulesDir: satisfying };
|
|
27229
28952
|
}
|
|
27230
28953
|
if (depNames.length === 0)
|
|
@@ -27233,12 +28956,12 @@ async function populateOuterHop(args) {
|
|
|
27233
28956
|
await installOuterDeps(runDir, installableDeps, fs, args.install ?? spawnNpmInstall);
|
|
27234
28957
|
await linkPinnedPackages({
|
|
27235
28958
|
depsRoot: args.depsRoot,
|
|
27236
|
-
nodeModulesDir:
|
|
28959
|
+
nodeModulesDir: join36(runDir, "node_modules"),
|
|
27237
28960
|
fs
|
|
27238
28961
|
});
|
|
27239
28962
|
const carriedOver = await carryOverPackages({
|
|
27240
|
-
projectModulesDir:
|
|
27241
|
-
nodeModulesDir:
|
|
28963
|
+
projectModulesDir: join36(projectDir, "node_modules"),
|
|
28964
|
+
nodeModulesDir: join36(runDir, "node_modules"),
|
|
27242
28965
|
fs
|
|
27243
28966
|
});
|
|
27244
28967
|
return { mode: "install", depCount: depNames.length, rejected, carriedOver };
|
|
@@ -27247,10 +28970,10 @@ async function findSatisfyingNodeModulesDir(startDir, depNames, fs) {
|
|
|
27247
28970
|
const rejected = [];
|
|
27248
28971
|
let dir = startDir;
|
|
27249
28972
|
while (true) {
|
|
27250
|
-
const candidate =
|
|
28973
|
+
const candidate = join36(dir, "node_modules");
|
|
27251
28974
|
const stats = await lstatSafe(candidate);
|
|
27252
28975
|
if (stats !== undefined && stats.isDirectory() && !stats.isSymbolicLink()) {
|
|
27253
|
-
const missing = depNames.filter((name) => !fs.existsSync(
|
|
28976
|
+
const missing = depNames.filter((name) => !fs.existsSync(join36(candidate, name, "package.json")));
|
|
27254
28977
|
if (missing.length === 0)
|
|
27255
28978
|
return { satisfying: candidate, rejected };
|
|
27256
28979
|
rejected.push({ dir: candidate, missing });
|
|
@@ -27271,7 +28994,7 @@ async function lstatSafe(path) {
|
|
|
27271
28994
|
async function readInstallableDeps(projectDir, fs) {
|
|
27272
28995
|
let content;
|
|
27273
28996
|
try {
|
|
27274
|
-
content = await fs.readFile(
|
|
28997
|
+
content = await fs.readFile(join36(projectDir, "package.json"));
|
|
27275
28998
|
} catch {
|
|
27276
28999
|
return {};
|
|
27277
29000
|
}
|
|
@@ -27289,8 +29012,8 @@ async function readInstallableDeps(projectDir, fs) {
|
|
|
27289
29012
|
return Object.fromEntries(Object.entries(rawDeps).filter(([name]) => !executorPackageNames2.has(name)).filter((entry) => typeof entry[1] === "string"));
|
|
27290
29013
|
}
|
|
27291
29014
|
async function installOuterDeps(runDir, deps, fs, install) {
|
|
27292
|
-
await fs.writeFile(
|
|
27293
|
-
await fs.writeFile(
|
|
29015
|
+
await fs.writeFile(join36(runDir, "package.json"), JSON.stringify({ name: "qawolf-run", private: true, dependencies: deps }, undefined, 2));
|
|
29016
|
+
await fs.writeFile(join36(runDir, ".npmrc"), `@qawolf:registry=https://registry.npmjs.org/
|
|
27294
29017
|
`);
|
|
27295
29018
|
const result = await install(runDir);
|
|
27296
29019
|
if (result.exitCode !== 0) {
|
|
@@ -27300,14 +29023,14 @@ async function installOuterDeps(runDir, deps, fs, install) {
|
|
|
27300
29023
|
|
|
27301
29024
|
// src/domains/runtimeEnv/stageFlowFiles.ts
|
|
27302
29025
|
import { createHash as createHash3 } from "node:crypto";
|
|
27303
|
-
import { basename as basename6, dirname as dirname12, join as
|
|
29026
|
+
import { basename as basename6, dirname as dirname12, join as join38, sep as sep4 } from "node:path";
|
|
27304
29027
|
|
|
27305
29028
|
// src/shell/copyDir.ts
|
|
27306
29029
|
import { cp, readdir as readdir2 } from "node:fs/promises";
|
|
27307
|
-
import { basename as basename5, join as
|
|
29030
|
+
import { basename as basename5, join as join37 } from "node:path";
|
|
27308
29031
|
async function copyDirExcluding(source, destination, excludedNames) {
|
|
27309
29032
|
const entries = await readdir2(source, { withFileTypes: true });
|
|
27310
|
-
await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(
|
|
29033
|
+
await Promise.all(entries.filter((entry) => !excludedNames.has(entry.name)).map((entry) => cp(join37(source, entry.name), join37(destination, entry.name), {
|
|
27311
29034
|
recursive: true,
|
|
27312
29035
|
filter: (path) => !excludedNames.has(basename5(path))
|
|
27313
29036
|
})));
|
|
@@ -27324,14 +29047,14 @@ async function stageFlowFiles(args) {
|
|
|
27324
29047
|
if (files.length > 1) {
|
|
27325
29048
|
return Promise.all(files.map((f) => stageOneFile(f, execDir, fs)));
|
|
27326
29049
|
}
|
|
27327
|
-
await Promise.all(files.map((f) => fs.copyFile(f,
|
|
27328
|
-
return files.map((f) =>
|
|
29050
|
+
await Promise.all(files.map((f) => fs.copyFile(f, join38(execDir, basename6(f)))));
|
|
29051
|
+
return files.map((f) => join38(execDir, basename6(f)));
|
|
27329
29052
|
}
|
|
27330
29053
|
async function stageOneFile(file, execDir, fs) {
|
|
27331
29054
|
const dirHash = createHash3("sha256").update(dirname12(file)).digest("hex").slice(0, 8);
|
|
27332
|
-
const subDir =
|
|
29055
|
+
const subDir = join38(execDir, dirHash);
|
|
27333
29056
|
await fs.mkdir(subDir, { recursive: true });
|
|
27334
|
-
const dest =
|
|
29057
|
+
const dest = join38(subDir, basename6(file));
|
|
27335
29058
|
await fs.copyFile(file, dest);
|
|
27336
29059
|
return dest;
|
|
27337
29060
|
}
|
|
@@ -27339,7 +29062,7 @@ function remapPath(file, projectDir, execDir) {
|
|
|
27339
29062
|
if (file === projectDir)
|
|
27340
29063
|
return execDir;
|
|
27341
29064
|
if (file.startsWith(projectDir + sep4)) {
|
|
27342
|
-
return
|
|
29065
|
+
return join38(execDir, file.slice(projectDir.length + 1));
|
|
27343
29066
|
}
|
|
27344
29067
|
return;
|
|
27345
29068
|
}
|
|
@@ -27349,10 +29072,10 @@ async function prepareRunDir(args) {
|
|
|
27349
29072
|
const { files, projectDir, depsRoot, runRoot } = args;
|
|
27350
29073
|
const fs = args.fs ?? makeDefaultFs();
|
|
27351
29074
|
const runId = buildRunId(projectDir, files);
|
|
27352
|
-
const runDir =
|
|
29075
|
+
const runDir = join39(runRoot, runId);
|
|
27353
29076
|
await fs.rm(runDir, { recursive: true, force: true });
|
|
27354
29077
|
await fs.mkdir(runDir, { recursive: true });
|
|
27355
|
-
const execDir =
|
|
29078
|
+
const execDir = join39(runDir, "exec");
|
|
27356
29079
|
await fs.mkdir(execDir, { recursive: true });
|
|
27357
29080
|
await populateInnerHop({ depsRoot, execDir, fs });
|
|
27358
29081
|
const stagedFiles = await stageFlowFiles({ files, projectDir, execDir, fs });
|
|
@@ -27383,17 +29106,17 @@ function buildRunId(projectDir, files) {
|
|
|
27383
29106
|
}
|
|
27384
29107
|
|
|
27385
29108
|
// src/shell/testkit.ts
|
|
27386
|
-
import { join as
|
|
29109
|
+
import { join as join40 } from "node:path";
|
|
27387
29110
|
function notAvailableLocally(name) {
|
|
27388
29111
|
throw new Error(runnerMessages.notAvailableLocally(name));
|
|
27389
29112
|
}
|
|
27390
29113
|
async function loadSdkDeps(cwd) {
|
|
27391
|
-
const testkitDir =
|
|
29114
|
+
const testkitDir = join40(cwd, "node_modules", "@qawolf", "testkit");
|
|
27392
29115
|
try {
|
|
27393
29116
|
const clientMod = await importFromPath(resolveFromEnvDir(cwd, "@qawolf/testkit/client"));
|
|
27394
29117
|
if (typeof clientMod.createTestkitClient !== "function")
|
|
27395
29118
|
throw new Error("createTestkitClient is not a function");
|
|
27396
|
-
const scopeMod = await importFromPath(
|
|
29119
|
+
const scopeMod = await importFromPath(join40(testkitDir, "dist", "clientScope.js"));
|
|
27397
29120
|
if (typeof scopeMod.configureTestkitClient !== "function")
|
|
27398
29121
|
throw new Error("configureTestkitClient is not a function");
|
|
27399
29122
|
return {
|
|
@@ -27451,9 +29174,9 @@ async function registerLazyEmailsClient(getInbox, cwd, deps) {
|
|
|
27451
29174
|
}
|
|
27452
29175
|
|
|
27453
29176
|
// src/commands/flows/envVarDeps.ts
|
|
27454
|
-
import { join as
|
|
29177
|
+
import { join as join41 } from "node:path";
|
|
27455
29178
|
function envFilePath(envDir) {
|
|
27456
|
-
return
|
|
29179
|
+
return join41(envDir, ".env");
|
|
27457
29180
|
}
|
|
27458
29181
|
async function readEnv(envDir, fs) {
|
|
27459
29182
|
try {
|
|
@@ -27577,7 +29300,7 @@ async function selectRunByTag(args) {
|
|
|
27577
29300
|
}
|
|
27578
29301
|
|
|
27579
29302
|
// src/commands/flows/runStagedFlows.ts
|
|
27580
|
-
import { join as
|
|
29303
|
+
import { join as join45 } from "node:path";
|
|
27581
29304
|
|
|
27582
29305
|
// src/shell/appium/createAppiumServer.ts
|
|
27583
29306
|
import { existsSync as existsSync2 } from "node:fs";
|
|
@@ -28883,14 +30606,14 @@ async function runWebFlow({
|
|
|
28883
30606
|
|
|
28884
30607
|
// src/shell/embeddedWorkerCli.ts
|
|
28885
30608
|
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
28886
|
-
import { basename as basename7, join as
|
|
30609
|
+
import { basename as basename7, join as join42 } from "node:path";
|
|
28887
30610
|
var embeddedCliPathEnv = "QAWOLF_EMBEDDED_CLI_PATH";
|
|
28888
30611
|
function extractEmbeddedWorkerCli() {
|
|
28889
30612
|
const assetPath = process.env[embeddedCliPathEnv];
|
|
28890
30613
|
if (assetPath === undefined || assetPath === "")
|
|
28891
30614
|
return;
|
|
28892
|
-
const dir =
|
|
28893
|
-
const dest =
|
|
30615
|
+
const dir = join42(getDataDir(), "worker");
|
|
30616
|
+
const dest = join42(dir, basename7(assetPath));
|
|
28894
30617
|
if (existsSync3(dest))
|
|
28895
30618
|
return dest;
|
|
28896
30619
|
mkdirSync3(dir, { recursive: true });
|
|
@@ -29128,7 +30851,7 @@ ${styleText3("dim", "─".repeat(width))}
|
|
|
29128
30851
|
};
|
|
29129
30852
|
}
|
|
29130
30853
|
// src/shell/reporter/createJUnitReporter.ts
|
|
29131
|
-
import { join as
|
|
30854
|
+
import { join as join43 } from "node:path";
|
|
29132
30855
|
|
|
29133
30856
|
// src/shell/reporter/junitXml.ts
|
|
29134
30857
|
function escapeXml(str) {
|
|
@@ -29167,7 +30890,7 @@ ${suites}`;
|
|
|
29167
30890
|
|
|
29168
30891
|
// src/shell/reporter/createJUnitReporter.ts
|
|
29169
30892
|
function resolveJUnitOutputPath(junit, outputDir) {
|
|
29170
|
-
return typeof junit === "string" && junit.trim() !== "" ? junit :
|
|
30893
|
+
return typeof junit === "string" && junit.trim() !== "" ? junit : join43(outputDir, "junit-report.xml");
|
|
29171
30894
|
}
|
|
29172
30895
|
function createJUnitReporter(deps) {
|
|
29173
30896
|
const flows = [];
|
|
@@ -29258,11 +30981,11 @@ function buildFlowsRunDeps(args) {
|
|
|
29258
30981
|
|
|
29259
30982
|
// src/commands/flows/loadEnvFile.ts
|
|
29260
30983
|
import { readFile } from "node:fs/promises";
|
|
29261
|
-
import { join as
|
|
30984
|
+
import { join as join44 } from "node:path";
|
|
29262
30985
|
async function loadEnvFile(envDir) {
|
|
29263
30986
|
let content;
|
|
29264
30987
|
try {
|
|
29265
|
-
content = await readFile(
|
|
30988
|
+
content = await readFile(join44(envDir, ".env"), "utf8");
|
|
29266
30989
|
} catch (err) {
|
|
29267
30990
|
if (isNoEntError(err))
|
|
29268
30991
|
return;
|
|
@@ -29307,7 +31030,7 @@ async function runStagedFlows(args) {
|
|
|
29307
31030
|
for (const candidate of staged.outerHop.rejected) {
|
|
29308
31031
|
log.debug(runnerMessages.outerHopCandidateRejected(candidate.dir, candidate.missing));
|
|
29309
31032
|
}
|
|
29310
|
-
const ownModules = projectDir === undefined ? undefined :
|
|
31033
|
+
const ownModules = projectDir === undefined ? undefined : join45(projectDir, "node_modules");
|
|
29311
31034
|
const own = staged.outerHop.rejected.find((c) => c.dir === ownModules);
|
|
29312
31035
|
if (own !== undefined) {
|
|
29313
31036
|
ctx.ui.warn(runnerMessages.outerHopFallbackNotice(own.dir, own.missing));
|
|
@@ -29342,7 +31065,7 @@ async function runStagedFlows(args) {
|
|
|
29342
31065
|
}
|
|
29343
31066
|
|
|
29344
31067
|
// src/commands/flows/runDefaults.ts
|
|
29345
|
-
function
|
|
31068
|
+
function makeDefaultDeps3(fs) {
|
|
29346
31069
|
return {
|
|
29347
31070
|
expandPatterns: (patterns, cwd, logger) => expandPatterns(patterns, cwd, logger, fs),
|
|
29348
31071
|
resolveDepsRoot: (args) => resolveDepsRoot({ ...args, fs }),
|
|
@@ -29354,7 +31077,7 @@ function makeDefaultDeps2(fs) {
|
|
|
29354
31077
|
};
|
|
29355
31078
|
}
|
|
29356
31079
|
async function handleFlowsRun(ctx, pattern, flags, deps, selectors = { tags: [] }, allEnvs = false) {
|
|
29357
|
-
const resolvedDeps = deps ??
|
|
31080
|
+
const resolvedDeps = deps ?? makeDefaultDeps3(ctx.fs);
|
|
29358
31081
|
const cwd = process.cwd();
|
|
29359
31082
|
const expandedFiles = await resolvedDeps.expandPatterns(buildPatternArgs(pattern), cwd, ctx.log("flows"));
|
|
29360
31083
|
ctx.log("flows").debug(`discovered ${pluralize(expandedFiles.length, "flow")}`);
|
|
@@ -29403,7 +31126,7 @@ async function chooseEnv(ctx, files, allEnvs) {
|
|
|
29403
31126
|
}
|
|
29404
31127
|
|
|
29405
31128
|
// src/commands/flows/hybridRunDefaults.ts
|
|
29406
|
-
import { join as
|
|
31129
|
+
import { join as join46, resolve as resolve10 } from "node:path";
|
|
29407
31130
|
|
|
29408
31131
|
// src/core/selectFlowFiles.ts
|
|
29409
31132
|
function selectFlowFiles(args) {
|
|
@@ -29508,7 +31231,7 @@ async function handleHybridFlowsRun(ctx, pattern, flags, options = {}) {
|
|
|
29508
31231
|
if (validation !== "ok") {
|
|
29509
31232
|
return { error: validation.error, exitCode: 2 };
|
|
29510
31233
|
}
|
|
29511
|
-
const envDir = resolve10(
|
|
31234
|
+
const envDir = resolve10(join46(".qawolf", flags.env));
|
|
29512
31235
|
const patternArgs = buildPatternArgs(pattern);
|
|
29513
31236
|
const globFlows = () => resolvedDeps.expandPatterns(patternArgs, envDir, ctx.log("flows"));
|
|
29514
31237
|
let files = await globFlows();
|
|
@@ -29704,12 +31427,12 @@ function registerFlowsCommand(program, signals, deps = { withResolvedEnv }) {
|
|
|
29704
31427
|
}
|
|
29705
31428
|
|
|
29706
31429
|
// src/domains/init/init.ts
|
|
29707
|
-
import { dirname as dirname14, join as
|
|
31430
|
+
import { dirname as dirname14, join as join48, relative as relative9 } from "node:path";
|
|
29708
31431
|
|
|
29709
31432
|
// src/domains/init/ensurePackageJson.ts
|
|
29710
|
-
import { join as
|
|
31433
|
+
import { join as join47 } from "node:path";
|
|
29711
31434
|
async function ensurePackageJson(ctx, yes, deps) {
|
|
29712
|
-
const pkgPath =
|
|
31435
|
+
const pkgPath = join47(deps.cwd, "package.json");
|
|
29713
31436
|
if (!await deps.fs.pathExists(pkgPath)) {
|
|
29714
31437
|
const confirmed = await ctx.ui.confirm(initMessages.createPackageJsonPrompt, { yes });
|
|
29715
31438
|
if (!confirmed.ok || !confirmed.value) {
|
|
@@ -29835,9 +31558,9 @@ function makeDefaultInitDeps() {
|
|
|
29835
31558
|
async function handleInit(ctx, opts, deps = makeDefaultInitDeps()) {
|
|
29836
31559
|
ctx.ui.gap();
|
|
29837
31560
|
ctx.ui.intro(initMessages.title);
|
|
29838
|
-
const configPath =
|
|
29839
|
-
const flowPath =
|
|
29840
|
-
const gitignorePath =
|
|
31561
|
+
const configPath = join48(deps.cwd, "qawolf.config.ts");
|
|
31562
|
+
const flowPath = join48(deps.cwd, "src", "flows", "example.flow.ts");
|
|
31563
|
+
const gitignorePath = join48(deps.cwd, ".qawolf", ".gitignore");
|
|
29841
31564
|
await writeWithPrompt(ctx, configPath, qawolfConfigTs, opts.yes, deps);
|
|
29842
31565
|
await writeWithPrompt(ctx, flowPath, exampleFlowTs, opts.yes, deps);
|
|
29843
31566
|
await writeWithPrompt(ctx, gitignorePath, qawolfGitignore, opts.yes, deps);
|
|
@@ -29875,7 +31598,7 @@ Examples:
|
|
|
29875
31598
|
import { parseExecutionTarget as parseExecutionTarget4 } from "@qawolf/flow-targets";
|
|
29876
31599
|
|
|
29877
31600
|
// src/domains/install/android/avd.ts
|
|
29878
|
-
import { join as
|
|
31601
|
+
import { join as join49 } from "node:path";
|
|
29879
31602
|
async function installAvds(ctx, specs, deps) {
|
|
29880
31603
|
const sdkManagerPath = sdkManagerBin(deps.androidHome, deps.platform);
|
|
29881
31604
|
const avdManagerPath = avdManagerBin(deps.androidHome, deps.platform);
|
|
@@ -29885,7 +31608,7 @@ async function installAvds(ctx, specs, deps) {
|
|
|
29885
31608
|
if (versionResult.exitCode !== 0) {
|
|
29886
31609
|
throw new Error(installMessages.android.sdkmanagerNotFound(sdkManagerPath));
|
|
29887
31610
|
}
|
|
29888
|
-
const licenseFile =
|
|
31611
|
+
const licenseFile = join49(deps.androidHome, "licenses", "android-sdk-license");
|
|
29889
31612
|
if (deps.checkExists(licenseFile)) {
|
|
29890
31613
|
ctx.ui.info(installMessages.android.licensesAlreadyAccepted);
|
|
29891
31614
|
} else {
|
|
@@ -29900,7 +31623,7 @@ async function installAvds(ctx, specs, deps) {
|
|
|
29900
31623
|
for (const spec of specs) {
|
|
29901
31624
|
if (installedImages.has(spec.systemImage))
|
|
29902
31625
|
continue;
|
|
29903
|
-
const systemImg =
|
|
31626
|
+
const systemImg = join49(deps.androidHome, ...spec.systemImage.split(";"), "system.img");
|
|
29904
31627
|
if (deps.checkExists(systemImg)) {
|
|
29905
31628
|
ctx.ui.info(installMessages.android.systemImageAlreadyInstalled(spec.systemImage));
|
|
29906
31629
|
installedImages.add(spec.systemImage);
|
|
@@ -30198,42 +31921,6 @@ Examples:
|
|
|
30198
31921
|
});
|
|
30199
31922
|
}
|
|
30200
31923
|
|
|
30201
|
-
// src/core/publicApi/flagKind.ts
|
|
30202
|
-
function flagKind(schema) {
|
|
30203
|
-
if (schema.type === "string")
|
|
30204
|
-
return { ok: true, kind: "string" };
|
|
30205
|
-
if (schema.type === "number" || schema.type === "integer") {
|
|
30206
|
-
return { ok: true, kind: "number" };
|
|
30207
|
-
}
|
|
30208
|
-
if (schema.type === "boolean")
|
|
30209
|
-
return { ok: true, kind: "boolean" };
|
|
30210
|
-
if (schema.type === "array") {
|
|
30211
|
-
if (schema.items?.type !== "string") {
|
|
30212
|
-
return {
|
|
30213
|
-
ok: false,
|
|
30214
|
-
reason: "only string arrays can be expressed as flags"
|
|
30215
|
-
};
|
|
30216
|
-
}
|
|
30217
|
-
return { ok: true, kind: "string-array" };
|
|
30218
|
-
}
|
|
30219
|
-
if (schema.type === "object") {
|
|
30220
|
-
if (schema.properties) {
|
|
30221
|
-
return {
|
|
30222
|
-
ok: false,
|
|
30223
|
-
reason: "nested objects cannot be expressed as flags"
|
|
30224
|
-
};
|
|
30225
|
-
}
|
|
30226
|
-
if (typeof schema.additionalProperties === "object" && schema.additionalProperties.type === "string") {
|
|
30227
|
-
return { ok: true, kind: "key-value-record" };
|
|
30228
|
-
}
|
|
30229
|
-
return { ok: false, reason: "nested objects cannot be expressed as flags" };
|
|
30230
|
-
}
|
|
30231
|
-
return {
|
|
30232
|
-
ok: false,
|
|
30233
|
-
reason: `type ${schema.type ?? "unknown"} cannot be expressed as a flag`
|
|
30234
|
-
};
|
|
30235
|
-
}
|
|
30236
|
-
|
|
30237
31924
|
// src/core/publicApi/flagNames.ts
|
|
30238
31925
|
var kebabCase = (segment) => segment.replace(/[A-Z]/g, (char) => `-${char.toLowerCase()}`);
|
|
30239
31926
|
var capitalize = (segment) => `${segment.slice(0, 1).toUpperCase()}${segment.slice(1)}`;
|
|
@@ -30252,108 +31939,6 @@ function flagUsage(path, kind) {
|
|
|
30252
31939
|
return `${name} <value>`;
|
|
30253
31940
|
}
|
|
30254
31941
|
|
|
30255
|
-
// src/core/publicApi/objectShape.ts
|
|
30256
|
-
var asObjectShape = (schema) => schema.type === "object" && schema.properties ? { properties: schema.properties, required: new Set(schema.required) } : undefined;
|
|
30257
|
-
function mergeIntersection(members) {
|
|
30258
|
-
const shape = { properties: {}, required: new Set };
|
|
30259
|
-
for (const member of members) {
|
|
30260
|
-
const memberShape = asObjectShape(member);
|
|
30261
|
-
if (!memberShape) {
|
|
30262
|
-
return {
|
|
30263
|
-
ok: false,
|
|
30264
|
-
field: "",
|
|
30265
|
-
reason: "intersection members must be object schemas"
|
|
30266
|
-
};
|
|
30267
|
-
}
|
|
30268
|
-
for (const [field, fieldSchema] of Object.entries(memberShape.properties)) {
|
|
30269
|
-
if (field in shape.properties) {
|
|
30270
|
-
return {
|
|
30271
|
-
ok: false,
|
|
30272
|
-
field,
|
|
30273
|
-
reason: "field appears in multiple intersection members"
|
|
30274
|
-
};
|
|
30275
|
-
}
|
|
30276
|
-
shape.properties[field] = fieldSchema;
|
|
30277
|
-
if (memberShape.required.has(field))
|
|
30278
|
-
shape.required.add(field);
|
|
30279
|
-
}
|
|
30280
|
-
}
|
|
30281
|
-
return { ok: true, shape };
|
|
30282
|
-
}
|
|
30283
|
-
function mergeUnion(branches) {
|
|
30284
|
-
const shapes = [];
|
|
30285
|
-
for (const branch of branches) {
|
|
30286
|
-
const branchShape = asObjectShape(branch);
|
|
30287
|
-
if (!branchShape) {
|
|
30288
|
-
return {
|
|
30289
|
-
ok: false,
|
|
30290
|
-
field: "",
|
|
30291
|
-
reason: "union branches must be object schemas"
|
|
30292
|
-
};
|
|
30293
|
-
}
|
|
30294
|
-
shapes.push(branchShape);
|
|
30295
|
-
}
|
|
30296
|
-
const shape = { properties: {}, required: new Set };
|
|
30297
|
-
for (const branchShape of shapes) {
|
|
30298
|
-
for (const [field, fieldSchema] of Object.entries(branchShape.properties)) {
|
|
30299
|
-
const existing = shape.properties[field];
|
|
30300
|
-
if (!existing) {
|
|
30301
|
-
shape.properties[field] = { ...fieldSchema };
|
|
30302
|
-
continue;
|
|
30303
|
-
}
|
|
30304
|
-
const kinds = [flagKind(existing), flagKind(fieldSchema)];
|
|
30305
|
-
if (kinds[0]?.ok && kinds[1]?.ok) {
|
|
30306
|
-
if (kinds[0].kind !== kinds[1].kind) {
|
|
30307
|
-
return {
|
|
30308
|
-
ok: false,
|
|
30309
|
-
field,
|
|
30310
|
-
reason: "field has conflicting types across union branches"
|
|
30311
|
-
};
|
|
30312
|
-
}
|
|
30313
|
-
} else if (JSON.stringify({ ...existing, description: undefined }) !== JSON.stringify({ ...fieldSchema, description: undefined })) {
|
|
30314
|
-
return {
|
|
30315
|
-
ok: false,
|
|
30316
|
-
field,
|
|
30317
|
-
reason: "field has conflicting object schemas across union branches"
|
|
30318
|
-
};
|
|
30319
|
-
}
|
|
30320
|
-
if (!existing.description && fieldSchema.description) {
|
|
30321
|
-
existing.description = fieldSchema.description;
|
|
30322
|
-
}
|
|
30323
|
-
}
|
|
30324
|
-
}
|
|
30325
|
-
const discriminator = Object.keys(shape.properties).find((field) => {
|
|
30326
|
-
const values = shapes.map((branch) => branch.properties[field]?.const);
|
|
30327
|
-
return values.every((value) => typeof value === "string") && new Set(values).size === shapes.length && shapes.every((branch) => branch.required.has(field));
|
|
30328
|
-
});
|
|
30329
|
-
for (const [field, fieldSchema] of Object.entries(shape.properties)) {
|
|
30330
|
-
const occurrences = shapes.filter((branch) => (field in branch.properties));
|
|
30331
|
-
if (occurrences.length === shapes.length && occurrences.every((branch) => branch.required.has(field))) {
|
|
30332
|
-
shape.required.add(field);
|
|
30333
|
-
}
|
|
30334
|
-
if (field === discriminator) {
|
|
30335
|
-
const values = shapes.map((branch) => branch.properties[field]?.const);
|
|
30336
|
-
fieldSchema.description = `One of: ${values.join(", ")}`;
|
|
30337
|
-
}
|
|
30338
|
-
}
|
|
30339
|
-
return { ok: true, shape };
|
|
30340
|
-
}
|
|
30341
|
-
function toObjectShape(schema) {
|
|
30342
|
-
if (schema.allOf)
|
|
30343
|
-
return mergeIntersection(schema.allOf);
|
|
30344
|
-
const branches = schema.oneOf ?? schema.anyOf;
|
|
30345
|
-
if (branches)
|
|
30346
|
-
return mergeUnion(branches);
|
|
30347
|
-
const shape = asObjectShape(schema);
|
|
30348
|
-
if (shape)
|
|
30349
|
-
return { ok: true, shape };
|
|
30350
|
-
return {
|
|
30351
|
-
ok: false,
|
|
30352
|
-
field: "",
|
|
30353
|
-
reason: "contract input must be an object schema"
|
|
30354
|
-
};
|
|
30355
|
-
}
|
|
30356
|
-
|
|
30357
31942
|
// src/core/publicApi/flagSpecs.ts
|
|
30358
31943
|
function describeFlag(schema) {
|
|
30359
31944
|
const description = schema.description ?? "";
|
|
@@ -30548,6 +32133,7 @@ var skippedContractNames = new Set([
|
|
|
30548
32133
|
"runner.inspect",
|
|
30549
32134
|
"runner.inspectMobile",
|
|
30550
32135
|
"runner.launch",
|
|
32136
|
+
"runner.list",
|
|
30551
32137
|
"runner.performAction",
|
|
30552
32138
|
"runner.promoteSnapshot",
|
|
30553
32139
|
"runner.readJournal",
|
|
@@ -30806,7 +32392,7 @@ function describeFailure(failureReason) {
|
|
|
30806
32392
|
}
|
|
30807
32393
|
|
|
30808
32394
|
// src/shell/interactiveRunner/collectRunFiles.ts
|
|
30809
|
-
import { join as
|
|
32395
|
+
import { join as join51 } from "node:path";
|
|
30810
32396
|
|
|
30811
32397
|
// src/core/interactiveRunner/getImports.ts
|
|
30812
32398
|
function getImports(options) {
|
|
@@ -30845,7 +32431,7 @@ function isPathAlias(importPath, paths) {
|
|
|
30845
32431
|
}
|
|
30846
32432
|
|
|
30847
32433
|
// src/core/interactiveRunner/resolveImportPath.ts
|
|
30848
|
-
import { dirname as dirname15, join as
|
|
32434
|
+
import { dirname as dirname15, join as join50, normalize as normalize3 } from "node:path/posix";
|
|
30849
32435
|
|
|
30850
32436
|
// src/core/interactiveRunner/tsconfigPaths.ts
|
|
30851
32437
|
function isRecord4(value) {
|
|
@@ -30898,7 +32484,7 @@ function candidatesForExplicitExtension(options) {
|
|
|
30898
32484
|
}
|
|
30899
32485
|
function resolveImportPath(options) {
|
|
30900
32486
|
const aliasTarget = resolvePathAlias(options.importPath, options.tsconfigPaths);
|
|
30901
|
-
const resolvedPath = aliasTarget === undefined ?
|
|
32487
|
+
const resolvedPath = aliasTarget === undefined ? normalize3(join50(dirname15(options.importingFilePath), options.importPath)) : normalize3(aliasTarget);
|
|
30902
32488
|
const hasExplicitExtension = supportedExtensions.some((extension) => options.importPath.endsWith(extension));
|
|
30903
32489
|
const candidates = hasExplicitExtension ? candidatesForExplicitExtension({
|
|
30904
32490
|
importPath: options.importPath,
|
|
@@ -30922,7 +32508,7 @@ function resolveImportPath(options) {
|
|
|
30922
32508
|
var tsconfigPath = "tsconfig.json";
|
|
30923
32509
|
async function collectRunFiles(options) {
|
|
30924
32510
|
const held = await listShippablePaths(options);
|
|
30925
|
-
const readFile = (path) => options.fs.readFile(
|
|
32511
|
+
const readFile = (path) => options.fs.readFile(join51(options.cwd, path));
|
|
30926
32512
|
const tsconfigContent = held.has(tsconfigPath) ? await readFile(tsconfigPath).catch(() => {
|
|
30927
32513
|
return;
|
|
30928
32514
|
}) : undefined;
|
|
@@ -30999,13 +32585,13 @@ async function walkImportGraph(options) {
|
|
|
30999
32585
|
}
|
|
31000
32586
|
|
|
31001
32587
|
// src/shell/interactiveRunner/makeRunnerId.ts
|
|
31002
|
-
import { randomUUID } from "node:crypto";
|
|
32588
|
+
import { randomUUID as randomUUID2 } from "node:crypto";
|
|
31003
32589
|
function makeRunnerId() {
|
|
31004
|
-
return `cli-${
|
|
32590
|
+
return `cli-${randomUUID2().replaceAll("-", "").slice(0, 12)}`;
|
|
31005
32591
|
}
|
|
31006
32592
|
|
|
31007
32593
|
// src/shell/interactiveRunner/runFilesManifest.ts
|
|
31008
|
-
import { join as
|
|
32594
|
+
import { join as join52 } from "node:path";
|
|
31009
32595
|
var manifestFileName = "runner-files.json";
|
|
31010
32596
|
var manifestSchema2 = object({
|
|
31011
32597
|
files: array(object({ contentHash: string2(), path: string2() })),
|
|
@@ -31013,8 +32599,8 @@ var manifestSchema2 = object({
|
|
|
31013
32599
|
version: literal(1)
|
|
31014
32600
|
});
|
|
31015
32601
|
function makeRunFilesManifestStore(options) {
|
|
31016
|
-
const directory =
|
|
31017
|
-
const path =
|
|
32602
|
+
const directory = join52(options.cwd, qawolfDir);
|
|
32603
|
+
const path = join52(directory, manifestFileName);
|
|
31018
32604
|
let pendingWrites = 0;
|
|
31019
32605
|
return {
|
|
31020
32606
|
async read() {
|
|
@@ -31044,7 +32630,7 @@ function parseJson(text) {
|
|
|
31044
32630
|
}
|
|
31045
32631
|
|
|
31046
32632
|
// src/shell/interactiveRunner/runnerStore.ts
|
|
31047
|
-
import { join as
|
|
32633
|
+
import { join as join53 } from "node:path";
|
|
31048
32634
|
var storeFileName = "runner.json";
|
|
31049
32635
|
var runnersDirName = "runners";
|
|
31050
32636
|
var storedRunnerSchema = object({
|
|
@@ -31063,11 +32649,11 @@ function parseJson2(text) {
|
|
|
31063
32649
|
}
|
|
31064
32650
|
var pendingWrites = 0;
|
|
31065
32651
|
function makeRunnerStore(options) {
|
|
31066
|
-
const directory =
|
|
31067
|
-
const path =
|
|
31068
|
-
const runnersDir =
|
|
32652
|
+
const directory = join53(options.cwd, qawolfDir);
|
|
32653
|
+
const path = join53(directory, storeFileName);
|
|
32654
|
+
const runnersDir = join53(directory, runnersDirName);
|
|
31069
32655
|
const nextPendingPath = (target) => `${target}.${process.pid}.${++pendingWrites}.tmp`;
|
|
31070
|
-
const runnerPath = (runnerId) =>
|
|
32656
|
+
const runnerPath = (runnerId) => join53(runnersDir, `${encodeURIComponent(runnerId)}.json`);
|
|
31071
32657
|
const writeAtomically = async (target, contents) => {
|
|
31072
32658
|
const pendingPath = nextPendingPath(target);
|
|
31073
32659
|
await options.fs.writeFile(pendingPath, `${JSON.stringify(contents, undefined, 2)}
|
|
@@ -31104,7 +32690,7 @@ function makeRunnerStore(options) {
|
|
|
31104
32690
|
readDefaultRunnerId,
|
|
31105
32691
|
async readRunners() {
|
|
31106
32692
|
const runners = await Promise.all((await readRunnerFileNames()).map(async (name) => {
|
|
31107
|
-
const contents = await options.fs.readFile(
|
|
32693
|
+
const contents = await options.fs.readFile(join53(runnersDir, name)).catch(() => {
|
|
31108
32694
|
return;
|
|
31109
32695
|
});
|
|
31110
32696
|
if (contents === undefined)
|
|
@@ -31123,7 +32709,7 @@ function makeRunnerStore(options) {
|
|
|
31123
32709
|
await Promise.all(runnerIds.map((runnerId) => options.fs.rm(runnerPath(runnerId), { force: true })));
|
|
31124
32710
|
const names = await readRunnerFileNames();
|
|
31125
32711
|
await Promise.all(names.map(async (name) => {
|
|
31126
|
-
const runnerFile =
|
|
32712
|
+
const runnerFile = join53(runnersDir, name);
|
|
31127
32713
|
const contents = await options.fs.readFile(runnerFile).catch(() => {
|
|
31128
32714
|
return;
|
|
31129
32715
|
});
|
|
@@ -31207,7 +32793,7 @@ function registerRunnerHighlightSelectorCommand(runner, signals) {
|
|
|
31207
32793
|
}
|
|
31208
32794
|
|
|
31209
32795
|
// src/domains/interactiveRunner/importPackage.ts
|
|
31210
|
-
import { join as
|
|
32796
|
+
import { join as join54 } from "node:path";
|
|
31211
32797
|
|
|
31212
32798
|
// src/core/interactiveRunner/npmDependencies.ts
|
|
31213
32799
|
var sectionsInPrecedenceOrder = ["dependencies", "devDependencies"];
|
|
@@ -31248,7 +32834,7 @@ function readNpmDependencies(packageJsonContent) {
|
|
|
31248
32834
|
// src/domains/interactiveRunner/importPackage.ts
|
|
31249
32835
|
var defaultPackageVersion = "latest";
|
|
31250
32836
|
async function handleRunnerImportPackage(ctx, options, deps) {
|
|
31251
|
-
const content = await deps.readFile(
|
|
32837
|
+
const content = await deps.readFile(join54(deps.cwd, runPackageJsonPath)).catch(() => {
|
|
31252
32838
|
return;
|
|
31253
32839
|
});
|
|
31254
32840
|
if (content === undefined) {
|
|
@@ -32052,68 +33638,56 @@ async function handleRunnerLaunch(ctx, options, deps) {
|
|
|
32052
33638
|
// src/domains/interactiveRunner/list.ts
|
|
32053
33639
|
var columns = [
|
|
32054
33640
|
{ header: "id", value: (row) => row.id },
|
|
32055
|
-
{ header: "family", value: (row) => row.runnerName
|
|
33641
|
+
{ header: "family", value: (row) => row.runnerName },
|
|
33642
|
+
{ header: "launched here", value: (row) => row.launchedHere ? "yes" : "" },
|
|
32056
33643
|
{ header: "default", value: (row) => row.isDefault ? "yes" : "" }
|
|
32057
33644
|
];
|
|
32058
|
-
|
|
32059
|
-
|
|
32060
|
-
|
|
32061
|
-
|
|
32062
|
-
const environmentValue = deps.env[runnerIdEnvironmentVariable]?.trim();
|
|
32063
|
-
const fromEnvironment = environmentValue ? environmentValue : undefined;
|
|
32064
|
-
const candidates = [...held];
|
|
32065
|
-
for (const id of [storedDefault, fromEnvironment]) {
|
|
32066
|
-
if (id !== undefined && !candidates.some((runner) => runner.id === id)) {
|
|
32067
|
-
candidates.push({ id });
|
|
32068
|
-
}
|
|
32069
|
-
}
|
|
32070
|
-
return { candidates, defaultRunnerId: fromEnvironment ?? storedDefault };
|
|
33645
|
+
function rank(item) {
|
|
33646
|
+
if (item.isDefault)
|
|
33647
|
+
return 0;
|
|
33648
|
+
return item.launchedHere ? 1 : 2;
|
|
32071
33649
|
}
|
|
32072
|
-
async function
|
|
32073
|
-
const
|
|
32074
|
-
if (
|
|
32075
|
-
return
|
|
32076
|
-
return
|
|
33650
|
+
async function readDefaultRunnerId(deps) {
|
|
33651
|
+
const environmentValue = deps.env[runnerIdEnvironmentVariable]?.trim();
|
|
33652
|
+
if (environmentValue)
|
|
33653
|
+
return environmentValue;
|
|
33654
|
+
return deps.store.readDefaultRunnerId();
|
|
32077
33655
|
}
|
|
32078
33656
|
async function listRunners(ctx, deps) {
|
|
32079
|
-
const
|
|
32080
|
-
|
|
32081
|
-
|
|
32082
|
-
|
|
32083
|
-
|
|
32084
|
-
|
|
32085
|
-
|
|
32086
|
-
running.push(probe.runner);
|
|
32087
|
-
else
|
|
32088
|
-
gone.push(probe.runner.id);
|
|
32089
|
-
}
|
|
33657
|
+
const listed = await ctx.platformClient.callPublicApi(publicContractsV1.runner.list, {}, runnerCallOptions);
|
|
33658
|
+
if (!listed.ok)
|
|
33659
|
+
return { ...failureFields(listed), ok: false };
|
|
33660
|
+
const defaultRunnerId = await readDefaultRunnerId(deps);
|
|
33661
|
+
const runningIds = new Set(listed.value.runners.map((runner) => runner.id));
|
|
33662
|
+
const held = await deps.store.readRunners();
|
|
33663
|
+
const gone = held.filter((runner) => !runningIds.has(runner.id)).map((runner) => runner.id);
|
|
32090
33664
|
await deps.store.dropRunners(gone).catch(() => {
|
|
32091
33665
|
return;
|
|
32092
33666
|
});
|
|
33667
|
+
const heldIds = new Set(held.map((runner) => runner.id));
|
|
33668
|
+
const items = listed.value.runners.map((runner) => ({
|
|
33669
|
+
id: runner.id,
|
|
33670
|
+
isDefault: runner.id === defaultRunnerId,
|
|
33671
|
+
launchedHere: heldIds.has(runner.id),
|
|
33672
|
+
runnerName: runner.runnerName
|
|
33673
|
+
}));
|
|
32093
33674
|
return {
|
|
32094
|
-
items:
|
|
32095
|
-
...running.filter((runner) => runner.id === defaultRunnerId),
|
|
32096
|
-
...running.filter((runner) => runner.id !== defaultRunnerId)
|
|
32097
|
-
].map((runner) => ({
|
|
32098
|
-
id: runner.id,
|
|
32099
|
-
isDefault: runner.id === defaultRunnerId,
|
|
32100
|
-
runnerName: runner.runnerName
|
|
32101
|
-
})),
|
|
33675
|
+
items: items.sort((left, right) => rank(left) - rank(right) || left.id.localeCompare(right.id)),
|
|
32102
33676
|
ok: true
|
|
32103
33677
|
};
|
|
32104
33678
|
}
|
|
32105
|
-
async function handleRunnerList(ctx, deps) {
|
|
33679
|
+
async function handleRunnerList(ctx, options, deps) {
|
|
32106
33680
|
const listed = await listRunners(ctx, deps);
|
|
32107
33681
|
if (!listed.ok) {
|
|
32108
33682
|
return { ...failureFields(listed), exitCode: exitCodes.network };
|
|
32109
33683
|
}
|
|
32110
|
-
const items = listed.items;
|
|
33684
|
+
const items = options.here ? listed.items.filter((item) => item.launchedHere) : listed.items;
|
|
32111
33685
|
if (ctx.ui.mode === "json") {
|
|
32112
33686
|
ctx.ui.json(items);
|
|
32113
33687
|
return;
|
|
32114
33688
|
}
|
|
32115
33689
|
if (items.length === 0) {
|
|
32116
|
-
ctx.ui.info(interactiveRunnerMessages.noRunners);
|
|
33690
|
+
ctx.ui.info(options.here ? interactiveRunnerMessages.noRunnersHere : interactiveRunnerMessages.noRunners);
|
|
32117
33691
|
return;
|
|
32118
33692
|
}
|
|
32119
33693
|
if (ctx.ui.mode === "agent") {
|
|
@@ -32175,6 +33749,7 @@ Examples:
|
|
|
32175
33749
|
var listExamples2 = `
|
|
32176
33750
|
Examples:
|
|
32177
33751
|
$ qawolf runner list
|
|
33752
|
+
$ qawolf runner list --here
|
|
32178
33753
|
$ qawolf runner list --json`;
|
|
32179
33754
|
var keepaliveExamples = `
|
|
32180
33755
|
Examples:
|
|
@@ -32182,7 +33757,7 @@ Examples:
|
|
|
32182
33757
|
$ qawolf runner keepalive --runner ci`;
|
|
32183
33758
|
function registerRunnerLifecycleCommands(runner, signals) {
|
|
32184
33759
|
declareCommandKind(runner.command("launch"), "write").description("Launch an interactive runner and make it this directory's default").option("--id <id>", "Id to launch under. Relaunching an id attaches to that runner").option("--name <family>", "Runner family to run, e.g. playwright").addHelpText("after", launchExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerLaunch(ctx, { id: opts.id, name: opts.name }, runnerDeps(ctx)))(opts, command));
|
|
32185
|
-
declareCommandKind(runner.command("list"), "read").description("List the runners
|
|
33760
|
+
declareCommandKind(runner.command("list"), "read").description("List the runners running on your team").option("--here", "Only the runners this directory launched").addHelpText("after", listExamples2).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerList(ctx, { here: opts.here === true }, runnerDeps(ctx)))(opts, command));
|
|
32186
33761
|
declareCommandKind(runner.command("terminate"), "write").description("End an interactive runner, and the pod it runs on with it").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerTerminate(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
|
|
32187
33762
|
declareCommandKind(runner.command("stop-run"), "write").description("Stop what a runner is currently executing, leaving the runner up").option("--runner <id>", runnerFlagDescription).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerStopRun(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
|
|
32188
33763
|
declareCommandKind(runner.command("keepalive"), "read").description("Reset a runner's inactivity clock, for a caller that pauses between actions").option("--runner <id>", runnerFlagDescription).addHelpText("after", keepaliveExamples).action((opts, command) => withAuthContext(signals, (ctx) => handleRunnerKeepalive(ctx, { runner: opts.runner }, runnerDeps(ctx)))(opts, command));
|
|
@@ -32983,4 +34558,4 @@ createProgram({ signals }).parseAsync().catch(() => {
|
|
|
32983
34558
|
process.exitCode = 1;
|
|
32984
34559
|
}).finally(() => flushAndExit(typeof process.exitCode === "number" ? process.exitCode : 0));
|
|
32985
34560
|
|
|
32986
|
-
//# debugId=
|
|
34561
|
+
//# debugId=DA8D4A9E373CFE5C64756E2164756E21
|