@logbrew/sdk 0.1.38 → 0.1.39

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 (90) hide show
  1. package/automatic-delivery.cjs +271 -0
  2. package/batch-sender.cjs +164 -0
  3. package/bounded-number.cjs +34 -0
  4. package/browser-breadcrumbs.cjs +346 -136
  5. package/capture-support.cjs +100 -0
  6. package/client-options.cjs +292 -0
  7. package/client-report.cjs +232 -0
  8. package/client.cjs +447 -0
  9. package/console-arguments.cjs +151 -0
  10. package/console-capture.cjs +194 -0
  11. package/core.cjs +30 -2810
  12. package/delivery-health.cjs +133 -0
  13. package/error-causes.cjs +130 -0
  14. package/error-details.cjs +124 -0
  15. package/error-issue.cjs +187 -0
  16. package/event-clone.cjs +55 -0
  17. package/event-metadata.cjs +40 -0
  18. package/event-pipeline.cjs +325 -0
  19. package/event-queue.cjs +306 -0
  20. package/event-store.cjs +237 -0
  21. package/event-validation.cjs +159 -0
  22. package/examples/agent-timeline.cjs +57 -46
  23. package/examples/agent-timeline.mjs +41 -35
  24. package/examples/index.mjs +122 -94
  25. package/examples/readme-example.cjs +36 -27
  26. package/examples/readme-example.mjs +21 -17
  27. package/examples/real-user-smoke.cjs +41 -32
  28. package/examples/real-user-smoke.mjs +26 -22
  29. package/exception-chain.cjs +205 -0
  30. package/index.d.ts +144 -1076
  31. package/index.js +1 -1
  32. package/issue-exception.cjs +13 -8
  33. package/metric-validation.cjs +95 -0
  34. package/network-milestone.cjs +155 -0
  35. package/null-value.cjs +4 -0
  36. package/opentelemetry-bridge.cjs +330 -0
  37. package/opentelemetry-context.cjs +245 -0
  38. package/opentelemetry-exceptions.cjs +69 -0
  39. package/opentelemetry-keys.cjs +183 -0
  40. package/opentelemetry-span.cjs +286 -0
  41. package/opentelemetry-summary.cjs +213 -0
  42. package/opentelemetry-time.cjs +131 -0
  43. package/opentelemetry.cjs +99 -1068
  44. package/package.json +55 -1
  45. package/pino-destination.cjs +108 -0
  46. package/pino-record.cjs +237 -0
  47. package/product-action.cjs +163 -0
  48. package/provided-breadcrumbs.cjs +117 -0
  49. package/random-hex.cjs +29 -0
  50. package/react-native.js +1 -1
  51. package/recording-transport.cjs +68 -0
  52. package/release-artifacts-build.cjs +263 -84
  53. package/release-artifacts-common.js +347 -66
  54. package/release-artifacts-debug-id.js +178 -0
  55. package/release-artifacts-inspect.js +213 -0
  56. package/release-artifacts-issue-event.js +99 -0
  57. package/release-artifacts-manifest-artifact.js +107 -0
  58. package/release-artifacts-manifest.js +77 -0
  59. package/release-artifacts-mappings.js +265 -0
  60. package/release-artifacts-prepare.js +297 -0
  61. package/release-artifacts-source-context.js +210 -0
  62. package/release-artifacts-symbolication.js +272 -421
  63. package/release-artifacts-upload-files.js +122 -0
  64. package/release-artifacts-upload-request.js +246 -0
  65. package/release-artifacts-upload.js +155 -300
  66. package/release-artifacts-urls.js +49 -0
  67. package/release-artifacts.js +189 -662
  68. package/retry-delay.cjs +32 -0
  69. package/span-validation.cjs +191 -0
  70. package/stored-events.cjs +243 -0
  71. package/types/client.d.ts +67 -0
  72. package/types/delivery.d.ts +125 -0
  73. package/types/issues.d.ts +249 -0
  74. package/types/logging.d.ts +175 -0
  75. package/types/opentelemetry.d.ts +205 -0
  76. package/types/product.d.ts +128 -0
  77. package/types/recording-transport.d.ts +15 -0
  78. package/types/sdk-error.d.ts +9 -0
  79. package/types/telemetry.d.ts +171 -0
  80. package/types/trace-context.d.ts +110 -0
  81. package/types/transport-error.d.ts +10 -0
  82. package/utf8.cjs +37 -0
  83. package/validation.cjs +49 -1
  84. package/vite-release-artifacts-config.cjs +120 -0
  85. package/vite-release-artifacts.cjs +199 -99
  86. package/vite-release-artifacts.d.cts +65 -40
  87. package/vite-release-artifacts.d.ts +65 -40
  88. package/vite-release-artifacts.js +2 -2
  89. package/winston-info.cjs +205 -0
  90. package/winston.cjs +189 -289
@@ -3,8 +3,25 @@
3
3
  const { spawnSync } = require("node:child_process");
4
4
  const net = require("node:net");
5
5
 
6
+ /**
7
+ * @typedef {{
8
+ * readonly allowHostedUpload: boolean,
9
+ * readonly dryRun: boolean,
10
+ * readonly endpoint: string,
11
+ * readonly maxRetries: number | undefined,
12
+ * readonly retryDelay: number | undefined,
13
+ * readonly timeout: number | undefined,
14
+ * readonly tokenEnv: string | undefined
15
+ * }} ReleaseArtifactUploadOptions
16
+ * @typedef {{ readonly integer: boolean, readonly integration: string, readonly maximum: number, readonly minimum: number }} NumberBounds
17
+ * @typedef {{ readonly report: unknown, readonly stdout: string }} ReleaseArtifactCliResult
18
+ */
19
+
6
20
  const HOSTED_UPLOAD_ENDPOINT = "https://api.logbrew.co/api/release-artifacts";
7
21
  const UUID_RE = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/iu;
22
+ const ENVIRONMENT_VARIABLE_NAME_RE = /^[A-Za-z_][A-Za-z0-9_]*$/u;
23
+ const REPORT_STATUS_RE = /^[a-z][a-z0-9_]{0,63}$/u;
24
+ const WHITESPACE_RUN_RE = /\s+/gu;
8
25
  const UPLOAD_OPTION_NAMES = new Set([
9
26
  "allowHostedUpload",
10
27
  "dryRun",
@@ -12,24 +29,58 @@ const UPLOAD_OPTION_NAMES = new Set([
12
29
  "maxRetries",
13
30
  "retryDelay",
14
31
  "timeout",
15
- "tokenEnv",
32
+ "tokenEnv"
16
33
  ]);
34
+ const HTTP_PROTOCOLS = new Set(["http:", "https:"]);
35
+ const MAX_RETRIES = 10;
36
+ const MAX_RETRY_DELAY_SECONDS = 60;
37
+ const MAX_TIMEOUT_SECONDS = 300;
38
+ const MIN_UPLOAD_SETTING = 0;
39
+ const LAST_CONTROL_CHARACTER = 31;
40
+ const DELETE_CHARACTER = 127;
41
+ const MAX_DIAGNOSTIC_LENGTH = 200;
42
+ const MAX_DIAGNOSTICS = 3;
43
+ const START = 0;
44
+ const NOT_AN_IP = 0;
45
+ const ONE_ARTIFACT = 1;
46
+ const SUCCESS_EXIT_CODE = 0;
47
+
48
+ /**
49
+ * @param {unknown} value
50
+ * @param {string} key
51
+ * @returns {unknown}
52
+ */
53
+ function field(value, key) {
54
+ return typeof value === "object" && value !== null ? Reflect.get(value, key) : undefined;
55
+ }
17
56
 
18
- function optionalBoolean(options, name, integration, defaultValue = false) {
19
- const value = options?.[name];
57
+ /**
58
+ * @param {unknown} options
59
+ * @param {string} name
60
+ * @param {string} integration
61
+ * @returns {boolean}
62
+ */
63
+ function optionalBoolean(options, name, integration) {
64
+ const value = field(options, name);
20
65
  if (value === undefined || value === null) {
21
- return defaultValue;
66
+ return false;
22
67
  }
23
- if (typeof value !== "boolean") {
24
- throw new Error(`LogBrew ${integration} release-artifact upload option ${name} must be a boolean`);
68
+ if (typeof value === "boolean") {
69
+ return value;
25
70
  }
26
- return value;
71
+ throw new Error(`LogBrew ${integration} release-artifact upload option ${name} must be a boolean`);
27
72
  }
28
73
 
74
+ /**
75
+ * @param {unknown} options
76
+ * @param {string} name
77
+ * @param {string} integration
78
+ * @returns {string | undefined}
79
+ */
29
80
  function optionalString(options, name, integration) {
30
- const value = options?.[name];
81
+ const value = field(options, name);
31
82
  if (value === undefined || value === null) {
32
- return null;
83
+ return undefined;
33
84
  }
34
85
  if (typeof value !== "string" || value.trim() === "") {
35
86
  throw new Error(`LogBrew ${integration} release-artifact upload option ${name} must be a non-empty string`);
@@ -37,18 +88,30 @@ function optionalString(options, name, integration) {
37
88
  return value.trim();
38
89
  }
39
90
 
91
+ /**
92
+ * @param {unknown} options
93
+ * @param {string} name
94
+ * @param {string} integration
95
+ * @returns {string}
96
+ */
40
97
  function requiredString(options, name, integration) {
41
98
  const value = optionalString(options, name, integration);
42
- if (!value) {
99
+ if (value === undefined) {
43
100
  throw new Error(`LogBrew ${integration} release-artifact upload requires ${name}`);
44
101
  }
45
102
  return value;
46
103
  }
47
104
 
48
- function optionalBoundedNumber(options, name, integration, minimum, maximum, integer = false) {
49
- const value = options?.[name];
105
+ /**
106
+ * @param {unknown} options
107
+ * @param {string} name
108
+ * @param {NumberBounds} bounds
109
+ * @returns {number | undefined}
110
+ */
111
+ function optionalBoundedNumber(options, name, { integer, integration, maximum, minimum }) {
112
+ const value = field(options, name);
50
113
  if (value === undefined || value === null) {
51
- return null;
114
+ return undefined;
52
115
  }
53
116
  if (
54
117
  typeof value !== "number" ||
@@ -59,15 +122,20 @@ function optionalBoundedNumber(options, name, integration, minimum, maximum, int
59
122
  ) {
60
123
  const kind = integer ? "an integer" : "a number";
61
124
  throw new Error(
62
- `LogBrew ${integration} release-artifact upload option ${name} must be ${kind} from ${minimum} to ${maximum}`,
125
+ `LogBrew ${integration} release-artifact upload option ${name} must be ${kind} from ${String(minimum)} to ${String(maximum)}`
63
126
  );
64
127
  }
65
128
  return value;
66
129
  }
67
130
 
131
+ /**
132
+ * @param {unknown} value
133
+ * @param {string} integration
134
+ * @returns {string | undefined}
135
+ */
68
136
  function normalizeReleaseArtifactProjectId(value, integration) {
69
137
  if (value === undefined || value === null) {
70
- return null;
138
+ return undefined;
71
139
  }
72
140
  if (typeof value !== "string" || !UUID_RE.test(value.trim())) {
73
141
  throw new Error(`LogBrew ${integration} release-artifact option projectId must be a UUID`);
@@ -75,142 +143,253 @@ function normalizeReleaseArtifactProjectId(value, integration) {
75
143
  return value.trim().toLowerCase();
76
144
  }
77
145
 
146
+ /**
147
+ * @param {string} endpoint
148
+ * @param {string} integration
149
+ * @returns {URL}
150
+ */
78
151
  function parseUploadEndpoint(endpoint, integration) {
79
- let parsed;
80
152
  try {
81
- parsed = new URL(endpoint);
153
+ return new URL(endpoint);
82
154
  } catch {
83
155
  throw new Error(`LogBrew ${integration} release-artifact upload endpoint must be a valid URL`);
84
156
  }
85
- if (!new Set(["http:", "https:"]).has(parsed.protocol)) {
157
+ }
158
+
159
+ /**
160
+ * @param {string} endpoint
161
+ * @param {string} integration
162
+ * @returns {string}
163
+ */
164
+ function uploadEndpointHostname(endpoint, integration) {
165
+ const { hostname, protocol } = parseUploadEndpoint(endpoint, integration);
166
+ if (!HTTP_PROTOCOLS.has(protocol)) {
86
167
  throw new Error(`LogBrew ${integration} release-artifact upload endpoint must use http or https`);
87
168
  }
88
- return parsed;
169
+ return hostname;
89
170
  }
90
171
 
91
- function isLoopbackEndpoint(parsed) {
92
- const hostname = parsed.hostname.toLowerCase();
172
+ /**
173
+ * @param {string} endpointHostname
174
+ * @returns {boolean}
175
+ */
176
+ function isLoopbackEndpoint(endpointHostname) {
177
+ const hostname = endpointHostname.toLowerCase();
93
178
  return (
94
179
  hostname === "localhost" ||
95
180
  hostname === "[::1]" ||
96
181
  hostname === "::1" ||
97
- (net.isIP(hostname) !== 0 && (hostname.startsWith("127.") || hostname === "::1"))
182
+ (net.isIP(hostname) !== NOT_AN_IP && (hostname.startsWith("127.") || hostname === "::1"))
98
183
  );
99
184
  }
100
185
 
101
- function normalizeReleaseArtifactUploadOptions(upload, { integration, projectId }) {
102
- if (upload === undefined || upload === null) {
103
- return null;
186
+ /**
187
+ * @param {string} endpoint
188
+ * @param {boolean} allowHostedUpload
189
+ * @param {Readonly<{ integration: string, projectId: string | null | undefined }>} context
190
+ * @returns {void}
191
+ */
192
+ function requireHostedUpload(endpoint, allowHostedUpload, { integration, projectId }) {
193
+ if (!allowHostedUpload) {
194
+ throw new Error(`LogBrew ${integration} release-artifact hosted upload requires allowHostedUpload`);
104
195
  }
105
- if (typeof upload !== "object" || Array.isArray(upload)) {
106
- throw new Error(`LogBrew ${integration} release-artifact option upload must be an object`);
196
+ if (endpoint !== HOSTED_UPLOAD_ENDPOINT) {
197
+ throw new Error(`LogBrew ${integration} hosted release-artifact uploads must use ${HOSTED_UPLOAD_ENDPOINT}`);
107
198
  }
108
- const unknownOption = Object.keys(upload).find((name) => !UPLOAD_OPTION_NAMES.has(name));
109
- if (unknownOption) {
110
- throw new Error(`LogBrew ${integration} release-artifact unknown upload option ${unknownOption}`);
199
+ if (projectId === undefined || projectId === null || projectId === "") {
200
+ throw new Error(`LogBrew ${integration} release-artifact hosted upload requires projectId`);
111
201
  }
202
+ }
112
203
 
204
+ /**
205
+ * @param {unknown} upload
206
+ * @param {string} integration
207
+ * @param {string | null | undefined} projectId
208
+ * @returns {{ readonly allowHostedUpload: boolean, readonly endpoint: string }}
209
+ */
210
+ function requireUploadTarget(upload, integration, projectId) {
113
211
  const endpoint = requiredString(upload, "endpoint", integration);
114
- const parsedEndpoint = parseUploadEndpoint(endpoint, integration);
212
+ const hostname = uploadEndpointHostname(endpoint, integration);
115
213
  const allowHostedUpload = optionalBoolean(upload, "allowHostedUpload", integration);
116
- const hosted = !isLoopbackEndpoint(parsedEndpoint);
117
- if (hosted && !allowHostedUpload) {
118
- throw new Error(`LogBrew ${integration} release-artifact hosted upload requires allowHostedUpload`);
119
- }
120
- if (hosted && endpoint !== HOSTED_UPLOAD_ENDPOINT) {
121
- throw new Error(`LogBrew ${integration} hosted release-artifact uploads must use ${HOSTED_UPLOAD_ENDPOINT}`);
122
- }
123
- if (hosted && !projectId) {
124
- throw new Error(`LogBrew ${integration} release-artifact hosted upload requires projectId`);
214
+ if (!isLoopbackEndpoint(hostname)) {
215
+ requireHostedUpload(endpoint, allowHostedUpload, { integration, projectId });
125
216
  }
217
+ return { allowHostedUpload, endpoint };
218
+ }
126
219
 
220
+ /**
221
+ * @param {unknown} upload
222
+ * @param {string} integration
223
+ * @returns {string | undefined}
224
+ */
225
+ function optionalTokenEnv(upload, integration) {
127
226
  const tokenEnv = optionalString(upload, "tokenEnv", integration);
128
- if (tokenEnv && !/^[A-Za-z_][A-Za-z0-9_]*$/u.test(tokenEnv)) {
227
+ if (tokenEnv !== undefined && !ENVIRONMENT_VARIABLE_NAME_RE.test(tokenEnv)) {
129
228
  throw new Error(`LogBrew ${integration} release-artifact upload option tokenEnv must be an environment variable name`);
130
229
  }
230
+ return tokenEnv;
231
+ }
131
232
 
132
- return {
133
- endpoint,
134
- allowHostedUpload,
135
- tokenEnv,
136
- dryRun: optionalBoolean(upload, "dryRun", integration),
137
- maxRetries: optionalBoundedNumber(upload, "maxRetries", integration, 0, 10, true),
138
- retryDelay: optionalBoundedNumber(upload, "retryDelay", integration, 0, 60),
139
- timeout: optionalBoundedNumber(upload, "timeout", integration, 0, 300),
140
- };
233
+ /**
234
+ * @param {unknown} upload
235
+ * @param {string} integration
236
+ * @returns {void}
237
+ */
238
+ function requireKnownUploadOptions(upload, integration) {
239
+ const uploadObject = typeof upload === "object" && upload !== null && !Array.isArray(upload);
240
+ if (!uploadObject) {
241
+ throw new Error(`LogBrew ${integration} release-artifact option upload must be an object`);
242
+ }
243
+ const unknownOption = Object.keys(upload).find((name) => !UPLOAD_OPTION_NAMES.has(name));
244
+ if (unknownOption !== undefined && unknownOption !== "") {
245
+ throw new Error(`LogBrew ${integration} release-artifact unknown upload option ${unknownOption}`);
246
+ }
141
247
  }
142
248
 
143
- function appendOption(args, name, value) {
144
- if (value !== null) {
145
- args.push(name, String(value));
249
+ /**
250
+ * @param {unknown} upload
251
+ * @param {Readonly<{ integration: string, projectId: string | null | undefined }>} context
252
+ * @returns {ReleaseArtifactUploadOptions | undefined}
253
+ */
254
+ function normalizeReleaseArtifactUploadOptions(upload, { integration, projectId }) {
255
+ if (upload === undefined || upload === null) {
256
+ return undefined;
146
257
  }
258
+ requireKnownUploadOptions(upload, integration);
259
+ const { allowHostedUpload, endpoint } = requireUploadTarget(upload, integration, projectId);
260
+ const tokenEnv = optionalTokenEnv(upload, integration);
261
+ const dryRun = optionalBoolean(upload, "dryRun", integration);
262
+ const maxRetries = optionalBoundedNumber(upload, "maxRetries", { integer: true, integration, maximum: MAX_RETRIES, minimum: MIN_UPLOAD_SETTING });
263
+ const retryDelay = optionalBoundedNumber(upload, "retryDelay", { integer: false, integration, maximum: MAX_RETRY_DELAY_SECONDS, minimum: MIN_UPLOAD_SETTING });
264
+ const timeout = optionalBoundedNumber(upload, "timeout", { integer: false, integration, maximum: MAX_TIMEOUT_SECONDS, minimum: MIN_UPLOAD_SETTING });
265
+ return { allowHostedUpload, dryRun, endpoint, maxRetries, retryDelay, timeout, tokenEnv };
266
+ }
267
+
268
+ /**
269
+ * @param {string} name
270
+ * @param {number | string | undefined} value
271
+ * @returns {string[]}
272
+ */
273
+ function optionArgs(name, value) {
274
+ return value === undefined ? [] : [name, String(value)];
147
275
  }
148
276
 
277
+ /**
278
+ * @param {string} buildDir
279
+ * @param {string} manifestPath
280
+ * @param {ReleaseArtifactUploadOptions} upload
281
+ * @returns {string[]}
282
+ */
149
283
  function createReleaseArtifactUploadArgs(buildDir, manifestPath, upload) {
150
- const args = ["--build-dir", buildDir, "--manifest", manifestPath, "--endpoint", upload.endpoint];
151
- appendOption(args, "--token-env", upload.tokenEnv);
152
- if (upload.allowHostedUpload) {
153
- args.push("--allow-hosted");
154
- }
155
- if (upload.dryRun) {
156
- args.push("--dry-run");
157
- }
158
- appendOption(args, "--max-retries", upload.maxRetries);
159
- appendOption(args, "--retry-delay", upload.retryDelay);
160
- appendOption(args, "--timeout", upload.timeout);
161
- return args;
284
+ return [
285
+ "--build-dir",
286
+ buildDir,
287
+ "--manifest",
288
+ manifestPath,
289
+ "--endpoint",
290
+ upload.endpoint,
291
+ ...optionArgs("--token-env", upload.tokenEnv),
292
+ ...(upload.allowHostedUpload ? ["--allow-hosted"] : []),
293
+ ...(upload.dryRun ? ["--dry-run"] : []),
294
+ ...optionArgs("--max-retries", upload.maxRetries),
295
+ ...optionArgs("--retry-delay", upload.retryDelay),
296
+ ...optionArgs("--timeout", upload.timeout)
297
+ ];
162
298
  }
163
299
 
300
+ /**
301
+ * @param {string} stdout
302
+ * @param {string} command
303
+ * @returns {unknown}
304
+ */
164
305
  function parseJson(stdout, command) {
165
306
  try {
166
- return JSON.parse(stdout);
307
+ /** @type {unknown} */
308
+ const report = JSON.parse(stdout);
309
+ return report;
167
310
  } catch (error) {
168
311
  throw new Error(`LogBrew release-artifact ${command} did not return JSON`, { cause: error });
169
312
  }
170
313
  }
171
314
 
315
+ /**
316
+ * @param {string} message
317
+ * @returns {string}
318
+ */
172
319
  function sanitizeValidationDiagnostic(message) {
173
320
  const printable = Array.from(message, (character) => {
174
- const codePoint = character.codePointAt(0);
175
- return codePoint <= 31 || codePoint === 127 ? " " : character;
321
+ const codePoint = character.codePointAt(START) ?? START;
322
+ return codePoint <= LAST_CONTROL_CHARACTER || codePoint === DELETE_CHARACTER ? " " : character;
176
323
  }).join("");
177
- return printable.replace(/\s+/gu, " ").trim().slice(0, 200);
324
+ return printable.replaceAll(WHITESPACE_RUN_RE, " ").trim().slice(START, MAX_DIAGNOSTIC_LENGTH);
178
325
  }
179
326
 
327
+ /**
328
+ * @param {unknown} report
329
+ * @returns {string[]}
330
+ */
180
331
  function validationErrors(report) {
181
- if (!report?.validation || !Array.isArray(report.validation.errors)) {
332
+ const errors = field(field(report, "validation"), "errors");
333
+ if (!Array.isArray(errors)) {
182
334
  return [];
183
335
  }
184
- return report.validation.errors
336
+ /** @type {readonly unknown[]} */
337
+ const messages = errors;
338
+ return messages
185
339
  .filter((message) => typeof message === "string")
186
- .map(sanitizeValidationDiagnostic)
340
+ .map((message) => sanitizeValidationDiagnostic(message))
187
341
  .filter((message) => message !== "")
188
- .slice(0, 3);
342
+ .slice(START, MAX_DIAGNOSTICS);
189
343
  }
190
344
 
345
+ /**
346
+ * @param {unknown} report
347
+ * @returns {string | undefined}
348
+ */
191
349
  function safeReportStatus(report) {
192
- const status = report?.status;
193
- return typeof status === "string" && /^[a-z][a-z0-9_]{0,63}$/u.test(status) ? status : null;
350
+ const status = field(report, "status");
351
+ return typeof status === "string" && REPORT_STATUS_RE.test(status) ? status : undefined;
352
+ }
353
+
354
+ /**
355
+ * @param {unknown} output
356
+ * @returns {string}
357
+ */
358
+ function textOutput(output) {
359
+ return typeof output === "string" ? output : "";
194
360
  }
195
361
 
362
+ /**
363
+ * @param {string} cliPath
364
+ * @param {string} command
365
+ * @param {readonly string[]} args
366
+ * @returns {ReleaseArtifactCliResult}
367
+ */
196
368
  function runReleaseArtifactCli(cliPath, command, args) {
197
369
  const result = spawnSync(process.execPath, [cliPath, command, ...args], {
198
370
  cwd: process.cwd(),
199
371
  encoding: "utf8",
200
- windowsHide: true,
372
+ windowsHide: true
201
373
  });
202
- const report = result.stdout ? parseJson(result.stdout, command) : null;
203
- if (result.status !== 0) {
204
- const details = validationErrors(report).join("; ") || safeReportStatus(report) || "execution_failed";
374
+ const stdout = textOutput(result.stdout);
375
+ const report = stdout === "" ? undefined : parseJson(stdout, command);
376
+ if (result.status !== SUCCESS_EXIT_CODE) {
377
+ const errors = validationErrors(report).join("; ");
378
+ const details = errors === "" ? safeReportStatus(report) ?? "execution_failed" : errors;
205
379
  throw new Error(`LogBrew release-artifact ${command} failed: ${details}`);
206
380
  }
207
- return { report, stdout: result.stdout };
381
+ return { report, stdout };
208
382
  }
209
383
 
384
+ /**
385
+ * @param {unknown} report
386
+ * @returns {string}
387
+ */
210
388
  function formatReleaseArtifactUploadSummary(report) {
211
- const artifactCount = Number.isSafeInteger(report?.artifactCount) && report.artifactCount >= 0 ? report.artifactCount : 0;
212
- const artifactLabel = artifactCount === 1 ? "artifact" : "artifacts";
213
- return `LogBrew release artifacts: ${safeReportStatus(report) ?? "unknown"} (${artifactCount} ${artifactLabel})`;
389
+ const count = field(report, "artifactCount");
390
+ const artifactCount = typeof count === "number" && Number.isSafeInteger(count) && count >= START ? count : START;
391
+ const artifactLabel = artifactCount === ONE_ARTIFACT ? "artifact" : "artifacts";
392
+ return `LogBrew release artifacts: ${safeReportStatus(report) ?? "unknown"} (${String(artifactCount)} ${artifactLabel})`;
214
393
  }
215
394
 
216
395
  module.exports = {
@@ -218,5 +397,5 @@ module.exports = {
218
397
  formatReleaseArtifactUploadSummary,
219
398
  normalizeReleaseArtifactProjectId,
220
399
  normalizeReleaseArtifactUploadOptions,
221
- runReleaseArtifactCli,
400
+ runReleaseArtifactCli
222
401
  };