@scrymore/scry-deployer 0.7.0-next.20260926193255 → 0.7.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.
- package/README.md +27 -3
- package/bin/cli.js +5 -3
- package/lib/telemetry.js +67 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,7 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
Deploy your Storybook to the cloud with one command. ⚡
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## Set up with your AI assistant
|
|
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.
|
|
6
30
|
|
|
7
31
|
### 1. Get your credentials
|
|
8
32
|
Visit the [Scry Dashboard](https://dashboard.scrymore.com) and:
|
|
@@ -100,8 +124,8 @@ yarn add github:scryorg/scry-node --dev
|
|
|
100
124
|
After installation, you can run commands using:
|
|
101
125
|
|
|
102
126
|
```bash
|
|
103
|
-
# Using the
|
|
104
|
-
|
|
127
|
+
# Using the scry-deployer binary
|
|
128
|
+
npm exec -- scry-deployer init --projectId xxx --apiKey yyy
|
|
105
129
|
|
|
106
130
|
# Using the scry alias
|
|
107
131
|
npx scry init --projectId xxx --apiKey yyy
|
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');
|
|
@@ -28,7 +28,8 @@ const { version: DEPLOYER_VERSION } = require('../package.json');
|
|
|
28
28
|
async function runAnalysis(argv) {
|
|
29
29
|
const logger = createLogger(argv);
|
|
30
30
|
logger.info('📊 Starting Storybook analysis...');
|
|
31
|
-
|
|
31
|
+
// Credentials masked: this line is also a Sentry breadcrumb.
|
|
32
|
+
logger.debug(`Received arguments: ${JSON.stringify(redactArgv(argv))}`);
|
|
32
33
|
|
|
33
34
|
const outPath = path.join(os.tmpdir(), `storybook-analysis-${Date.now()}.zip`);
|
|
34
35
|
|
|
@@ -132,7 +133,8 @@ function describeCapture(report) {
|
|
|
132
133
|
async function runDeployment(argv) {
|
|
133
134
|
const logger = createLogger(argv);
|
|
134
135
|
logger.info('🚀 Starting deployment...');
|
|
135
|
-
|
|
136
|
+
// Credentials masked: this line is also a Sentry breadcrumb.
|
|
137
|
+
logger.debug(`Received arguments: ${JSON.stringify(redactArgv(argv))}`);
|
|
136
138
|
|
|
137
139
|
const analysis = resolveAnalysis(argv);
|
|
138
140
|
argv = { ...argv, withAnalysis: analysis.enabled };
|
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
|
};
|