bdy 1.23.15-dev-target-commands → 1.23.15-dev
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/distTs/README.md +181 -0
- package/distTs/package.json +3 -3
- package/distTs/src/agent/agent.js +27 -2
- package/distTs/src/agent/linux.js +7 -5
- package/distTs/src/agent/manager.js +34 -2
- package/distTs/src/agent/osx.js +7 -9
- package/distTs/src/agent/socket/client.js +10 -0
- package/distTs/src/agent/system.js +16 -4
- package/distTs/src/agent/windows.js +1 -2
- package/distTs/src/api/client.js +16 -1
- package/distTs/src/command/agent/install.js +1 -1
- package/distTs/src/command/agent/run.js +16 -0
- package/distTs/src/command/api/request.js +5 -4
- package/distTs/src/command/environment/delete.js +2 -1
- package/distTs/src/command/environment/resolve.js +18 -8
- package/distTs/src/command/environment/update.js +2 -1
- package/distTs/src/command/pre.js +9 -8
- package/distTs/src/command/project/get.js +18 -0
- package/distTs/src/command/project/git/credential.js +6 -4
- package/distTs/src/command/project/set.js +31 -0
- package/distTs/src/command/sandbox/exec/command.js +1 -1
- package/distTs/src/command/sandbox/exec/logs.js +1 -1
- package/distTs/src/command/sandbox/get/yaml.js +30 -0
- package/distTs/src/command/target/delete.js +2 -1
- package/distTs/src/command/target/exec/command.js +2 -1
- package/distTs/src/command/target/scope.js +33 -10
- package/distTs/src/command/target/update.js +2 -1
- package/distTs/src/command/version.js +1 -1
- package/distTs/src/command/vt/scrape.js +193 -0
- package/distTs/src/index.js +7 -0
- package/distTs/src/logger.js +7 -1
- package/distTs/src/output/pipeline.js +5 -3
- package/distTs/src/output.js +12 -3
- package/distTs/src/texts.js +15 -12
- package/distTs/src/tunnel/server/sftp.js +24 -5
- package/distTs/src/tunnel/ssh/client.js +35 -4
- package/distTs/src/types/tunnel.js +11 -0
- package/distTs/src/utils.js +49 -14
- package/package.json +3 -3
- package/distTs/detect-rules.json +0 -351
- package/distTs/src/command/pipeline/run/apply.js +0 -62
- package/distTs/src/command/yaml/actions/detect.js +0 -268
- package/distTs/src/command/yaml/actions/info.js +0 -56
- package/distTs/src/command/yaml/actions/list.js +0 -70
- package/distTs/src/command/yaml/actions/schema.js +0 -104
- package/distTs/src/command/yaml/actions.js +0 -13
- package/distTs/src/command/yaml/agents.js +0 -88
- package/distTs/src/command/yaml/cache.js +0 -98
- package/distTs/src/command/yaml/init.js +0 -110
- package/distTs/src/command/yaml/pipeline.js +0 -42
- package/distTs/src/command/yaml/render.js +0 -83
- package/distTs/src/command/yaml/schemaUtils.js +0 -139
- package/distTs/src/command/yaml/validate.js +0 -259
- package/distTs/src/command/yaml.js +0 -18
- package/distTs/src/diskCache.js +0 -82
|
@@ -46,14 +46,15 @@ const commandPre = async (_, command) => {
|
|
|
46
46
|
if (!output_1.default.isTTY() || command.hideVersionUpdate || ['json', 'jsonl'].includes(command.opts()?.format)) {
|
|
47
47
|
return;
|
|
48
48
|
}
|
|
49
|
-
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
49
|
+
// No truthiness guards on the versions: an empty or unparsable one is never newer
|
|
50
|
+
const latest = command.latestVersion || '';
|
|
51
|
+
const newCli = (0, utils_1.isVersionNewer)(latest, command.currentVersion || '');
|
|
52
|
+
const newAgent = !!command.agentStatus && (0, utils_1.isVersionNewer)(latest, command.agentStatus.version);
|
|
53
|
+
if ((0, utils_1.isDocker)() && newCli) {
|
|
54
|
+
output_1.default.newCliDockerVersion(latest);
|
|
55
|
+
}
|
|
56
|
+
else if (newCli) {
|
|
57
|
+
output_1.default.newCliVersion(latest);
|
|
57
58
|
}
|
|
58
59
|
else if (newAgent) {
|
|
59
60
|
output_1.default.newAgentVersion();
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const cfg_1 = __importDefault(require("../../tunnel/cfg"));
|
|
7
|
+
const output_1 = __importDefault(require("../../output"));
|
|
8
|
+
const texts_1 = require("../../texts");
|
|
9
|
+
const utils_1 = require("../../utils");
|
|
10
|
+
const commandProjectGet = (0, utils_1.newCommand)('get', texts_1.DESC_COMMAND_PROJECT_GET);
|
|
11
|
+
commandProjectGet.action(async () => {
|
|
12
|
+
const project = cfg_1.default.getProject();
|
|
13
|
+
if (!project) {
|
|
14
|
+
output_1.default.exitError(texts_1.TXT_PROJECT_NONE);
|
|
15
|
+
}
|
|
16
|
+
output_1.default.exitNormal(project);
|
|
17
|
+
});
|
|
18
|
+
exports.default = commandProjectGet;
|
|
@@ -60,10 +60,12 @@ commandProjectGitCredential.action(async (action) => {
|
|
|
60
60
|
projects.projects[0].html_url.includes(input.host);
|
|
61
61
|
}
|
|
62
62
|
if (myHost) {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
output_1.default.
|
|
66
|
-
output_1.default.
|
|
63
|
+
// git credential protocol output - a machine-read payload, so it
|
|
64
|
+
// bypasses terminal-kit formatting
|
|
65
|
+
output_1.default.data(`protocol=${input.protocol || 'https'}`);
|
|
66
|
+
output_1.default.data(`host=${input.host}`);
|
|
67
|
+
output_1.default.data(`username=${client.token}`);
|
|
68
|
+
output_1.default.data(`password=`);
|
|
67
69
|
}
|
|
68
70
|
}
|
|
69
71
|
catch {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const cfg_1 = __importDefault(require("../../tunnel/cfg"));
|
|
7
|
+
const output_1 = __importDefault(require("../../output"));
|
|
8
|
+
const texts_1 = require("../../texts");
|
|
9
|
+
const utils_1 = require("../../utils");
|
|
10
|
+
const input_1 = __importDefault(require("../../input"));
|
|
11
|
+
const commandProjectSet = (0, utils_1.newCommand)('set', texts_1.DESC_COMMAND_PROJECT_SET);
|
|
12
|
+
commandProjectSet.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
|
|
13
|
+
commandProjectSet.argument('[project]', texts_1.ARG_COMMAND_PROJECT_NAME);
|
|
14
|
+
commandProjectSet.action(async (project, options) => {
|
|
15
|
+
output_1.default.handleSignals();
|
|
16
|
+
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
17
|
+
const client = input_1.default.restApiTokenClient();
|
|
18
|
+
if (project) {
|
|
19
|
+
await client.getProject(workspace, project);
|
|
20
|
+
}
|
|
21
|
+
else {
|
|
22
|
+
const response = await client.getProjects(workspace);
|
|
23
|
+
project = await output_1.default.selectProject(response.projects);
|
|
24
|
+
}
|
|
25
|
+
cfg_1.default.setProject(project);
|
|
26
|
+
if (!project)
|
|
27
|
+
output_1.default.exitSuccess(texts_1.TXT_PROJECT_SET_CLEARED);
|
|
28
|
+
else
|
|
29
|
+
output_1.default.exitSuccess((0, texts_1.TXT_PROJECT_SET_SUCCESS)(project));
|
|
30
|
+
});
|
|
31
|
+
exports.default = commandProjectSet;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = require("../../../utils");
|
|
7
|
+
const texts_1 = require("../../../texts");
|
|
8
|
+
const input_1 = __importDefault(require("../../../input"));
|
|
9
|
+
const output_1 = __importDefault(require("../../../output"));
|
|
10
|
+
const commandSandboxGetYaml = (0, utils_1.newCommand)('yaml', texts_1.DESC_COMMAND_SANDBOX_GET_YAML);
|
|
11
|
+
commandSandboxGetYaml.hideVersionUpdate = true;
|
|
12
|
+
commandSandboxGetYaml.alias('yml');
|
|
13
|
+
commandSandboxGetYaml.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
|
|
14
|
+
commandSandboxGetYaml.option('-p, --project <name>', texts_1.OPTION_REST_API_PROJECT);
|
|
15
|
+
commandSandboxGetYaml.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
|
|
16
|
+
commandSandboxGetYaml.action(async (identifier, options) => {
|
|
17
|
+
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
18
|
+
const project = input_1.default.restApiProject(options.project);
|
|
19
|
+
const client = input_1.default.restApiTokenClient();
|
|
20
|
+
let result = await client.listSandboxes(workspace, project);
|
|
21
|
+
const sandboxes = result.sandboxes || [];
|
|
22
|
+
const found = sandboxes.find((s) => s.identifier === identifier);
|
|
23
|
+
if (!found) {
|
|
24
|
+
output_1.default.exitError(texts_1.ERR_SANDBOX_NOT_FOUND);
|
|
25
|
+
}
|
|
26
|
+
const sandboxId = found.id;
|
|
27
|
+
result = await client.getSandboxYaml(workspace, sandboxId);
|
|
28
|
+
output_1.default.exitNormal(Buffer.from(result.yaml, 'base64').toString('utf8'));
|
|
29
|
+
});
|
|
30
|
+
exports.default = commandSandboxGetYaml;
|
|
@@ -20,7 +20,8 @@ commandTargetDelete.addHelpText('after', `\nEXAMPLES:${texts_1.EXAMPLE_TARGET_DE
|
|
|
20
20
|
commandTargetDelete.action(async (identifier, options) => {
|
|
21
21
|
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
22
22
|
const client = input_1.default.restApiTokenClient();
|
|
23
|
-
const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options
|
|
23
|
+
const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options,
|
|
24
|
+
/* strictLine */ true);
|
|
24
25
|
const confirmed = options.force || (await output_1.default.confirm((0, texts_1.TXT_TARGET_DELETE_CONFIRM)(identifier)));
|
|
25
26
|
if (!confirmed)
|
|
26
27
|
output_1.default.exitNormal();
|
|
@@ -41,7 +41,8 @@ commandTargetExecCommand.action(async (identifier, command, options) => {
|
|
|
41
41
|
}
|
|
42
42
|
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
43
43
|
const client = input_1.default.restApiTokenClient();
|
|
44
|
-
const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options
|
|
44
|
+
const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options,
|
|
45
|
+
/* strictLine */ true);
|
|
45
46
|
let exec = await client.executeTargetCommand(workspace, target_id, {
|
|
46
47
|
command: cmd,
|
|
47
48
|
});
|
|
@@ -10,14 +10,42 @@ const output_1 = __importDefault(require("../../output"));
|
|
|
10
10
|
const input_1 = __importDefault(require("../../input"));
|
|
11
11
|
// Shared resolve preamble for commands taking a target identifier: line
|
|
12
12
|
// context from flags/env -> /identifiers -> hash id, exiting on a miss.
|
|
13
|
-
|
|
13
|
+
//
|
|
14
|
+
// strictLine is for mutating commands (update/delete/exec command): an
|
|
15
|
+
// explicit line flag is a statement "the target of this project/pipeline/
|
|
16
|
+
// environment", yet /identifiers falls back to the workspace target of the
|
|
17
|
+
// same identifier when the requested line exists but does not hold it - the
|
|
18
|
+
// operation would silently land on a workspace-wide resource. Reads keep
|
|
19
|
+
// the fallback (workspace targets stay addressable from a line context).
|
|
20
|
+
const resolveTargetId = async (client, workspace, identifier, options, strictLine = false) => {
|
|
14
21
|
const project = input_1.default.restApiProject(options.project, true);
|
|
15
22
|
const line = input_1.default.targetLine(project, options.pipeline, options.environment);
|
|
16
|
-
const
|
|
17
|
-
if (!target_id) {
|
|
23
|
+
const resolved = await client.getTargetByIdentifier(workspace, identifier, line);
|
|
24
|
+
if (!resolved.target_id) {
|
|
18
25
|
output_1.default.exitError(texts_1.ERR_TARGET_NOT_FOUND);
|
|
19
26
|
}
|
|
20
|
-
|
|
27
|
+
if (strictLine) {
|
|
28
|
+
const requested = options.pipeline
|
|
29
|
+
? utils_1.TARGET_SCOPE.PIPELINE
|
|
30
|
+
: options.environment
|
|
31
|
+
? utils_1.TARGET_SCOPE.ENVIRONMENT
|
|
32
|
+
: options.project
|
|
33
|
+
? utils_1.TARGET_SCOPE.PROJECT
|
|
34
|
+
: null;
|
|
35
|
+
if (requested) {
|
|
36
|
+
const target = await client.getTarget(workspace, resolved.target_id);
|
|
37
|
+
const matches = requested === utils_1.TARGET_SCOPE.PIPELINE
|
|
38
|
+
? String(target.pipeline?.id) === String(resolved.pipeline_id)
|
|
39
|
+
: requested === utils_1.TARGET_SCOPE.ENVIRONMENT
|
|
40
|
+
? target.environment?.id === resolved.environment_id
|
|
41
|
+
: (0, exports.targetScopeOf)(target) === utils_1.TARGET_SCOPE.PROJECT &&
|
|
42
|
+
target.project?.name === resolved.project_identifier;
|
|
43
|
+
if (!matches) {
|
|
44
|
+
output_1.default.exitError(texts_1.ERR_TARGET_NOT_FOUND);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return resolved.target_id;
|
|
21
49
|
};
|
|
22
50
|
exports.resolveTargetId = resolveTargetId;
|
|
23
51
|
// Lines the CLI requests per log fetch.
|
|
@@ -96,12 +124,7 @@ const resolveTargetScope = async (client, workspace, project, scope, options) =>
|
|
|
96
124
|
if (project)
|
|
97
125
|
query.project = project;
|
|
98
126
|
const resolved = await client.getResourceByIdentifier(workspace, query);
|
|
99
|
-
//
|
|
100
|
-
// requested project does not exist; a provided project must itself
|
|
101
|
-
// resolve or the scope would silently point elsewhere.
|
|
102
|
-
if (project && !resolved.project_identifier) {
|
|
103
|
-
output_1.default.exitError(texts_1.ERR_PROJECT_NOT_FOUND);
|
|
104
|
-
}
|
|
127
|
+
// a nonexistent project is rejected by getResourceByIdentifier itself
|
|
105
128
|
if (!resolved.environment_id) {
|
|
106
129
|
output_1.default.exitError(texts_1.ERR_TARGET_ENVIRONMENT_NOT_FOUND);
|
|
107
130
|
}
|
|
@@ -21,7 +21,8 @@ commandTargetUpdate.action(async (identifier, options) => {
|
|
|
21
21
|
const yaml = input_1.default.restApiYaml(options.yaml);
|
|
22
22
|
const workspace = input_1.default.restApiWorkspace(options.workspace);
|
|
23
23
|
const client = input_1.default.restApiTokenClient();
|
|
24
|
-
const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options
|
|
24
|
+
const target_id = await (0, scope_1.resolveTargetId)(client, workspace, identifier, options,
|
|
25
|
+
/* strictLine */ true);
|
|
25
26
|
const body = {
|
|
26
27
|
yaml: Buffer.from(yaml, 'utf8').toString('base64'),
|
|
27
28
|
};
|
|
@@ -78,7 +78,7 @@ commandVersion.action(async () => {
|
|
|
78
78
|
const currVersion = commandVersion.currentVersion || '';
|
|
79
79
|
const lastVersion = commandVersion.latestVersion || '';
|
|
80
80
|
const env = commandVersion.env || '';
|
|
81
|
-
if (
|
|
81
|
+
if ((0, utils_1.isVersionNewer)(lastVersion, currVersion)) {
|
|
82
82
|
const platform = (0, utils_1.getPlatform)();
|
|
83
83
|
const isMac = platform === 'darwin';
|
|
84
84
|
const isWin = platform === 'win32';
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
const utils_1 = require("../../utils");
|
|
7
|
+
const commander_1 = require("commander");
|
|
8
|
+
const texts_1 = require("../../texts");
|
|
9
|
+
const validation_1 = require("../../visualTest/validation");
|
|
10
|
+
const output_1 = __importDefault(require("../../output"));
|
|
11
|
+
const requests_1 = require("../../visualTest/requests");
|
|
12
|
+
const zod_1 = require("zod");
|
|
13
|
+
const node_zlib_1 = require("node:zlib");
|
|
14
|
+
const tar_stream_1 = __importDefault(require("tar-stream"));
|
|
15
|
+
const promises_1 = require("node:stream/promises");
|
|
16
|
+
const node_fs_1 = require("node:fs");
|
|
17
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
18
|
+
const promises_2 = require("node:fs/promises");
|
|
19
|
+
const commandScrape = (0, utils_1.newCommand)('scrape', texts_1.DESC_COMMAND_VT_SCRAPE);
|
|
20
|
+
commandScrape.argument('<url>', texts_1.OPTION_SCRAPE_URL);
|
|
21
|
+
commandScrape.option('--follow', texts_1.OPTION_SCRAPE_FOLLOW, false);
|
|
22
|
+
commandScrape.addOption(new commander_1.Option('--outputType <type>', texts_1.OPTION_SCRAPE_OUTPUT_TYPE)
|
|
23
|
+
.choices(['jpeg', 'png', 'md', 'html'])
|
|
24
|
+
.makeOptionMandatory());
|
|
25
|
+
commandScrape.option('--quality <quality>', texts_1.OPTION_SCRAPE_QUALITY);
|
|
26
|
+
commandScrape.option('--fullPage', texts_1.OPTION_SCRAPE_FULL_PAGE, false);
|
|
27
|
+
commandScrape.option('--cssSelector <selector>', texts_1.OPTION_SCRAPE_CSS_SELECTOR);
|
|
28
|
+
commandScrape.option('--xpathSelector <selector>', texts_1.OPTION_SCRAPE_XPATH_SELECTOR);
|
|
29
|
+
commandScrape.addOption(new commander_1.Option('--browser <browser>', texts_1.OPTION_SCRAPE_BROWSER)
|
|
30
|
+
.choices(['chrome', 'firefox', 'safari'])
|
|
31
|
+
.default('chrome'));
|
|
32
|
+
commandScrape.option('--viewport <viewport>', texts_1.OPTION_SCRAPE_VIEWPORT, '1920x1080');
|
|
33
|
+
commandScrape.option('--devicePixelRatio <ratio>', texts_1.OPTION_SCRAPE_DEVICE_PIXEL_RATIO, '1');
|
|
34
|
+
commandScrape.option('--waitForElement <selector>', texts_1.OPTION_SCRAPE_WAIT_FOR_ELEMENT);
|
|
35
|
+
commandScrape.option('--darkMode', texts_1.OPTION_SCRAPE_DARK_MODE, false);
|
|
36
|
+
commandScrape.option('--delay <delay>', texts_1.OPTION_SCRAPE_DELAY, '0');
|
|
37
|
+
commandScrape.option('--outputDir <dir>', texts_1.OPTION_SCRAPE_OUTPUT_DIR, '.');
|
|
38
|
+
commandScrape.action(async (inputUrl, options) => {
|
|
39
|
+
if (!(0, validation_1.checkToken)()) {
|
|
40
|
+
output_1.default.exitError(texts_1.ERR_MISSING_VT_TOKEN);
|
|
41
|
+
}
|
|
42
|
+
const { url, follow, outputType, quality, outputDir, fullPage, cssSelector, xpathSelector, browser, viewport, devicePixelRatio, darkMode, delay, waitForElement, } = validateInputAndOptions(inputUrl, options);
|
|
43
|
+
try {
|
|
44
|
+
const { buildId } = await (0, requests_1.sendScrap)(url, outputType, follow, quality, fullPage, cssSelector, xpathSelector, browser, viewport, devicePixelRatio, darkMode, delay, waitForElement);
|
|
45
|
+
output_1.default.normal('Starting scrape session');
|
|
46
|
+
const status = await watchSessionStatus(buildId);
|
|
47
|
+
if (!status.ok) {
|
|
48
|
+
output_1.default.exitError(`Unexpected error while watching session status: ${status.error}`);
|
|
49
|
+
}
|
|
50
|
+
output_1.default.normal('Downloading scrape package');
|
|
51
|
+
const scrapPackageStream = await (0, requests_1.downloadScrapPackage)(buildId);
|
|
52
|
+
const brotliDecompressor = (0, node_zlib_1.createBrotliDecompress)();
|
|
53
|
+
const unpack = tar_stream_1.default.extract();
|
|
54
|
+
unpack.on('entry', async (header, stream, next) => {
|
|
55
|
+
const currentDir = process.cwd();
|
|
56
|
+
const preparedOutputDir = outputDir.startsWith('.')
|
|
57
|
+
? node_path_1.default.join(currentDir, outputDir)
|
|
58
|
+
: outputDir;
|
|
59
|
+
const newFilePath = node_path_1.default.join(preparedOutputDir, header.name);
|
|
60
|
+
try {
|
|
61
|
+
if (header.type === 'file') {
|
|
62
|
+
await (0, promises_2.mkdir)(node_path_1.default.dirname(newFilePath), { recursive: true });
|
|
63
|
+
const fileWriteStream = (0, node_fs_1.createWriteStream)(newFilePath);
|
|
64
|
+
await (0, promises_1.pipeline)(stream, fileWriteStream);
|
|
65
|
+
next();
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
stream.resume();
|
|
69
|
+
next();
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
catch (entryError) {
|
|
73
|
+
output_1.default.error(`Error processing entry ${header.name}: ${entryError}`);
|
|
74
|
+
next(entryError);
|
|
75
|
+
}
|
|
76
|
+
});
|
|
77
|
+
await (0, promises_1.pipeline)(scrapPackageStream, brotliDecompressor, unpack);
|
|
78
|
+
output_1.default.exitSuccess('Downloading scrape package finished');
|
|
79
|
+
}
|
|
80
|
+
catch (error) {
|
|
81
|
+
output_1.default.exitError(`${error}`);
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
function validateInputAndOptions(input, options) {
|
|
85
|
+
const urlSchema = zod_1.z.string().url();
|
|
86
|
+
const optionsSchema = zod_1.z.object({
|
|
87
|
+
follow: zod_1.z.boolean(),
|
|
88
|
+
outputType: zod_1.z.enum(['jpeg', 'png', 'md', 'html']),
|
|
89
|
+
quality: zod_1.z.coerce.number().min(1).max(100).optional(),
|
|
90
|
+
outputDir: zod_1.z.string().default('.'),
|
|
91
|
+
fullPage: zod_1.z.boolean().optional(),
|
|
92
|
+
cssSelector: zod_1.z.string().optional(),
|
|
93
|
+
xpathSelector: zod_1.z.string().optional(),
|
|
94
|
+
browser: zod_1.z.enum(['chrome', 'firefox', 'safari']),
|
|
95
|
+
viewport: zod_1.z
|
|
96
|
+
.string()
|
|
97
|
+
.refine((value) => {
|
|
98
|
+
const [width, height] = value.split('x');
|
|
99
|
+
return (width &&
|
|
100
|
+
height &&
|
|
101
|
+
!isNaN(Number(width)) &&
|
|
102
|
+
!isNaN(Number(height)) &&
|
|
103
|
+
Number(width) > 0 &&
|
|
104
|
+
Number(height) > 0);
|
|
105
|
+
}, 'Invalid viewport format, example: 1920x1080')
|
|
106
|
+
.transform((value) => {
|
|
107
|
+
const [width, height] = value.split('x');
|
|
108
|
+
return {
|
|
109
|
+
width: Number(width),
|
|
110
|
+
height: Number(height),
|
|
111
|
+
};
|
|
112
|
+
}),
|
|
113
|
+
devicePixelRatio: zod_1.z.coerce.number().min(1).max(4),
|
|
114
|
+
darkMode: zod_1.z.boolean(),
|
|
115
|
+
delay: zod_1.z.coerce.number().min(0).max(10000),
|
|
116
|
+
waitForElement: zod_1.z.string().optional(),
|
|
117
|
+
});
|
|
118
|
+
try {
|
|
119
|
+
const url = urlSchema.parse(input);
|
|
120
|
+
const { follow, outputType, quality, outputDir, fullPage, cssSelector, xpathSelector, browser, viewport, devicePixelRatio, darkMode, delay, waitForElement, } = optionsSchema.parse(options);
|
|
121
|
+
if (typeof quality === 'number' && outputType !== 'jpeg') {
|
|
122
|
+
output_1.default.exitError('Quality is only supported for jpeg output type, use --outputType jpeg');
|
|
123
|
+
}
|
|
124
|
+
if (cssSelector && xpathSelector) {
|
|
125
|
+
output_1.default.exitError('Only one of --cssSelector or --xpathSelector can be used');
|
|
126
|
+
}
|
|
127
|
+
return {
|
|
128
|
+
url,
|
|
129
|
+
follow,
|
|
130
|
+
outputType,
|
|
131
|
+
quality,
|
|
132
|
+
outputDir,
|
|
133
|
+
fullPage,
|
|
134
|
+
cssSelector,
|
|
135
|
+
xpathSelector,
|
|
136
|
+
browser,
|
|
137
|
+
viewport,
|
|
138
|
+
devicePixelRatio,
|
|
139
|
+
darkMode,
|
|
140
|
+
delay,
|
|
141
|
+
waitForElement,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
catch (error) {
|
|
145
|
+
if (error instanceof zod_1.ZodError) {
|
|
146
|
+
output_1.default.exitError(error.errors.map((e) => `${e.path}: ${e.message}`).join(', '));
|
|
147
|
+
}
|
|
148
|
+
else {
|
|
149
|
+
throw error;
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
async function watchSessionStatus(buildId) {
|
|
154
|
+
return new Promise((resolve) => {
|
|
155
|
+
const eventSource = (0, requests_1.connectToScrapSession)(buildId);
|
|
156
|
+
eventSource.addEventListener('SESSION_STATUS', (event) => {
|
|
157
|
+
const data = JSON.parse(event.data);
|
|
158
|
+
if (data.status === 'GATHER_URLS_COMPLETED') {
|
|
159
|
+
output_1.default.normal(`Gathering URLs completed, found ${data.text} URLs`);
|
|
160
|
+
}
|
|
161
|
+
else if (data.status === 'GATHER_URLS_FAILED') {
|
|
162
|
+
output_1.default.error('Gathering URLs failed');
|
|
163
|
+
}
|
|
164
|
+
else if (data.status === 'SCRAPE_URL_COMPLETED') {
|
|
165
|
+
output_1.default.normal(`Scraping ${data.text} completed`);
|
|
166
|
+
}
|
|
167
|
+
else if (data.status === 'SCRAPE_URL_FAILED') {
|
|
168
|
+
output_1.default.error(`Scraping ${data.text} failed`);
|
|
169
|
+
}
|
|
170
|
+
else if (data.status === 'CREATE_PACKAGE_COMPLETED') {
|
|
171
|
+
output_1.default.normal('Package created');
|
|
172
|
+
}
|
|
173
|
+
else if (data.status === 'CREATE_PACKAGE_FAILED') {
|
|
174
|
+
output_1.default.error('Package creation failed');
|
|
175
|
+
}
|
|
176
|
+
else if (data.status === 'FINISHED') {
|
|
177
|
+
eventSource.close();
|
|
178
|
+
output_1.default.normal('Scrape session finished');
|
|
179
|
+
resolve({ ok: true });
|
|
180
|
+
}
|
|
181
|
+
});
|
|
182
|
+
eventSource.addEventListener('error', (event) => {
|
|
183
|
+
if (event.code) {
|
|
184
|
+
eventSource.close();
|
|
185
|
+
if (event.code === 410) {
|
|
186
|
+
output_1.default.normal('Scrape session finished');
|
|
187
|
+
}
|
|
188
|
+
resolve({ ok: event.code === 410, error: event.code });
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
exports.default = commandScrape;
|
package/distTs/src/index.js
CHANGED
|
@@ -16,7 +16,14 @@ process.title = 'bdy';
|
|
|
16
16
|
for (const s of [process.stdout, process.stderr]) {
|
|
17
17
|
s._handle?.setBlocking?.(true);
|
|
18
18
|
}
|
|
19
|
+
// The cost of blocking mode: a reader that closes early (`bdy ... | head`)
|
|
20
|
+
// turns further writes into EPIPE errors, which surface here as uncaught
|
|
21
|
+
// exceptions. That is the reader saying "got enough", not a failure - end
|
|
22
|
+
// quietly like any well-behaved pipe writer.
|
|
23
|
+
const isEpipe = (err) => err?.code === 'EPIPE' || err?.cause?.code === 'EPIPE';
|
|
19
24
|
process.on('uncaughtException', (err) => {
|
|
25
|
+
if (isEpipe(err))
|
|
26
|
+
process.exit(0);
|
|
20
27
|
logger_1.default.fatal(err);
|
|
21
28
|
output_1.default.exitError(err);
|
|
22
29
|
});
|
package/distTs/src/logger.js
CHANGED
|
@@ -64,7 +64,13 @@ class Logger {
|
|
|
64
64
|
this.logPath = (0, path_1.resolve)(this.rootPath, 'cli.log');
|
|
65
65
|
this.log1Path = (0, path_1.resolve)(this.rootPath, 'cli.1.log');
|
|
66
66
|
try {
|
|
67
|
-
|
|
67
|
+
// 'a', not 'w': every start used to truncate the log, so an agent that died and was
|
|
68
|
+
// restarted by its service manager came back having erased the only record of why -
|
|
69
|
+
// and a crash loop leaves nothing at all. Append also survives an external truncation
|
|
70
|
+
// (rotation, someone clearing the file) without the zero-padding a tracked write offset
|
|
71
|
+
// produces, because O_APPEND puts every write at the current end of the file.
|
|
72
|
+
// checkLogSize() below still bounds it at 5 MB.
|
|
73
|
+
this.logStream = fs_1.default.openSync(this.logPath, 'a');
|
|
68
74
|
}
|
|
69
75
|
catch {
|
|
70
76
|
// A log we cannot open is not a reason to fail the command - a read-only home, a full
|
|
@@ -1191,7 +1191,9 @@ class OutputPipeline {
|
|
|
1191
1191
|
this.jsonLog(logs[actionLogsStart]);
|
|
1192
1192
|
}
|
|
1193
1193
|
else {
|
|
1194
|
-
|
|
1194
|
+
// log lines are payload inside markup - escape so % and ^
|
|
1195
|
+
// sequences in build output render verbatim
|
|
1196
|
+
output_1.default.normal(output_1.default.getTermKitDimColor(` ${output_1.default.escapeTermKit(logs[actionLogsStart])}`));
|
|
1195
1197
|
}
|
|
1196
1198
|
}
|
|
1197
1199
|
if (action &&
|
|
@@ -1221,9 +1223,9 @@ class OutputPipeline {
|
|
|
1221
1223
|
let line = '';
|
|
1222
1224
|
if (i > 0)
|
|
1223
1225
|
line += ' ';
|
|
1224
|
-
line += output_1.default.getTermKitMutedColor(v.key);
|
|
1226
|
+
line += output_1.default.getTermKitMutedColor(output_1.default.escapeTermKit(v.key));
|
|
1225
1227
|
line += output_1.default.getTermKitDimColor(` = "`);
|
|
1226
|
-
line += output_1.default.getTermKitBlueColor(v.value);
|
|
1228
|
+
line += output_1.default.getTermKitBlueColor(output_1.default.escapeTermKit(v.value));
|
|
1227
1229
|
line += output_1.default.getTermKitDimColor('"');
|
|
1228
1230
|
output_1.default.normal(line);
|
|
1229
1231
|
});
|
package/distTs/src/output.js
CHANGED
|
@@ -136,9 +136,18 @@ class Output {
|
|
|
136
136
|
}
|
|
137
137
|
// Payload output (command results, logs, JSON): bypasses terminal-kit,
|
|
138
138
|
// whose printf-style formatting and ^-markup corrupt data containing
|
|
139
|
-
// % or ^ sequences.
|
|
140
|
-
|
|
141
|
-
|
|
139
|
+
// % or ^ sequences. newLine=false emits a mid-stream chunk, keeping
|
|
140
|
+
// chunk boundaries out of the payload.
|
|
141
|
+
static data(txt, newLine = true) {
|
|
142
|
+
process.stdout.write(newLine ? `${txt}\n` : txt);
|
|
143
|
+
}
|
|
144
|
+
// For payload embedded in terminal-kit markup (e.g. a dimmed log line):
|
|
145
|
+
// neutralizes the ^-markup and %-formatting the payload would otherwise
|
|
146
|
+
// trigger, so it renders verbatim while the surrounding markup still
|
|
147
|
+
// works. Only for strings printed via terminal() - ScreenBuffer.put does
|
|
148
|
+
// not printf-format, so there %% would render double.
|
|
149
|
+
static escapeTermKit(txt) {
|
|
150
|
+
return txt.replace(/[%^]/g, '$&$&');
|
|
142
151
|
}
|
|
143
152
|
static writeStderr(txt) {
|
|
144
153
|
process.stderr.write(`${txt}\n`);
|