@scrymore/scry-deployer 0.4.0 → 0.5.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/bin/cli.js CHANGED
@@ -147,6 +147,30 @@ async function runDeployment(argv) {
147
147
  '\n⚠️ Metadata was uploaded but not queued for processing.\n' +
148
148
  ' The Storybook is hosted, but its components are NOT being indexed.'
149
149
  );
150
+ } else if (argv.withAnalysis) {
151
+ // The gap between the two branches above, and the most damaging
152
+ // state of the three: analysis was asked for, produced nothing, and
153
+ // this command used to say "Upload successful" and stop. Nothing is
154
+ // ever indexed, no error is printed, and CI stays green — so the
155
+ // first sign of trouble is a customer reporting that search is empty
156
+ // days later (ISSUES.md #24).
157
+ //
158
+ // Exit non-zero. The Storybook is hosted, so "failure" overstates it
159
+ // slightly, but the job asked for was to make components searchable
160
+ // and that did not happen. A red build is the only signal that gets
161
+ // acted on.
162
+ process.exitCode = 1;
163
+ logger.error(
164
+ '\n❌ Analysis produced no metadata, so NOTHING WILL BE INDEXED.\n' +
165
+ ' The Storybook is hosted and browsable, but no component will be\n' +
166
+ ' searchable from this build.\n\n' +
167
+ ' You asked for --with-analysis and it did not complete. The cause is\n' +
168
+ ' in the coverage output above — commonly a missing Playwright browser\n' +
169
+ ' (run: npx playwright install chromium-headless-shell) or a TypeScript\n' +
170
+ ' resolution error in the analyzer.\n\n' +
171
+ ' Exiting non-zero deliberately: a green build here would mean search\n' +
172
+ ' silently returns nothing.'
173
+ );
150
174
  }
151
175
 
152
176
  } finally {
package/lib/init.js CHANGED
@@ -79,14 +79,18 @@ async function runInit(argv) {
79
79
  logger.success('✅ Created .github/workflows/deploy-pr-preview.yml\n');
80
80
 
81
81
  // Step 6: Setup GitHub variables (if gh CLI available)
82
+ // Tracked, not assumed — the closing summary reports what actually happened.
83
+ let ghConfigured = null;
82
84
  const step6Start = Date.now();
83
85
  if (!argv.skipGhSetup && isGhCliAvailable()) {
84
86
  logger.info('6/8: Setting up GitHub repository variables...');
85
87
  try {
86
88
  await setupGitHubVariables(argv.project, argv.apiKey, argv.apiUrl, logger);
89
+ ghConfigured = true;
87
90
  const step6Duration = Date.now() - step6Start;
88
91
  logger.success(`✅ GitHub variables configured [${step6Duration}ms]\n`);
89
92
  } catch (error) {
93
+ ghConfigured = false;
90
94
  const step6Duration = Date.now() - step6Start;
91
95
  logger.error(`⚠️ GitHub setup failed: ${error.message} [${step6Duration}ms]`);
92
96
  logger.info('You can set these up manually later.\n');
@@ -126,7 +130,10 @@ async function runInit(argv) {
126
130
  logger.info('━'.repeat(50));
127
131
  logger.info(`[TIMING] Total setup time: ${totalDuration}ms (${(totalDuration / 1000).toFixed(2)}s)\n`);
128
132
 
129
- showSuccessMessage(argv.project, envInfo, argv.apiUrl, pushResult.success);
133
+ showSuccessMessage(argv.project, envInfo, argv.apiUrl, pushResult.success, {
134
+ ghConfigured,
135
+ committed: commitResult.success,
136
+ });
130
137
 
131
138
  } catch (error) {
132
139
  logger.error(`\n❌ Setup failed: ${error.message}`);
@@ -313,22 +320,63 @@ function isGhCliAvailable() {
313
320
  /**
314
321
  * Setup GitHub variables using gh CLI
315
322
  */
316
- async function setupGitHubVariables(projectId, apiKey, apiUrl, logger) {
323
+ /**
324
+ * Does this `gh` know about `gh variable`? It arrived in gh 2.21 (December 2022),
325
+ * and Ubuntu 22.04 still ships 2.4.0 — so plenty of machines do not have it.
326
+ * Without this check the very first call throws, the secret after it is never
327
+ * reached, and CI ends up with no credentials at all.
328
+ */
329
+ function ghSupportsVariables() {
317
330
  try {
318
- // Set variables
319
- execSync(`gh variable set SCRY_PROJECT_ID --body "${projectId}"`, { stdio: 'pipe' });
320
- logger.debug(' ✓ Set SCRY_PROJECT_ID');
331
+ execSync('gh variable --help', { stdio: 'pipe' });
332
+ return true;
333
+ } catch {
334
+ return false;
335
+ }
336
+ }
321
337
 
322
- execSync(`gh variable set SCRY_API_URL --body "${apiUrl}"`, { stdio: 'pipe' });
323
- logger.debug(' ✓ Set SCRY_API_URL');
338
+ function setVariableViaApi(repo, name, value) {
339
+ // `gh api` predates `gh variable` by years, so it is the safe fallback.
340
+ const payload = JSON.stringify({ name, value });
341
+ try {
342
+ execSync(`gh api -X POST repos/${repo}/actions/variables --input -`, {
343
+ stdio: ['pipe', 'pipe', 'pipe'], input: payload,
344
+ });
345
+ } catch {
346
+ // Already present: update rather than create.
347
+ execSync(`gh api -X PATCH repos/${repo}/actions/variables/${name} --input -`, {
348
+ stdio: ['pipe', 'pipe', 'pipe'], input: payload,
349
+ });
350
+ }
351
+ }
324
352
 
325
- // Set secret (API key)
326
- execSync(`gh secret set SCRY_API_KEY --body "${apiKey}"`, { stdio: 'pipe' });
327
- logger.debug(' ✓ Set SCRY_API_KEY');
353
+ async function setupGitHubVariables(projectId, apiKey, apiUrl, logger) {
354
+ const repo = execSync('gh repo view --json nameWithOwner -q .nameWithOwner', { stdio: 'pipe' })
355
+ .toString().trim();
328
356
 
329
- } catch (error) {
330
- throw new Error(`Failed to set GitHub variables: ${error.message}`);
357
+ const useCli = ghSupportsVariables();
358
+ if (!useCli) {
359
+ logger.debug(' gh is too old for `gh variable`; using `gh api` instead');
331
360
  }
361
+
362
+ // Each of these is reported separately. A failure part-way through used to
363
+ // abandon the rest silently, which is how a repository ended up with the
364
+ // variables missing *and* the secret missing while setup reported success.
365
+ const setVar = (name, value) => {
366
+ if (useCli) execSync(`gh variable set ${name} --body "${value}"`, { stdio: 'pipe' });
367
+ else setVariableViaApi(repo, name, value);
368
+ };
369
+
370
+ setVar('SCRY_PROJECT_ID', projectId);
371
+ logger.debug(' ✓ Set SCRY_PROJECT_ID');
372
+
373
+ setVar('SCRY_API_URL', apiUrl);
374
+ logger.debug(' ✓ Set SCRY_API_URL');
375
+
376
+ // `gh secret set` needs libsodium encryption, so there is no simple `gh api`
377
+ // fallback — but it has existed since gh 1.x, so it works where variables do not.
378
+ execSync(`gh secret set SCRY_API_KEY --body "${apiKey}"`, { stdio: 'pipe' });
379
+ logger.debug(' ✓ Set SCRY_API_KEY');
332
380
  }
333
381
 
334
382
  /**
@@ -377,12 +425,25 @@ function gitCommit(logger) {
377
425
  '.storybook-deployer.json'
378
426
  ];
379
427
 
428
+ // Each file is staged on its own. `git add` fails on a path that
429
+ // .gitignore excludes, and one throw used to abort the loop before the
430
+ // commit — so a leftover `.storybook-deployer.json` ignore rule from the
431
+ // pre-0.4.0 workaround silently prevented the *workflows* being committed,
432
+ // and CI was never set up at all.
433
+ let staged = 0;
380
434
  for (const file of filesToAdd) {
381
- if (fs.existsSync(file)) {
435
+ if (!fs.existsSync(file)) continue;
436
+ try {
382
437
  execSync(`git add "${file}"`, { stdio: 'pipe' });
438
+ staged++;
383
439
  logger.debug(` ✓ Added ${file}`);
440
+ } catch {
441
+ logger.debug(` • Skipped ${file} (ignored by .gitignore)`);
384
442
  }
385
443
  }
444
+ if (staged === 0) {
445
+ return { success: false, message: 'Nothing could be staged' };
446
+ }
386
447
 
387
448
  // Commit
388
449
  const commitMessage = 'chore: add Scry Storybook deployment workflows';
@@ -433,7 +494,7 @@ function gitPush(logger) {
433
494
  /**
434
495
  * Show success message
435
496
  */
436
- function showSuccessMessage(projectId, envInfo, apiUrl, pushed) {
497
+ function showSuccessMessage(projectId, envInfo, apiUrl, pushed, results = {}) {
437
498
  console.log(`
438
499
  🎉 ${pushed ? 'Setup Complete and Deployed!' : 'Setup Complete!'}
439
500
 
@@ -442,9 +503,14 @@ Your Storybook deployment is configured and ready to go.
442
503
  📦 What was set up:
443
504
  ✅ Configuration file (.storybook-deployer.json — no credentials, safe to commit)
444
505
  ✅ GitHub Actions workflows (.github/workflows/)
445
- Repository variables (SCRY_PROJECT_ID, SCRY_API_URL)
446
- ✅ Repository secret (SCRY_API_KEY)
447
- ${pushed ? '✅ Changes committed and pushed' : '⚠️ Manual push required'}
506
+ ${results.ghConfigured === true
507
+ ? '✅ Repository variables and secret (SCRY_PROJECT_ID, SCRY_API_URL, SCRY_API_KEY)'
508
+ : results.ghConfigured === false
509
+ ? '❌ Repository variables and secret NOT set — CI cannot deploy until you fix this'
510
+ : '⚠️ Repository variables and secret not attempted — CI cannot deploy until you set them'}
511
+ ${results.committed === false
512
+ ? '❌ Changes NOT committed — the workflows are staged but uncommitted'
513
+ : (pushed ? '✅ Changes committed and pushed' : '⚠️ Committed; manual push required')}
448
514
 
449
515
  ${!pushed ? `
450
516
  📌 Next Step:
@@ -474,4 +540,4 @@ Happy deploying! ✨
474
540
  `);
475
541
  }
476
542
 
477
- module.exports = { runInit, createConfigFile };
543
+ module.exports = { runInit, createConfigFile, gitCommit };
package/lib/templates.js CHANGED
@@ -59,11 +59,27 @@ function getCacheValue(packageManager) {
59
59
  /**
60
60
  * Generate main deployment workflow
61
61
  */
62
+ /**
63
+ * The command that fetches and runs a package that is not a project dependency.
64
+ *
65
+ * `npx` cannot be assumed. In a pnpm workflow it resolves against the
66
+ * pnpm-managed environment and reports "playwright: not found" — exit 127 —
67
+ * even with --yes. Each package manager has its own equivalent, so use it.
68
+ */
69
+ function getDlxCommand(packageManager) {
70
+ switch (packageManager) {
71
+ case 'pnpm': return 'pnpm dlx';
72
+ case 'yarn': return 'yarn dlx';
73
+ default: return 'npx --yes';
74
+ }
75
+ }
76
+
62
77
  function generateMainWorkflow(projectId, apiUrl, packageManager, buildCmd) {
63
78
  const pmSetup = getPackageManagerSetup(packageManager);
64
79
  const installCmd = getInstallCommand(packageManager);
65
80
  const cache = getCacheValue(packageManager);
66
81
  const runCmd = packageManager === 'npm' ? `npm run ${buildCmd}` : `${packageManager} run ${buildCmd}`;
82
+ const dlx = getDlxCommand(packageManager);
67
83
 
68
84
  return `# Auto-generated by Scry Storybook Deployer
69
85
  # Deploy Storybook to production on push to main branch
@@ -96,6 +112,16 @@ ${cache}
96
112
  - name: Build Storybook
97
113
  run: ${runCmd}
98
114
 
115
+ - name: Install Playwright browser
116
+ # Screenshot capture drives --with-analysis, and without a browser every
117
+ # story fails, no metadata archive is produced, and nothing is ever
118
+ # indexed — while the deploy still exits 0 and the workflow goes green.
119
+ # Chromium headless shell only: a few seconds, versus minutes for the
120
+ # full browser set.
121
+ # The version is left floating so it tracks sbcov's own playwright
122
+ # ^1.41.0 optional dependency and installs a matching browser build.
123
+ run: ${dlx} playwright install --with-deps chromium-headless-shell
124
+
99
125
  - name: Deploy to Scry
100
126
  run: |
101
127
  npx @scrymore/scry-deployer \\
@@ -121,6 +147,7 @@ function generatePRWorkflow(projectId, apiUrl, packageManager, buildCmd) {
121
147
  const installCmd = getInstallCommand(packageManager);
122
148
  const cache = getCacheValue(packageManager);
123
149
  const runCmd = packageManager === 'npm' ? `npm run ${buildCmd}` : `${packageManager} run ${buildCmd}`;
150
+ const dlx = getDlxCommand(packageManager);
124
151
 
125
152
  return `# Auto-generated by Scry Storybook Deployer
126
153
  # Deploy Storybook preview for pull requests
@@ -157,6 +184,16 @@ ${cache}
157
184
  - name: Build Storybook
158
185
  run: ${runCmd}
159
186
 
187
+ - name: Install Playwright browser
188
+ # Screenshot capture drives --with-analysis, and without a browser every
189
+ # story fails, no metadata archive is produced, and nothing is ever
190
+ # indexed — while the deploy still exits 0 and the workflow goes green.
191
+ # Chromium headless shell only: a few seconds, versus minutes for the
192
+ # full browser set.
193
+ # The version is left floating so it tracks sbcov's own playwright
194
+ # ^1.41.0 optional dependency and installs a matching browser build.
195
+ run: ${dlx} playwright install --with-deps chromium-headless-shell
196
+
160
197
  - name: Deploy Preview
161
198
  id: deploy
162
199
  run: |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@scrymore/scry-deployer",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
4
4
  "description": "A CLI to automate the deployment of Storybook static builds.",
5
5
  "main": "index.js",
6
6
  "bin": {