bdy 1.25.3-dev → 1.25.5-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 (47) hide show
  1. package/distTs/package.json +2 -1
  2. package/distTs/src/agent/socket/tunnel.js +2 -0
  3. package/distTs/src/api/client.js +22 -55
  4. package/distTs/src/cliCommand.js +52 -0
  5. package/distTs/src/cliError.js +34 -0
  6. package/distTs/src/cliIndex.js +3 -1
  7. package/distTs/src/command/agent/proxy/status.js +9 -2
  8. package/distTs/src/command/agent/status.js +22 -6
  9. package/distTs/src/command/agent/tags/get.js +9 -2
  10. package/distTs/src/command/agent/target/status.js +9 -2
  11. package/distTs/src/command/agent/tunnel/list.js +6 -1
  12. package/distTs/src/command/agent/tunnel/status.js +13 -1
  13. package/distTs/src/command/agent/version.js +7 -2
  14. package/distTs/src/command/api/info.js +50 -2
  15. package/distTs/src/command/api/list.js +18 -2
  16. package/distTs/src/command/artifact/create.js +5 -0
  17. package/distTs/src/command/artifact/publish.js +20 -22
  18. package/distTs/src/command/artifact/version/create.js +5 -0
  19. package/distTs/src/command/config/get/tunnel.js +8 -3
  20. package/distTs/src/command/config/get/tunnels.js +8 -3
  21. package/distTs/src/command/discover.js +9 -11
  22. package/distTs/src/command/distro/create.js +5 -0
  23. package/distTs/src/command/distro/route/create.js +5 -0
  24. package/distTs/src/command/distro/route/update.js +5 -0
  25. package/distTs/src/command/distro/update.js +5 -0
  26. package/distTs/src/command/domain/search.js +31 -10
  27. package/distTs/src/command/environment/create.js +5 -0
  28. package/distTs/src/command/environment/update.js +5 -0
  29. package/distTs/src/command/sandbox/app/add.js +7 -1
  30. package/distTs/src/command/sandbox/app/logs.js +9 -4
  31. package/distTs/src/command/sandbox/create.js +13 -5
  32. package/distTs/src/command/sandbox/endpoint/add.js +5 -0
  33. package/distTs/src/command/sandbox/exec/command.js +15 -30
  34. package/distTs/src/command/sandbox/exec/logs.js +4 -29
  35. package/distTs/src/command/sandbox/finishExec.js +34 -0
  36. package/distTs/src/command/sandbox/logs.js +14 -1
  37. package/distTs/src/command/sandbox/snapshot/create.js +9 -1
  38. package/distTs/src/command/target/create.js +5 -0
  39. package/distTs/src/command/target/exec/logs.js +15 -2
  40. package/distTs/src/command/target/update.js +5 -0
  41. package/distTs/src/command/yaml/validate.js +111 -52
  42. package/distTs/src/index.js +6 -1
  43. package/distTs/src/input.js +12 -11
  44. package/distTs/src/openapi.js +8 -1
  45. package/distTs/src/output.js +55 -13
  46. package/distTs/src/texts.js +272 -59
  47. package/package.json +2 -1
@@ -7,43 +7,18 @@ const utils_1 = require("../../../utils");
7
7
  const texts_1 = require("../../../texts");
8
8
  const output_1 = __importDefault(require("../../../output"));
9
9
  const input_1 = __importDefault(require("../../../input"));
10
- const commandSandboxExecLogs = (0, utils_1.newCommand)('logs', texts_1.DESC_COMMAND_SANDBOX_EXEC_LOGS, { examples: texts_1.EXAMPLE_SANDBOX_EXEC_LOGS });
10
+ const finishExec_1 = require("../finishExec");
11
+ const commandSandboxExecLogs = (0, utils_1.newCommand)('logs', texts_1.DESC_COMMAND_SANDBOX_EXEC_LOGS, { helpText: texts_1.HELP_SANDBOX_EXEC_JSONL, examples: texts_1.EXAMPLE_SANDBOX_EXEC_LOGS });
11
12
  (0, utils_1.sandboxScopeOptions)(commandSandboxExecLogs);
12
13
  commandSandboxExecLogs.option('--wait', texts_1.OPTION_SANDBOX_WAIT);
13
14
  commandSandboxExecLogs.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
14
15
  commandSandboxExecLogs.argument('<command-id>', texts_1.OPTION_SANDBOX_COMMAND_ID);
16
+ commandSandboxExecLogs.option('--format <text|jsonl>', texts_1.OPTION_FORMAT_JSONL);
15
17
  commandSandboxExecLogs.action(async (identifier, commandId, options) => {
16
18
  output_1.default.handleSignals();
17
19
  const workspace = input_1.default.restApiWorkspace(options.workspace);
18
20
  const client = input_1.default.restApiTokenClient();
19
21
  const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
20
- const body = await client.streamSandboxCommandLogs(workspace, sandboxId, commandId, !!options.wait);
21
- body.on('data', (data) => {
22
- if (data) {
23
- try {
24
- const str = data.toString('utf8').split('\n');
25
- str.forEach((s) => {
26
- const json = JSON.parse(s);
27
- if (json.data)
28
- output_1.default.data(json.data);
29
- });
30
- }
31
- catch {
32
- // do nothing
33
- }
34
- }
35
- });
36
- body.on('close', async () => {
37
- const cmdExit = await client.getSandboxCommand(workspace, sandboxId, commandId);
38
- if (cmdExit.status === utils_1.SANDBOX_EXEC_STATUS.SUCCESSFUL) {
39
- output_1.default.exitSuccess((0, texts_1.TXT_SANDBOX_EXEC_SUCCESS)(identifier, commandId));
40
- }
41
- else if (cmdExit.status === utils_1.SANDBOX_EXEC_STATUS.INPROGRESS) {
42
- output_1.default.exitNormal((0, texts_1.TXT_SANDBOX_EXEC_INPROGRESS)(identifier, commandId));
43
- }
44
- else {
45
- output_1.default.exitError((0, texts_1.TXT_SANDBOX_EXEC_FAILED)(identifier, commandId));
46
- }
47
- });
22
+ await (0, finishExec_1.finishExec)(client, workspace, sandboxId, identifier, commandId, !!options.wait, options.format === 'jsonl');
48
23
  });
49
24
  exports.default = commandSandboxExecLogs;
@@ -0,0 +1,34 @@
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.finishExec = finishExec;
7
+ const output_1 = __importDefault(require("../../output"));
8
+ const utils_1 = require("../../utils");
9
+ const texts_1 = require("../../texts");
10
+ async function finishExec(client, workspace, sandboxId, identifier, commandId, follow, jsonl) {
11
+ const body = await client.streamSandboxCommandLogs(workspace, sandboxId, commandId, follow);
12
+ await output_1.default.streamCommandLogs(body, jsonl);
13
+ const cmdExit = await client.getSandboxCommand(workspace, sandboxId, commandId);
14
+ if (jsonl) {
15
+ output_1.default.json({
16
+ type: 'exit',
17
+ status: cmdExit.status,
18
+ exit_code: cmdExit.exit_code ?? null,
19
+ });
20
+ if ([utils_1.SANDBOX_EXEC_STATUS.SUCCESSFUL, utils_1.SANDBOX_EXEC_STATUS.INPROGRESS].includes(cmdExit.status)) {
21
+ output_1.default.exitNormal();
22
+ }
23
+ output_1.default.exitCode(1);
24
+ }
25
+ if (cmdExit.status === utils_1.SANDBOX_EXEC_STATUS.SUCCESSFUL) {
26
+ output_1.default.exitSuccess((0, texts_1.TXT_SANDBOX_EXEC_SUCCESS)(identifier, commandId));
27
+ }
28
+ else if (cmdExit.status === utils_1.SANDBOX_EXEC_STATUS.INPROGRESS) {
29
+ output_1.default.exitNormal((0, texts_1.TXT_SANDBOX_EXEC_INPROGRESS)(identifier, commandId));
30
+ }
31
+ else {
32
+ output_1.default.exitError((0, texts_1.TXT_SANDBOX_EXEC_FAILED)(identifier, commandId));
33
+ }
34
+ }
@@ -7,15 +7,28 @@ const utils_1 = require("../../utils");
7
7
  const texts_1 = require("../../texts");
8
8
  const input_1 = __importDefault(require("../../input"));
9
9
  const output_1 = __importDefault(require("../../output"));
10
- const commandSandboxLogs = (0, utils_1.newCommand)('logs', texts_1.DESC_COMMAND_SANDBOX_LOGS);
10
+ const commandSandboxLogs = (0, utils_1.newCommand)('logs', texts_1.DESC_COMMAND_SANDBOX_LOGS, { examples: texts_1.EXAMPLE_SANDBOX_LOGS });
11
11
  commandSandboxLogs.hideVersionUpdate = true;
12
12
  (0, utils_1.sandboxScopeOptions)(commandSandboxLogs);
13
13
  commandSandboxLogs.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
14
+ commandSandboxLogs.option('--format <text|jsonl>', texts_1.OPTION_FORMAT_JSONL);
14
15
  commandSandboxLogs.action(async (identifier, options) => {
15
16
  const workspace = input_1.default.restApiWorkspace(options.workspace);
16
17
  const client = input_1.default.restApiTokenClient();
17
18
  const sandboxId = await input_1.default.sandboxId(client, workspace, options, identifier);
18
19
  const { boot_logs } = await client.getSandbox(workspace, sandboxId);
20
+ if (options.format === 'jsonl') {
21
+ for (const log of boot_logs || []) {
22
+ output_1.default.json(typeof log === 'string'
23
+ ? { type: 'log', data: log }
24
+ : {
25
+ type: 'log',
26
+ data: log.data ?? JSON.stringify(log),
27
+ ...(log.source ? { source: log.source } : {}),
28
+ });
29
+ }
30
+ output_1.default.exitNormal();
31
+ }
19
32
  output_1.default.exitNormal((boot_logs || []).join('\n'));
20
33
  });
21
34
  exports.default = commandSandboxLogs;
@@ -13,6 +13,7 @@ commandSandboxSnapshotCreate.alias('add');
13
13
  commandSandboxSnapshotCreate.option('-n, --name <name>', texts_1.OPTION_SANDBOX_SNAPSHOT_NAME);
14
14
  commandSandboxSnapshotCreate.argument('<identifier>', texts_1.OPTION_SANDBOX_IDENTIFIER);
15
15
  commandSandboxSnapshotCreate.option('--wait [seconds]', texts_1.OPTION_SANDBOX_WAIT);
16
+ commandSandboxSnapshotCreate.option('--format <text|json>', texts_1.OPTION_FORMAT);
16
17
  commandSandboxSnapshotCreate.action(async (identifier, options) => {
17
18
  const humanId = require('human-id').default;
18
19
  const workspace = input_1.default.restApiWorkspace(options.workspace);
@@ -26,7 +27,8 @@ commandSandboxSnapshotCreate.action(async (identifier, options) => {
26
27
  const snapshot = await client.createSandboxSnapshot(workspace, sandboxId, body);
27
28
  const snapshotId = snapshot.id;
28
29
  if (options.wait) {
29
- output_1.default.normal(texts_1.TXT_SANDBOX_SNAPSHOT_WAITING);
30
+ if (options.format !== 'json')
31
+ output_1.default.normal(texts_1.TXT_SANDBOX_SNAPSHOT_WAITING);
30
32
  const timeout = parseInt(options.wait, 10) || 300;
31
33
  try {
32
34
  const status = await client.sandboxWaitForSnapshot(workspace, sandboxId, snapshotId, timeout);
@@ -38,6 +40,12 @@ commandSandboxSnapshotCreate.action(async (identifier, options) => {
38
40
  output_1.default.exitError(texts_1.ERR_SANDBOX_SNAPSHOT_TIMEOUT);
39
41
  }
40
42
  }
43
+ if (options.format === 'json') {
44
+ output_1.default.json(options.wait
45
+ ? await client.getSandboxSnapshot(workspace, sandboxId, snapshotId)
46
+ : snapshot);
47
+ output_1.default.exitNormal();
48
+ }
41
49
  output_1.default.exitSuccess((0, texts_1.TXT_SANDBOX_SNAPSHOT_CREATED)(snapshotName));
42
50
  });
43
51
  exports.default = commandSandboxSnapshotCreate;
@@ -18,6 +18,7 @@ commandTargetCreate.option('--pipeline <identifier>', texts_1.OPTION_TARGET_PIPE
18
18
  commandTargetCreate.option('--environment <identifier>', texts_1.OPTION_TARGET_ENVIRONMENT);
19
19
  commandTargetCreate.option('--scope <scope>', texts_1.OPT_COMMAND_TARGET_SCOPE);
20
20
  commandTargetCreate.option('--yaml <content|@path>', texts_1.OPTION_TARGET_YAML);
21
+ commandTargetCreate.option('--format <text|json>', texts_1.OPTION_FORMAT);
21
22
  commandTargetCreate.action(async (options) => {
22
23
  const yaml = input_1.default.restApiYaml(options.yaml);
23
24
  const workspace = input_1.default.restApiWorkspace(options.workspace);
@@ -29,6 +30,10 @@ commandTargetCreate.action(async (options) => {
29
30
  yaml: Buffer.from(yaml, 'utf8').toString('base64'),
30
31
  };
31
32
  const result = await client.createTargetByYaml(workspace, scopeParams, body);
33
+ if (options.format === 'json') {
34
+ output_1.default.json(result);
35
+ output_1.default.exitNormal();
36
+ }
32
37
  output_1.default.exitSuccess((0, texts_1.TXT_TARGET_CREATED)(result.identifier, result.html_url));
33
38
  });
34
39
  exports.default = commandTargetCreate;
@@ -8,19 +8,32 @@ const texts_1 = require("../../../texts");
8
8
  const input_1 = __importDefault(require("../../../input"));
9
9
  const output_1 = __importDefault(require("../../../output"));
10
10
  const scope_1 = require("../scope");
11
- const commandTargetExecLogs = (0, utils_1.newCommand)('logs', texts_1.DESC_COMMAND_TARGET_EXEC_LOGS, { examples: texts_1.EXAMPLE_TARGET_EXEC_LOGS });
11
+ const commandTargetExecLogs = (0, utils_1.newCommand)('logs', texts_1.DESC_COMMAND_TARGET_EXEC_LOGS, { helpText: texts_1.HELP_TARGET_EXEC_LOGS_JSONL, examples: texts_1.EXAMPLE_TARGET_EXEC_LOGS });
12
12
  commandTargetExecLogs.option('-w, --workspace <domain>', texts_1.OPTION_REST_API_WORKSPACE);
13
13
  commandTargetExecLogs.option('--offset <n>', texts_1.OPTION_TARGET_EXEC_LOG_OFFSET);
14
14
  commandTargetExecLogs.option('--limit <n>', texts_1.OPTION_TARGET_EXEC_LOG_LIMIT);
15
15
  commandTargetExecLogs.argument('<exec-id>', texts_1.OPTION_TARGET_EXEC_ID);
16
+ commandTargetExecLogs.option('--format <text|jsonl>', texts_1.OPTION_FORMAT_JSONL);
16
17
  commandTargetExecLogs.action(async (execId, options) => {
17
18
  const workspace = input_1.default.restApiWorkspace(options.workspace);
18
19
  const client = input_1.default.restApiTokenClient();
19
20
  const offset = options.offset ? parseInt(options.offset, 10) : 0;
20
21
  const limit = options.limit ? parseInt(options.limit, 10) : undefined;
21
- const { truncated } = await (0, scope_1.forwardExecLog)(client, workspace, execId, offset, undefined, limit);
22
+ const { truncated } = await (0, scope_1.forwardExecLog)(client, workspace, execId, offset, options.format === 'jsonl'
23
+ ? (lines) => lines.forEach((data) => output_1.default.json({ type: 'log', data }))
24
+ : undefined, limit);
22
25
  if (truncated)
23
26
  output_1.default.noteWarning(texts_1.WARN_TARGET_EXEC_TRUNCATED);
27
+ if (options.format === 'jsonl') {
28
+ const exec = await client.getTargetExec(workspace, execId);
29
+ output_1.default.json({
30
+ type: 'exit',
31
+ status: exec.status,
32
+ exit_code: exec.exit_code ?? null,
33
+ truncated,
34
+ });
35
+ output_1.default.exitNormal();
36
+ }
24
37
  output_1.default.exitNormal();
25
38
  });
26
39
  exports.default = commandTargetExecLogs;
@@ -18,6 +18,7 @@ commandTargetUpdate.option('--pipeline <identifier>', texts_1.OPTION_TARGET_PIPE
18
18
  commandTargetUpdate.option('--environment <identifier>', texts_1.OPTION_TARGET_ENVIRONMENT);
19
19
  commandTargetUpdate.option('--yaml <content|@path>', texts_1.OPTION_TARGET_YAML);
20
20
  commandTargetUpdate.argument('<identifier>', texts_1.OPTION_TARGET_IDENTIFIER);
21
+ commandTargetUpdate.option('--format <text|json>', texts_1.OPTION_FORMAT);
21
22
  commandTargetUpdate.action(async (identifier, options) => {
22
23
  const yaml = input_1.default.restApiYaml(options.yaml);
23
24
  const workspace = input_1.default.restApiWorkspace(options.workspace);
@@ -28,6 +29,10 @@ commandTargetUpdate.action(async (identifier, options) => {
28
29
  yaml: Buffer.from(yaml, 'utf8').toString('base64'),
29
30
  };
30
31
  const result = await client.updateTargetByYaml(workspace, target_id, body);
32
+ if (options.format === 'json') {
33
+ output_1.default.json(result);
34
+ output_1.default.exitNormal();
35
+ }
31
36
  output_1.default.exitSuccess((0, texts_1.TXT_TARGET_UPDATED)(result.identifier, result.html_url));
32
37
  });
33
38
  exports.default = commandTargetUpdate;
@@ -22,9 +22,17 @@ function prettifyKey(key) {
22
22
  .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
23
23
  .join(' ');
24
24
  }
25
- function outputErrors(result, indent) {
25
+ function recordError(context, path, message) {
26
+ context.errors.push({
27
+ pipeline: context.pipeline,
28
+ path: path.replace(/\.(\d+)(?=\.|$)/g, '[$1]'),
29
+ message,
30
+ });
31
+ }
32
+ function outputErrors(result, indent, context, prefix = '') {
26
33
  for (const err of result.errors) {
27
- output_1.default.muted(`${indent}${err.path}: ${err.message}`);
34
+ recordError(context, [prefix, err.path].filter(Boolean).join('.'), err.message);
35
+ context.output.muted(`${indent}${err.path}: ${err.message}`);
28
36
  }
29
37
  }
30
38
  function valueType(value) {
@@ -34,9 +42,10 @@ function valueType(value) {
34
42
  return 'array';
35
43
  return typeof value;
36
44
  }
37
- function reportWrongContainer(header, expected, value) {
38
- output_1.default.error(header);
39
- output_1.default.muted(` ${(0, texts_1.ERR_YAML_MUST_BE)(expected, valueType(value))}`);
45
+ function reportWrongContainer(header, expected, value, context, path) {
46
+ recordError(context, path, (0, texts_1.ERR_YAML_MUST_BE)(expected, valueType(value)));
47
+ context.output.error(header);
48
+ context.output.muted(` ${(0, texts_1.ERR_YAML_MUST_BE)(expected, valueType(value))}`);
40
49
  return 1;
41
50
  }
42
51
  // Array whose items reference a def — directly, or via a single $ref branch in
@@ -117,85 +126,101 @@ function itemLabel(value, index, discProp) {
117
126
  name = `#${index + 1}`;
118
127
  return discProp && value && value[discProp] ? `${name} (${value[discProp]})` : name;
119
128
  }
120
- function validateScalarItem(item, section) {
129
+ function validateScalarItem(item, section, context, path) {
121
130
  // Null errors even where the section takes a string: coerceTypes cannot fix a root scalar.
122
131
  if (item === null || !section.scalar) {
123
- output_1.default.error(` ${String(item)}: ${(0, texts_1.ERR_YAML_MUST_BE)('object', valueType(item))}`);
132
+ recordError(context, path, (0, texts_1.ERR_YAML_MUST_BE)('object', valueType(item)));
133
+ context.output.error(` ${String(item)}: ${(0, texts_1.ERR_YAML_MUST_BE)('object', valueType(item))}`);
124
134
  return 1;
125
135
  }
126
136
  const result = (0, schemaUtils_1.validateAgainstSchema)({ ...section.scalar, $defs: (0, schemaUtils_1.loadRawSchema)().$defs }, item);
127
137
  if (result.valid) {
128
- output_1.default.green(` ${String(item)}`);
138
+ context.output.green(` ${String(item)}`);
129
139
  return 0;
130
140
  }
131
- output_1.default.error(` ${String(item)}`);
132
- outputErrors(result, ' ');
141
+ context.output.error(` ${String(item)}`);
142
+ outputErrors(result, ' ', context, path);
133
143
  return result.errors.length;
134
144
  }
135
- function validateItemSection(unions, items, section) {
145
+ function validateItemSection(unions, items, section, context) {
136
146
  const refName = section.defName;
137
147
  const discProp = unions.get(refName)?.property ?? null;
138
148
  let errors = 0;
139
149
  for (let i = 0; i < items.length; i++) {
140
150
  const item = items[i];
151
+ const path = `${section.key}[${i}]`;
141
152
  if (item === null || typeof item !== 'object' || Array.isArray(item)) {
142
- errors += validateScalarItem(item, section);
153
+ errors += validateScalarItem(item, section, context, path);
143
154
  continue;
144
155
  }
145
156
  const label = itemLabel(item, i, discProp);
146
157
  const resolved = resolveDef(unions, refName, item);
147
158
  if ('missingType' in resolved) {
148
- output_1.default.error(` ${label}: ${(0, texts_1.ERR_YAML_ITEM_MISSING)(resolved.missingType)}`);
159
+ recordError(context, `${path}.${resolved.missingType}`, (0, texts_1.ERR_YAML_ITEM_MISSING)(resolved.missingType));
160
+ context.output.error(` ${label}: ${(0, texts_1.ERR_YAML_ITEM_MISSING)(resolved.missingType)}`);
149
161
  errors++;
150
162
  continue;
151
163
  }
152
164
  if ('unknownType' in resolved) {
153
- output_1.default.error(` ${label}: ${(0, texts_1.ERR_YAML_ITEM_UNKNOWN)(resolved.prop, resolved.unknownType)}`);
165
+ recordError(context, `${path}.${resolved.prop}`, (0, texts_1.ERR_YAML_ITEM_UNKNOWN)(resolved.prop, resolved.unknownType));
166
+ context.output.error(` ${label}: ${(0, texts_1.ERR_YAML_ITEM_UNKNOWN)(resolved.prop, resolved.unknownType)}`);
154
167
  errors++;
155
168
  continue;
156
169
  }
157
170
  const result = (0, schemaUtils_1.validateAgainstDef)(resolved.defName, item);
158
171
  if (result.valid) {
159
- output_1.default.green(` ${label}`);
172
+ context.output.green(` ${label}`);
160
173
  }
161
174
  else {
162
175
  errors += result.errors.length;
163
- output_1.default.error(` ${label}`);
164
- outputErrors(result, ' ');
176
+ context.output.error(` ${label}`);
177
+ outputErrors(result, ' ', context, path);
165
178
  }
166
179
  }
167
180
  return errors;
168
181
  }
169
- function validateObjectSection(unions, label, value, refName) {
182
+ function validateObjectSection(unions, label, value, refName, context, path) {
170
183
  const resolved = resolveDef(unions, refName, value);
171
184
  if ('missingType' in resolved) {
172
- output_1.default.error(label);
173
- output_1.default.muted(` ${(0, texts_1.ERR_YAML_ITEM_MISSING)(resolved.missingType)}`);
185
+ context.output.error(label);
186
+ recordError(context, `${path}.${resolved.missingType}`, (0, texts_1.ERR_YAML_ITEM_MISSING)(resolved.missingType));
187
+ context.output.muted(` ${(0, texts_1.ERR_YAML_ITEM_MISSING)(resolved.missingType)}`);
174
188
  return 1;
175
189
  }
176
190
  if ('unknownType' in resolved) {
177
- output_1.default.error(label);
178
- output_1.default.muted(` ${(0, texts_1.ERR_YAML_ITEM_UNKNOWN)(resolved.prop, resolved.unknownType)}`);
191
+ context.output.error(label);
192
+ recordError(context, `${path}.${resolved.prop}`, (0, texts_1.ERR_YAML_ITEM_UNKNOWN)(resolved.prop, resolved.unknownType));
193
+ context.output.muted(` ${(0, texts_1.ERR_YAML_ITEM_UNKNOWN)(resolved.prop, resolved.unknownType)}`);
179
194
  return 1;
180
195
  }
181
196
  const result = (0, schemaUtils_1.validateAgainstDef)(resolved.defName, value);
182
197
  if (result.valid) {
183
- output_1.default.green(label);
198
+ context.output.green(label);
184
199
  return 0;
185
200
  }
186
- output_1.default.error(label);
187
- outputErrors(result, ' ');
201
+ context.output.error(label);
202
+ outputErrors(result, ' ', context, path);
188
203
  return result.errors.length;
189
204
  }
190
- function validateSinglePipeline(parsed, label, unions) {
205
+ function validateSinglePipeline(parsed, label, unions, context) {
191
206
  let errors = 0;
192
207
  const defs = (0, schemaUtils_1.getAllDefs)();
193
208
  const pipelineDef = defs['PipelineYaml'];
194
209
  const props = pipelineDef?.properties || {};
195
- const allKnownKeys = new Set([...Object.keys(props), ...SCHEMA_OMITTED_TOP_LEVEL_KEYS]);
210
+ const allKnownKeys = new Set([
211
+ ...Object.keys(props),
212
+ ...SCHEMA_OMITTED_TOP_LEVEL_KEYS,
213
+ ]);
196
214
  const unknownKeys = Object.keys(parsed).filter((k) => !allKnownKeys.has(k));
197
215
  if (unknownKeys.length > 0) {
198
- output_1.default.yellow(`${label}${(0, texts_1.TXT_YAML_UNKNOWN_TOP_LEVEL_KEYS)(unknownKeys.join(', '))}`);
216
+ for (const key of unknownKeys) {
217
+ context.warnings.push({
218
+ pipeline: context.pipeline,
219
+ path: key,
220
+ message: (0, texts_1.TXT_YAML_UNKNOWN_TOP_LEVEL_KEYS)(key),
221
+ });
222
+ }
223
+ context.output.yellow(`${label}${(0, texts_1.TXT_YAML_UNKNOWN_TOP_LEVEL_KEYS)(unknownKeys.join(', '))}`);
199
224
  }
200
225
  const { itemSections, objectSections, settingsProps } = classifySections(props);
201
226
  if (Object.keys(settingsProps).length > 0) {
@@ -212,12 +237,12 @@ function validateSinglePipeline(parsed, label, unions) {
212
237
  $defs: rawSchema.$defs,
213
238
  }, settingsData);
214
239
  if (result.valid) {
215
- output_1.default.green(`${label}Pipeline settings`);
240
+ context.output.green(`${label}Pipeline settings`);
216
241
  }
217
242
  else {
218
243
  errors += result.errors.length;
219
- output_1.default.error(`${label}Pipeline settings`);
220
- outputErrors(result, ' ');
244
+ context.output.error(`${label}Pipeline settings`);
245
+ outputErrors(result, ' ', context);
221
246
  }
222
247
  }
223
248
  const itemByKey = new Map(itemSections.map((s) => [s.key, s]));
@@ -232,27 +257,52 @@ function validateSinglePipeline(parsed, label, unions) {
232
257
  const header = `${label}${prettifyKey(key)}`;
233
258
  if (itemByKey.has(key)) {
234
259
  if (!Array.isArray(value)) {
235
- errors += reportWrongContainer(header, 'array', value);
260
+ errors += reportWrongContainer(header, 'array', value, context, key);
236
261
  continue;
237
262
  }
238
- output_1.default.blue(header);
239
- errors += validateItemSection(unions, value, itemByKey.get(key));
263
+ context.output.blue(header);
264
+ errors += validateItemSection(unions, value, itemByKey.get(key), context);
240
265
  }
241
266
  else if (objByKey.has(key)) {
242
267
  if (typeof value !== 'object' || Array.isArray(value)) {
243
- errors += reportWrongContainer(header, 'object', value);
268
+ errors += reportWrongContainer(header, 'object', value, context, key);
244
269
  continue;
245
270
  }
246
- errors += validateObjectSection(unions, header, value, objByKey.get(key));
271
+ errors += validateObjectSection(unions, header, value, objByKey.get(key), context, key);
247
272
  }
248
273
  }
249
274
  return errors;
250
275
  }
251
- function validatePipelineFile(file, unions) {
276
+ function validatePipelineFile(file, unions, json) {
277
+ const context = {
278
+ output: {
279
+ normal: output_1.default.progress(json),
280
+ error: output_1.default.progress(json, output_1.default.error.bind(output_1.default)),
281
+ muted: output_1.default.progress(json, output_1.default.muted.bind(output_1.default)),
282
+ green: output_1.default.progress(json, output_1.default.green.bind(output_1.default)),
283
+ yellow: output_1.default.progress(json, output_1.default.yellow.bind(output_1.default)),
284
+ blue: output_1.default.progress(json, output_1.default.blue.bind(output_1.default)),
285
+ },
286
+ pipeline: 0,
287
+ errors: [],
288
+ warnings: [],
289
+ };
290
+ const invalidDocument = (message) => {
291
+ if (json) {
292
+ recordError(context, '', message);
293
+ output_1.default.json({
294
+ valid: false,
295
+ pipelines: 0,
296
+ errors: context.errors,
297
+ warnings: context.warnings,
298
+ });
299
+ output_1.default.exitCode(1);
300
+ }
301
+ output_1.default.exitError(message);
302
+ };
252
303
  const filePath = (0, path_1.resolve)(file);
253
304
  if (!(0, fs_1.existsSync)(filePath)) {
254
- output_1.default.exitError((0, texts_1.ERR_YAML_VALIDATE_FILE_NOT_FOUND)(file));
255
- return;
305
+ invalidDocument((0, texts_1.ERR_YAML_VALIDATE_FILE_NOT_FOUND)(file));
256
306
  }
257
307
  const YAML = require('yaml');
258
308
  const content = (0, fs_1.readFileSync)(filePath, 'utf8');
@@ -261,33 +311,40 @@ function validatePipelineFile(file, unions) {
261
311
  parsed = YAML.parse(content);
262
312
  }
263
313
  catch (e) {
264
- output_1.default.exitError((0, texts_1.ERR_YAML_PARSE)(e.message));
265
- return;
314
+ invalidDocument((0, texts_1.ERR_YAML_PARSE)(e.message));
266
315
  }
267
316
  if (!parsed || typeof parsed !== 'object') {
268
- output_1.default.exitError(texts_1.ERR_YAML_NOT_ARRAY);
269
- return;
317
+ invalidDocument(texts_1.ERR_YAML_NOT_ARRAY);
270
318
  }
271
319
  if (!Array.isArray(parsed)) {
272
- output_1.default.exitError(texts_1.ERR_YAML_PLAIN_OBJECT);
273
- return;
320
+ invalidDocument(texts_1.ERR_YAML_PLAIN_OBJECT);
274
321
  }
275
322
  if (parsed.length === 0) {
276
- output_1.default.exitError(texts_1.ERR_YAML_EMPTY_ARRAY);
277
- return;
323
+ invalidDocument(texts_1.ERR_YAML_EMPTY_ARRAY);
278
324
  }
279
325
  let errors = 0;
280
326
  for (let i = 0; i < parsed.length; i++) {
281
327
  const pipeline = parsed[i];
328
+ context.pipeline = i + 1;
282
329
  if (!pipeline || typeof pipeline !== 'object' || Array.isArray(pipeline)) {
283
- output_1.default.error(` ${(0, texts_1.ERR_YAML_PIPELINE_NOT_OBJECT)(i + 1)}`);
330
+ recordError(context, '', (0, texts_1.ERR_YAML_PIPELINE_NOT_OBJECT)(i + 1));
331
+ context.output.error(` ${(0, texts_1.ERR_YAML_PIPELINE_NOT_OBJECT)(i + 1)}`);
284
332
  errors++;
285
333
  continue;
286
334
  }
287
335
  const pipelineLabel = parsed.length > 1 ? `[${pipeline.pipeline || i + 1}] ` : '';
288
- errors += validateSinglePipeline(pipeline, pipelineLabel, unions);
336
+ errors += validateSinglePipeline(pipeline, pipelineLabel, unions, context);
337
+ }
338
+ if (json) {
339
+ output_1.default.json({
340
+ valid: errors === 0,
341
+ pipelines: parsed.length,
342
+ errors: context.errors,
343
+ warnings: context.warnings,
344
+ });
345
+ output_1.default.exitCode(errors > 0 ? 1 : 0);
289
346
  }
290
- output_1.default.normal('');
347
+ context.output.normal('');
291
348
  if (errors > 0) {
292
349
  output_1.default.exitError((0, texts_1.ERR_YAML_VALIDATE_FAILED_COUNT)(errors));
293
350
  }
@@ -296,11 +353,13 @@ function validatePipelineFile(file, unions) {
296
353
  }
297
354
  }
298
355
  const commandYamlValidate = (0, utils_1.newCommand)('validate', texts_1.DESC_COMMAND_YAML_VALIDATE, {
356
+ helpText: texts_1.HELP_YAML_VALIDATE_JSON,
299
357
  examples: texts_1.EXAMPLE_YAML_VALIDATE,
300
358
  });
301
359
  commandYamlValidate.argument('<file>', texts_1.OPTION_YAML_VALIDATE_FILE);
302
- commandYamlValidate.action(async (file) => {
360
+ commandYamlValidate.option('--format <text|json>', texts_1.OPTION_FORMAT);
361
+ commandYamlValidate.action(async (file, options) => {
303
362
  await (0, schemaUtils_1.ensureYamlSchema)();
304
- validatePipelineFile(file, await unionMappings((0, schemaUtils_1.getAllDefs)()));
363
+ validatePipelineFile(file, await unionMappings((0, schemaUtils_1.getAllDefs)()), options.format === 'json');
305
364
  });
306
365
  exports.default = commandYamlValidate;
@@ -43,4 +43,9 @@ const isAgentRun = process.argv[2] === 'agent' && process.argv[3] === 'run';
43
43
  const program = isAgentRun
44
44
  ? require('./agentIndex').default()
45
45
  : require('./cliIndex').default();
46
- program.parse();
46
+ program.parseAsync().catch((err) => {
47
+ if (isEpipe(err))
48
+ process.exit(0);
49
+ logger_1.default.fatal(err);
50
+ output_1.default.exitError(err);
51
+ });
@@ -36,6 +36,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
+ const cliError_1 = __importDefault(require("./cliError"));
39
40
  const output_1 = __importDefault(require("./output"));
40
41
  const netmask_1 = require("netmask");
41
42
  const punycode_1 = __importDefault(require("punycode/"));
@@ -360,7 +361,7 @@ class Input {
360
361
  if (!t && inSandbox && tokenEnv)
361
362
  t = tokenEnv;
362
363
  if (!t && !allowNoToken) {
363
- output_1.default.exitError(texts_1.ERR_REST_API_TOKEN);
364
+ output_1.default.exitError(new cliError_1.default('unauthorized', texts_1.ERR_REST_API_NOT_LOGGED_IN, texts_1.TXT_CLI_LOGIN_HINT));
364
365
  }
365
366
  return {
366
367
  token: t || '',
@@ -488,7 +489,7 @@ class Input {
488
489
  static runVariablesFromFile(p, encrypted) {
489
490
  const s = (0, node_path_1.resolve)(p);
490
491
  if (!node_fs_1.default.existsSync(s)) {
491
- output_1.default.exitError(texts_1.ERR_RUN_VAR_FILE_NOT_FOUND);
492
+ output_1.default.exitError(new cliError_1.default('file_not_found', texts_1.ERR_RUN_VAR_FILE_NOT_FOUND));
492
493
  }
493
494
  let parsed;
494
495
  try {
@@ -524,7 +525,7 @@ class Input {
524
525
  }
525
526
  catch (err) {
526
527
  if (err.code === 'ENOENT')
527
- output_1.default.exitError(texts_1.ERR_RUN_VAR_FILE_NOT_FOUND);
528
+ output_1.default.exitError(new cliError_1.default('file_not_found', texts_1.ERR_RUN_VAR_FILE_NOT_FOUND));
528
529
  output_1.default.exitError(texts_1.ERR_RUN_VAR_FILE_INVALID);
529
530
  }
530
531
  list.push({
@@ -835,7 +836,7 @@ class Input {
835
836
  return r;
836
837
  }
837
838
  else {
838
- output_1.default.exitError(texts_1.ERR_REST_API_REGION);
839
+ output_1.default.exitError(new cliError_1.default('usage_error', texts_1.ERR_REST_API_REGION));
839
840
  }
840
841
  }
841
842
  static restApiBaseUrlFromToken(token) {
@@ -894,14 +895,14 @@ class Input {
894
895
  return new URL(`https://${u}`);
895
896
  }
896
897
  catch {
897
- output_1.default.exitError(texts_1.ERR_REST_API_URL);
898
+ output_1.default.exitError(new cliError_1.default('usage_error', texts_1.ERR_REST_API_URL));
898
899
  }
899
900
  }
900
901
  static resolvePath(path) {
901
902
  const p = (0, node_path_1.resolve)(path);
902
903
  const exists = node_fs_1.default.existsSync(p);
903
904
  if (!exists) {
904
- output_1.default.exitError(texts_1.ERR_PATH_NOT_EXISTS);
905
+ output_1.default.exitError(new cliError_1.default('file_not_found', texts_1.ERR_PATH_NOT_EXISTS));
905
906
  }
906
907
  return p;
907
908
  }
@@ -923,7 +924,7 @@ class Input {
923
924
  if (!w) {
924
925
  if (allowNull)
925
926
  return null;
926
- output_1.default.exitError(texts_1.ERR_REST_API_WORKSPACE);
927
+ output_1.default.exitError(new cliError_1.default('usage_error', texts_1.ERR_REST_API_WORKSPACE, texts_1.TXT_CLI_WORKSPACE_HINT));
927
928
  }
928
929
  return w;
929
930
  }
@@ -1255,14 +1256,14 @@ class Input {
1255
1256
  if (!p) {
1256
1257
  if (allowNull)
1257
1258
  return null;
1258
- output_1.default.exitError(texts_1.ERR_REST_API_PROJECT);
1259
+ output_1.default.exitError(new cliError_1.default('usage_error', texts_1.ERR_REST_API_PROJECT, texts_1.TXT_CLI_PROJECT_HINT));
1259
1260
  }
1260
1261
  return p;
1261
1262
  }
1262
1263
  static sandboxBootScript(s) {
1263
1264
  const p = (0, node_path_1.resolve)(s);
1264
1265
  if (!node_fs_1.default.existsSync(p))
1265
- output_1.default.exitError(texts_1.ERR_SANDBOX_BOOT_SCRIPT_NOT_EXISTS);
1266
+ output_1.default.exitError(new cliError_1.default('file_not_found', texts_1.ERR_SANDBOX_BOOT_SCRIPT_NOT_EXISTS));
1266
1267
  return node_fs_1.default.readFileSync(p, 'utf8');
1267
1268
  }
1268
1269
  static tags(tags) {
@@ -1273,11 +1274,11 @@ class Input {
1273
1274
  }
1274
1275
  static restApiYaml(yaml) {
1275
1276
  if (!yaml)
1276
- output_1.default.exitError(texts_1.ERR_REST_API_YAML);
1277
+ output_1.default.exitError(new cliError_1.default('usage_error', texts_1.ERR_REST_API_YAML));
1277
1278
  if (yaml.startsWith('@')) {
1278
1279
  const p = (0, node_path_1.resolve)(yaml.replace(/^@/, ''));
1279
1280
  if (!node_fs_1.default.existsSync(p))
1280
- output_1.default.exitError(texts_1.ERR_REST_API_YAML_NOT_FOUND);
1281
+ output_1.default.exitError(new cliError_1.default('file_not_found', texts_1.ERR_REST_API_YAML_NOT_FOUND));
1281
1282
  return node_fs_1.default.readFileSync(p, 'utf8');
1282
1283
  }
1283
1284
  return yaml;