toolcraft 0.0.26 → 0.0.27

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.
@@ -116,6 +116,25 @@ function redactValue(value) {
116
116
  }
117
117
  return `<set, ${value.length} chars>`;
118
118
  }
119
+ function collectStringLeaves(value, output) {
120
+ if (typeof value === "string") {
121
+ if (value.length > 0) {
122
+ output.add(value);
123
+ }
124
+ return;
125
+ }
126
+ if (Array.isArray(value)) {
127
+ for (const entry of value) {
128
+ collectStringLeaves(entry, output);
129
+ }
130
+ return;
131
+ }
132
+ if (isPlainObject(value)) {
133
+ for (const entry of Object.values(value)) {
134
+ collectStringLeaves(entry, output);
135
+ }
136
+ }
137
+ }
119
138
  function schemaSecretValue(schema) {
120
139
  const unwrapped = unwrapOptional(schema);
121
140
  if (unwrapped.kind === "string" || unwrapped.kind === "number") {
@@ -155,6 +174,52 @@ function redactParams(params, command) {
155
174
  }
156
175
  return redactParamsValue(params, command.params, "");
157
176
  }
177
+ function collectSensitiveParamValues(value, schema, name, output) {
178
+ if (shouldRedactParam(name, schema)) {
179
+ collectStringLeaves(value, output);
180
+ return;
181
+ }
182
+ const unwrapped = unwrapOptional(schema);
183
+ if (unwrapped.kind === "object" && isPlainObject(value)) {
184
+ for (const [key, childValue] of Object.entries(value)) {
185
+ const childSchema = unwrapped.shape[key];
186
+ if (childSchema !== undefined) {
187
+ collectSensitiveParamValues(childValue, childSchema, key, output);
188
+ }
189
+ }
190
+ return;
191
+ }
192
+ if (unwrapped.kind === "array" && Array.isArray(value)) {
193
+ for (const entry of value) {
194
+ collectSensitiveParamValues(entry, unwrapped.item, name, output);
195
+ }
196
+ }
197
+ }
198
+ function createReportStringRedactor(context, env) {
199
+ const values = new Set();
200
+ for (const value of Object.values(context.secrets ?? {})) {
201
+ if (value !== undefined && value.length > 0) {
202
+ values.add(value);
203
+ }
204
+ }
205
+ for (const [name, secret] of Object.entries(context.command?.secrets ?? {})) {
206
+ const value = context.secrets?.[name] ?? env[secret.env];
207
+ if (value !== undefined && value.length > 0) {
208
+ values.add(value);
209
+ }
210
+ }
211
+ if (context.command !== undefined) {
212
+ collectSensitiveParamValues(context.params, context.command.params, "", values);
213
+ }
214
+ const orderedValues = [...values].sort((left, right) => right.length - left.length);
215
+ return (value) => {
216
+ let redacted = value;
217
+ for (const secretValue of orderedValues) {
218
+ redacted = redacted.split(secretValue).join("<redacted>");
219
+ }
220
+ return redacted;
221
+ };
222
+ }
158
223
  function commandSecretEnvNames(secrets) {
159
224
  if (secrets === undefined) {
160
225
  return [];
@@ -203,7 +268,7 @@ function redactArgv(argv, options) {
203
268
  function stableJson(value) {
204
269
  return JSON.stringify(value, null, 2) ?? "undefined";
205
270
  }
206
- function redactStructuredErrorField(name, value) {
271
+ function redactStructuredErrorField(name, value, redactString) {
207
272
  if (typeof value === "string") {
208
273
  const redactedHeaderValue = redactHttpHeaderValue(name, value);
209
274
  if (redactedHeaderValue !== value) {
@@ -212,23 +277,27 @@ function redactStructuredErrorField(name, value) {
212
277
  if (isSensitiveName(name)) {
213
278
  return "<redacted>";
214
279
  }
280
+ return redactString(value);
215
281
  }
216
282
  if (Array.isArray(value)) {
217
- return value.map((entry) => redactStructuredErrorField(name, entry));
283
+ return value.map((entry) => redactStructuredErrorField(name, entry, redactString));
218
284
  }
219
285
  if (isPlainObject(value)) {
220
- return Object.fromEntries(Object.entries(value).map(([key, entry]) => [key, redactStructuredErrorField(key, entry)]));
286
+ return Object.fromEntries(Object.entries(value).map(([key, entry]) => [
287
+ key,
288
+ redactStructuredErrorField(key, entry, redactString)
289
+ ]));
221
290
  }
222
291
  return value;
223
292
  }
224
- function ownStructuredFields(error) {
293
+ function ownStructuredFields(error, redactString) {
225
294
  const fields = {};
226
295
  for (const key of Object.keys(error)) {
227
296
  if (key === "name" || key === "message" || key === "stack" || key === "cause") {
228
297
  continue;
229
298
  }
230
299
  Object.defineProperty(fields, key, {
231
- value: redactStructuredErrorField(key, error[key]),
300
+ value: redactStructuredErrorField(key, error[key], redactString),
232
301
  enumerable: true,
233
302
  configurable: true,
234
303
  writable: true
@@ -236,47 +305,47 @@ function ownStructuredFields(error) {
236
305
  }
237
306
  return fields;
238
307
  }
239
- function formatStackChain(error) {
308
+ function formatStackChain(error, redactString) {
240
309
  const lines = [];
241
310
  let current = error;
242
311
  let index = 0;
243
312
  while (current !== undefined) {
244
313
  if (current instanceof Error) {
245
- lines.push(index === 0
246
- ? (current.stack ?? String(current))
247
- : `Caused by: ${current.stack ?? String(current)}`);
314
+ const stack = current.stack ?? String(current);
315
+ lines.push(redactString(index === 0 ? stack : `Caused by: ${stack}`));
248
316
  current = current.cause;
249
317
  }
250
318
  else {
251
- lines.push(index === 0 ? String(current) : `Caused by: ${String(current)}`);
319
+ const message = String(current);
320
+ lines.push(redactString(index === 0 ? message : `Caused by: ${message}`));
252
321
  current = undefined;
253
322
  }
254
323
  index += 1;
255
324
  }
256
325
  return lines.join("\n");
257
326
  }
258
- function formatHeaderValue(name, value) {
259
- return redactHttpHeaderValue(name, value);
327
+ function formatHeaderValue(name, value, redactString) {
328
+ return redactString(redactHttpHeaderValue(name, value));
260
329
  }
261
- function formatHeaders(headers) {
330
+ function formatHeaders(headers, redactString) {
262
331
  return Object.entries(headers)
263
- .map(([name, value]) => `${name}: ${formatHeaderValue(name, value)}`)
332
+ .map(([name, value]) => `${name}: ${formatHeaderValue(name, value, redactString)}`)
264
333
  .join("\n");
265
334
  }
266
- function formatBody(body) {
335
+ function formatBody(body, redactString) {
267
336
  const redactedBody = redactHttpBody(body);
268
337
  if (typeof redactedBody === "string") {
269
- return redactedBody;
338
+ return redactString(redactedBody);
270
339
  }
271
- return stableJson(redactedBody);
340
+ return redactString(stableJson(redactedBody));
272
341
  }
273
- function formatHttpTranscript(error) {
342
+ function formatHttpTranscript(error, redactString) {
274
343
  const requestLines = [
275
344
  `${error.request.method} ${error.request.url}`,
276
- formatHeaders(error.request.headers)
345
+ formatHeaders(error.request.headers, redactString)
277
346
  ].filter((line) => line.length > 0);
278
347
  if (error.request.body !== undefined) {
279
- requestLines.push("", formatBody(error.request.body));
348
+ requestLines.push("", formatBody(error.request.body, redactString));
280
349
  }
281
350
  return [
282
351
  "Request:",
@@ -284,9 +353,9 @@ function formatHttpTranscript(error) {
284
353
  "",
285
354
  "Response:",
286
355
  `${error.response.status} ${error.response.statusText}`,
287
- formatHeaders(error.response.headers),
356
+ formatHeaders(error.response.headers, redactString),
288
357
  "",
289
- formatBody(error.response.body)
358
+ formatBody(error.response.body, redactString)
290
359
  ].join("\n");
291
360
  }
292
361
  function resolveToolcraftVersion(version) {
@@ -297,9 +366,10 @@ function resolveToolcraftVersion(version) {
297
366
  function buildReport(context) {
298
367
  const env = context.env ?? process.env;
299
368
  const error = context.error;
369
+ const redactString = createReportStringRedactor(context, env);
300
370
  const errorName = error instanceof Error ? error.name : typeof error;
301
- const errorMessage = error instanceof Error ? error.message : String(error);
302
- const structuredFields = error instanceof Error ? ownStructuredFields(error) : {};
371
+ const errorMessage = redactString(error instanceof Error ? error.message : String(error));
372
+ const structuredFields = error instanceof Error ? ownStructuredFields(error, redactString) : {};
303
373
  const secretLines = Object.entries(context.command?.secrets ?? {}).map(([name, secret]) => {
304
374
  const value = context.secrets?.[name] ?? env[secret.env];
305
375
  return `${secret.env}=${redactValue(value)}`;
@@ -313,7 +383,7 @@ function buildReport(context) {
313
383
  `platform: ${process.platform} ${process.arch}`,
314
384
  "",
315
385
  "Argv",
316
- stableJson(redactArgv(context.argv, { command: context.command, secrets: context.secrets })),
386
+ redactString(stableJson(redactArgv(context.argv, { command: context.command, secrets: context.secrets }))),
317
387
  "",
318
388
  "Resolved Secrets",
319
389
  ...(secretLines.length === 0 ? ["<none>"] : secretLines),
@@ -324,19 +394,19 @@ function buildReport(context) {
324
394
  : context.commandPath,
325
395
  "",
326
396
  "Parsed Params",
327
- stableJson(redactParams(context.params, context.command)),
397
+ redactString(stableJson(redactParams(context.params, context.command))),
328
398
  "",
329
399
  "Error",
330
400
  `name: ${errorName}`,
331
401
  `message: ${errorMessage}`,
332
402
  "structured fields:",
333
- stableJson(structuredFields),
403
+ redactString(stableJson(structuredFields)),
334
404
  "",
335
405
  "Stack",
336
- formatStackChain(error)
406
+ formatStackChain(error, redactString)
337
407
  ];
338
408
  if (hasHttpContext(error)) {
339
- lines.push("", "HTTP Transcript", formatHttpTranscript(error));
409
+ lines.push("", "HTTP Transcript", formatHttpTranscript(error, redactString));
340
410
  }
341
411
  return `${lines.join("\n")}\n`;
342
412
  }
@@ -126,6 +126,14 @@ function buildVerifyReport(lookup, opts) {
126
126
  export async function syncGhProject(opts) {
127
127
  const client = resolveGhClient(opts);
128
128
  let lookup = await lookupProject(client, opts.owner, opts.number);
129
+ const initialReport = buildVerifyReport(lookup, opts);
130
+ if (initialReport.ok || opts.yes !== true) {
131
+ return {
132
+ ...initialReport,
133
+ created: [],
134
+ updated: []
135
+ };
136
+ }
129
137
  let resolvedNumber = opts.number;
130
138
  const created = [];
131
139
  if (lookup.project === null) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "toolcraft",
3
- "version": "0.0.26",
3
+ "version": "0.0.27",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -45,7 +45,7 @@
45
45
  "dependencies": {
46
46
  "@clack/core": "^1.0.0",
47
47
  "@clack/prompts": "^1.0.0",
48
- "toolcraft-schema": "0.0.26",
48
+ "toolcraft-schema": "0.0.27",
49
49
  "commander": "^14.0.3",
50
50
  "jose": "^6.1.2",
51
51
  "jsonc-parser": "^3.3.1",