@scrymore/scry-deployer 0.6.1-nightly.20260925022633 → 0.6.1

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.
package/bin/cli.js CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const { initTelemetry, captureCliError, flushTelemetry } = require('../lib/telemetry.js');
3
+ const { initTelemetry, captureCliError, flushTelemetry, redactArgv } = require('../lib/telemetry.js');
4
4
  const yargs = require('yargs/yargs');
5
5
  const { hideBin } = require('yargs/helpers');
6
6
  const fs = require('fs');
@@ -25,7 +25,8 @@ const { resolveBuildGitContext } = require('../lib/gitContext.js');
25
25
  async function runAnalysis(argv) {
26
26
  const logger = createLogger(argv);
27
27
  logger.info('📊 Starting Storybook analysis...');
28
- logger.debug(`Received arguments: ${JSON.stringify(argv)}`);
28
+ // Credentials masked: this line is also a Sentry breadcrumb.
29
+ logger.debug(`Received arguments: ${JSON.stringify(redactArgv(argv))}`);
29
30
 
30
31
  const outPath = path.join(os.tmpdir(), `storybook-analysis-${Date.now()}.zip`);
31
32
 
@@ -85,7 +86,8 @@ async function runAnalysis(argv) {
85
86
  async function runDeployment(argv) {
86
87
  const logger = createLogger(argv);
87
88
  logger.info('🚀 Starting deployment...');
88
- logger.debug(`Received arguments: ${JSON.stringify(argv)}`);
89
+ // Credentials masked: this line is also a Sentry breadcrumb.
90
+ logger.debug(`Received arguments: ${JSON.stringify(redactArgv(argv))}`);
89
91
 
90
92
  const outPath = path.join(os.tmpdir(), `storybook-deployment-${Date.now()}.zip`);
91
93
  let metadataZipPath = null;
package/lib/telemetry.js CHANGED
@@ -21,6 +21,8 @@ const Sentry = require('@sentry/node');
21
21
  * None of that is needed to know that an upload failed with EAI_AGAIN.
22
22
  */
23
23
 
24
+ const { version: PACKAGE_VERSION } = require('../package.json');
25
+
24
26
  const DSN =
25
27
  'https://c66ce229a1db2289f145eebd02436d9c@o4507889391828992.ingest.us.sentry.io/4510699330732032';
26
28
 
@@ -41,8 +43,58 @@ const SECRET_PATTERNS = [
41
43
  [/scry_proj_[A-Za-z0-9_\-]+/g, 'scry_proj_<redacted>'],
42
44
  [/(X-Amz-Signature=)[^&\s]+/gi, '$1<redacted>'],
43
45
  [/(Bearer\s+)[A-Za-z0-9._\-]+/gi, '$1<redacted>'],
46
+ // A credential flag echoed on a command line: --api-key X, --api-key=X,
47
+ // -k X, --token X, --openai-api-key X, ...
48
+ [/(--[\w-]*(?:api-key|apikey|token|secret|password)[\w-]*[=\s]+|(?:^|\s)-k[=\s]+)(?!<redacted>)[^\s"',]+/gi, '$1<redacted>'],
49
+ // A credential field inside serialized JSON: "apiKey":"X", "token": "X".
50
+ [/("[\w-]*(?:api[-_]?key|token|secret|password)[\w-]*"\s*:\s*)"[^"]*"/gi, '$1"<redacted>"'],
51
+ // An env echo: SCRY_API_KEY=X, STORYBOOK_DEPLOYER_API_KEY=X, JINA_API_KEY=X.
52
+ [/(\b[A-Z][A-Z0-9_]*(?:KEY|TOKEN|SECRET|PASSWORD)[A-Z0-9_]*=)(?!<redacted>)[^\s"',]+/g, '$1<redacted>'],
44
53
  ];
45
54
 
55
+ /** Field names whose values are credentials, in any spelling yargs produces. */
56
+ const SECRET_FIELD = /(^k$|api[-_]?key|token|secret|password)/i;
57
+
58
+ /**
59
+ * A copy of argv with every credential-named field masked, at any depth.
60
+ *
61
+ * For logging argv whole (the --verbose "Received arguments" line), which is
62
+ * also a Sentry breadcrumb. Unlike sanitizeArgv this keeps every field, so it
63
+ * must only be used for local diagnostics, never as event context.
64
+ */
65
+ function redactArgv(value, depth = 0) {
66
+ if (depth > 5 || value === null || typeof value !== 'object') return value;
67
+ if (Array.isArray(value)) return value.map((v) => redactArgv(v, depth + 1));
68
+ const out = {};
69
+ for (const [k, v] of Object.entries(value)) {
70
+ out[k] = SECRET_FIELD.test(k) && v !== undefined && v !== null && v !== '' ? '<redacted>' : redactArgv(v, depth + 1);
71
+ }
72
+ return out;
73
+ }
74
+
75
+ /** Scrub every string in a JSON-like value (breadcrumb data), in place-safe copy. */
76
+ function scrubDeep(value, depth = 0) {
77
+ if (typeof value === 'string') return scrub(value);
78
+ if (depth > 5 || value === null || typeof value !== 'object') return value;
79
+ if (Array.isArray(value)) return value.map((v) => scrubDeep(v, depth + 1));
80
+ const out = {};
81
+ for (const [k, v] of Object.entries(value)) {
82
+ out[k] = SECRET_FIELD.test(k) && typeof v === 'string' && v ? '<redacted>' : scrubDeep(v, depth + 1);
83
+ }
84
+ return out;
85
+ }
86
+
87
+ /**
88
+ * Breadcrumbs are sent with every event and beforeSend does not see them as
89
+ * separate items, so they get the same scrubbing on the way in.
90
+ */
91
+ function scrubBreadcrumb(breadcrumb) {
92
+ if (!breadcrumb) return breadcrumb;
93
+ if (breadcrumb.message) breadcrumb.message = scrub(breadcrumb.message);
94
+ if (breadcrumb.data) breadcrumb.data = scrubDeep(breadcrumb.data);
95
+ return breadcrumb;
96
+ }
97
+
46
98
  function scrub(value) {
47
99
  if (typeof value !== 'string') return value;
48
100
  return SECRET_PATTERNS.reduce((acc, [pattern, replacement]) => acc.replace(pattern, replacement), value);
@@ -77,6 +129,9 @@ function initTelemetry() {
77
129
  Sentry.init({
78
130
  dsn: DSN,
79
131
  environment: process.env.NODE_ENV || 'production',
132
+ // Which published deployer produced the event, so a fix can be matched to
133
+ // the versions still in the wild.
134
+ release: `@scrymore/scry-deployer@${PACKAGE_VERSION}`,
80
135
 
81
136
  // Errors only. Traces from a CLI describe the customer's build pipeline,
82
137
  // which is more than is needed to fix a crash.
@@ -86,8 +141,18 @@ function initTelemetry() {
86
141
  sendDefaultPii: false,
87
142
  serverName: false,
88
143
 
144
+ // The logger turns every line (including debug) into a breadcrumb, and
145
+ // Sentry's console integration does the same for console.*. None of that
146
+ // passed through beforeSend's scrubbing before.
147
+ beforeBreadcrumb(breadcrumb) {
148
+ return scrubBreadcrumb(breadcrumb);
149
+ },
150
+
89
151
  beforeSend(event) {
90
152
  if (event.message) event.message = scrub(event.message);
153
+ // Belt and braces for breadcrumbs added before init or by integrations
154
+ // that bypass beforeBreadcrumb.
155
+ if (Array.isArray(event.breadcrumbs)) event.breadcrumbs = event.breadcrumbs.map(scrubBreadcrumb);
91
156
 
92
157
  for (const entry of event.exception?.values ?? []) {
93
158
  if (entry.value) entry.value = scrub(entry.value);
@@ -135,6 +200,8 @@ module.exports = {
135
200
  flushTelemetry,
136
201
  telemetryDisabled,
137
202
  sanitizeArgv,
203
+ redactArgv,
138
204
  scrub,
205
+ scrubBreadcrumb,
139
206
  SAFE_ARGV_FIELDS,
140
207
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrymore/scry-deployer",
3
- "version": "0.6.1-nightly.20260925022633",
3
+ "version": "0.6.1",
4
4
  "description": "A CLI to automate the deployment of Storybook static builds.",
5
5
  "main": "index.js",
6
6
  "bin": {