bdy 1.23.11-dev → 1.23.13-dev-target-commands

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 (53) hide show
  1. package/distTs/detect-rules.json +351 -0
  2. package/distTs/package.json +3 -1
  3. package/distTs/src/api/client.js +222 -12
  4. package/distTs/src/cliIndex.js +4 -0
  5. package/distTs/src/command/artifact/list.js +2 -6
  6. package/distTs/src/command/distro/list.js +1 -0
  7. package/distTs/src/command/distro/route/update.js +1 -1
  8. package/distTs/src/command/distro/update.js +9 -9
  9. package/distTs/src/command/environment/create.js +70 -0
  10. package/distTs/src/command/environment/delete.js +29 -0
  11. package/distTs/src/command/environment/get.js +48 -0
  12. package/distTs/src/command/environment/list.js +45 -0
  13. package/distTs/src/command/environment/resolve.js +22 -0
  14. package/distTs/src/command/environment/update.js +59 -0
  15. package/distTs/src/command/environment.js +20 -0
  16. package/distTs/src/command/pipeline/run/apply.js +62 -0
  17. package/distTs/src/command/target/create.js +33 -0
  18. package/distTs/src/command/target/delete.js +30 -0
  19. package/distTs/src/command/target/exec/command.js +103 -0
  20. package/distTs/src/command/target/exec/kill.js +31 -0
  21. package/distTs/src/command/target/exec/list.js +53 -0
  22. package/distTs/src/command/target/exec/logs.js +27 -0
  23. package/distTs/src/command/target/exec/status.js +44 -0
  24. package/distTs/src/command/target/exec.js +24 -0
  25. package/distTs/src/command/target/get.js +63 -0
  26. package/distTs/src/command/target/list.js +49 -0
  27. package/distTs/src/command/target/scope.js +108 -0
  28. package/distTs/src/command/target/update.js +31 -0
  29. package/distTs/src/command/target.js +22 -0
  30. package/distTs/src/command/yaml/actions/detect.js +268 -0
  31. package/distTs/src/command/yaml/actions/info.js +56 -0
  32. package/distTs/src/command/yaml/actions/list.js +70 -0
  33. package/distTs/src/command/yaml/actions/schema.js +104 -0
  34. package/distTs/src/command/yaml/actions.js +13 -0
  35. package/distTs/src/command/yaml/agents.js +88 -0
  36. package/distTs/src/command/yaml/cache.js +98 -0
  37. package/distTs/src/command/yaml/init.js +110 -0
  38. package/distTs/src/command/yaml/pipeline.js +42 -0
  39. package/distTs/src/command/yaml/render.js +83 -0
  40. package/distTs/src/command/yaml/schemaUtils.js +139 -0
  41. package/distTs/src/command/yaml/validate.js +259 -0
  42. package/distTs/src/command/yaml.js +18 -0
  43. package/distTs/src/diskCache.js +82 -0
  44. package/distTs/src/input.js +65 -0
  45. package/distTs/src/output.js +21 -2
  46. package/distTs/src/texts.js +252 -27
  47. package/distTs/src/utils.js +18 -2
  48. package/package.json +3 -1
  49. package/distTs/README.md +0 -181
  50. package/distTs/src/command/project/get.js +0 -18
  51. package/distTs/src/command/project/set.js +0 -31
  52. package/distTs/src/command/sandbox/get/yaml.js +0 -30
  53. package/distTs/src/command/vt/scrape.js +0 -193
@@ -0,0 +1,259 @@
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 fs_1 = require("fs");
7
+ const path_1 = require("path");
8
+ const utils_1 = require("../../utils");
9
+ const output_1 = __importDefault(require("../../output"));
10
+ const texts_1 = require("../../texts");
11
+ const schemaUtils_1 = require("./schemaUtils");
12
+ // Valid top-level keys the schema's PipelineYaml.properties omits (everything
13
+ // else is derived from the schema, so this only covers genuine gaps).
14
+ const SCHEMA_OMITTED_TOP_LEVEL_KEYS = ['git_config_ref'];
15
+ const LABEL_KEYS = ['action', 'target', 'name', 'key', 'title', 'trigger_condition'];
16
+ function stripRef(ref) {
17
+ return ref.replace('#/$defs/', '');
18
+ }
19
+ function prettifyKey(key) {
20
+ return key
21
+ .split('_')
22
+ .map((w) => w.charAt(0).toUpperCase() + w.slice(1))
23
+ .join(' ');
24
+ }
25
+ function outputErrors(result, indent) {
26
+ for (const err of result.errors) {
27
+ output_1.default.muted(`${indent}${err.path}: ${err.message}`);
28
+ }
29
+ }
30
+ // Array whose items reference a def — directly, or via a single $ref branch in
31
+ // items.oneOf (how `targets` mixes a string ref with an object form).
32
+ function itemRef(prop) {
33
+ if (!prop || prop.type !== 'array' || !prop.items)
34
+ return null;
35
+ if (prop.items.$ref)
36
+ return stripRef(prop.items.$ref);
37
+ if (Array.isArray(prop.items.oneOf)) {
38
+ const refs = prop.items.oneOf.filter((b) => b && b.$ref);
39
+ if (refs.length === 1)
40
+ return stripRef(refs[0].$ref);
41
+ }
42
+ return null;
43
+ }
44
+ function classifySections(props) {
45
+ const itemSections = [];
46
+ const objectSections = [];
47
+ const settingsProps = {};
48
+ for (const [key, prop] of Object.entries(props)) {
49
+ const ref = itemRef(prop);
50
+ if (ref) {
51
+ itemSections.push({ key, defName: ref });
52
+ }
53
+ else if (prop && prop.$ref) {
54
+ objectSections.push({ key, defName: stripRef(prop.$ref) });
55
+ }
56
+ else {
57
+ settingsProps[key] = prop;
58
+ }
59
+ }
60
+ return { itemSections, objectSections, settingsProps };
61
+ }
62
+ function discriminatorProp(def) {
63
+ return def && def.oneOf && def.discriminator ? def.discriminator.propertyName : null;
64
+ }
65
+ // Resolve a discriminated def to its concrete branch by the item's `type`, so
66
+ // AJV always validates against a single def (never a oneOf). Plain defs pass through.
67
+ function resolveDef(defs, refName, value) {
68
+ const def = defs[refName];
69
+ const prop = discriminatorProp(def);
70
+ if (!prop)
71
+ return { defName: refName };
72
+ const dv = value?.[prop];
73
+ if (dv === undefined || dv === null)
74
+ return { missingType: prop };
75
+ for (const branch of def.oneOf) {
76
+ const branchName = branch && branch.$ref ? stripRef(branch.$ref) : null;
77
+ if (!branchName)
78
+ continue;
79
+ if (defs[branchName]?.properties?.[prop]?.const === dv)
80
+ return { defName: branchName };
81
+ }
82
+ return { unknownType: String(dv), prop };
83
+ }
84
+ function itemLabel(value, index, discProp) {
85
+ let name;
86
+ for (const k of LABEL_KEYS) {
87
+ if (value && value[k]) {
88
+ name = String(value[k]);
89
+ break;
90
+ }
91
+ }
92
+ if (!name && !discProp && value && value.type)
93
+ name = String(value.type);
94
+ if (!name)
95
+ name = `#${index + 1}`;
96
+ return discProp && value && value[discProp] ? `${name} (${value[discProp]})` : name;
97
+ }
98
+ function validateItemSection(defs, items, refName) {
99
+ const discProp = discriminatorProp(defs[refName]);
100
+ let errors = 0;
101
+ for (let i = 0; i < items.length; i++) {
102
+ const item = items[i];
103
+ if (item === null || typeof item !== 'object' || Array.isArray(item)) {
104
+ output_1.default.green(` ${String(item)}`);
105
+ continue;
106
+ }
107
+ const label = itemLabel(item, i, discProp);
108
+ const resolved = resolveDef(defs, refName, item);
109
+ if ('missingType' in resolved) {
110
+ output_1.default.error(` ${label}: missing "${resolved.missingType}"`);
111
+ errors++;
112
+ continue;
113
+ }
114
+ if ('unknownType' in resolved) {
115
+ output_1.default.error(` ${label}: unknown ${resolved.prop} "${resolved.unknownType}"`);
116
+ errors++;
117
+ continue;
118
+ }
119
+ const result = (0, schemaUtils_1.validateAgainstDef)(resolved.defName, item);
120
+ if (result.valid) {
121
+ output_1.default.green(` ${label}`);
122
+ }
123
+ else {
124
+ errors += result.errors.length;
125
+ output_1.default.error(` ${label}`);
126
+ outputErrors(result, ' ');
127
+ }
128
+ }
129
+ return errors;
130
+ }
131
+ function validateObjectSection(defs, label, value, refName) {
132
+ const resolved = resolveDef(defs, refName, value);
133
+ if ('missingType' in resolved) {
134
+ output_1.default.error(label);
135
+ output_1.default.muted(` missing "${resolved.missingType}"`);
136
+ return 1;
137
+ }
138
+ if ('unknownType' in resolved) {
139
+ output_1.default.error(label);
140
+ output_1.default.muted(` unknown ${resolved.prop} "${resolved.unknownType}"`);
141
+ return 1;
142
+ }
143
+ const result = (0, schemaUtils_1.validateAgainstDef)(resolved.defName, value);
144
+ if (result.valid) {
145
+ output_1.default.green(label);
146
+ return 0;
147
+ }
148
+ output_1.default.error(label);
149
+ outputErrors(result, ' ');
150
+ return result.errors.length;
151
+ }
152
+ function validateSinglePipeline(parsed, label) {
153
+ let errors = 0;
154
+ const defs = (0, schemaUtils_1.getAllDefs)();
155
+ const pipelineDef = defs['PipelineYaml'];
156
+ const props = pipelineDef?.properties || {};
157
+ const allKnownKeys = new Set([...Object.keys(props), ...SCHEMA_OMITTED_TOP_LEVEL_KEYS]);
158
+ const unknownKeys = Object.keys(parsed).filter((k) => !allKnownKeys.has(k));
159
+ if (unknownKeys.length > 0) {
160
+ output_1.default.yellow(`${label}Unknown top-level keys: ${unknownKeys.join(', ')}`);
161
+ }
162
+ const { itemSections, objectSections, settingsProps } = classifySections(props);
163
+ if (Object.keys(settingsProps).length > 0) {
164
+ const settingsData = {};
165
+ for (const key of Object.keys(settingsProps)) {
166
+ if (key in parsed)
167
+ settingsData[key] = parsed[key];
168
+ }
169
+ const rawSchema = (0, schemaUtils_1.loadRawSchema)();
170
+ const result = (0, schemaUtils_1.validateAgainstSchema)({
171
+ type: 'object',
172
+ properties: settingsProps,
173
+ required: (pipelineDef?.required || []).filter((r) => r in settingsProps),
174
+ $defs: rawSchema.$defs,
175
+ }, settingsData);
176
+ if (result.valid) {
177
+ output_1.default.green(`${label}Pipeline settings`);
178
+ }
179
+ else {
180
+ errors += result.errors.length;
181
+ output_1.default.error(`${label}Pipeline settings`);
182
+ outputErrors(result, ' ');
183
+ }
184
+ }
185
+ const itemByKey = new Map(itemSections.map((s) => [s.key, s.defName]));
186
+ const objByKey = new Map(objectSections.map((s) => [s.key, s.defName]));
187
+ for (const key of Object.keys(props)) {
188
+ if (!(key in parsed))
189
+ continue;
190
+ const value = parsed[key];
191
+ const header = `${label}${prettifyKey(key)}`;
192
+ if (itemByKey.has(key)) {
193
+ if (!Array.isArray(value))
194
+ continue;
195
+ output_1.default.blue(header);
196
+ errors += validateItemSection(defs, value, itemByKey.get(key));
197
+ }
198
+ else if (objByKey.has(key)) {
199
+ if (value === null || typeof value !== 'object' || Array.isArray(value))
200
+ continue;
201
+ errors += validateObjectSection(defs, header, value, objByKey.get(key));
202
+ }
203
+ }
204
+ return errors;
205
+ }
206
+ function validatePipelineFile(file) {
207
+ const filePath = (0, path_1.resolve)(file);
208
+ if (!(0, fs_1.existsSync)(filePath)) {
209
+ output_1.default.exitError((0, texts_1.ERR_YAML_VALIDATE_FILE_NOT_FOUND)(file));
210
+ return;
211
+ }
212
+ const YAML = require('yaml');
213
+ const content = (0, fs_1.readFileSync)(filePath, 'utf8');
214
+ let parsed;
215
+ try {
216
+ parsed = YAML.parse(content);
217
+ }
218
+ catch (e) {
219
+ output_1.default.exitError(`YAML parse error: ${e.message}`);
220
+ return;
221
+ }
222
+ if (!parsed || typeof parsed !== 'object') {
223
+ output_1.default.exitError('Invalid pipeline YAML: expected an array of pipeline objects');
224
+ return;
225
+ }
226
+ if (!Array.isArray(parsed)) {
227
+ output_1.default.exitError('Invalid pipeline YAML: expected an array (e.g. "- pipeline: my-pipeline"), got a plain object');
228
+ return;
229
+ }
230
+ if (parsed.length === 0) {
231
+ output_1.default.exitError('Invalid pipeline YAML: empty array');
232
+ return;
233
+ }
234
+ let errors = 0;
235
+ for (let i = 0; i < parsed.length; i++) {
236
+ const pipeline = parsed[i];
237
+ if (!pipeline || typeof pipeline !== 'object' || Array.isArray(pipeline)) {
238
+ output_1.default.error(` Pipeline #${i + 1}: expected an object`);
239
+ errors++;
240
+ continue;
241
+ }
242
+ const pipelineLabel = parsed.length > 1 ? `[${pipeline.pipeline || i + 1}] ` : '';
243
+ errors += validateSinglePipeline(pipeline, pipelineLabel);
244
+ }
245
+ output_1.default.normal('');
246
+ if (errors > 0) {
247
+ output_1.default.exitError(`${texts_1.ERR_YAML_VALIDATE_FAILED} (${errors} ${errors === 1 ? 'error' : 'errors'})`);
248
+ }
249
+ else {
250
+ output_1.default.exitSuccess(texts_1.TXT_YAML_VALIDATE_ALL_PASS);
251
+ }
252
+ }
253
+ const commandYamlValidate = (0, utils_1.newCommand)('validate', texts_1.DESC_COMMAND_YAML_VALIDATE);
254
+ commandYamlValidate.argument('<file>', texts_1.OPTION_YAML_VALIDATE_FILE);
255
+ commandYamlValidate.action(async (file) => {
256
+ await (0, schemaUtils_1.ensureYamlSchema)();
257
+ validatePipelineFile(file);
258
+ });
259
+ exports.default = commandYamlValidate;
@@ -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 utils_1 = require("../utils");
7
+ const texts_1 = require("../texts");
8
+ const pipeline_1 = __importDefault(require("./yaml/pipeline"));
9
+ const actions_1 = __importDefault(require("./yaml/actions"));
10
+ const validate_1 = __importDefault(require("./yaml/validate"));
11
+ const init_1 = __importDefault(require("./yaml/init"));
12
+ const commandYaml = (0, utils_1.newCommand)('yaml', texts_1.DESC_COMMAND_YAML);
13
+ commandYaml.addCommand(pipeline_1.default);
14
+ commandYaml.addCommand(actions_1.default);
15
+ commandYaml.addCommand(validate_1.default);
16
+ commandYaml.addCommand(init_1.default);
17
+ commandYaml.addHelpText('after', texts_1.EXAMPLE_YAML);
18
+ exports.default = commandYaml;
@@ -0,0 +1,82 @@
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.CACHE_TTL_MS = void 0;
7
+ exports.cacheRoot = cacheRoot;
8
+ exports.hashKey = hashKey;
9
+ exports.cachePaths = cachePaths;
10
+ exports.readMeta = readMeta;
11
+ exports.isHardFresh = isHardFresh;
12
+ exports.writeAtomic = writeAtomic;
13
+ exports.refresh = refresh;
14
+ const node_crypto_1 = require("node:crypto");
15
+ const node_fs_1 = require("node:fs");
16
+ const node_path_1 = __importDefault(require("node:path"));
17
+ const undici_1 = require("undici");
18
+ const utils_1 = require("./utils");
19
+ exports.CACHE_TTL_MS = 60 * 60 * 1000; // 1h
20
+ function cacheRoot() {
21
+ const dir = node_path_1.default.join((0, utils_1.getHomeDirectory)(), 'cache');
22
+ if (!(0, node_fs_1.existsSync)(dir))
23
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
24
+ return dir;
25
+ }
26
+ function hashKey(input) {
27
+ return (0, node_crypto_1.createHash)('sha256').update(input).digest('hex').slice(0, 16);
28
+ }
29
+ function cachePaths(subdir, url) {
30
+ const dir = node_path_1.default.join(cacheRoot(), subdir);
31
+ if (!(0, node_fs_1.existsSync)(dir))
32
+ (0, node_fs_1.mkdirSync)(dir, { recursive: true });
33
+ const key = hashKey(url);
34
+ return {
35
+ raw: node_path_1.default.join(dir, `${key}.json`),
36
+ schema: node_path_1.default.join(dir, `${key}.v8`),
37
+ meta: node_path_1.default.join(dir, `${key}.meta.json`),
38
+ };
39
+ }
40
+ function readMeta(metaPath) {
41
+ try {
42
+ return JSON.parse((0, node_fs_1.readFileSync)(metaPath, 'utf8'));
43
+ }
44
+ catch {
45
+ return {};
46
+ }
47
+ }
48
+ function isHardFresh(meta, ttlMs = exports.CACHE_TTL_MS) {
49
+ return !!meta.fetchedAt && Date.now() - meta.fetchedAt < ttlMs;
50
+ }
51
+ function writeAtomic(filePath, data) {
52
+ const tmp = `${filePath}.${process.pid}.tmp`;
53
+ (0, node_fs_1.writeFileSync)(tmp, data);
54
+ (0, node_fs_1.renameSync)(tmp, filePath);
55
+ }
56
+ async function refresh(url, paths) {
57
+ const hasRaw = (0, node_fs_1.existsSync)(paths.raw);
58
+ const meta = hasRaw ? readMeta(paths.meta) : {};
59
+ // The server serves a strong content-based ETag, so revalidate with If-None-Match.
60
+ const headers = {};
61
+ if (hasRaw && meta.etag)
62
+ headers['if-none-match'] = meta.etag;
63
+ try {
64
+ const res = await (0, undici_1.request)(url, { headers });
65
+ const etag = res.headers['etag'];
66
+ if (res.statusCode === 304 && hasRaw) {
67
+ await res.body.dump();
68
+ writeAtomic(paths.meta, JSON.stringify({ etag: meta.etag, fetchedAt: Date.now() }));
69
+ return 'unchanged';
70
+ }
71
+ if (res.statusCode >= 200 && res.statusCode < 300) {
72
+ writeAtomic(paths.raw, await res.body.text());
73
+ writeAtomic(paths.meta, JSON.stringify({ etag: typeof etag === 'string' ? etag : undefined, fetchedAt: Date.now() }));
74
+ return 'updated';
75
+ }
76
+ await res.body.dump();
77
+ }
78
+ catch {
79
+ // network error — fall back to the cached body if present
80
+ }
81
+ return hasRaw ? 'unchanged' : 'none';
82
+ }
@@ -368,8 +368,19 @@ class Input {
368
368
  const ApiClient = require('./api/client').default;
369
369
  const { token: t, refreshToken, clientId, clientToken, clientSecret, } = this.restApiToken(allowNoToken, token);
370
370
  const baseUrl = this.restApiBaseUrl(api, region, t);
371
+ ApiClient.onRateLimitLow = (remaining, reset) => output_1.default.noteWarning((0, texts_1.WARN_REST_API_RATE_LIMIT_LOW)(remaining, reset));
371
372
  return new ApiClient(baseUrl, t, refreshToken, clientId, clientSecret, clientToken);
372
373
  }
374
+ // Stderr note for list commands whose PROJECT scope was inferred (linked
375
+ // project / BUDDY_PROJECT) rather than requested via -p or --scope, so the
376
+ // narrowing is never silent.
377
+ static projectScopeNote(options, project, scope, resource) {
378
+ if (options.format === 'json' || options.scope || options.project)
379
+ return;
380
+ if (scope !== 'PROJECT' || !project)
381
+ return;
382
+ output_1.default.note((0, texts_1.TXT_LIST_PROJECT_SCOPE_NOTE)(resource, project));
383
+ }
373
384
  static routeType(type) {
374
385
  if (!type)
375
386
  return distro_1.ROUTE_TYPE.DIRECT_PROXY;
@@ -1087,6 +1098,54 @@ class Input {
1087
1098
  return distro_1.SCOPE.PROJECT;
1088
1099
  return distro_1.SCOPE.WORKSPACE;
1089
1100
  }
1101
+ static targetScope(project, pipeline, environment, scope) {
1102
+ Input.targetLine(project, pipeline, environment);
1103
+ if (scope) {
1104
+ scope = scope.toUpperCase();
1105
+ if (scope === utils_1.TARGET_SCOPE.WORKSPACE) {
1106
+ return scope;
1107
+ }
1108
+ if (scope === utils_1.TARGET_SCOPE.PROJECT) {
1109
+ if (!project)
1110
+ output_1.default.exitError(texts_1.ERR_COMMAND_SCOPE_NO_PROJECT);
1111
+ return scope;
1112
+ }
1113
+ if (scope === utils_1.TARGET_SCOPE.PIPELINE) {
1114
+ if (!pipeline)
1115
+ output_1.default.exitError(texts_1.ERR_TARGET_SCOPE_NO_PIPELINE);
1116
+ return scope;
1117
+ }
1118
+ if (scope === utils_1.TARGET_SCOPE.ENVIRONMENT) {
1119
+ if (!environment)
1120
+ output_1.default.exitError(texts_1.ERR_TARGET_SCOPE_NO_ENVIRONMENT);
1121
+ return scope;
1122
+ }
1123
+ return output_1.default.exitError(texts_1.ERR_TARGET_SCOPE);
1124
+ }
1125
+ if (pipeline)
1126
+ return utils_1.TARGET_SCOPE.PIPELINE;
1127
+ if (environment)
1128
+ return utils_1.TARGET_SCOPE.ENVIRONMENT;
1129
+ if (project)
1130
+ return utils_1.TARGET_SCOPE.PROJECT;
1131
+ return utils_1.TARGET_SCOPE.WORKSPACE;
1132
+ }
1133
+ // Line context for resolving a target identifier via /identifiers:
1134
+ // params describe one line from workspace down (ws -> project -> pipeline
1135
+ // or environment), searched from the deepest scope up.
1136
+ static targetLine(project, pipeline, environment) {
1137
+ if (pipeline && !project) {
1138
+ output_1.default.exitError(texts_1.ERR_TARGET_PIPELINE_REQUIRES_PROJECT);
1139
+ }
1140
+ if (pipeline && environment) {
1141
+ output_1.default.exitError(texts_1.ERR_TARGET_PIPELINE_ENV_CONFLICT);
1142
+ }
1143
+ return {
1144
+ project: project || undefined,
1145
+ pipeline,
1146
+ environment,
1147
+ };
1148
+ }
1090
1149
  static restApiProject(project, allowNull) {
1091
1150
  const ProjectCfg = require('./project/cfg').default;
1092
1151
  let p = process.env.BUDDY_PROJECT;
@@ -1107,6 +1166,12 @@ class Input {
1107
1166
  output_1.default.exitError(texts_1.ERR_SANDBOX_BOOT_SCRIPT_NOT_EXISTS);
1108
1167
  return node_fs_1.default.readFileSync(p, 'utf8');
1109
1168
  }
1169
+ static tags(tags) {
1170
+ return tags
1171
+ .split(',')
1172
+ .map((t) => t.trim())
1173
+ .filter(Boolean);
1174
+ }
1110
1175
  static restApiYaml(yaml) {
1111
1176
  if (!yaml)
1112
1177
  output_1.default.exitError(texts_1.ERR_REST_API_YAML);
@@ -134,6 +134,23 @@ class Output {
134
134
  msg += '\n';
135
135
  getTerminal()(msg);
136
136
  }
137
+ // Payload output (command results, logs, JSON): bypasses terminal-kit,
138
+ // whose printf-style formatting and ^-markup corrupt data containing
139
+ // % or ^ sequences.
140
+ static data(txt) {
141
+ process.stdout.write(`${txt}\n`);
142
+ }
143
+ static writeStderr(txt) {
144
+ process.stderr.write(`${txt}\n`);
145
+ }
146
+ // Narration accompanying payload output goes to stderr so stdout stays
147
+ // parseable by scripts.
148
+ static note(txt) {
149
+ this.writeStderr(this.getMutedColor(txt));
150
+ }
151
+ static noteWarning(txt) {
152
+ this.writeStderr(this.getYellowColor(txt));
153
+ }
137
154
  static gray(txt, newLine = true) {
138
155
  this.normal(this.getMutedColor(txt), newLine);
139
156
  }
@@ -624,7 +641,7 @@ class Output {
624
641
  _terminal.grabInput(false);
625
642
  }
626
643
  static json(json) {
627
- this.normal(JSON.stringify(json));
644
+ this.data(JSON.stringify(json));
628
645
  }
629
646
  static exitError(err) {
630
647
  this.clearUndici();
@@ -642,7 +659,9 @@ class Output {
642
659
  if (isDebug)
643
660
  msg += `\n${e.stack}`;
644
661
  }
645
- getTerminal().red.error(`${msg}\n`);
662
+ // Raw stderr: error messages carry user/API data (SQL, URLs) that
663
+ // terminal-kit's %-formatting would corrupt.
664
+ this.writeStderr(this.getRedColor(String(msg)));
646
665
  process.exit(1);
647
666
  }
648
667
  }