feat(M4): Reusable code abstractions and dead-code removal
This commit is contained in:
@@ -25,7 +25,13 @@
|
||||
"path": "scripts/src/cli.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98"
|
||||
"sha256": "90dcc029adf0625b86c5eec44c5c1fd11bbf95ffe1185016d139c8a6982d54ff"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/command-helpers.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "aa03d8d288c8c00485ea10d3b3a60804c1b9ee23ef265004e7912f3242dbcee7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/config.ts",
|
||||
@@ -37,7 +43,7 @@
|
||||
"path": "scripts/src/confluence.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73"
|
||||
"sha256": "28f65f280cd9b6119ce7eab583d0083231525ad6dc04b73389cb5dcbab5bf095"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/files.ts",
|
||||
@@ -61,7 +67,7 @@
|
||||
"path": "scripts/src/jira.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f"
|
||||
"sha256": "bec0e81a0424dd412c36988cef42c01a95f044ee8346ba626e7eb8bd79379f07"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/output.ts",
|
||||
@@ -73,7 +79,7 @@
|
||||
"path": "scripts/src/raw.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8"
|
||||
"sha256": "48fd54bd0cdb421badb58f9be2933a039fe3b9350bbe6191070c9f7bb0054670"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/types.ts",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url";
|
||||
|
||||
import { Command } from "commander";
|
||||
|
||||
import { resolveFormat } from "./command-helpers.js";
|
||||
import { createConfluenceClient } from "./confluence.js";
|
||||
import { loadConfig } from "./config.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
@@ -11,7 +12,7 @@ import { runHealthCheck } from "./health.js";
|
||||
import { createJiraClient } from "./jira.js";
|
||||
import { writeOutput } from "./output.js";
|
||||
import { runRawCommand } from "./raw.js";
|
||||
import type { FetchLike, OutputFormat, Writer } from "./types.js";
|
||||
import type { FetchLike, Writer } from "./types.js";
|
||||
|
||||
type CliContext = {
|
||||
cwd?: string;
|
||||
@@ -21,10 +22,6 @@ type CliContext = {
|
||||
stderr?: Writer;
|
||||
};
|
||||
|
||||
function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
|
||||
function createRuntime(context: CliContext) {
|
||||
const cwd = context.cwd ?? process.cwd();
|
||||
const env = context.env ?? process.env;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import type { CommandOutput, OutputFormat } from "./types.js";
|
||||
|
||||
/**
|
||||
* Produce the standard dry-run response payload for write operations.
|
||||
*
|
||||
* Use this when `--dry-run` is passed to skip the actual API call and
|
||||
* echo the pending request back to the caller.
|
||||
*
|
||||
* @example
|
||||
* if (input.dryRun) return dryRunResponse(request);
|
||||
*/
|
||||
export function dryRunResponse<T>(data: T): CommandOutput<T> {
|
||||
return { ok: true, dryRun: true, data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the `--format` CLI option to a typed OutputFormat.
|
||||
*
|
||||
* Returns `"text"` only for the exact string `"text"`;
|
||||
* all other values (including `undefined`) fall back to `"json"`.
|
||||
*/
|
||||
export function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
|
||||
@@ -178,13 +179,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -224,13 +219,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -267,13 +256,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { markdownToAdf } from "./adf.js";
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js";
|
||||
|
||||
@@ -162,13 +163,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", "/rest/api/3/issue", request.body);
|
||||
return { ok: true, data: raw };
|
||||
@@ -193,13 +188,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
fields,
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("PUT", `/rest/api/3/issue/${input.issue}`, request.body);
|
||||
return {
|
||||
@@ -216,13 +205,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
body: markdownToAdf(input.body),
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", `/rest/api/3/issue/${input.issue}/comment`, request.body);
|
||||
return {
|
||||
@@ -243,13 +226,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
);
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("POST", `/rest/api/3/issue/${input.issue}/transitions`, request.body);
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
@@ -62,13 +63,7 @@ export async function runRawCommand(
|
||||
...(body === undefined ? {} : { body }),
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const data = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -25,7 +25,13 @@
|
||||
"path": "scripts/src/cli.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98"
|
||||
"sha256": "90dcc029adf0625b86c5eec44c5c1fd11bbf95ffe1185016d139c8a6982d54ff"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/command-helpers.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "aa03d8d288c8c00485ea10d3b3a60804c1b9ee23ef265004e7912f3242dbcee7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/config.ts",
|
||||
@@ -37,7 +43,7 @@
|
||||
"path": "scripts/src/confluence.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73"
|
||||
"sha256": "28f65f280cd9b6119ce7eab583d0083231525ad6dc04b73389cb5dcbab5bf095"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/files.ts",
|
||||
@@ -61,7 +67,7 @@
|
||||
"path": "scripts/src/jira.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f"
|
||||
"sha256": "bec0e81a0424dd412c36988cef42c01a95f044ee8346ba626e7eb8bd79379f07"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/output.ts",
|
||||
@@ -73,7 +79,7 @@
|
||||
"path": "scripts/src/raw.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8"
|
||||
"sha256": "48fd54bd0cdb421badb58f9be2933a039fe3b9350bbe6191070c9f7bb0054670"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/types.ts",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url";
|
||||
|
||||
import { Command } from "commander";
|
||||
|
||||
import { resolveFormat } from "./command-helpers.js";
|
||||
import { createConfluenceClient } from "./confluence.js";
|
||||
import { loadConfig } from "./config.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
@@ -11,7 +12,7 @@ import { runHealthCheck } from "./health.js";
|
||||
import { createJiraClient } from "./jira.js";
|
||||
import { writeOutput } from "./output.js";
|
||||
import { runRawCommand } from "./raw.js";
|
||||
import type { FetchLike, OutputFormat, Writer } from "./types.js";
|
||||
import type { FetchLike, Writer } from "./types.js";
|
||||
|
||||
type CliContext = {
|
||||
cwd?: string;
|
||||
@@ -21,10 +22,6 @@ type CliContext = {
|
||||
stderr?: Writer;
|
||||
};
|
||||
|
||||
function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
|
||||
function createRuntime(context: CliContext) {
|
||||
const cwd = context.cwd ?? process.cwd();
|
||||
const env = context.env ?? process.env;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import type { CommandOutput, OutputFormat } from "./types.js";
|
||||
|
||||
/**
|
||||
* Produce the standard dry-run response payload for write operations.
|
||||
*
|
||||
* Use this when `--dry-run` is passed to skip the actual API call and
|
||||
* echo the pending request back to the caller.
|
||||
*
|
||||
* @example
|
||||
* if (input.dryRun) return dryRunResponse(request);
|
||||
*/
|
||||
export function dryRunResponse<T>(data: T): CommandOutput<T> {
|
||||
return { ok: true, dryRun: true, data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the `--format` CLI option to a typed OutputFormat.
|
||||
*
|
||||
* Returns `"text"` only for the exact string `"text"`;
|
||||
* all other values (including `undefined`) fall back to `"json"`.
|
||||
*/
|
||||
export function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
|
||||
@@ -178,13 +179,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -224,13 +219,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -267,13 +256,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { markdownToAdf } from "./adf.js";
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js";
|
||||
|
||||
@@ -162,13 +163,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", "/rest/api/3/issue", request.body);
|
||||
return { ok: true, data: raw };
|
||||
@@ -193,13 +188,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
fields,
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("PUT", `/rest/api/3/issue/${input.issue}`, request.body);
|
||||
return {
|
||||
@@ -216,13 +205,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
body: markdownToAdf(input.body),
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", `/rest/api/3/issue/${input.issue}/comment`, request.body);
|
||||
return {
|
||||
@@ -243,13 +226,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
);
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("POST", `/rest/api/3/issue/${input.issue}/transitions`, request.body);
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
@@ -62,13 +63,7 @@ export async function runRawCommand(
|
||||
...(body === undefined ? {} : { body }),
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const data = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -25,7 +25,13 @@
|
||||
"path": "scripts/src/cli.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98"
|
||||
"sha256": "90dcc029adf0625b86c5eec44c5c1fd11bbf95ffe1185016d139c8a6982d54ff"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/command-helpers.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "aa03d8d288c8c00485ea10d3b3a60804c1b9ee23ef265004e7912f3242dbcee7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/config.ts",
|
||||
@@ -37,7 +43,7 @@
|
||||
"path": "scripts/src/confluence.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73"
|
||||
"sha256": "28f65f280cd9b6119ce7eab583d0083231525ad6dc04b73389cb5dcbab5bf095"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/files.ts",
|
||||
@@ -61,7 +67,7 @@
|
||||
"path": "scripts/src/jira.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f"
|
||||
"sha256": "bec0e81a0424dd412c36988cef42c01a95f044ee8346ba626e7eb8bd79379f07"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/output.ts",
|
||||
@@ -73,7 +79,7 @@
|
||||
"path": "scripts/src/raw.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8"
|
||||
"sha256": "48fd54bd0cdb421badb58f9be2933a039fe3b9350bbe6191070c9f7bb0054670"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/types.ts",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url";
|
||||
|
||||
import { Command } from "commander";
|
||||
|
||||
import { resolveFormat } from "./command-helpers.js";
|
||||
import { createConfluenceClient } from "./confluence.js";
|
||||
import { loadConfig } from "./config.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
@@ -11,7 +12,7 @@ import { runHealthCheck } from "./health.js";
|
||||
import { createJiraClient } from "./jira.js";
|
||||
import { writeOutput } from "./output.js";
|
||||
import { runRawCommand } from "./raw.js";
|
||||
import type { FetchLike, OutputFormat, Writer } from "./types.js";
|
||||
import type { FetchLike, Writer } from "./types.js";
|
||||
|
||||
type CliContext = {
|
||||
cwd?: string;
|
||||
@@ -21,10 +22,6 @@ type CliContext = {
|
||||
stderr?: Writer;
|
||||
};
|
||||
|
||||
function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
|
||||
function createRuntime(context: CliContext) {
|
||||
const cwd = context.cwd ?? process.cwd();
|
||||
const env = context.env ?? process.env;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import type { CommandOutput, OutputFormat } from "./types.js";
|
||||
|
||||
/**
|
||||
* Produce the standard dry-run response payload for write operations.
|
||||
*
|
||||
* Use this when `--dry-run` is passed to skip the actual API call and
|
||||
* echo the pending request back to the caller.
|
||||
*
|
||||
* @example
|
||||
* if (input.dryRun) return dryRunResponse(request);
|
||||
*/
|
||||
export function dryRunResponse<T>(data: T): CommandOutput<T> {
|
||||
return { ok: true, dryRun: true, data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the `--format` CLI option to a typed OutputFormat.
|
||||
*
|
||||
* Returns `"text"` only for the exact string `"text"`;
|
||||
* all other values (including `undefined`) fall back to `"json"`.
|
||||
*/
|
||||
export function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
|
||||
@@ -178,13 +179,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -224,13 +219,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -267,13 +256,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { markdownToAdf } from "./adf.js";
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js";
|
||||
|
||||
@@ -162,13 +163,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", "/rest/api/3/issue", request.body);
|
||||
return { ok: true, data: raw };
|
||||
@@ -193,13 +188,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
fields,
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("PUT", `/rest/api/3/issue/${input.issue}`, request.body);
|
||||
return {
|
||||
@@ -216,13 +205,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
body: markdownToAdf(input.body),
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", `/rest/api/3/issue/${input.issue}/comment`, request.body);
|
||||
return {
|
||||
@@ -243,13 +226,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
);
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("POST", `/rest/api/3/issue/${input.issue}/transitions`, request.body);
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
@@ -62,13 +63,7 @@ export async function runRawCommand(
|
||||
...(body === undefined ? {} : { body }),
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const data = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -25,7 +25,13 @@
|
||||
"path": "scripts/src/cli.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98"
|
||||
"sha256": "90dcc029adf0625b86c5eec44c5c1fd11bbf95ffe1185016d139c8a6982d54ff"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/command-helpers.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "aa03d8d288c8c00485ea10d3b3a60804c1b9ee23ef265004e7912f3242dbcee7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/config.ts",
|
||||
@@ -37,7 +43,7 @@
|
||||
"path": "scripts/src/confluence.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73"
|
||||
"sha256": "28f65f280cd9b6119ce7eab583d0083231525ad6dc04b73389cb5dcbab5bf095"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/files.ts",
|
||||
@@ -61,7 +67,7 @@
|
||||
"path": "scripts/src/jira.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f"
|
||||
"sha256": "bec0e81a0424dd412c36988cef42c01a95f044ee8346ba626e7eb8bd79379f07"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/output.ts",
|
||||
@@ -73,7 +79,7 @@
|
||||
"path": "scripts/src/raw.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8"
|
||||
"sha256": "48fd54bd0cdb421badb58f9be2933a039fe3b9350bbe6191070c9f7bb0054670"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/types.ts",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url";
|
||||
|
||||
import { Command } from "commander";
|
||||
|
||||
import { resolveFormat } from "./command-helpers.js";
|
||||
import { createConfluenceClient } from "./confluence.js";
|
||||
import { loadConfig } from "./config.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
@@ -11,7 +12,7 @@ import { runHealthCheck } from "./health.js";
|
||||
import { createJiraClient } from "./jira.js";
|
||||
import { writeOutput } from "./output.js";
|
||||
import { runRawCommand } from "./raw.js";
|
||||
import type { FetchLike, OutputFormat, Writer } from "./types.js";
|
||||
import type { FetchLike, Writer } from "./types.js";
|
||||
|
||||
type CliContext = {
|
||||
cwd?: string;
|
||||
@@ -21,10 +22,6 @@ type CliContext = {
|
||||
stderr?: Writer;
|
||||
};
|
||||
|
||||
function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
|
||||
function createRuntime(context: CliContext) {
|
||||
const cwd = context.cwd ?? process.cwd();
|
||||
const env = context.env ?? process.env;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import type { CommandOutput, OutputFormat } from "./types.js";
|
||||
|
||||
/**
|
||||
* Produce the standard dry-run response payload for write operations.
|
||||
*
|
||||
* Use this when `--dry-run` is passed to skip the actual API call and
|
||||
* echo the pending request back to the caller.
|
||||
*
|
||||
* @example
|
||||
* if (input.dryRun) return dryRunResponse(request);
|
||||
*/
|
||||
export function dryRunResponse<T>(data: T): CommandOutput<T> {
|
||||
return { ok: true, dryRun: true, data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the `--format` CLI option to a typed OutputFormat.
|
||||
*
|
||||
* Returns `"text"` only for the exact string `"text"`;
|
||||
* all other values (including `undefined`) fall back to `"json"`.
|
||||
*/
|
||||
export function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
|
||||
@@ -178,13 +179,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -224,13 +219,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -267,13 +256,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { markdownToAdf } from "./adf.js";
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js";
|
||||
|
||||
@@ -162,13 +163,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", "/rest/api/3/issue", request.body);
|
||||
return { ok: true, data: raw };
|
||||
@@ -193,13 +188,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
fields,
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("PUT", `/rest/api/3/issue/${input.issue}`, request.body);
|
||||
return {
|
||||
@@ -216,13 +205,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
body: markdownToAdf(input.body),
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", `/rest/api/3/issue/${input.issue}/comment`, request.body);
|
||||
return {
|
||||
@@ -243,13 +226,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
);
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("POST", `/rest/api/3/issue/${input.issue}/transitions`, request.body);
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
@@ -62,13 +63,7 @@ export async function runRawCommand(
|
||||
...(body === undefined ? {} : { body }),
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const data = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -25,7 +25,13 @@
|
||||
"path": "scripts/src/cli.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5c4f4db76817fa9dbdae0fd0c75be302248d4b87fc0a53f6bd3c90407a75ae98"
|
||||
"sha256": "90dcc029adf0625b86c5eec44c5c1fd11bbf95ffe1185016d139c8a6982d54ff"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/command-helpers.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "aa03d8d288c8c00485ea10d3b3a60804c1b9ee23ef265004e7912f3242dbcee7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/config.ts",
|
||||
@@ -37,7 +43,7 @@
|
||||
"path": "scripts/src/confluence.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "709d5d61fdb14e37aa4eaa7175eb7f17f0ec661376c96071020fbc9574ddbb73"
|
||||
"sha256": "28f65f280cd9b6119ce7eab583d0083231525ad6dc04b73389cb5dcbab5bf095"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/files.ts",
|
||||
@@ -61,7 +67,7 @@
|
||||
"path": "scripts/src/jira.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "485d8d618fe04eb1ce546c1694eadf15d867bc83c2a6f7df994688ab0335ea4f"
|
||||
"sha256": "bec0e81a0424dd412c36988cef42c01a95f044ee8346ba626e7eb8bd79379f07"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/output.ts",
|
||||
@@ -73,7 +79,7 @@
|
||||
"path": "scripts/src/raw.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "2309c96dd45a03509df204803de9ecf0b5ff82fd488730f55ac5dd6a23b81dd8"
|
||||
"sha256": "48fd54bd0cdb421badb58f9be2933a039fe3b9350bbe6191070c9f7bb0054670"
|
||||
},
|
||||
{
|
||||
"path": "scripts/src/types.ts",
|
||||
|
||||
@@ -4,6 +4,7 @@ import { pathToFileURL } from "node:url";
|
||||
|
||||
import { Command } from "commander";
|
||||
|
||||
import { resolveFormat } from "./command-helpers.js";
|
||||
import { createConfluenceClient } from "./confluence.js";
|
||||
import { loadConfig } from "./config.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
@@ -11,7 +12,7 @@ import { runHealthCheck } from "./health.js";
|
||||
import { createJiraClient } from "./jira.js";
|
||||
import { writeOutput } from "./output.js";
|
||||
import { runRawCommand } from "./raw.js";
|
||||
import type { FetchLike, OutputFormat, Writer } from "./types.js";
|
||||
import type { FetchLike, Writer } from "./types.js";
|
||||
|
||||
type CliContext = {
|
||||
cwd?: string;
|
||||
@@ -21,10 +22,6 @@ type CliContext = {
|
||||
stderr?: Writer;
|
||||
};
|
||||
|
||||
function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
|
||||
function createRuntime(context: CliContext) {
|
||||
const cwd = context.cwd ?? process.cwd();
|
||||
const env = context.env ?? process.env;
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import type { CommandOutput, OutputFormat } from "./types.js";
|
||||
|
||||
/**
|
||||
* Produce the standard dry-run response payload for write operations.
|
||||
*
|
||||
* Use this when `--dry-run` is passed to skip the actual API call and
|
||||
* echo the pending request back to the caller.
|
||||
*
|
||||
* @example
|
||||
* if (input.dryRun) return dryRunResponse(request);
|
||||
*/
|
||||
export function dryRunResponse<T>(data: T): CommandOutput<T> {
|
||||
return { ok: true, dryRun: true, data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the `--format` CLI option to a typed OutputFormat.
|
||||
*
|
||||
* Returns `"text"` only for the exact string `"text"`;
|
||||
* all other values (including `undefined`) fall back to `"json"`.
|
||||
*/
|
||||
export function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
|
||||
@@ -178,13 +179,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -224,13 +219,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -267,13 +256,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { markdownToAdf } from "./adf.js";
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js";
|
||||
|
||||
@@ -162,13 +163,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", "/rest/api/3/issue", request.body);
|
||||
return { ok: true, data: raw };
|
||||
@@ -193,13 +188,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
fields,
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("PUT", `/rest/api/3/issue/${input.issue}`, request.body);
|
||||
return {
|
||||
@@ -216,13 +205,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
body: markdownToAdf(input.body),
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", `/rest/api/3/issue/${input.issue}/comment`, request.body);
|
||||
return {
|
||||
@@ -243,13 +226,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
);
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("POST", `/rest/api/3/issue/${input.issue}/transitions`, request.body);
|
||||
return {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/atlassian/shared/scripts/ and run `pnpm run sync:pi`.
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
@@ -62,13 +63,7 @@ export async function runRawCommand(
|
||||
...(body === undefined ? {} : { body }),
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const data = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -3,6 +3,7 @@ import { pathToFileURL } from "node:url";
|
||||
|
||||
import { Command } from "commander";
|
||||
|
||||
import { resolveFormat } from "./command-helpers.js";
|
||||
import { createConfluenceClient } from "./confluence.js";
|
||||
import { loadConfig } from "./config.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
@@ -10,7 +11,7 @@ import { runHealthCheck } from "./health.js";
|
||||
import { createJiraClient } from "./jira.js";
|
||||
import { writeOutput } from "./output.js";
|
||||
import { runRawCommand } from "./raw.js";
|
||||
import type { FetchLike, OutputFormat, Writer } from "./types.js";
|
||||
import type { FetchLike, Writer } from "./types.js";
|
||||
|
||||
type CliContext = {
|
||||
cwd?: string;
|
||||
@@ -20,10 +21,6 @@ type CliContext = {
|
||||
stderr?: Writer;
|
||||
};
|
||||
|
||||
function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
|
||||
function createRuntime(context: CliContext) {
|
||||
const cwd = context.cwd ?? process.cwd();
|
||||
const env = context.env ?? process.env;
|
||||
|
||||
@@ -0,0 +1,24 @@
|
||||
import type { CommandOutput, OutputFormat } from "./types.js";
|
||||
|
||||
/**
|
||||
* Produce the standard dry-run response payload for write operations.
|
||||
*
|
||||
* Use this when `--dry-run` is passed to skip the actual API call and
|
||||
* echo the pending request back to the caller.
|
||||
*
|
||||
* @example
|
||||
* if (input.dryRun) return dryRunResponse(request);
|
||||
*/
|
||||
export function dryRunResponse<T>(data: T): CommandOutput<T> {
|
||||
return { ok: true, dryRun: true, data };
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve the `--format` CLI option to a typed OutputFormat.
|
||||
*
|
||||
* Returns `"text"` only for the exact string `"text"`;
|
||||
* all other values (including `undefined`) fall back to `"json"`.
|
||||
*/
|
||||
export function resolveFormat(format: string | undefined): OutputFormat {
|
||||
return format === "text" ? "text" : "json";
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
|
||||
@@ -177,13 +178,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -223,13 +218,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
@@ -266,13 +255,7 @@ export function createConfluenceClient(options: ConfluenceClientOptions) {
|
||||
},
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import { markdownToAdf } from "./adf.js";
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike, JiraIssueSummary } from "./types.js";
|
||||
|
||||
@@ -161,13 +162,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", "/rest/api/3/issue", request.body);
|
||||
return { ok: true, data: raw };
|
||||
@@ -192,13 +187,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
fields,
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("PUT", `/rest/api/3/issue/${input.issue}`, request.body);
|
||||
return {
|
||||
@@ -215,13 +204,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
body: markdownToAdf(input.body),
|
||||
});
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const raw = await send("POST", `/rest/api/3/issue/${input.issue}/comment`, request.body);
|
||||
return {
|
||||
@@ -242,13 +225,7 @@ export function createJiraClient(options: JiraClientOptions) {
|
||||
},
|
||||
);
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
await send("POST", `/rest/api/3/issue/${input.issue}/transitions`, request.body);
|
||||
return {
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { dryRunResponse } from "./command-helpers.js";
|
||||
import { readWorkspaceFile } from "./files.js";
|
||||
import { sendJsonRequest } from "./http.js";
|
||||
import type { AtlassianConfig, CommandOutput, FetchLike } from "./types.js";
|
||||
@@ -61,13 +62,7 @@ export async function runRawCommand(
|
||||
...(body === undefined ? {} : { body }),
|
||||
};
|
||||
|
||||
if (input.dryRun) {
|
||||
return {
|
||||
ok: true,
|
||||
dryRun: true,
|
||||
data: request,
|
||||
};
|
||||
}
|
||||
if (input.dryRun) return dryRunResponse(request);
|
||||
|
||||
const data = await sendJsonRequest({
|
||||
config,
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
|
||||
import { dryRunResponse, resolveFormat } from "../src/command-helpers.js";
|
||||
|
||||
// ── dryRunResponse ────────────────────────────────────────────────────────
|
||||
|
||||
test("dryRunResponse wraps data with ok:true and dryRun:true", () => {
|
||||
const data = { method: "POST", url: "https://example.com/api/v2/pages" };
|
||||
const result = dryRunResponse(data);
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.dryRun, true);
|
||||
assert.deepEqual(result.data, data);
|
||||
});
|
||||
|
||||
test("dryRunResponse preserves the exact data reference", () => {
|
||||
const data = { nested: { key: "value" } };
|
||||
const result = dryRunResponse(data);
|
||||
assert.strictEqual(result.data, data);
|
||||
});
|
||||
|
||||
test("dryRunResponse works with primitive data", () => {
|
||||
const result = dryRunResponse("dry-run-string");
|
||||
assert.equal(result.ok, true);
|
||||
assert.equal(result.dryRun, true);
|
||||
assert.equal(result.data, "dry-run-string");
|
||||
});
|
||||
|
||||
// ── resolveFormat ─────────────────────────────────────────────────────────
|
||||
|
||||
test("resolveFormat returns json by default for undefined", () => {
|
||||
assert.equal(resolveFormat(undefined), "json");
|
||||
});
|
||||
|
||||
test("resolveFormat returns json for unrecognised values", () => {
|
||||
assert.equal(resolveFormat("xml"), "json");
|
||||
assert.equal(resolveFormat(""), "json");
|
||||
assert.equal(resolveFormat("TEXT"), "json");
|
||||
});
|
||||
|
||||
test("resolveFormat returns text only for the exact string 'text'", () => {
|
||||
assert.equal(resolveFormat("text"), "text");
|
||||
});
|
||||
@@ -7,13 +7,13 @@
|
||||
"path": "scripts/auth.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066"
|
||||
"sha256": "c0940f452437b05b95e58a9a7ab265fb50aa412bd672e82fedd6a37cbfb3d505"
|
||||
},
|
||||
{
|
||||
"path": "scripts/browse.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9"
|
||||
"sha256": "d7e4b4c50116032e5a00f90bca27e069dfc5bbf6eeb06ec8f8edc9e5a9792ab8"
|
||||
},
|
||||
{
|
||||
"path": "scripts/check-install.js",
|
||||
@@ -31,7 +31,13 @@
|
||||
"path": "scripts/flow.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051"
|
||||
"sha256": "94f3e7987cab253dc3c9e80656a11759fada13b3915608bff7ae08418602f366"
|
||||
},
|
||||
{
|
||||
"path": "scripts/lib/browser.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "879b5f883ff1f888d45ed20be05c2d9bc3d6fe5305a1972b7d49a7e6c0e24934"
|
||||
},
|
||||
{
|
||||
"path": "scripts/package.json",
|
||||
@@ -49,7 +55,7 @@
|
||||
"path": "scripts/scan-local-app.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4"
|
||||
"sha256": "9e1818c254a633e087715609152936dcb3613a0aa724d40a8a13460510691dc7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/scrape.ts",
|
||||
@@ -79,7 +85,7 @@
|
||||
"path": "scripts/tsconfig.json",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0"
|
||||
"sha256": "e5f22d72266068cf410976c880511f2ec1875445256e11739a5e1de6ffedf38d"
|
||||
},
|
||||
{
|
||||
"path": "scripts/turndown-plugin-gfm.d.ts",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* npx tsx auth.ts --url "https://example.com" --type auto
|
||||
*/
|
||||
|
||||
import { getPage, launchBrowser } from './browse.js';
|
||||
import { getPage, launchBrowser } from './lib/browser.js';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import { createInterface } from 'readline';
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
* npx tsx browse.ts --url "https://example.com" --headless false --wait 5000
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import type { BrowserContext } from 'playwright-core';
|
||||
|
||||
import { getProfilePath, launchBrowser, getPage } from './lib/browser.js';
|
||||
|
||||
// Re-export shared helpers so existing imports of browse.ts continue to work.
|
||||
export { getProfilePath, launchBrowser, getPage };
|
||||
|
||||
interface BrowseOptions {
|
||||
url: string;
|
||||
@@ -37,36 +38,6 @@ function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const getProfilePath = (): string => {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
};
|
||||
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
const browser = await launchBrowser({ headless: options.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
@@ -112,14 +83,6 @@ export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
return { page, browser };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2), {
|
||||
string: ['url', 'output'],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { launchBrowser } from './browse';
|
||||
import { launchBrowser } from './lib/browser.js';
|
||||
|
||||
type Step =
|
||||
| { action: 'goto'; url: string }
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`.
|
||||
/**
|
||||
* Shared browser-launch and profile helpers for web-automation scripts.
|
||||
*
|
||||
* Centralises the three reusable primitives that every command entry point
|
||||
* needs:
|
||||
* - getProfilePath() — resolve the persistent CloakBrowser profile dir
|
||||
* - launchBrowser() — launch a CloakBrowser persistent context
|
||||
* - getPage() — get a ready Page + BrowserContext pair
|
||||
*
|
||||
* All command entry points (auth.ts, browse.ts, flow.ts, scan-local-app.ts)
|
||||
* import from here instead of duplicating these bodies.
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import type { BrowserContext, Page } from 'playwright-core';
|
||||
|
||||
/**
|
||||
* Return the path to the persistent CloakBrowser profile directory.
|
||||
*
|
||||
* Uses `CLOAKBROWSER_PROFILE_PATH` env var when set; otherwise defaults to
|
||||
* `~/.cloakbrowser-profile/` and creates it if it does not exist.
|
||||
*/
|
||||
export function getProfilePath(): string {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a CloakBrowser persistent context with the shared profile.
|
||||
*
|
||||
* Headless mode is resolved in order:
|
||||
* 1. `options.headless` (explicit caller preference)
|
||||
* 2. `CLOAKBROWSER_HEADLESS` env var
|
||||
* 3. `true` (safe default)
|
||||
*/
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ready `{ page, browser }` pair using the shared persistent profile.
|
||||
*
|
||||
* Re-uses the first existing page or opens a new one if the context is empty.
|
||||
*/
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || (await browser.newPage());
|
||||
return { page, browser };
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { getPage } from './browse.js';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { getPage } from './lib/browser.js';
|
||||
|
||||
type NavResult = {
|
||||
requestedUrl: string;
|
||||
@@ -40,30 +41,34 @@ function getRoutes(baseUrl: string): string[] {
|
||||
return [baseUrl];
|
||||
}
|
||||
|
||||
async function gotoWithStatus(page: any, url: string): Promise<NavResult> {
|
||||
type GotoError = { error: unknown };
|
||||
|
||||
async function gotoWithStatus(page: Page, url: string): Promise<NavResult> {
|
||||
const response = await page
|
||||
.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
|
||||
.catch((error: unknown) => ({ error }));
|
||||
.catch((error: unknown): GotoError => ({ error }));
|
||||
|
||||
if (response?.error) {
|
||||
if (response !== null && response !== undefined && 'error' in response) {
|
||||
const gotoError = response as GotoError;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: null,
|
||||
title: await page.title().catch(() => ''),
|
||||
error: String(response.error),
|
||||
error: String(gotoError.error),
|
||||
};
|
||||
}
|
||||
|
||||
const httpResponse = response as Awaited<ReturnType<Page['goto']>>;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: response ? response.status() : null,
|
||||
status: httpResponse ? httpResponse.status() : null,
|
||||
title: await page.title().catch(() => ''),
|
||||
};
|
||||
}
|
||||
|
||||
async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
async function textOrNull(page: Page, selector: string): Promise<string | null> {
|
||||
const locator = page.locator(selector).first();
|
||||
try {
|
||||
if ((await locator.count()) === 0) return null;
|
||||
@@ -74,7 +79,7 @@ async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
async function loginIfConfigured(page: Page, baseUrl: string, lines: string[]) {
|
||||
const loginPath = env('SCAN_LOGIN_PATH');
|
||||
const username = env('SCAN_USERNAME') ?? env('CLOAKBROWSER_USERNAME');
|
||||
const password = env('SCAN_PASSWORD') ?? env('CLOAKBROWSER_PASSWORD');
|
||||
@@ -110,7 +115,7 @@ async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
async function checkRoutes(page: any, baseUrl: string, lines: string[]) {
|
||||
async function checkRoutes(page: Page, baseUrl: string, lines: string[]) {
|
||||
const routes = getRoutes(baseUrl);
|
||||
const routeChecks: RouteCheck[] = [];
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"outDir": "./dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"include": ["*.ts", "lib/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
"path": "scripts/auth.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066"
|
||||
"sha256": "c0940f452437b05b95e58a9a7ab265fb50aa412bd672e82fedd6a37cbfb3d505"
|
||||
},
|
||||
{
|
||||
"path": "scripts/browse.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9"
|
||||
"sha256": "d7e4b4c50116032e5a00f90bca27e069dfc5bbf6eeb06ec8f8edc9e5a9792ab8"
|
||||
},
|
||||
{
|
||||
"path": "scripts/check-install.js",
|
||||
@@ -31,7 +31,13 @@
|
||||
"path": "scripts/flow.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051"
|
||||
"sha256": "94f3e7987cab253dc3c9e80656a11759fada13b3915608bff7ae08418602f366"
|
||||
},
|
||||
{
|
||||
"path": "scripts/lib/browser.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "879b5f883ff1f888d45ed20be05c2d9bc3d6fe5305a1972b7d49a7e6c0e24934"
|
||||
},
|
||||
{
|
||||
"path": "scripts/package.json",
|
||||
@@ -49,7 +55,7 @@
|
||||
"path": "scripts/scan-local-app.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4"
|
||||
"sha256": "9e1818c254a633e087715609152936dcb3613a0aa724d40a8a13460510691dc7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/scrape.ts",
|
||||
@@ -79,7 +85,7 @@
|
||||
"path": "scripts/tsconfig.json",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0"
|
||||
"sha256": "e5f22d72266068cf410976c880511f2ec1875445256e11739a5e1de6ffedf38d"
|
||||
},
|
||||
{
|
||||
"path": "scripts/turndown-plugin-gfm.d.ts",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* npx tsx auth.ts --url "https://example.com" --type auto
|
||||
*/
|
||||
|
||||
import { getPage, launchBrowser } from './browse.js';
|
||||
import { getPage, launchBrowser } from './lib/browser.js';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import { createInterface } from 'readline';
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
* npx tsx browse.ts --url "https://example.com" --headless false --wait 5000
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import type { BrowserContext } from 'playwright-core';
|
||||
|
||||
import { getProfilePath, launchBrowser, getPage } from './lib/browser.js';
|
||||
|
||||
// Re-export shared helpers so existing imports of browse.ts continue to work.
|
||||
export { getProfilePath, launchBrowser, getPage };
|
||||
|
||||
interface BrowseOptions {
|
||||
url: string;
|
||||
@@ -37,36 +38,6 @@ function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const getProfilePath = (): string => {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
};
|
||||
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
const browser = await launchBrowser({ headless: options.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
@@ -112,14 +83,6 @@ export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
return { page, browser };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2), {
|
||||
string: ['url', 'output'],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { launchBrowser } from './browse';
|
||||
import { launchBrowser } from './lib/browser.js';
|
||||
|
||||
type Step =
|
||||
| { action: 'goto'; url: string }
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`.
|
||||
/**
|
||||
* Shared browser-launch and profile helpers for web-automation scripts.
|
||||
*
|
||||
* Centralises the three reusable primitives that every command entry point
|
||||
* needs:
|
||||
* - getProfilePath() — resolve the persistent CloakBrowser profile dir
|
||||
* - launchBrowser() — launch a CloakBrowser persistent context
|
||||
* - getPage() — get a ready Page + BrowserContext pair
|
||||
*
|
||||
* All command entry points (auth.ts, browse.ts, flow.ts, scan-local-app.ts)
|
||||
* import from here instead of duplicating these bodies.
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import type { BrowserContext, Page } from 'playwright-core';
|
||||
|
||||
/**
|
||||
* Return the path to the persistent CloakBrowser profile directory.
|
||||
*
|
||||
* Uses `CLOAKBROWSER_PROFILE_PATH` env var when set; otherwise defaults to
|
||||
* `~/.cloakbrowser-profile/` and creates it if it does not exist.
|
||||
*/
|
||||
export function getProfilePath(): string {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a CloakBrowser persistent context with the shared profile.
|
||||
*
|
||||
* Headless mode is resolved in order:
|
||||
* 1. `options.headless` (explicit caller preference)
|
||||
* 2. `CLOAKBROWSER_HEADLESS` env var
|
||||
* 3. `true` (safe default)
|
||||
*/
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ready `{ page, browser }` pair using the shared persistent profile.
|
||||
*
|
||||
* Re-uses the first existing page or opens a new one if the context is empty.
|
||||
*/
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || (await browser.newPage());
|
||||
return { page, browser };
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { getPage } from './browse.js';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { getPage } from './lib/browser.js';
|
||||
|
||||
type NavResult = {
|
||||
requestedUrl: string;
|
||||
@@ -40,30 +41,34 @@ function getRoutes(baseUrl: string): string[] {
|
||||
return [baseUrl];
|
||||
}
|
||||
|
||||
async function gotoWithStatus(page: any, url: string): Promise<NavResult> {
|
||||
type GotoError = { error: unknown };
|
||||
|
||||
async function gotoWithStatus(page: Page, url: string): Promise<NavResult> {
|
||||
const response = await page
|
||||
.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
|
||||
.catch((error: unknown) => ({ error }));
|
||||
.catch((error: unknown): GotoError => ({ error }));
|
||||
|
||||
if (response?.error) {
|
||||
if (response !== null && response !== undefined && 'error' in response) {
|
||||
const gotoError = response as GotoError;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: null,
|
||||
title: await page.title().catch(() => ''),
|
||||
error: String(response.error),
|
||||
error: String(gotoError.error),
|
||||
};
|
||||
}
|
||||
|
||||
const httpResponse = response as Awaited<ReturnType<Page['goto']>>;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: response ? response.status() : null,
|
||||
status: httpResponse ? httpResponse.status() : null,
|
||||
title: await page.title().catch(() => ''),
|
||||
};
|
||||
}
|
||||
|
||||
async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
async function textOrNull(page: Page, selector: string): Promise<string | null> {
|
||||
const locator = page.locator(selector).first();
|
||||
try {
|
||||
if ((await locator.count()) === 0) return null;
|
||||
@@ -74,7 +79,7 @@ async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
async function loginIfConfigured(page: Page, baseUrl: string, lines: string[]) {
|
||||
const loginPath = env('SCAN_LOGIN_PATH');
|
||||
const username = env('SCAN_USERNAME') ?? env('CLOAKBROWSER_USERNAME');
|
||||
const password = env('SCAN_PASSWORD') ?? env('CLOAKBROWSER_PASSWORD');
|
||||
@@ -110,7 +115,7 @@ async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
async function checkRoutes(page: any, baseUrl: string, lines: string[]) {
|
||||
async function checkRoutes(page: Page, baseUrl: string, lines: string[]) {
|
||||
const routes = getRoutes(baseUrl);
|
||||
const routeChecks: RouteCheck[] = [];
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"outDir": "./dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"include": ["*.ts", "lib/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
"path": "scripts/auth.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066"
|
||||
"sha256": "c0940f452437b05b95e58a9a7ab265fb50aa412bd672e82fedd6a37cbfb3d505"
|
||||
},
|
||||
{
|
||||
"path": "scripts/browse.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9"
|
||||
"sha256": "d7e4b4c50116032e5a00f90bca27e069dfc5bbf6eeb06ec8f8edc9e5a9792ab8"
|
||||
},
|
||||
{
|
||||
"path": "scripts/check-install.js",
|
||||
@@ -31,7 +31,13 @@
|
||||
"path": "scripts/flow.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051"
|
||||
"sha256": "94f3e7987cab253dc3c9e80656a11759fada13b3915608bff7ae08418602f366"
|
||||
},
|
||||
{
|
||||
"path": "scripts/lib/browser.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "879b5f883ff1f888d45ed20be05c2d9bc3d6fe5305a1972b7d49a7e6c0e24934"
|
||||
},
|
||||
{
|
||||
"path": "scripts/package.json",
|
||||
@@ -49,7 +55,7 @@
|
||||
"path": "scripts/scan-local-app.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4"
|
||||
"sha256": "9e1818c254a633e087715609152936dcb3613a0aa724d40a8a13460510691dc7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/scrape.ts",
|
||||
@@ -79,7 +85,7 @@
|
||||
"path": "scripts/tsconfig.json",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0"
|
||||
"sha256": "e5f22d72266068cf410976c880511f2ec1875445256e11739a5e1de6ffedf38d"
|
||||
},
|
||||
{
|
||||
"path": "scripts/turndown-plugin-gfm.d.ts",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* npx tsx auth.ts --url "https://example.com" --type auto
|
||||
*/
|
||||
|
||||
import { getPage, launchBrowser } from './browse.js';
|
||||
import { getPage, launchBrowser } from './lib/browser.js';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import { createInterface } from 'readline';
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
* npx tsx browse.ts --url "https://example.com" --headless false --wait 5000
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import type { BrowserContext } from 'playwright-core';
|
||||
|
||||
import { getProfilePath, launchBrowser, getPage } from './lib/browser.js';
|
||||
|
||||
// Re-export shared helpers so existing imports of browse.ts continue to work.
|
||||
export { getProfilePath, launchBrowser, getPage };
|
||||
|
||||
interface BrowseOptions {
|
||||
url: string;
|
||||
@@ -37,36 +38,6 @@ function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const getProfilePath = (): string => {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
};
|
||||
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
const browser = await launchBrowser({ headless: options.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
@@ -112,14 +83,6 @@ export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
return { page, browser };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2), {
|
||||
string: ['url', 'output'],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { launchBrowser } from './browse';
|
||||
import { launchBrowser } from './lib/browser.js';
|
||||
|
||||
type Step =
|
||||
| { action: 'goto'; url: string }
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`.
|
||||
/**
|
||||
* Shared browser-launch and profile helpers for web-automation scripts.
|
||||
*
|
||||
* Centralises the three reusable primitives that every command entry point
|
||||
* needs:
|
||||
* - getProfilePath() — resolve the persistent CloakBrowser profile dir
|
||||
* - launchBrowser() — launch a CloakBrowser persistent context
|
||||
* - getPage() — get a ready Page + BrowserContext pair
|
||||
*
|
||||
* All command entry points (auth.ts, browse.ts, flow.ts, scan-local-app.ts)
|
||||
* import from here instead of duplicating these bodies.
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import type { BrowserContext, Page } from 'playwright-core';
|
||||
|
||||
/**
|
||||
* Return the path to the persistent CloakBrowser profile directory.
|
||||
*
|
||||
* Uses `CLOAKBROWSER_PROFILE_PATH` env var when set; otherwise defaults to
|
||||
* `~/.cloakbrowser-profile/` and creates it if it does not exist.
|
||||
*/
|
||||
export function getProfilePath(): string {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a CloakBrowser persistent context with the shared profile.
|
||||
*
|
||||
* Headless mode is resolved in order:
|
||||
* 1. `options.headless` (explicit caller preference)
|
||||
* 2. `CLOAKBROWSER_HEADLESS` env var
|
||||
* 3. `true` (safe default)
|
||||
*/
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ready `{ page, browser }` pair using the shared persistent profile.
|
||||
*
|
||||
* Re-uses the first existing page or opens a new one if the context is empty.
|
||||
*/
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || (await browser.newPage());
|
||||
return { page, browser };
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { getPage } from './browse.js';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { getPage } from './lib/browser.js';
|
||||
|
||||
type NavResult = {
|
||||
requestedUrl: string;
|
||||
@@ -40,30 +41,34 @@ function getRoutes(baseUrl: string): string[] {
|
||||
return [baseUrl];
|
||||
}
|
||||
|
||||
async function gotoWithStatus(page: any, url: string): Promise<NavResult> {
|
||||
type GotoError = { error: unknown };
|
||||
|
||||
async function gotoWithStatus(page: Page, url: string): Promise<NavResult> {
|
||||
const response = await page
|
||||
.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
|
||||
.catch((error: unknown) => ({ error }));
|
||||
.catch((error: unknown): GotoError => ({ error }));
|
||||
|
||||
if (response?.error) {
|
||||
if (response !== null && response !== undefined && 'error' in response) {
|
||||
const gotoError = response as GotoError;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: null,
|
||||
title: await page.title().catch(() => ''),
|
||||
error: String(response.error),
|
||||
error: String(gotoError.error),
|
||||
};
|
||||
}
|
||||
|
||||
const httpResponse = response as Awaited<ReturnType<Page['goto']>>;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: response ? response.status() : null,
|
||||
status: httpResponse ? httpResponse.status() : null,
|
||||
title: await page.title().catch(() => ''),
|
||||
};
|
||||
}
|
||||
|
||||
async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
async function textOrNull(page: Page, selector: string): Promise<string | null> {
|
||||
const locator = page.locator(selector).first();
|
||||
try {
|
||||
if ((await locator.count()) === 0) return null;
|
||||
@@ -74,7 +79,7 @@ async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
async function loginIfConfigured(page: Page, baseUrl: string, lines: string[]) {
|
||||
const loginPath = env('SCAN_LOGIN_PATH');
|
||||
const username = env('SCAN_USERNAME') ?? env('CLOAKBROWSER_USERNAME');
|
||||
const password = env('SCAN_PASSWORD') ?? env('CLOAKBROWSER_PASSWORD');
|
||||
@@ -110,7 +115,7 @@ async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
async function checkRoutes(page: any, baseUrl: string, lines: string[]) {
|
||||
async function checkRoutes(page: Page, baseUrl: string, lines: string[]) {
|
||||
const routes = getRoutes(baseUrl);
|
||||
const routeChecks: RouteCheck[] = [];
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"outDir": "./dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"include": ["*.ts", "lib/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
"path": "scripts/auth.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066"
|
||||
"sha256": "c0940f452437b05b95e58a9a7ab265fb50aa412bd672e82fedd6a37cbfb3d505"
|
||||
},
|
||||
{
|
||||
"path": "scripts/browse.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9"
|
||||
"sha256": "d7e4b4c50116032e5a00f90bca27e069dfc5bbf6eeb06ec8f8edc9e5a9792ab8"
|
||||
},
|
||||
{
|
||||
"path": "scripts/check-install.js",
|
||||
@@ -31,7 +31,13 @@
|
||||
"path": "scripts/flow.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051"
|
||||
"sha256": "94f3e7987cab253dc3c9e80656a11759fada13b3915608bff7ae08418602f366"
|
||||
},
|
||||
{
|
||||
"path": "scripts/lib/browser.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "879b5f883ff1f888d45ed20be05c2d9bc3d6fe5305a1972b7d49a7e6c0e24934"
|
||||
},
|
||||
{
|
||||
"path": "scripts/package.json",
|
||||
@@ -49,7 +55,7 @@
|
||||
"path": "scripts/scan-local-app.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4"
|
||||
"sha256": "9e1818c254a633e087715609152936dcb3613a0aa724d40a8a13460510691dc7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/scrape.ts",
|
||||
@@ -79,7 +85,7 @@
|
||||
"path": "scripts/tsconfig.json",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0"
|
||||
"sha256": "e5f22d72266068cf410976c880511f2ec1875445256e11739a5e1de6ffedf38d"
|
||||
},
|
||||
{
|
||||
"path": "scripts/turndown-plugin-gfm.d.ts",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* npx tsx auth.ts --url "https://example.com" --type auto
|
||||
*/
|
||||
|
||||
import { getPage, launchBrowser } from './browse.js';
|
||||
import { getPage, launchBrowser } from './lib/browser.js';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import { createInterface } from 'readline';
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
* npx tsx browse.ts --url "https://example.com" --headless false --wait 5000
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import type { BrowserContext } from 'playwright-core';
|
||||
|
||||
import { getProfilePath, launchBrowser, getPage } from './lib/browser.js';
|
||||
|
||||
// Re-export shared helpers so existing imports of browse.ts continue to work.
|
||||
export { getProfilePath, launchBrowser, getPage };
|
||||
|
||||
interface BrowseOptions {
|
||||
url: string;
|
||||
@@ -37,36 +38,6 @@ function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const getProfilePath = (): string => {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
};
|
||||
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
const browser = await launchBrowser({ headless: options.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
@@ -112,14 +83,6 @@ export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
return { page, browser };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2), {
|
||||
string: ['url', 'output'],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { launchBrowser } from './browse';
|
||||
import { launchBrowser } from './lib/browser.js';
|
||||
|
||||
type Step =
|
||||
| { action: 'goto'; url: string }
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`.
|
||||
/**
|
||||
* Shared browser-launch and profile helpers for web-automation scripts.
|
||||
*
|
||||
* Centralises the three reusable primitives that every command entry point
|
||||
* needs:
|
||||
* - getProfilePath() — resolve the persistent CloakBrowser profile dir
|
||||
* - launchBrowser() — launch a CloakBrowser persistent context
|
||||
* - getPage() — get a ready Page + BrowserContext pair
|
||||
*
|
||||
* All command entry points (auth.ts, browse.ts, flow.ts, scan-local-app.ts)
|
||||
* import from here instead of duplicating these bodies.
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import type { BrowserContext, Page } from 'playwright-core';
|
||||
|
||||
/**
|
||||
* Return the path to the persistent CloakBrowser profile directory.
|
||||
*
|
||||
* Uses `CLOAKBROWSER_PROFILE_PATH` env var when set; otherwise defaults to
|
||||
* `~/.cloakbrowser-profile/` and creates it if it does not exist.
|
||||
*/
|
||||
export function getProfilePath(): string {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a CloakBrowser persistent context with the shared profile.
|
||||
*
|
||||
* Headless mode is resolved in order:
|
||||
* 1. `options.headless` (explicit caller preference)
|
||||
* 2. `CLOAKBROWSER_HEADLESS` env var
|
||||
* 3. `true` (safe default)
|
||||
*/
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ready `{ page, browser }` pair using the shared persistent profile.
|
||||
*
|
||||
* Re-uses the first existing page or opens a new one if the context is empty.
|
||||
*/
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || (await browser.newPage());
|
||||
return { page, browser };
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { getPage } from './browse.js';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { getPage } from './lib/browser.js';
|
||||
|
||||
type NavResult = {
|
||||
requestedUrl: string;
|
||||
@@ -40,30 +41,34 @@ function getRoutes(baseUrl: string): string[] {
|
||||
return [baseUrl];
|
||||
}
|
||||
|
||||
async function gotoWithStatus(page: any, url: string): Promise<NavResult> {
|
||||
type GotoError = { error: unknown };
|
||||
|
||||
async function gotoWithStatus(page: Page, url: string): Promise<NavResult> {
|
||||
const response = await page
|
||||
.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
|
||||
.catch((error: unknown) => ({ error }));
|
||||
.catch((error: unknown): GotoError => ({ error }));
|
||||
|
||||
if (response?.error) {
|
||||
if (response !== null && response !== undefined && 'error' in response) {
|
||||
const gotoError = response as GotoError;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: null,
|
||||
title: await page.title().catch(() => ''),
|
||||
error: String(response.error),
|
||||
error: String(gotoError.error),
|
||||
};
|
||||
}
|
||||
|
||||
const httpResponse = response as Awaited<ReturnType<Page['goto']>>;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: response ? response.status() : null,
|
||||
status: httpResponse ? httpResponse.status() : null,
|
||||
title: await page.title().catch(() => ''),
|
||||
};
|
||||
}
|
||||
|
||||
async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
async function textOrNull(page: Page, selector: string): Promise<string | null> {
|
||||
const locator = page.locator(selector).first();
|
||||
try {
|
||||
if ((await locator.count()) === 0) return null;
|
||||
@@ -74,7 +79,7 @@ async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
async function loginIfConfigured(page: Page, baseUrl: string, lines: string[]) {
|
||||
const loginPath = env('SCAN_LOGIN_PATH');
|
||||
const username = env('SCAN_USERNAME') ?? env('CLOAKBROWSER_USERNAME');
|
||||
const password = env('SCAN_PASSWORD') ?? env('CLOAKBROWSER_PASSWORD');
|
||||
@@ -110,7 +115,7 @@ async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
async function checkRoutes(page: any, baseUrl: string, lines: string[]) {
|
||||
async function checkRoutes(page: Page, baseUrl: string, lines: string[]) {
|
||||
const routes = getRoutes(baseUrl);
|
||||
const routeChecks: RouteCheck[] = [];
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"outDir": "./dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"include": ["*.ts", "lib/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@
|
||||
"path": "scripts/auth.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "ce0a8aae0bc41b86e11aab51cc0e0cfa484a1934807f147c05c9bd38d416c066"
|
||||
"sha256": "c0940f452437b05b95e58a9a7ab265fb50aa412bd672e82fedd6a37cbfb3d505"
|
||||
},
|
||||
{
|
||||
"path": "scripts/browse.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "42da9cdc6806b8d7d8d814952ad9540033b6c6a4cbe9844ada328b2ceace67c9"
|
||||
"sha256": "d7e4b4c50116032e5a00f90bca27e069dfc5bbf6eeb06ec8f8edc9e5a9792ab8"
|
||||
},
|
||||
{
|
||||
"path": "scripts/check-install.js",
|
||||
@@ -31,7 +31,13 @@
|
||||
"path": "scripts/flow.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "b1c256bf6a206473512a4c0555c891893a48025529da282fa6cd07e68ad3d051"
|
||||
"sha256": "94f3e7987cab253dc3c9e80656a11759fada13b3915608bff7ae08418602f366"
|
||||
},
|
||||
{
|
||||
"path": "scripts/lib/browser.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "879b5f883ff1f888d45ed20be05c2d9bc3d6fe5305a1972b7d49a7e6c0e24934"
|
||||
},
|
||||
{
|
||||
"path": "scripts/package.json",
|
||||
@@ -49,7 +55,7 @@
|
||||
"path": "scripts/scan-local-app.ts",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "3f42f9bb2d355fefc8645d2b2acfa3107bd87f9c2579b2631c94132bed0abea4"
|
||||
"sha256": "9e1818c254a633e087715609152936dcb3613a0aa724d40a8a13460510691dc7"
|
||||
},
|
||||
{
|
||||
"path": "scripts/scrape.ts",
|
||||
@@ -79,7 +85,7 @@
|
||||
"path": "scripts/tsconfig.json",
|
||||
"kind": "file",
|
||||
"mode": "644",
|
||||
"sha256": "5f9a83c8caab167eb20defbb5afde58f2bb573a300af99654997dcb3372408e0"
|
||||
"sha256": "e5f22d72266068cf410976c880511f2ec1875445256e11739a5e1de6ffedf38d"
|
||||
},
|
||||
{
|
||||
"path": "scripts/turndown-plugin-gfm.d.ts",
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* npx tsx auth.ts --url "https://example.com" --type auto
|
||||
*/
|
||||
|
||||
import { getPage, launchBrowser } from './browse.js';
|
||||
import { getPage, launchBrowser } from './lib/browser.js';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import { createInterface } from 'readline';
|
||||
|
||||
@@ -10,12 +10,13 @@
|
||||
* npx tsx browse.ts --url "https://example.com" --headless false --wait 5000
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import type { BrowserContext } from 'playwright-core';
|
||||
|
||||
import { getProfilePath, launchBrowser, getPage } from './lib/browser.js';
|
||||
|
||||
// Re-export shared helpers so existing imports of browse.ts continue to work.
|
||||
export { getProfilePath, launchBrowser, getPage };
|
||||
|
||||
interface BrowseOptions {
|
||||
url: string;
|
||||
@@ -37,36 +38,6 @@ function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const getProfilePath = (): string => {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
};
|
||||
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
const browser = await launchBrowser({ headless: options.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
@@ -112,14 +83,6 @@ export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
return { page, browser };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2), {
|
||||
string: ['url', 'output'],
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { launchBrowser } from './browse';
|
||||
import { launchBrowser } from './lib/browser.js';
|
||||
|
||||
type Step =
|
||||
| { action: 'goto'; url: string }
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
// ⚠️ GENERATED FILE – do not edit directly. Edit the canonical source in skills/web-automation/shared/ and run `pnpm run sync:pi`.
|
||||
/**
|
||||
* Shared browser-launch and profile helpers for web-automation scripts.
|
||||
*
|
||||
* Centralises the three reusable primitives that every command entry point
|
||||
* needs:
|
||||
* - getProfilePath() — resolve the persistent CloakBrowser profile dir
|
||||
* - launchBrowser() — launch a CloakBrowser persistent context
|
||||
* - getPage() — get a ready Page + BrowserContext pair
|
||||
*
|
||||
* All command entry points (auth.ts, browse.ts, flow.ts, scan-local-app.ts)
|
||||
* import from here instead of duplicating these bodies.
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import type { BrowserContext, Page } from 'playwright-core';
|
||||
|
||||
/**
|
||||
* Return the path to the persistent CloakBrowser profile directory.
|
||||
*
|
||||
* Uses `CLOAKBROWSER_PROFILE_PATH` env var when set; otherwise defaults to
|
||||
* `~/.cloakbrowser-profile/` and creates it if it does not exist.
|
||||
*/
|
||||
export function getProfilePath(): string {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a CloakBrowser persistent context with the shared profile.
|
||||
*
|
||||
* Headless mode is resolved in order:
|
||||
* 1. `options.headless` (explicit caller preference)
|
||||
* 2. `CLOAKBROWSER_HEADLESS` env var
|
||||
* 3. `true` (safe default)
|
||||
*/
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ready `{ page, browser }` pair using the shared persistent profile.
|
||||
*
|
||||
* Re-uses the first existing page or opens a new one if the context is empty.
|
||||
*/
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || (await browser.newPage());
|
||||
return { page, browser };
|
||||
}
|
||||
@@ -3,7 +3,8 @@
|
||||
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { getPage } from './browse.js';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { getPage } from './lib/browser.js';
|
||||
|
||||
type NavResult = {
|
||||
requestedUrl: string;
|
||||
@@ -40,30 +41,34 @@ function getRoutes(baseUrl: string): string[] {
|
||||
return [baseUrl];
|
||||
}
|
||||
|
||||
async function gotoWithStatus(page: any, url: string): Promise<NavResult> {
|
||||
type GotoError = { error: unknown };
|
||||
|
||||
async function gotoWithStatus(page: Page, url: string): Promise<NavResult> {
|
||||
const response = await page
|
||||
.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
|
||||
.catch((error: unknown) => ({ error }));
|
||||
.catch((error: unknown): GotoError => ({ error }));
|
||||
|
||||
if (response?.error) {
|
||||
if (response !== null && response !== undefined && 'error' in response) {
|
||||
const gotoError = response as GotoError;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: null,
|
||||
title: await page.title().catch(() => ''),
|
||||
error: String(response.error),
|
||||
error: String(gotoError.error),
|
||||
};
|
||||
}
|
||||
|
||||
const httpResponse = response as Awaited<ReturnType<Page['goto']>>;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: response ? response.status() : null,
|
||||
status: httpResponse ? httpResponse.status() : null,
|
||||
title: await page.title().catch(() => ''),
|
||||
};
|
||||
}
|
||||
|
||||
async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
async function textOrNull(page: Page, selector: string): Promise<string | null> {
|
||||
const locator = page.locator(selector).first();
|
||||
try {
|
||||
if ((await locator.count()) === 0) return null;
|
||||
@@ -74,7 +79,7 @@ async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
async function loginIfConfigured(page: Page, baseUrl: string, lines: string[]) {
|
||||
const loginPath = env('SCAN_LOGIN_PATH');
|
||||
const username = env('SCAN_USERNAME') ?? env('CLOAKBROWSER_USERNAME');
|
||||
const password = env('SCAN_PASSWORD') ?? env('CLOAKBROWSER_PASSWORD');
|
||||
@@ -110,7 +115,7 @@ async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
async function checkRoutes(page: any, baseUrl: string, lines: string[]) {
|
||||
async function checkRoutes(page: Page, baseUrl: string, lines: string[]) {
|
||||
const routes = getRoutes(baseUrl);
|
||||
const routeChecks: RouteCheck[] = [];
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"outDir": "./dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"include": ["*.ts", "lib/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
* npx tsx auth.ts --url "https://example.com" --type auto
|
||||
*/
|
||||
|
||||
import { getPage, launchBrowser } from './browse.js';
|
||||
import { getPage, launchBrowser } from './lib/browser.js';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import { createInterface } from 'readline';
|
||||
|
||||
@@ -9,12 +9,13 @@
|
||||
* npx tsx browse.ts --url "https://example.com" --headless false --wait 5000
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page, BrowserContext } from 'playwright-core';
|
||||
import type { BrowserContext } from 'playwright-core';
|
||||
|
||||
import { getProfilePath, launchBrowser, getPage } from './lib/browser.js';
|
||||
|
||||
// Re-export shared helpers so existing imports of browse.ts continue to work.
|
||||
export { getProfilePath, launchBrowser, getPage };
|
||||
|
||||
interface BrowseOptions {
|
||||
url: string;
|
||||
@@ -36,36 +37,6 @@ function sleep(ms: number): Promise<void> {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
const getProfilePath = (): string => {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
};
|
||||
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
const browser = await launchBrowser({ headless: options.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
@@ -111,14 +82,6 @@ export async function browse(options: BrowseOptions): Promise<BrowseResult> {
|
||||
}
|
||||
}
|
||||
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || await browser.newPage();
|
||||
return { page, browser };
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const args = parseArgs(process.argv.slice(2), {
|
||||
string: ['url', 'output'],
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
import parseArgs from 'minimist';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { launchBrowser } from './browse';
|
||||
import { launchBrowser } from './lib/browser.js';
|
||||
|
||||
type Step =
|
||||
| { action: 'goto'; url: string }
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Shared browser-launch and profile helpers for web-automation scripts.
|
||||
*
|
||||
* Centralises the three reusable primitives that every command entry point
|
||||
* needs:
|
||||
* - getProfilePath() — resolve the persistent CloakBrowser profile dir
|
||||
* - launchBrowser() — launch a CloakBrowser persistent context
|
||||
* - getPage() — get a ready Page + BrowserContext pair
|
||||
*
|
||||
* All command entry points (auth.ts, browse.ts, flow.ts, scan-local-app.ts)
|
||||
* import from here instead of duplicating these bodies.
|
||||
*/
|
||||
|
||||
import { launchPersistentContext } from 'cloakbrowser';
|
||||
import { existsSync, mkdirSync } from 'fs';
|
||||
import { homedir } from 'os';
|
||||
import { join } from 'path';
|
||||
import type { BrowserContext, Page } from 'playwright-core';
|
||||
|
||||
/**
|
||||
* Return the path to the persistent CloakBrowser profile directory.
|
||||
*
|
||||
* Uses `CLOAKBROWSER_PROFILE_PATH` env var when set; otherwise defaults to
|
||||
* `~/.cloakbrowser-profile/` and creates it if it does not exist.
|
||||
*/
|
||||
export function getProfilePath(): string {
|
||||
const customPath = process.env.CLOAKBROWSER_PROFILE_PATH;
|
||||
if (customPath) return customPath;
|
||||
|
||||
const profileDir = join(homedir(), '.cloakbrowser-profile');
|
||||
if (!existsSync(profileDir)) {
|
||||
mkdirSync(profileDir, { recursive: true });
|
||||
}
|
||||
return profileDir;
|
||||
}
|
||||
|
||||
/**
|
||||
* Launch a CloakBrowser persistent context with the shared profile.
|
||||
*
|
||||
* Headless mode is resolved in order:
|
||||
* 1. `options.headless` (explicit caller preference)
|
||||
* 2. `CLOAKBROWSER_HEADLESS` env var
|
||||
* 3. `true` (safe default)
|
||||
*/
|
||||
export async function launchBrowser(options: {
|
||||
headless?: boolean;
|
||||
}): Promise<BrowserContext> {
|
||||
const profilePath = getProfilePath();
|
||||
const envHeadless = process.env.CLOAKBROWSER_HEADLESS;
|
||||
const headless = options.headless ?? (envHeadless ? envHeadless === 'true' : true);
|
||||
|
||||
console.log(`Using profile: ${profilePath}`);
|
||||
console.log(`Headless mode: ${headless}`);
|
||||
|
||||
const context = await launchPersistentContext({
|
||||
userDataDir: profilePath,
|
||||
headless,
|
||||
humanize: true,
|
||||
});
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a ready `{ page, browser }` pair using the shared persistent profile.
|
||||
*
|
||||
* Re-uses the first existing page or opens a new one if the context is empty.
|
||||
*/
|
||||
export async function getPage(options?: {
|
||||
headless?: boolean;
|
||||
}): Promise<{ page: Page; browser: BrowserContext }> {
|
||||
const browser = await launchBrowser({ headless: options?.headless });
|
||||
const page = browser.pages()[0] || (await browser.newPage());
|
||||
return { page, browser };
|
||||
}
|
||||
@@ -2,7 +2,8 @@
|
||||
|
||||
import { mkdirSync, writeFileSync } from 'fs';
|
||||
import { dirname, resolve } from 'path';
|
||||
import { getPage } from './browse.js';
|
||||
import type { Page } from 'playwright-core';
|
||||
import { getPage } from './lib/browser.js';
|
||||
|
||||
type NavResult = {
|
||||
requestedUrl: string;
|
||||
@@ -39,30 +40,34 @@ function getRoutes(baseUrl: string): string[] {
|
||||
return [baseUrl];
|
||||
}
|
||||
|
||||
async function gotoWithStatus(page: any, url: string): Promise<NavResult> {
|
||||
type GotoError = { error: unknown };
|
||||
|
||||
async function gotoWithStatus(page: Page, url: string): Promise<NavResult> {
|
||||
const response = await page
|
||||
.goto(url, { waitUntil: 'domcontentloaded', timeout: 60000 })
|
||||
.catch((error: unknown) => ({ error }));
|
||||
.catch((error: unknown): GotoError => ({ error }));
|
||||
|
||||
if (response?.error) {
|
||||
if (response !== null && response !== undefined && 'error' in response) {
|
||||
const gotoError = response as GotoError;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: null,
|
||||
title: await page.title().catch(() => ''),
|
||||
error: String(response.error),
|
||||
error: String(gotoError.error),
|
||||
};
|
||||
}
|
||||
|
||||
const httpResponse = response as Awaited<ReturnType<Page['goto']>>;
|
||||
return {
|
||||
requestedUrl: url,
|
||||
url: page.url(),
|
||||
status: response ? response.status() : null,
|
||||
status: httpResponse ? httpResponse.status() : null,
|
||||
title: await page.title().catch(() => ''),
|
||||
};
|
||||
}
|
||||
|
||||
async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
async function textOrNull(page: Page, selector: string): Promise<string | null> {
|
||||
const locator = page.locator(selector).first();
|
||||
try {
|
||||
if ((await locator.count()) === 0) return null;
|
||||
@@ -73,7 +78,7 @@ async function textOrNull(page: any, selector: string): Promise<string | null> {
|
||||
}
|
||||
}
|
||||
|
||||
async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
async function loginIfConfigured(page: Page, baseUrl: string, lines: string[]) {
|
||||
const loginPath = env('SCAN_LOGIN_PATH');
|
||||
const username = env('SCAN_USERNAME') ?? env('CLOAKBROWSER_USERNAME');
|
||||
const password = env('SCAN_PASSWORD') ?? env('CLOAKBROWSER_PASSWORD');
|
||||
@@ -109,7 +114,7 @@ async function loginIfConfigured(page: any, baseUrl: string, lines: string[]) {
|
||||
lines.push('');
|
||||
}
|
||||
|
||||
async function checkRoutes(page: any, baseUrl: string, lines: string[]) {
|
||||
async function checkRoutes(page: Page, baseUrl: string, lines: string[]) {
|
||||
const routes = getRoutes(baseUrl);
|
||||
const routeChecks: RouteCheck[] = [];
|
||||
|
||||
|
||||
@@ -11,6 +11,6 @@
|
||||
"outDir": "./dist",
|
||||
"rootDir": "."
|
||||
},
|
||||
"include": ["*.ts"],
|
||||
"include": ["*.ts", "lib/**/*.ts"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user