@scrymore/scry-deployer 0.6.1 → 0.7.0-next.20260926151750
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/README.md +24 -36
- package/bin/cli.js +37 -5
- package/lib/config.js +5 -1
- package/lib/coverage.js +69 -0
- package/lib/telemetry.js +0 -67
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,31 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Deploy your Storybook to the cloud with one command. ⚡
|
|
4
4
|
|
|
5
|
-
##
|
|
6
|
-
|
|
7
|
-
From your application's repository, install the Scry setup skill:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx skills add epinnock/scry-node --skill scry-setup
|
|
11
|
-
```
|
|
12
|
-
|
|
13
|
-
The current installer requires Node.js 22.20 or newer. Choose your assistant
|
|
14
|
-
(Claude Code, Codex, Cursor, or another compatible agent), then ask:
|
|
15
|
-
**“Set up Scry for this project and connect my assistant to its components.”**
|
|
16
|
-
|
|
17
|
-
The skill handles deployment configuration, GitHub Actions, component indexing,
|
|
18
|
-
MCP connections, and optional Figma linking. You can also ask for MCP alone.
|
|
19
|
-
Complete account sign-in in your browser and keep API keys in your environment
|
|
20
|
-
or CI secret store.
|
|
21
|
-
|
|
22
|
-
See the [setup guide](https://docs.scrymore.com/guide/skill) for installation
|
|
23
|
-
options, or inspect the [skill instructions](skills/scry-setup/SKILL.md).
|
|
24
|
-
|
|
25
|
-
## Set up directly with the CLI
|
|
26
|
-
|
|
27
|
-
`init` writes configuration and workflows, configures GitHub secrets, then
|
|
28
|
-
commits and pushes. For local preparation before publishing, use the skill or
|
|
29
|
-
the manual deployment path below. `--skip-gh-setup` still commits and pushes.
|
|
5
|
+
## 🎯 Quick Start (5 seconds)
|
|
30
6
|
|
|
31
7
|
### 1. Get your credentials
|
|
32
8
|
Visit the [Scry Dashboard](https://dashboard.scrymore.com) and:
|
|
@@ -102,7 +78,7 @@ This deploys your Storybook immediately without setting up GitHub Actions.
|
|
|
102
78
|
npx @scrymore/scry-deployer init --projectId xxx --apiKey yyy
|
|
103
79
|
|
|
104
80
|
# From GitHub (latest from main branch)
|
|
105
|
-
npx github:
|
|
81
|
+
npx github:scryorg/scry-node init --projectId xxx --apiKey yyy
|
|
106
82
|
```
|
|
107
83
|
|
|
108
84
|
### Installing as a Dependency
|
|
@@ -114,18 +90,18 @@ If you prefer to install it as a development dependency:
|
|
|
114
90
|
npm install @scrymore/scry-deployer --save-dev
|
|
115
91
|
|
|
116
92
|
# From GitHub
|
|
117
|
-
npm install github:
|
|
93
|
+
npm install github:scryorg/scry-node --save-dev
|
|
118
94
|
# or
|
|
119
|
-
pnpm add github:
|
|
95
|
+
pnpm add github:scryorg/scry-node -D
|
|
120
96
|
# or
|
|
121
|
-
yarn add github:
|
|
97
|
+
yarn add github:scryorg/scry-node --dev
|
|
122
98
|
```
|
|
123
99
|
|
|
124
100
|
After installation, you can run commands using:
|
|
125
101
|
|
|
126
102
|
```bash
|
|
127
|
-
# Using the
|
|
128
|
-
|
|
103
|
+
# Using the storybook-deployer binary
|
|
104
|
+
npx storybook-deployer init --projectId xxx --apiKey yyy
|
|
129
105
|
|
|
130
106
|
# Using the scry alias
|
|
131
107
|
npx scry init --projectId xxx --apiKey yyy
|
|
@@ -205,13 +181,16 @@ The CLI is configured through a combination of command-line options and environm
|
|
|
205
181
|
|----------------|---------------------------------------|--------------------------------------------------------------|----------|--------------------------------------|
|
|
206
182
|
| `--dir` | `STORYBOOK_DEPLOYER_DIR` | Path to the built Storybook directory (e.g., `storybook-static`). | Yes | - |
|
|
207
183
|
| `--api-key` | `STORYBOOK_DEPLOYER_API_KEY` | The API key for the deployment service. | No | - |
|
|
208
|
-
| `--api-url` | `STORYBOOK_DEPLOYER_API_URL` | Base URL for the deployment service API. | No | `https://
|
|
184
|
+
| `--api-url` | `STORYBOOK_DEPLOYER_API_URL` | Base URL for the deployment service API. | No | `https://storybook-deployment-service.epinnock.workers.dev` |
|
|
209
185
|
| `--project` | `STORYBOOK_DEPLOYER_PROJECT` | The project name/identifier. | No | `main` |
|
|
210
186
|
| `--version` | `STORYBOOK_DEPLOYER_VERSION` | The version identifier for the deployment. | No | `latest` |
|
|
211
187
|
| `--with-analysis` | `STORYBOOK_DEPLOYER_WITH_ANALYSIS` | Enable Storybook analysis (story crawling + screenshots). Enabled by default in generated workflows. | No | `false` |
|
|
212
188
|
| `--stories-dir` | `STORYBOOK_DEPLOYER_STORIES_DIR` | Path to stories directory (optional, auto-detects .stories.* files). | No | Auto-detect |
|
|
213
189
|
| `--screenshots-dir` | `STORYBOOK_DEPLOYER_SCREENSHOTS_DIR` | Directory for captured screenshots. | No | `./screenshots` |
|
|
214
190
|
| `--storybook-url` | `STORYBOOK_DEPLOYER_STORYBOOK_URL` | URL of running Storybook server for screenshot capture. | No | `http://localhost:6006` |
|
|
191
|
+
| `--capture-mode` | `SCRY_CAPTURE_MODE` | Screenshot framing forwarded to scry-sbcov: `root` (crop to the component) or `viewport`. | No | unset: sbcov decides (`root` from sbcov 0.6) |
|
|
192
|
+
| `--capture-scale` | `SCRY_CAPTURE_SCALE` | Screenshot device scale factor forwarded to scry-sbcov, `0 < n <= 4`. | No | unset: sbcov decides (`2` from sbcov 0.6) |
|
|
193
|
+
| `--capture-viewport` | `SCRY_CAPTURE_VIEWPORT` | Browser viewport `WIDTHxHEIGHT` forwarded to scry-sbcov. | No | unset: sbcov decides (`1280x720`) |
|
|
215
194
|
| `--verbose` | `STORYBOOK_DEPLOYER_VERBOSE` | Enable verbose logging for debugging purposes. | No | `false` |
|
|
216
195
|
| `--help`, `-h` | - | Show the help message. | - | - |
|
|
217
196
|
| `--version`, `-v`| - | Show the version number. | - | - |
|
|
@@ -284,6 +263,15 @@ The configuration file (`.storybook-deployer.json`) is automatically created in
|
|
|
284
263
|
- `project` → `--project` CLI option
|
|
285
264
|
- `version` → `--version` CLI option
|
|
286
265
|
- `verbose` → `--verbose` CLI option
|
|
266
|
+
- `captureMode` → `--capture-mode` CLI option
|
|
267
|
+
- `captureScale` → `--capture-scale` CLI option
|
|
268
|
+
- `captureViewport` → `--capture-viewport` CLI option (`"390x844"` or `{ "width": 390, "height": 844 }`)
|
|
269
|
+
|
|
270
|
+
**Screenshot capture settings.** `captureMode`, `captureScale` and `captureViewport` are validated and passed to
|
|
271
|
+
scry-sbcov as `--capture-mode`, `--capture-scale` and `--capture-viewport`. Only the ones you set are passed, so
|
|
272
|
+
leaving them out keeps sbcov's defaults and any `scry-sbcov.config.*` in your project in charge. An invalid value
|
|
273
|
+
fails the run. To keep whole-window 1x screenshots, set `"captureMode": "viewport", "captureScale": 1`. For
|
|
274
|
+
`root` mode, mark the component with `data-scry-root` (see the scry-sbcov README, "Capture settings").
|
|
287
275
|
|
|
288
276
|
**See [`.storybook-deployer.example.json`](.storybook-deployer.example.json) for a complete configuration file with all available options and their default values.**
|
|
289
277
|
|
|
@@ -450,7 +438,7 @@ If you're installing from GitHub, the workflow file is already included.
|
|
|
450
438
|
| Variable Name | Value | Example |
|
|
451
439
|
|--------------|-------|---------|
|
|
452
440
|
| `SCRY_PROJECT_ID` | Your project identifier | `my-storybook` or `company-design-system` |
|
|
453
|
-
| `SCRY_API_URL` | Backend API endpoint for uploads | `https://
|
|
441
|
+
| `SCRY_API_URL` | Backend API endpoint for uploads (the upload service) | `https://storybook-deployment-service.epinnock.workers.dev` |
|
|
454
442
|
| `SCRY_VIEW_URL` | Base URL where users view deployed Storybooks | `https://view.scrymore.com` |
|
|
455
443
|
|
|
456
444
|
**Note:** The `SCRY_VIEW_URL` is where users will access your deployed Storybook (e.g., `https://view.scrymore.com/{project}/pr-{number}/`). This is separate from `SCRY_API_URL`, which is the backend API endpoint used for uploads.
|
|
@@ -1040,6 +1028,6 @@ See [CONTRIBUTING.md](CONTRIBUTING.md) for detailed contribution guidelines.
|
|
|
1040
1028
|
## 🆘 Support
|
|
1041
1029
|
|
|
1042
1030
|
Need help?
|
|
1043
|
-
- 📖 [Documentation](https://github.com/
|
|
1044
|
-
- 🐛 [Report an issue](https://github.com/
|
|
1045
|
-
- 💬 [Discussions](https://github.com/
|
|
1031
|
+
- 📖 [Documentation](https://github.com/scryorg/scry-node)
|
|
1032
|
+
- 🐛 [Report an issue](https://github.com/scryorg/scry-node/issues)
|
|
1033
|
+
- 💬 [Discussions](https://github.com/scryorg/scry-node/discussions)
|
package/bin/cli.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
|
-
const { initTelemetry, captureCliError, flushTelemetry
|
|
3
|
+
const { initTelemetry, captureCliError, flushTelemetry } = 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,8 +25,7 @@ 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
|
-
|
|
29
|
-
logger.debug(`Received arguments: ${JSON.stringify(redactArgv(argv))}`);
|
|
28
|
+
logger.debug(`Received arguments: ${JSON.stringify(argv)}`);
|
|
30
29
|
|
|
31
30
|
const outPath = path.join(os.tmpdir(), `storybook-analysis-${Date.now()}.zip`);
|
|
32
31
|
|
|
@@ -86,8 +85,7 @@ async function runAnalysis(argv) {
|
|
|
86
85
|
async function runDeployment(argv) {
|
|
87
86
|
const logger = createLogger(argv);
|
|
88
87
|
logger.info('🚀 Starting deployment...');
|
|
89
|
-
|
|
90
|
-
logger.debug(`Received arguments: ${JSON.stringify(redactArgv(argv))}`);
|
|
88
|
+
logger.debug(`Received arguments: ${JSON.stringify(argv)}`);
|
|
91
89
|
|
|
92
90
|
const outPath = path.join(os.tmpdir(), `storybook-deployment-${Date.now()}.zip`);
|
|
93
91
|
let metadataZipPath = null;
|
|
@@ -281,6 +279,19 @@ async function main() {
|
|
|
281
279
|
type: 'boolean',
|
|
282
280
|
default: false,
|
|
283
281
|
})
|
|
282
|
+
.option('capture-mode', {
|
|
283
|
+
describe: 'Screenshot framing forwarded to scry-sbcov: root or viewport (unset: sbcov default)',
|
|
284
|
+
type: 'string',
|
|
285
|
+
choices: ['root', 'viewport'],
|
|
286
|
+
})
|
|
287
|
+
.option('capture-scale', {
|
|
288
|
+
describe: 'Screenshot device scale factor forwarded to scry-sbcov, 0 < n <= 4 (unset: sbcov default)',
|
|
289
|
+
type: 'string',
|
|
290
|
+
})
|
|
291
|
+
.option('capture-viewport', {
|
|
292
|
+
describe: 'Browser viewport WIDTHxHEIGHT forwarded to scry-sbcov (unset: sbcov default)',
|
|
293
|
+
type: 'string',
|
|
294
|
+
})
|
|
284
295
|
.option('with-analysis', {
|
|
285
296
|
describe: 'Include Storybook analysis (screenshots, metadata)',
|
|
286
297
|
type: 'boolean',
|
|
@@ -389,6 +400,19 @@ async function main() {
|
|
|
389
400
|
default: false,
|
|
390
401
|
alias: 'coverageExecute'
|
|
391
402
|
})
|
|
403
|
+
.option('capture-mode', {
|
|
404
|
+
describe: 'Screenshot framing forwarded to scry-sbcov: root or viewport (unset: sbcov default)',
|
|
405
|
+
type: 'string',
|
|
406
|
+
choices: ['root', 'viewport'],
|
|
407
|
+
})
|
|
408
|
+
.option('capture-scale', {
|
|
409
|
+
describe: 'Screenshot device scale factor forwarded to scry-sbcov, 0 < n <= 4 (unset: sbcov default)',
|
|
410
|
+
type: 'string',
|
|
411
|
+
})
|
|
412
|
+
.option('capture-viewport', {
|
|
413
|
+
describe: 'Browser viewport WIDTHxHEIGHT forwarded to scry-sbcov (unset: sbcov default)',
|
|
414
|
+
type: 'string',
|
|
415
|
+
})
|
|
392
416
|
.option('output', {
|
|
393
417
|
describe: 'Where to write the JSON coverage report',
|
|
394
418
|
type: 'string',
|
|
@@ -402,6 +426,8 @@ async function main() {
|
|
|
402
426
|
}, async (argv) => {
|
|
403
427
|
const logger = createLogger(argv);
|
|
404
428
|
|
|
429
|
+
// Capture settings: CLI flag > env > .storybook-deployer.json; unset = sbcov decides.
|
|
430
|
+
const { captureMode, captureScale, captureViewport } = loadConfig(argv);
|
|
405
431
|
const result = await runCoverageAnalysis({
|
|
406
432
|
storybookDir: argv.dir,
|
|
407
433
|
baseBranch: argv.coverageBase || 'main',
|
|
@@ -409,6 +435,9 @@ async function main() {
|
|
|
409
435
|
execute: Boolean(argv.coverageExecute),
|
|
410
436
|
outputPath: argv.output,
|
|
411
437
|
keepReport: true,
|
|
438
|
+
captureMode,
|
|
439
|
+
captureScale,
|
|
440
|
+
captureViewport,
|
|
412
441
|
});
|
|
413
442
|
const report = result.report;
|
|
414
443
|
|
|
@@ -612,6 +641,9 @@ async function resolveCoverage(argv, logger) {
|
|
|
612
641
|
execute: Boolean(argv.coverageExecute) || needsScreenshots,
|
|
613
642
|
screenshots: needsScreenshots,
|
|
614
643
|
outputZipPath,
|
|
644
|
+
captureMode: argv.captureMode,
|
|
645
|
+
captureScale: argv.captureScale,
|
|
646
|
+
captureViewport: argv.captureViewport,
|
|
615
647
|
});
|
|
616
648
|
report = result.report;
|
|
617
649
|
metadataZipPath = result.metadataZipPath;
|
package/lib/config.js
CHANGED
|
@@ -119,7 +119,11 @@ function loadEnvConfig() {
|
|
|
119
119
|
'WITH_ANALYSIS': 'withAnalysis',
|
|
120
120
|
'STORIES_DIR': 'storiesDir',
|
|
121
121
|
'SCREENSHOTS_DIR': 'screenshotsDir',
|
|
122
|
-
'STORYBOOK_URL': 'storybookUrl'
|
|
122
|
+
'STORYBOOK_URL': 'storybookUrl',
|
|
123
|
+
// Screenshot capture settings forwarded to scry-sbcov (unset = sbcov decides)
|
|
124
|
+
'CAPTURE_MODE': 'captureMode',
|
|
125
|
+
'CAPTURE_SCALE': 'captureScale',
|
|
126
|
+
'CAPTURE_VIEWPORT': 'captureViewport'
|
|
123
127
|
};
|
|
124
128
|
|
|
125
129
|
Object.keys(envMapping).forEach(envKey => {
|
package/lib/coverage.js
CHANGED
|
@@ -12,8 +12,68 @@ const chalk = require('chalk');
|
|
|
12
12
|
* @property {boolean} [keepReport=false] If true, do not delete the output file after reading
|
|
13
13
|
* @property {boolean} [screenshots=false] Enable passing-story screenshots in scry-sbcov
|
|
14
14
|
* @property {string|null} [outputZipPath=null] Where to write metadata+screenshots ZIP
|
|
15
|
+
* @property {'root'|'viewport'} [captureMode] Screenshot framing forwarded as --capture-mode
|
|
16
|
+
* @property {number|string} [captureScale] Device scale factor (0 < n <= 4) forwarded as --capture-scale
|
|
17
|
+
* @property {string|{width:number,height:number}} [captureViewport] "WxH" or {width,height}, forwarded as --capture-viewport
|
|
15
18
|
*/
|
|
16
19
|
|
|
20
|
+
const CAPTURE_MODES = ['root', 'viewport'];
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* Turn a project's capture settings into scry-sbcov flags.
|
|
24
|
+
*
|
|
25
|
+
* Only settings the project actually set are forwarded: an unset value is left
|
|
26
|
+
* to scry-sbcov, so its own defaults and any scry-sbcov.config.* in the project
|
|
27
|
+
* still apply. Every value is validated to a fixed shape (enum, bounded number,
|
|
28
|
+
* WxH integers) before it gets near a shell command; anything else throws.
|
|
29
|
+
*
|
|
30
|
+
* @param {{captureMode?:any, captureScale?:any, captureViewport?:any}} [settings]
|
|
31
|
+
* @returns {string[]}
|
|
32
|
+
*/
|
|
33
|
+
function buildCaptureArgs(settings = {}) {
|
|
34
|
+
const { captureMode, captureScale, captureViewport } = settings || {};
|
|
35
|
+
const args = [];
|
|
36
|
+
const isSet = (v) => v !== undefined && v !== null && v !== '';
|
|
37
|
+
|
|
38
|
+
if (isSet(captureMode)) {
|
|
39
|
+
if (typeof captureMode !== 'string' || !CAPTURE_MODES.includes(captureMode)) {
|
|
40
|
+
throw new Error(`Invalid captureMode ${JSON.stringify(captureMode)}: expected "root" or "viewport"`);
|
|
41
|
+
}
|
|
42
|
+
args.push('--capture-mode', captureMode);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
if (isSet(captureScale)) {
|
|
46
|
+
const text = String(captureScale).trim();
|
|
47
|
+
const n = Number(text);
|
|
48
|
+
if (!/^\d+(\.\d+)?$/.test(text) || !(n > 0) || n > 4) {
|
|
49
|
+
throw new Error(`Invalid captureScale ${JSON.stringify(captureScale)}: expected a number in (0, 4]`);
|
|
50
|
+
}
|
|
51
|
+
args.push('--capture-scale', String(n));
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
if (isSet(captureViewport)) {
|
|
55
|
+
let width;
|
|
56
|
+
let height;
|
|
57
|
+
if (typeof captureViewport === 'string') {
|
|
58
|
+
const m = /^\s*(\d{1,5})\s*[xX]\s*(\d{1,5})\s*$/.exec(captureViewport);
|
|
59
|
+
if (m) {
|
|
60
|
+
width = Number(m[1]);
|
|
61
|
+
height = Number(m[2]);
|
|
62
|
+
}
|
|
63
|
+
} else if (typeof captureViewport === 'object') {
|
|
64
|
+
width = captureViewport.width;
|
|
65
|
+
height = captureViewport.height;
|
|
66
|
+
}
|
|
67
|
+
const ok = (v) => Number.isInteger(v) && v >= 1 && v <= 10000;
|
|
68
|
+
if (!ok(width) || !ok(height)) {
|
|
69
|
+
throw new Error(`Invalid captureViewport ${JSON.stringify(captureViewport)}: expected "WIDTHxHEIGHT" or { width, height } in whole CSS px`);
|
|
70
|
+
}
|
|
71
|
+
args.push('--capture-viewport', `${width}x${height}`);
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
return args;
|
|
75
|
+
}
|
|
76
|
+
|
|
17
77
|
/**
|
|
18
78
|
* Run Storybook coverage analysis via `@scrymore/scry-sbcov`.
|
|
19
79
|
*
|
|
@@ -39,12 +99,19 @@ async function runCoverageAnalysis(options) {
|
|
|
39
99
|
keepReport = false,
|
|
40
100
|
screenshots = false,
|
|
41
101
|
outputZipPath = null,
|
|
102
|
+
captureMode,
|
|
103
|
+
captureScale,
|
|
104
|
+
captureViewport,
|
|
42
105
|
} = options || {};
|
|
43
106
|
|
|
44
107
|
if (!storybookDir || typeof storybookDir !== 'string') {
|
|
45
108
|
throw new Error('runCoverageAnalysis: options.storybookDir is required');
|
|
46
109
|
}
|
|
47
110
|
|
|
111
|
+
// Validate before doing anything: a bad explicit setting should fail loudly,
|
|
112
|
+
// not be swallowed by the failOnThreshold=false path below.
|
|
113
|
+
const captureArgs = buildCaptureArgs({ captureMode, captureScale, captureViewport });
|
|
114
|
+
|
|
48
115
|
console.log(chalk.blue('Running Storybook coverage analysis...'));
|
|
49
116
|
|
|
50
117
|
const outputPath = providedOutputPath
|
|
@@ -77,6 +144,7 @@ async function runCoverageAnalysis(options) {
|
|
|
77
144
|
cliArgs.push('--output-zip', outputZipPath);
|
|
78
145
|
}
|
|
79
146
|
}
|
|
147
|
+
cliArgs.push(...captureArgs);
|
|
80
148
|
|
|
81
149
|
// Allow local override for E2E testing before package publication.
|
|
82
150
|
// Example:
|
|
@@ -305,6 +373,7 @@ function readGithubPullRequestBaseSha(eventPath) {
|
|
|
305
373
|
|
|
306
374
|
module.exports = {
|
|
307
375
|
runCoverageAnalysis,
|
|
376
|
+
buildCaptureArgs,
|
|
308
377
|
loadCoverageReport,
|
|
309
378
|
extractCoverageSummary,
|
|
310
379
|
normalizeGitBaseRef,
|
package/lib/telemetry.js
CHANGED
|
@@ -21,8 +21,6 @@ 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
|
-
|
|
26
24
|
const DSN =
|
|
27
25
|
'https://c66ce229a1db2289f145eebd02436d9c@o4507889391828992.ingest.us.sentry.io/4510699330732032';
|
|
28
26
|
|
|
@@ -43,58 +41,8 @@ const SECRET_PATTERNS = [
|
|
|
43
41
|
[/scry_proj_[A-Za-z0-9_\-]+/g, 'scry_proj_<redacted>'],
|
|
44
42
|
[/(X-Amz-Signature=)[^&\s]+/gi, '$1<redacted>'],
|
|
45
43
|
[/(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>'],
|
|
53
44
|
];
|
|
54
45
|
|
|
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
|
-
|
|
98
46
|
function scrub(value) {
|
|
99
47
|
if (typeof value !== 'string') return value;
|
|
100
48
|
return SECRET_PATTERNS.reduce((acc, [pattern, replacement]) => acc.replace(pattern, replacement), value);
|
|
@@ -129,9 +77,6 @@ function initTelemetry() {
|
|
|
129
77
|
Sentry.init({
|
|
130
78
|
dsn: DSN,
|
|
131
79
|
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}`,
|
|
135
80
|
|
|
136
81
|
// Errors only. Traces from a CLI describe the customer's build pipeline,
|
|
137
82
|
// which is more than is needed to fix a crash.
|
|
@@ -141,18 +86,8 @@ function initTelemetry() {
|
|
|
141
86
|
sendDefaultPii: false,
|
|
142
87
|
serverName: false,
|
|
143
88
|
|
|
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
|
-
|
|
151
89
|
beforeSend(event) {
|
|
152
90
|
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);
|
|
156
91
|
|
|
157
92
|
for (const entry of event.exception?.values ?? []) {
|
|
158
93
|
if (entry.value) entry.value = scrub(entry.value);
|
|
@@ -200,8 +135,6 @@ module.exports = {
|
|
|
200
135
|
flushTelemetry,
|
|
201
136
|
telemetryDisabled,
|
|
202
137
|
sanitizeArgv,
|
|
203
|
-
redactArgv,
|
|
204
138
|
scrub,
|
|
205
|
-
scrubBreadcrumb,
|
|
206
139
|
SAFE_ARGV_FIELDS,
|
|
207
140
|
};
|