bdy 1.25.6-dev → 1.25.8-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.
Files changed (46) hide show
  1. package/distTs/package.json +1 -1
  2. package/distTs/src/cliCommand.js +43 -0
  3. package/distTs/src/cliIndex.js +10 -0
  4. package/distTs/src/command/agent/uninstall.js +1 -1
  5. package/distTs/src/command/artifact/delete.js +1 -2
  6. package/distTs/src/command/artifact/version/delete.js +1 -2
  7. package/distTs/src/command/context/add.js +38 -0
  8. package/distTs/src/command/context/list.js +33 -0
  9. package/distTs/src/command/context/remove.js +35 -0
  10. package/distTs/src/command/context/show.js +35 -0
  11. package/distTs/src/command/context/use.js +25 -0
  12. package/distTs/src/command/context.js +22 -0
  13. package/distTs/src/command/distro/delete.js +1 -2
  14. package/distTs/src/command/distro/route/delete.js +1 -2
  15. package/distTs/src/command/environment/delete.js +1 -2
  16. package/distTs/src/command/login.js +1 -0
  17. package/distTs/src/command/logout.js +1 -0
  18. package/distTs/src/command/pipeline/delete.js +1 -2
  19. package/distTs/src/command/pipeline/run/approve.js +3 -2
  20. package/distTs/src/command/pipeline/run/cancel.js +3 -2
  21. package/distTs/src/command/pipeline/run/find.js +87 -0
  22. package/distTs/src/command/pipeline/run/findFilters.js +68 -0
  23. package/distTs/src/command/pipeline/run/retry.js +5 -2
  24. package/distTs/src/command/pipeline/run/start.js +3 -2
  25. package/distTs/src/command/pipeline/run/status.js +3 -2
  26. package/distTs/src/command/pipeline/run.js +3 -0
  27. package/distTs/src/command/project/link.js +1 -0
  28. package/distTs/src/command/register.js +1 -0
  29. package/distTs/src/command/sandbox/destroy.js +1 -2
  30. package/distTs/src/command/sandbox/endpoint/delete.js +1 -2
  31. package/distTs/src/command/sandbox/exec/kill.js +1 -2
  32. package/distTs/src/command/sandbox/snapshot/delete.js +1 -2
  33. package/distTs/src/command/target/delete.js +1 -1
  34. package/distTs/src/command/target/exec/kill.js +2 -3
  35. package/distTs/src/command/whoami.js +7 -10
  36. package/distTs/src/command/workspace/get.js +2 -2
  37. package/distTs/src/command/workspace/set.js +1 -0
  38. package/distTs/src/input.js +240 -26
  39. package/distTs/src/output/pipeline.js +84 -13
  40. package/distTs/src/output.js +24 -5
  41. package/distTs/src/project/context.js +97 -0
  42. package/distTs/src/texts.js +275 -29
  43. package/distTs/src/tunnel/cfg.js +43 -4
  44. package/distTs/src/types/context.js +2 -0
  45. package/distTs/src/types/pipelineRunFind.js +2 -0
  46. package/package.json +1 -1
@@ -65,15 +65,34 @@ class OutputPipeline {
65
65
  }
66
66
  return await this._runLogsNonInteractive(client, workspace, project, identifier, pipelineId, runId, runActionId, noWait, isJson, semanticExitCode);
67
67
  }
68
- static async runStatus(client, workspace, project, identifier, pipelineId, runId, noWait, noFollow, isJson, semanticExitCode = false) {
68
+ static findRuns(result) {
69
+ if (result.executions.length) {
70
+ const rows = result.executions.map((run) => [
71
+ String(run.id),
72
+ this.runStatusText((run.status || '-')),
73
+ run.start_date || '-',
74
+ [run.branch?.name, run.tag?.name, run.pull_request?.name]
75
+ .filter(Boolean)
76
+ .join(', ') || '-',
77
+ run.to_revision?.revision?.slice(0, 7) || '-',
78
+ run.html_url || '-',
79
+ ]);
80
+ output_1.default.table([[...texts_1.TXT_PIPELINE_RUN_FIND_COLUMNS], ...rows]);
81
+ }
82
+ output_1.default.normal((0, texts_1.TXT_PIPELINE_RUN_FIND_SUMMARY)(result.executions.length, result.scanned));
83
+ if (result.has_more)
84
+ output_1.default.normal(texts_1.TXT_PIPELINE_RUN_FIND_MORE);
85
+ }
86
+ static async runStatus(client, workspace, project, identifier, pipelineId, runId, noWait, noFollow, isJson, semanticExitCode = false, failedLogs = false) {
69
87
  if (output_1.default.isTTY() &&
70
88
  !noWait &&
71
89
  !noFollow &&
72
90
  !isJson &&
73
- !semanticExitCode) {
91
+ !semanticExitCode &&
92
+ !failedLogs) {
74
93
  return await this._runStatusInteractive(client, workspace, project, pipelineId, runId);
75
94
  }
76
- return await this._runStatusNonInteractive(client, workspace, project, identifier, pipelineId, runId, noWait, noFollow, isJson, semanticExitCode);
95
+ return await this._runStatusNonInteractive(client, workspace, project, identifier, pipelineId, runId, noWait, noFollow, isJson, semanticExitCode, failedLogs);
77
96
  }
78
97
  static _canCancel(status) {
79
98
  return ![
@@ -1327,7 +1346,12 @@ class OutputPipeline {
1327
1346
  }
1328
1347
  }, false, semanticExitCode);
1329
1348
  }
1330
- static async _runStatusNonInteractive(client, workspace, project, identifier, pipelineId, runId, noWait, noFollow, isJson, semanticExitCode = false) {
1349
+ static async _runStatusNonInteractive(client, workspace, project, identifier, pipelineId, runId, noWait, noFollow, isJson, semanticExitCode = false, failedLogs = false) {
1350
+ const finishRun = async (run) => {
1351
+ if (failedLogs)
1352
+ await this.failedActionLogs(client, workspace, project, pipelineId, run, isJson);
1353
+ this._exitByStatus(isJson, run.status, semanticExitCode);
1354
+ };
1331
1355
  let run = null;
1332
1356
  let totalFetchesCount = 0;
1333
1357
  const tabs = 40;
@@ -1395,7 +1419,7 @@ class OutputPipeline {
1395
1419
  });
1396
1420
  run = r;
1397
1421
  };
1398
- run = await this._runStatusLoop(client, workspace, project, pipelineId, runId, (run) => {
1422
+ run = await this._runStatusLoop(client, workspace, project, pipelineId, runId, async (run) => {
1399
1423
  totalFetchesCount += 1;
1400
1424
  if (!noFollow || noWait) {
1401
1425
  if (isJson)
@@ -1418,9 +1442,8 @@ class OutputPipeline {
1418
1442
  throw error;
1419
1443
  }
1420
1444
  }
1421
- if (noWait) {
1422
- this._exitByStatus(isJson, run.status, semanticExitCode);
1423
- }
1445
+ if (noWait)
1446
+ await finishRun(run);
1424
1447
  }, false, semanticExitCode);
1425
1448
  if (isJson) {
1426
1449
  this.jsonRun(identifier, run);
@@ -1465,7 +1488,55 @@ class OutputPipeline {
1465
1488
  output_1.default.normal(this._dimLine());
1466
1489
  output_1.default.normal(this._runActionsLogsHelp(identifier, runId));
1467
1490
  }
1468
- this._exitByStatus(isJson, run.status, semanticExitCode);
1491
+ await finishRun(run);
1492
+ }
1493
+ static async failedActionLogs(client, workspace, project, pipelineId, run, isJson) {
1494
+ for (const action of run.actions) {
1495
+ if (action.status !== pipeline_1.PIPELINE_RUN_STATUS.FAILED)
1496
+ continue;
1497
+ const identity = {
1498
+ action_run_id: action.runId,
1499
+ action_name: action.name,
1500
+ };
1501
+ try {
1502
+ let page = await client.getPipelineRunActionLogs(workspace, project, pipelineId, run.runId, action.runId, 0, texts_1.FAILED_LOGS_LIMIT);
1503
+ if (typeof page.total_element_count !== 'number' ||
1504
+ !Number.isInteger(page.total_element_count) ||
1505
+ page.total_element_count < 0) {
1506
+ throw new cliError_1.default('command_failed', texts_1.ERR_PIPELINE_FAILED_LOGS_COUNT);
1507
+ }
1508
+ // The count lets us jump to the tail without downloading the full log.
1509
+ if (page.total_element_count > texts_1.FAILED_LOGS_LIMIT) {
1510
+ page = await client.getPipelineRunActionLogs(workspace, project, pipelineId, run.runId, action.runId, page.total_element_count - texts_1.FAILED_LOGS_LIMIT, texts_1.FAILED_LOGS_LIMIT);
1511
+ }
1512
+ if (!isJson)
1513
+ output_1.default.data((0, texts_1.TXT_PIPELINE_FAILED_LOGS_HEADER)(action.name, action.runId));
1514
+ const logs = page.logs.slice(-texts_1.FAILED_LOGS_LIMIT);
1515
+ if (!logs.length) {
1516
+ if (isJson)
1517
+ output_1.default.json({
1518
+ type: 'info',
1519
+ ...identity,
1520
+ message: texts_1.TXT_PIPELINE_FAILED_LOGS_EMPTY,
1521
+ });
1522
+ else
1523
+ output_1.default.data(texts_1.TXT_PIPELINE_FAILED_LOGS_EMPTY);
1524
+ }
1525
+ for (const log of logs) {
1526
+ if (isJson)
1527
+ output_1.default.json({ type: 'log', ...identity, log });
1528
+ else
1529
+ output_1.default.data(log);
1530
+ }
1531
+ }
1532
+ catch (error) {
1533
+ const details = cliError_1.default.details(error);
1534
+ if (isJson)
1535
+ output_1.default.json({ type: 'warning', ...identity, error: details });
1536
+ else
1537
+ output_1.default.noteWarning((0, texts_1.TXT_PIPELINE_FAILED_LOGS_ERROR)(action.name, action.runId, details.message));
1538
+ }
1539
+ }
1469
1540
  }
1470
1541
  static async _actionLogsLoop(client, workspace, project, pipelineId, runId, actionExecutionId, onChange) {
1471
1542
  let logs = [];
@@ -1664,11 +1735,11 @@ class OutputPipeline {
1664
1735
  context = {};
1665
1736
  context['Environment'] = run.environment.identifier;
1666
1737
  }
1667
- if (run.package && run.package_version) {
1738
+ if (run.artifact && run.artifact_version) {
1668
1739
  if (!context)
1669
1740
  context = {};
1670
- context['Package'] =
1671
- `${run.package.identifier}:${run.package_version.version}`;
1741
+ context['Artifact'] =
1742
+ `${run.artifact.identifier}:${run.artifact_version.version}`;
1672
1743
  }
1673
1744
  if (run.loop_details) {
1674
1745
  if (!context)
@@ -1768,7 +1839,7 @@ class OutputPipeline {
1768
1839
  context,
1769
1840
  actions,
1770
1841
  };
1771
- onChange(r, forceFetchRun);
1842
+ await onChange(r, forceFetchRun);
1772
1843
  return r;
1773
1844
  };
1774
1845
  lastRun = await forceFetchRun();
@@ -187,6 +187,15 @@ class Output {
187
187
  static data(txt, newLine = true) {
188
188
  process.stdout.write(newLine ? `${txt}\n` : txt);
189
189
  }
190
+ static examples(command, examples, format) {
191
+ if (format === 'json')
192
+ return this.json({ command, examples });
193
+ if (format === 'jsonl')
194
+ return this.json({ type: 'examples', command, examples });
195
+ if (!examples.length)
196
+ return this.data(`No examples for ${command}.`);
197
+ this.data(`EXAMPLES:\n${examples.map((example) => ` ### ${example.description}\n ${example.command}`).join('\n')}`);
198
+ }
190
199
  // For payload embedded in terminal-kit markup (e.g. a dimmed log line):
191
200
  // neutralizes the ^-markup and %-formatting the payload would otherwise
192
201
  // trigger, so it renders verbatim while the surrounding markup still
@@ -375,7 +384,15 @@ class Output {
375
384
  }
376
385
  });
377
386
  }
378
- static async confirm(message = 'Are you sure?') {
387
+ static async confirmDestructive(force, message, quiet = false) {
388
+ if (force)
389
+ return true;
390
+ if (!this.isStdInTTY()) {
391
+ throw new cliError_1.default('usage_error', texts_1.ERR_CONFIRM_REQUIRED, texts_1.TXT_CONFIRM_FORCE_HINT);
392
+ }
393
+ return await this.confirm(message, quiet);
394
+ }
395
+ static async confirm(message = 'Are you sure?', quiet = false) {
379
396
  if (this.isStdInTTY()) {
380
397
  const prompts = require('@inquirer/prompts');
381
398
  try {
@@ -383,15 +400,17 @@ class Output {
383
400
  message,
384
401
  theme: this.getInquirerTheme(),
385
402
  default: false,
386
- });
403
+ }, quiet ? { output: process.stderr } : undefined);
387
404
  }
388
405
  catch {
389
406
  this.exitError(texts_1.ERR_CANCELLED);
390
407
  }
391
408
  }
392
- this.cyan('? ', false);
393
- this.label(message, false);
394
- this.cyan(` No`);
409
+ if (!quiet) {
410
+ this.cyan('? ', false);
411
+ this.label(message, false);
412
+ this.cyan(` No`);
413
+ }
395
414
  return false;
396
415
  }
397
416
  static async inputString(message, def, required, signal) {
@@ -0,0 +1,97 @@
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
+ exports.parseBuddyRemote = parseBuddyRemote;
7
+ exports.resolveContextScope = resolveContextScope;
8
+ const node_child_process_1 = require("node:child_process");
9
+ const node_fs_1 = require("node:fs");
10
+ const node_path_1 = require("node:path");
11
+ const cliError_1 = __importDefault(require("../cliError"));
12
+ const utils_1 = require("../utils");
13
+ const texts_1 = require("../texts");
14
+ function parseBuddyRemote(remote) {
15
+ try {
16
+ // Accept both standard URLs and Git's scp-like SSH syntax.
17
+ const value = remote.includes('://')
18
+ ? remote
19
+ : remote.replace(/^(?:[^@/:]+@)?([^/:]+):(.+)$/, 'ssh://$1/$2');
20
+ const url = new URL(value);
21
+ if (!['https:', 'ssh:'].includes(url.protocol) ||
22
+ !Object.values(utils_1.REST_API_ENDPOINT).some((endpoint) => (0, utils_1.getAppHostByApiBaseUrl)(new URL(`https://${endpoint}`)) ===
23
+ url.hostname) ||
24
+ url.search ||
25
+ url.hash)
26
+ throw new Error();
27
+ const parts = url.pathname
28
+ .replace(/\/$/, '')
29
+ .replace(/\.git$/, '')
30
+ .split('/')
31
+ .slice(1);
32
+ if (parts.length !== 2 ||
33
+ parts.some((part) => !/^[a-zA-Z0-9_-]+$/.test(part)))
34
+ throw new Error();
35
+ return { workspace: parts[0], project: parts[1] };
36
+ }
37
+ catch {
38
+ // Do not include the raw URL: remotes can contain credentials.
39
+ throw new cliError_1.default('git_remote_unsupported', texts_1.ERR_CONTEXT_GIT_URL, texts_1.TXT_CONTEXT_GIT_HINT);
40
+ }
41
+ }
42
+ function resolveContextScope(context, cwd) {
43
+ if (context.source === 'explicit')
44
+ return {
45
+ workspace: context.workspace || null,
46
+ project: context.project || null,
47
+ remote: null,
48
+ };
49
+ if (context.source === 'link') {
50
+ let data;
51
+ try {
52
+ data = JSON.parse((0, node_fs_1.readFileSync)((0, node_path_1.join)(cwd, '.bdy/project.json'), 'utf8'));
53
+ }
54
+ catch (err) {
55
+ const missing = err.code === 'ENOENT';
56
+ throw new cliError_1.default(missing ? 'project_link_not_found' : 'project_link_invalid', missing ? texts_1.ERR_CONTEXT_LINK_MISSING : texts_1.ERR_CONTEXT_LINK_INVALID, texts_1.TXT_CONTEXT_LINK_HINT);
57
+ }
58
+ if (typeof data?.workspace !== 'string' ||
59
+ !data.workspace.trim() ||
60
+ typeof data?.project !== 'string' ||
61
+ !data.project.trim())
62
+ throw new cliError_1.default('project_link_invalid', texts_1.ERR_CONTEXT_LINK_INVALID, texts_1.TXT_CONTEXT_LINK_HINT);
63
+ return {
64
+ workspace: data.workspace,
65
+ project: data.project,
66
+ remote: null,
67
+ };
68
+ }
69
+ const remote = context.remote || 'origin';
70
+ let url;
71
+ try {
72
+ url = (0, node_child_process_1.execFileSync)('git', ['remote', 'get-url', '--', remote], {
73
+ cwd,
74
+ encoding: 'utf8',
75
+ timeout: 3000,
76
+ stdio: ['ignore', 'pipe', 'pipe'],
77
+ // Git diagnostics are classified below; keep their language deterministic.
78
+ env: { ...process.env, LC_ALL: 'C', LANG: 'C' },
79
+ }).trim();
80
+ }
81
+ catch (err) {
82
+ const failure = err;
83
+ if (failure.code === 'ENOENT')
84
+ throw new cliError_1.default('git_not_found', texts_1.ERR_CONTEXT_GIT_MISSING, texts_1.TXT_CONTEXT_GIT_HINT);
85
+ if (failure.code === 'ETIMEDOUT')
86
+ throw new cliError_1.default('timeout', texts_1.ERR_CONTEXT_GIT_TIMEOUT, texts_1.TXT_CONTEXT_GIT_HINT);
87
+ const stderr = String(failure.stderr || '');
88
+ if (failure.status === 128 && /fatal: not a git repository/i.test(stderr))
89
+ throw new cliError_1.default('git_repository_not_found', texts_1.ERR_CONTEXT_GIT_REPO, texts_1.TXT_CONTEXT_GIT_HINT);
90
+ if (failure.status === 2 && /error: No such remote /i.test(stderr))
91
+ throw new cliError_1.default('git_remote_not_found', (0, texts_1.ERR_CONTEXT_GIT_REMOTE)(remote), texts_1.TXT_CONTEXT_GIT_HINT);
92
+ // Neither permission errors nor an interrupted Git process imply a missing repo.
93
+ // Never forward raw diagnostics, which may contain remote credentials.
94
+ throw new cliError_1.default('command_failed', texts_1.ERR_CONTEXT_GIT_FAILED, texts_1.TXT_CONTEXT_GIT_HINT);
95
+ }
96
+ return { ...parseBuddyRemote(url), remote };
97
+ }