Skip to content

Commit

Permalink
simplify error handling, rethrow errors
Browse files Browse the repository at this point in the history
  • Loading branch information
JReinhold committed Feb 12, 2024
1 parent 1122215 commit 78f122c
Show file tree
Hide file tree
Showing 2 changed files with 136 additions and 88 deletions.
6 changes: 1 addition & 5 deletions .github/workflows/generate-sandboxes.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,8 +72,4 @@ jobs:
with:
args: |
The generation of some or all sandboxes on the **next** branch has failed.
[View Job](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ github.job }})
${{ steps.generate.outputs.failed-before-script-templates && format('The following templates failed to execute the before script:\n- {0}', steps.generate.outputs.failed-before-script-templates) || '' }}
${{ steps.generate.outputs.failed-before-script-templates && format('The following templates failed to initialize Storybook:\n- {0}', steps.generate.outputs.failed-init-templates) || '' }}
[See the job summary for details](https://github.com/${{ github.repository }}/actions/runs/${{ github.run_id }}/job/${{ github.job }})
218 changes: 135 additions & 83 deletions scripts/sandbox/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,14 @@ import {
REPROS_DIRECTORY,
LOCAL_REGISTRY_URL,
} from '../utils/constants';
import { setOutput } from '@actions/core';
import * as ghActions from '@actions/core';

const isCI = process.env.GITHUB_ACTIONS === 'true';

const logError = isCI ? ghActions.error : console.error;

class BeforeScriptExecutionError extends Error {}
class StorybookInitError extends Error {}

const sbInit = async (
cwd: string,
Expand Down Expand Up @@ -152,105 +159,150 @@ const runGenerators = async (

const limit = pLimit(1);

const failedBeforeScriptTemplates: string[] = [];
const failedInitTemplates: string[] = [];

await Promise.allSettled(
const generationResults = await Promise.allSettled(
generators.map(({ dirName, name, script, expected, env }) =>
limit(async () => {
let flags: string[] = [];
if (expected.renderer === '@storybook/html') flags = ['--type html'];
else if (expected.renderer === '@storybook/server') flags = ['--type server'];

const time = process.hrtime();
console.log(`🧬 Generating ${name}`);
try {
if (isCI) {
ghActions.startGroup(`Generating sandbox for ${name}`);
}

const baseDir = join(REPROS_DIRECTORY, dirName);
const beforeDir = join(baseDir, BEFORE_DIR_NAME);
await emptyDir(baseDir);
let flags: string[] = [];
if (expected.renderer === '@storybook/html') flags = ['--type html'];
else if (expected.renderer === '@storybook/server') flags = ['--type server'];

// We do the creation inside a temp dir to avoid yarn container problems
const createBaseDir = directory();
if (!script.includes('pnp')) {
await setupYarn({ cwd: createBaseDir });
}
const time = process.hrtime();
console.log(`🧬 Generating ${name}`);

const createBeforeDir = join(createBaseDir, BEFORE_DIR_NAME);
const baseDir = join(REPROS_DIRECTORY, dirName);
const beforeDir = join(baseDir, BEFORE_DIR_NAME);
await emptyDir(baseDir);

// Some tools refuse to run inside an existing directory and replace the contents,
// where as others are very picky about what directories can be called. So we need to
// handle different modes of operation.
try {
if (Math.random() < 0.2) {
throw new Error('Blip Bloop random error when executing before-script');
// We do the creation inside a temp dir to avoid yarn container problems
const createBaseDir = directory();
if (!script.includes('pnp')) {
await setupYarn({ cwd: createBaseDir });
}
if (script.includes('{{beforeDir}}')) {
const scriptWithBeforeDir = script.replaceAll('{{beforeDir}}', BEFORE_DIR_NAME);
await runCommand(
scriptWithBeforeDir,
{
cwd: createBaseDir,
timeout: SCRIPT_TIMEOUT,
},
debug
);
} else {
await ensureDir(createBeforeDir);
await runCommand(script, { cwd: createBeforeDir, timeout: SCRIPT_TIMEOUT }, debug);
}
} catch (error) {
console.error(`❌ Failed to execute before-script for template: ${name}`);
console.error(error);
failedBeforeScriptTemplates.push(name);
return;
}

await localizeYarnConfigFiles(createBaseDir, createBeforeDir);

// Now move the created before dir into it's final location and add storybook
await move(createBeforeDir, beforeDir);
const createBeforeDir = join(createBaseDir, BEFORE_DIR_NAME);

// Some tools refuse to run inside an existing directory and replace the contents,
// where as others are very picky about what directories can be called. So we need to
// handle different modes of operation.
try {
if (Math.random() < 0.4) {
throw new Error('Blip Bloop random error when executing before-script');
}
if (script.includes('{{beforeDir}}')) {
const scriptWithBeforeDir = script.replaceAll('{{beforeDir}}', BEFORE_DIR_NAME);
await runCommand(
scriptWithBeforeDir,
{
cwd: createBaseDir,
timeout: SCRIPT_TIMEOUT,
},
debug
);
} else {
await ensureDir(createBeforeDir);
await runCommand(script, { cwd: createBeforeDir, timeout: SCRIPT_TIMEOUT }, debug);
}
} catch (error) {
const message = `❌ Failed to execute before-script for template: ${name}`;
logError(message);
logError(error);
logError((error as any).stack);
throw new BeforeScriptExecutionError(message, { cause: error });
}

// Make sure there are no git projects in the folder
await remove(join(beforeDir, '.git'));
await localizeYarnConfigFiles(createBaseDir, createBeforeDir);

// Now move the created before dir into it's final location and add storybook
await move(createBeforeDir, beforeDir);

// Make sure there are no git projects in the folder
await remove(join(beforeDir, '.git'));

try {
if (Math.random() < 0.4) {
throw new Error('Blip Bloop random error when init storybook');
}
await addStorybook({ baseDir, localRegistry, flags, debug, env });
} catch (error) {
const message = `❌ Failed to initialize Storybook in template: ${name}`;
logError(message);
logError(error);
logError((error as any).stack);
throw new StorybookInitError(message, {
cause: error,
});
}
if (Math.random() < 0.4) {
throw new Error('Blip Bloop random error anywhere');
}
await addDocumentation(baseDir, { name, dirName });

// Remove node_modules to save space and avoid GH actions failing
// They're not uploaded to the git sandboxes repo anyway
if (process.env.CLEANUP_SANDBOX_NODE_MODULES) {
console.log(`🗑️ Removing ${join(beforeDir, 'node_modules')}`);
await remove(join(beforeDir, 'node_modules'));
console.log(`🗑️ Removing ${join(baseDir, AFTER_DIR_NAME, 'node_modules')}`);
await remove(join(baseDir, AFTER_DIR_NAME, 'node_modules'));
}

try {
await addStorybook({ baseDir, localRegistry, flags, debug, env });
console.log(
`✅ Created ${dirName} in ./${relative(
process.cwd(),
baseDir
)} successfully in ${prettyTime(process.hrtime(time))}`
);
} catch (error) {
console.error(`❌ Failed to add Storybook to template: ${name}`);
console.error(error);
failedInitTemplates.push(name);
return;
}

await addDocumentation(baseDir, { name, dirName });

// Remove node_modules to save space and avoid GH actions failing
// They're not uploaded to the git sandboxes repo anyway
if (process.env.CLEANUP_SANDBOX_NODE_MODULES) {
console.log(`🗑️ Removing ${join(beforeDir, 'node_modules')}`);
await remove(join(beforeDir, 'node_modules'));
console.log(`🗑️ Removing ${join(baseDir, AFTER_DIR_NAME, 'node_modules')}`);
await remove(join(baseDir, AFTER_DIR_NAME, 'node_modules'));
throw error;
} finally {
if (isCI) {
ghActions.endGroup();
}
}

console.log(
`✅ Created ${dirName} in ./${relative(
process.cwd(),
baseDir
)} successfully in ${prettyTime(process.hrtime(time))}`
);
})
)
);

if (process.env.GITHUB_ACTIONS === 'true') {
if (failedBeforeScriptTemplates.length > 0) {
setOutput('failed-before-script-templates', failedBeforeScriptTemplates.join('\n- '));
}
if (failedInitTemplates.length > 0) {
setOutput('failed-init-templates', failedInitTemplates.join('\n- '));
}
ghActions.summary.addHeading('Sandbox generation summary');

if (!generationResults.some((result) => result.status === 'rejected')) {
await ghActions.summary.addRaw('✅ Success!').write();
return;
}

if (isCI) {
await ghActions.summary
.addRaw('Some sandboxes failed, see the action log for details')
.addTable([
[
{ data: 'Template', header: true },
{ data: 'Result', header: true },
],
...generationResults.map((result, index) => {
const template = generators[index].name;
if (result.status === 'fulfilled') {
return [template, '🟢 Pass'];
}
const generationError = (result as PromiseRejectedResult).reason as Error;
const errorCause = generationError.cause;
if (errorCause instanceof BeforeScriptExecutionError) {
return [template, '🔴 Failed to execute before script'];
} else if (errorCause instanceof StorybookInitError) {
return [template, '🔴 Failed to initialize Storybook'];
} else {
return [template, '🔴 Failed with unknown error'];
}
}),
])
.write();
}

throw new Error(`Some sandboxes failed to generate`);
};

export const options = createOptions({
Expand Down

0 comments on commit 78f122c

Please sign in to comment.