@cedarjs/cli-helpers 8.0.0-canary.3592 → 8.0.0-canary.3597

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.
@@ -1,4 +1,5 @@
1
1
  import type { AttributeValue, Span } from '@opentelemetry/api';
2
+ export { redactError, redactErrorMessage } from './redact.js';
2
3
  type TelemetryAttributes = {
3
4
  [key: string]: AttributeValue | undefined;
4
5
  };
@@ -12,9 +13,12 @@ export declare function recordTelemetryAttributes(attributes: TelemetryAttribute
12
13
  /**
13
14
  * Safely records an error to the opentelemetry span
14
15
  *
16
+ * Only a redacted version of the error is recorded: the first line of the
17
+ * message, with URLs and file paths replaced by placeholders, and no stack
18
+ * trace. See `redactErrorMessage`.
19
+ *
15
20
  * @param error An error to record to the span
16
21
  * @param span An optional span to record the error to. If not provided, the current active span will be used
17
22
  */
18
- export declare function recordTelemetryError(error: any, span?: Span): void;
19
- export {};
23
+ export declare function recordTelemetryError(error: unknown, span?: Span): void;
20
24
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAG9D,KAAK,mBAAmB,GAAG;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAA;CAC1C,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,mBAAmB,EAC/B,IAAI,CAAC,EAAE,IAAI,QAaZ;AAED;;;;;GAKG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,GAAG,EAAE,IAAI,CAAC,EAAE,IAAI,QAe3D"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/telemetry/index.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,IAAI,EAAE,MAAM,oBAAoB,CAAA;AAK9D,OAAO,EAAE,WAAW,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAA;AAE7D,KAAK,mBAAmB,GAAG;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,cAAc,GAAG,SAAS,CAAA;CAC1C,CAAA;AAED;;;;;GAKG;AACH,wBAAgB,yBAAyB,CACvC,UAAU,EAAE,mBAAmB,EAC/B,IAAI,CAAC,EAAE,IAAI,QAaZ;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,OAAO,EAAE,IAAI,CAAC,EAAE,IAAI,QAa/D"}
@@ -1,4 +1,6 @@
1
1
  import opentelemetry, { SpanStatusCode } from "@opentelemetry/api";
2
+ import { redactError } from "./redact.js";
3
+ import { redactError as redactError2, redactErrorMessage } from "./redact.js";
2
4
  function recordTelemetryAttributes(attributes, span) {
3
5
  const spanToRecord = span ?? opentelemetry.trace.getActiveSpan();
4
6
  if (spanToRecord === void 0) {
@@ -15,15 +17,16 @@ function recordTelemetryError(error, span) {
15
17
  if (spanToRecord === void 0) {
16
18
  return;
17
19
  }
18
- const message = error?.message ?? error?.toString() ?? "Unknown error";
19
- const firstLineOfError = message.split("\n")[0];
20
+ const redacted = redactError(error ?? "Unknown error");
20
21
  spanToRecord.setStatus({
21
22
  code: SpanStatusCode.ERROR,
22
- message: firstLineOfError
23
+ message: redacted.message
23
24
  });
24
- spanToRecord.recordException(error ?? new Error(firstLineOfError));
25
+ spanToRecord.recordException(redacted);
25
26
  }
26
27
  export {
27
28
  recordTelemetryAttributes,
28
- recordTelemetryError
29
+ recordTelemetryError,
30
+ redactError2 as redactError,
31
+ redactErrorMessage
29
32
  };
@@ -0,0 +1,27 @@
1
+ /**
2
+ * Turns an error (or any thrown value) into a message that is safe to send
3
+ * with telemetry.
4
+ *
5
+ * Only the first line is kept, because some errors include the full stack
6
+ * trace in the message. Then every word that could identify a user, their
7
+ * project or their infrastructure is replaced: anything containing `/`, `\`,
8
+ * `@` or `:`, and IPv4 addresses. Replacements are labelled `[url]`, `[host]`,
9
+ * `[path]` or `[redacted]`. Quoted paths are replaced as a whole, since they
10
+ * can contain spaces, and so are hostnames in DNS errors.
11
+ *
12
+ * Words that describe the failure without identifying anyone are kept:
13
+ * scoped package names (`@cedarjs/vite`), known specifiers (`node:fs`),
14
+ * version numbers, clock times and words that only end in a colon (`Error:`).
15
+ * What is left is the shape of the error, e.g.
16
+ * `ENOENT: no such file or directory, open '[path]'`.
17
+ *
18
+ * This module has no imports so it can be bundled into create-cedar-app
19
+ * without pulling in anything else.
20
+ */
21
+ export declare function redactErrorMessage(error: unknown): string;
22
+ /**
23
+ * Returns a new Error that only carries the redacted message of `error`.
24
+ * The original stack trace is left out, since it contains file paths.
25
+ */
26
+ export declare function redactError(error: unknown): Error;
27
+ //# sourceMappingURL=redact.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"redact.d.ts","sourceRoot":"","sources":["../../src/telemetry/redact.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,KAAK,EAAE,OAAO,GAAG,MAAM,CAwBzD;AA2FD;;;GAGG;AACH,wBAAgB,WAAW,CAAC,KAAK,EAAE,OAAO,GAAG,KAAK,CAYjD"}
@@ -0,0 +1,78 @@
1
+ function redactErrorMessage(error) {
2
+ const message = error instanceof Error ? error.message : typeof error === "string" ? error : String(error);
3
+ return (message.split("\n")[0] || "Unknown error").replace(
4
+ /(['"`])([^'"`\\/\n]*[\\/][^'"`\n]*)\1/g,
5
+ (_match, quote, inner) => `${quote}${isSafeWord(inner) ? inner : placeholderFor(inner)}${quote}`
6
+ ).replace(/\b(ENOTFOUND|EAI_AGAIN)\s+[^\s'"`]+/g, "$1 [host]").replace(/\S+/g, redactWord);
7
+ }
8
+ const SCOPED_PACKAGE = /^@[a-z0-9~-][\w.~-]*\/[a-z0-9~-][\w.~-]*(?:\/[\w.-]+)*(?:@[\w.^~*<>=-]+)?$/i;
9
+ const KNOWN_SPECIFIER = /^(?:node|npm|workspace|catalog):[\w.^~*<>=-]*$/;
10
+ const CLOCK_TIME = /^\d{1,2}:\d{2}(?::\d{2})?$/;
11
+ const IPV4 = /(?:^|[^\d.])\d{1,3}(?:\.\d{1,3}){3}(?![.\d])/;
12
+ const IPV6 = /^\[?[\da-f]*:[\da-f]*:[\da-f:]*\]?(?::\d+)?$/i;
13
+ const HOST_AND_PORT = /^[\w.-]+:\d+$/;
14
+ const LEADING_PUNCTUATION = /* @__PURE__ */ new Set(["(", "'", '"', "`", "<", "[", "{"]);
15
+ const TRAILING_PUNCTUATION = /* @__PURE__ */ new Set([
16
+ ")",
17
+ "'",
18
+ '"',
19
+ "`",
20
+ ">",
21
+ "]",
22
+ "}",
23
+ ",",
24
+ ";",
25
+ ".",
26
+ "!",
27
+ "?"
28
+ ]);
29
+ function isSafeWord(word) {
30
+ if (!/[/\\@:]/.test(word) && !IPV4.test(word)) {
31
+ return true;
32
+ }
33
+ return SCOPED_PACKAGE.test(word) || KNOWN_SPECIFIER.test(word) || word.startsWith("npm:") && SCOPED_PACKAGE.test(word.slice(4)) || CLOCK_TIME.test(word);
34
+ }
35
+ function placeholderFor(word) {
36
+ if (word.includes("://")) {
37
+ return "[url]";
38
+ }
39
+ if (IPV4.test(word) || IPV6.test(word) || HOST_AND_PORT.test(word)) {
40
+ return "[host]";
41
+ }
42
+ if (/[/\\]/.test(word)) {
43
+ return "[path]";
44
+ }
45
+ return "[redacted]";
46
+ }
47
+ function redactWord(word) {
48
+ let start = 0;
49
+ let end = word.length;
50
+ while (end > start && TRAILING_PUNCTUATION.has(word[end - 1])) {
51
+ end--;
52
+ }
53
+ const withoutTrailing = word.slice(0, end);
54
+ if (!CLOCK_TIME.test(withoutTrailing) && IPV6.test(withoutTrailing)) {
55
+ return "[host]" + word.slice(end);
56
+ }
57
+ while (start < end && LEADING_PUNCTUATION.has(word[start])) {
58
+ start++;
59
+ }
60
+ const core = word.slice(start, end);
61
+ const checked = core.endsWith(":") ? core.slice(0, -1) : core;
62
+ if (checked === "" || isSafeWord(checked)) {
63
+ return word;
64
+ }
65
+ return word.slice(0, start) + placeholderFor(core) + word.slice(end);
66
+ }
67
+ function redactError(error) {
68
+ const redacted = new Error(redactErrorMessage(error));
69
+ if (error instanceof Error) {
70
+ redacted.name = error.name;
71
+ }
72
+ redacted.stack = `${redacted.name}: ${redacted.message}`;
73
+ return redacted;
74
+ }
75
+ export {
76
+ redactError,
77
+ redactErrorMessage
78
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@cedarjs/cli-helpers",
3
- "version": "8.0.0-canary.3592",
3
+ "version": "8.0.0-canary.3597",
4
4
  "repository": {
5
5
  "type": "git",
6
6
  "url": "git+https://github.com/cedarjs/cedar.git",
@@ -125,8 +125,8 @@
125
125
  "test:watch": "vitest watch"
126
126
  },
127
127
  "dependencies": {
128
- "@cedarjs/project-config": "8.0.0-canary.3592",
129
- "@cedarjs/telemetry": "8.0.0-canary.3592",
128
+ "@cedarjs/project-config": "8.0.0-canary.3597",
129
+ "@cedarjs/telemetry": "8.0.0-canary.3597",
130
130
  "@listr2/prompt-adapter-enquirer": "4.4.3",
131
131
  "@opentelemetry/api": "1.9.1",
132
132
  "ansis": "4.4.0",
@@ -146,7 +146,7 @@
146
146
  "yargs-parser": "21.1.1"
147
147
  },
148
148
  "devDependencies": {
149
- "@cedarjs/framework-tools": "8.0.0-canary.3592",
149
+ "@cedarjs/framework-tools": "8.0.0-canary.3597",
150
150
  "@types/dotenv-defaults": "^5.0.0",
151
151
  "@types/lodash": "4.17.27",
152
152
  "@types/pascalcase": "1.0.3",