@naturalcycles/nodejs-lib 15.107.3 → 15.108.0

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.
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/bin/generate-build-info.js'
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/bin/json2env.js'
package/bin/kpy.js ADDED
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/bin/kpy.js'
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/bin/secrets-decrypt.js'
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/bin/secrets-encrypt.js'
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/bin/secrets-gen-key.js'
@@ -0,0 +1,2 @@
1
+ #!/usr/bin/env node
2
+ import '../dist/bin/slack-this.js'
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export {};
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import fs from 'node:fs';
3
2
  import path from 'node:path';
4
3
  import { appendToBashEnv, appendToGithubEnv, appendToGithubOutput } from '../fs/json2env.js';
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export {};
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import { json2env } from '../fs/json2env.js';
3
2
  import { runScript } from '../script/runScript.js';
4
3
  import { _yargs } from '../yargs/yargs.util.js';
package/dist/bin/kpy.d.ts CHANGED
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export {};
package/dist/bin/kpy.js CHANGED
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import { kpySync } from '../fs/kpy.js';
3
2
  import { runScript } from '../script/runScript.js';
4
3
  import { _yargs } from '../yargs/yargs.util.js';
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export {};
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import { dimGrey } from '../colors/colors.js';
3
2
  import { runScript } from '../script/runScript.js';
4
3
  import { secretsDecrypt } from '../secret/secrets-decrypt.util.js';
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export {};
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import { dimGrey } from '../colors/colors.js';
3
2
  import { runScript } from '../script/runScript.js';
4
3
  import { secretsEncrypt } from '../secret/secrets-encrypt.util.js';
@@ -1,2 +1 @@
1
- #!/usr/bin/env node
2
1
  export {};
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import { randomBytes } from 'node:crypto';
3
2
  import { dimGrey } from '../colors/colors.js';
4
3
  import { runScript } from '../script/runScript.js';
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  declare global {
3
2
  namespace NodeJS {
4
3
  interface ProcessEnv {
@@ -1,4 +1,3 @@
1
- #!/usr/bin/env node
2
1
  import { runScript } from '../script/runScript.js';
3
2
  import { SlackService } from '../slack/index.js';
4
3
  import { _yargs } from '../yargs/yargs.util.js';
@@ -1180,7 +1180,7 @@ function executeValidation(fn, builtSchema, input, opt = {}, defaultInputName) {
1180
1180
  const dataVar = [inputName, inputId].filter(Boolean).join('.');
1181
1181
  // Build fingerprint before applyImprovementsOnErrorMessages: after it, /items/0/name becomes
1182
1182
  // .items[0].name, embedding the index into the segment and making it harder to strip without regex
1183
- const fingerprint = buildAjvErrorFingerprint(errors[0], inputName);
1183
+ const fingerprint = buildAjvErrorFingerprint(errors[0], inputName, resolveCustomErrorMessage(builtSchema, errors[0]));
1184
1184
  applyImprovementsOnErrorMessages(errors, builtSchema);
1185
1185
  let message = getAjv().errorsText(errors, {
1186
1186
  dataVar,
@@ -1204,33 +1204,46 @@ function applyImprovementsOnErrorMessages(errors, schema) {
1204
1204
  if (!errors)
1205
1205
  return;
1206
1206
  filterNullableAnyOfErrors(errors, schema);
1207
- const { errorMessages } = schema;
1208
1207
  for (const error of errors) {
1209
- const errorMessage = getErrorMessageForInstancePath(schema, error.instancePath, error.keyword);
1210
- if (errorMessage) {
1211
- error.message = errorMessage;
1212
- }
1213
- else if (errorMessages?.[error.keyword]) {
1214
- error.message = errorMessages[error.keyword];
1215
- }
1216
- else {
1217
- const unwrapped = unwrapNullableAnyOf(schema);
1218
- if (unwrapped?.errorMessages?.[error.keyword]) {
1219
- error.message = unwrapped.errorMessages[error.keyword];
1220
- }
1208
+ const customMessage = resolveCustomErrorMessage(schema, error);
1209
+ if (customMessage) {
1210
+ error.message = customMessage;
1211
+ // A custom `msg` signals "the underlying rule param is an implementation detail".
1212
+ // Drop params so consumers (e.g. HTTP responses, Sentry payloads) don't surface
1213
+ // the raw regex/limit/etc. — the custom message is now the canonical rule label.
1214
+ error.params = {};
1221
1215
  }
1222
1216
  error.instancePath = error.instancePath.replaceAll(/\/(\d+)/g, `[$1]`).replaceAll('/', '.');
1223
1217
  }
1224
1218
  }
1219
+ /**
1220
+ * Looks up the user-provided custom error message (set via `{ msg }` / `{ name }`)
1221
+ * for a given AJV error, walking the schema along the error's instancePath and
1222
+ * falling back to top-level `errorMessages` (including through nullable wrappers).
1223
+ * Returns undefined if no custom message was registered for the failing keyword.
1224
+ */
1225
+ function resolveCustomErrorMessage(schema, error) {
1226
+ const byPath = getErrorMessageForInstancePath(schema, error.instancePath, error.keyword);
1227
+ if (byPath)
1228
+ return byPath;
1229
+ if (schema.errorMessages?.[error.keyword])
1230
+ return schema.errorMessages[error.keyword];
1231
+ const unwrapped = unwrapNullableAnyOf(schema);
1232
+ return unwrapped?.errorMessages?.[error.keyword];
1233
+ }
1225
1234
  /**
1226
1235
  * Groups repeated validation errors by rule rather than by unique request content.
1227
1236
  * Excludes instance-specific data like record IDs and array indices.
1237
+ *
1238
+ * When a custom error message is registered for the failing rule, prefer it over
1239
+ * the raw param value (e.g. regex source) — it is both stable across regex tweaks
1240
+ * and avoids leaking the underlying pattern into Sentry fingerprints.
1228
1241
  */
1229
- function buildAjvErrorFingerprint(e, inputName) {
1230
- const value = Object.values(e.params || {})[0];
1242
+ function buildAjvErrorFingerprint(e, inputName, customMessage) {
1243
+ const ruleValue = customMessage ?? Object.values(e.params || {})[0];
1231
1244
  let rule = e.keyword;
1232
- if (value !== undefined)
1233
- rule += `:${value}`;
1245
+ if (ruleValue !== undefined)
1246
+ rule += `:${ruleValue}`;
1234
1247
  const path = e.instancePath
1235
1248
  .split('/')
1236
1249
  .filter(s => s && isNaN(Number(s)))
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@naturalcycles/nodejs-lib",
3
3
  "type": "module",
4
- "version": "15.107.3",
4
+ "version": "15.108.0",
5
5
  "dependencies": {
6
6
  "@naturalcycles/js-lib": "^15",
7
7
  "@standard-schema/spec": "^1",
@@ -41,15 +41,16 @@
41
41
  "./zip": "./dist/zip/zip2.js"
42
42
  },
43
43
  "bin": {
44
- "kpy": "dist/bin/kpy.js",
45
- "json2env": "dist/bin/json2env.js",
46
- "generate-build-info": "dist/bin/generate-build-info.js",
47
- "slack-this": "dist/bin/slack-this.js",
48
- "secrets-gen-key": "dist/bin/secrets-gen-key.js",
49
- "secrets-encrypt": "dist/bin/secrets-encrypt.js",
50
- "secrets-decrypt": "dist/bin/secrets-decrypt.js"
44
+ "kpy": "bin/kpy.js",
45
+ "json2env": "bin/json2env.js",
46
+ "generate-build-info": "bin/generate-build-info.js",
47
+ "slack-this": "bin/slack-this.js",
48
+ "secrets-gen-key": "bin/secrets-gen-key.js",
49
+ "secrets-encrypt": "bin/secrets-encrypt.js",
50
+ "secrets-decrypt": "bin/secrets-decrypt.js"
51
51
  },
52
52
  "files": [
53
+ "bin",
53
54
  "dist",
54
55
  "src",
55
56
  "!src/test",
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  import fs from 'node:fs'
4
2
  import path from 'node:path'
5
3
  import type { UnixTimestamp } from '@naturalcycles/js-lib/types'
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  import { json2env } from '../fs/json2env.js'
4
2
  import { runScript } from '../script/runScript.js'
5
3
  import { _yargs } from '../yargs/yargs.util.js'
package/src/bin/kpy.ts CHANGED
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  import { kpySync } from '../fs/kpy.js'
4
2
  import { runScript } from '../script/runScript.js'
5
3
  import { _yargs } from '../yargs/yargs.util.js'
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  import { dimGrey } from '../colors/colors.js'
4
2
  import { runScript } from '../script/runScript.js'
5
3
  import type { DecryptCLIOptions } from '../secret/secrets-decrypt.util.js'
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  import { dimGrey } from '../colors/colors.js'
4
2
  import { runScript } from '../script/runScript.js'
5
3
  import type { EncryptCLIOptions } from '../secret/secrets-encrypt.util.js'
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  import { randomBytes } from 'node:crypto'
4
2
  import { dimGrey } from '../colors/colors.js'
5
3
  import { runScript } from '../script/runScript.js'
@@ -1,5 +1,3 @@
1
- #!/usr/bin/env node
2
-
3
1
  import { runScript } from '../script/runScript.js'
4
2
  import { SlackService } from '../slack/index.js'
5
3
  import { _yargs } from '../yargs/yargs.util.js'
@@ -1694,7 +1694,11 @@ function executeValidation<OUT>(
1694
1694
 
1695
1695
  // Build fingerprint before applyImprovementsOnErrorMessages: after it, /items/0/name becomes
1696
1696
  // .items[0].name, embedding the index into the segment and making it harder to strip without regex
1697
- const fingerprint = buildAjvErrorFingerprint(errors[0], inputName)
1697
+ const fingerprint = buildAjvErrorFingerprint(
1698
+ errors[0],
1699
+ inputName,
1700
+ resolveCustomErrorMessage(builtSchema, errors[0]),
1701
+ )
1698
1702
 
1699
1703
  applyImprovementsOnErrorMessages(errors, builtSchema)
1700
1704
 
@@ -1731,34 +1735,51 @@ function applyImprovementsOnErrorMessages(
1731
1735
 
1732
1736
  filterNullableAnyOfErrors(errors, schema)
1733
1737
 
1734
- const { errorMessages } = schema
1735
-
1736
1738
  for (const error of errors) {
1737
- const errorMessage = getErrorMessageForInstancePath(schema, error.instancePath, error.keyword)
1738
-
1739
- if (errorMessage) {
1740
- error.message = errorMessage
1741
- } else if (errorMessages?.[error.keyword]) {
1742
- error.message = errorMessages[error.keyword]
1743
- } else {
1744
- const unwrapped = unwrapNullableAnyOf(schema)
1745
- if (unwrapped?.errorMessages?.[error.keyword]) {
1746
- error.message = unwrapped.errorMessages[error.keyword]
1747
- }
1739
+ const customMessage = resolveCustomErrorMessage(schema, error)
1740
+
1741
+ if (customMessage) {
1742
+ error.message = customMessage
1743
+ // A custom `msg` signals "the underlying rule param is an implementation detail".
1744
+ // Drop params so consumers (e.g. HTTP responses, Sentry payloads) don't surface
1745
+ // the raw regex/limit/etc. — the custom message is now the canonical rule label.
1746
+ error.params = {}
1748
1747
  }
1749
1748
 
1750
1749
  error.instancePath = error.instancePath.replaceAll(/\/(\d+)/g, `[$1]`).replaceAll('/', '.')
1751
1750
  }
1752
1751
  }
1753
1752
 
1753
+ /**
1754
+ * Looks up the user-provided custom error message (set via `{ msg }` / `{ name }`)
1755
+ * for a given AJV error, walking the schema along the error's instancePath and
1756
+ * falling back to top-level `errorMessages` (including through nullable wrappers).
1757
+ * Returns undefined if no custom message was registered for the failing keyword.
1758
+ */
1759
+ function resolveCustomErrorMessage(schema: JsonSchema, error: ErrorObject): string | undefined {
1760
+ const byPath = getErrorMessageForInstancePath(schema, error.instancePath, error.keyword)
1761
+ if (byPath) return byPath
1762
+ if (schema.errorMessages?.[error.keyword]) return schema.errorMessages[error.keyword]
1763
+ const unwrapped = unwrapNullableAnyOf(schema)
1764
+ return unwrapped?.errorMessages?.[error.keyword]
1765
+ }
1766
+
1754
1767
  /**
1755
1768
  * Groups repeated validation errors by rule rather than by unique request content.
1756
1769
  * Excludes instance-specific data like record IDs and array indices.
1770
+ *
1771
+ * When a custom error message is registered for the failing rule, prefer it over
1772
+ * the raw param value (e.g. regex source) — it is both stable across regex tweaks
1773
+ * and avoids leaking the underlying pattern into Sentry fingerprints.
1757
1774
  */
1758
- function buildAjvErrorFingerprint(e: ErrorObject, inputName: string): string {
1759
- const value = Object.values(e.params || {})[0]
1775
+ function buildAjvErrorFingerprint(
1776
+ e: ErrorObject,
1777
+ inputName: string,
1778
+ customMessage: string | undefined,
1779
+ ): string {
1780
+ const ruleValue = customMessage ?? Object.values(e.params || {})[0]
1760
1781
  let rule = e.keyword
1761
- if (value !== undefined) rule += `:${value}`
1782
+ if (ruleValue !== undefined) rule += `:${ruleValue}`
1762
1783
  const path = e.instancePath
1763
1784
  .split('/')
1764
1785
  .filter(s => s && isNaN(Number(s)))