storybook/scripts/build-storybooks.js
2022-07-23 17:15:13 +02:00

230 lines
6.2 KiB
JavaScript
Executable File

import { spawn } from 'child_process';
import { promisify } from 'util';
import { readdir as readdirRaw, writeFile as writeFileRaw, readFileSync, existsSync } from 'fs';
import { join } from 'path';
import program from 'commander';
import prompts from 'prompts';
import chalk from 'chalk';
import { getDeployables } from './utils/list-examples';
import { filterDataForCurrentCircleCINode } from './utils/concurrency';
program
.option(
'--skip <value>',
'Skip an example, accepts multiple values like "--skip vue-kitchen-sink official-storybook"',
(value, previous) => previous.concat([value]),
[]
)
.option('--all', `run e2e tests for every example`, false);
program.parse(process.argv);
const { all: shouldRunAllExamples, args: exampleArgs, skip: examplesToSkip } = program;
const readdir = promisify(readdirRaw);
const writeFile = promisify(writeFileRaw);
const p = (l) => join(__dirname, '..', 'code', ...l);
const logger = console;
const exec = async (command, args = [], options = {}) =>
new Promise((resolve, reject) => {
const child = spawn(command, args, {
...options,
stdio: 'inherit',
shell: true,
});
child
.on('close', (code) => {
if (code) {
reject();
} else {
resolve();
}
})
.on('error', (e) => {
logger.error(e);
reject();
});
});
const hasBuildScript = (l) => {
const text = readFileSync(l, 'utf8');
const json = JSON.parse(text);
return !!json.scripts['build-storybook'];
};
const createContent = (deployables) => {
return `
<style>
body {
background: black;
color: white;
}
#frame {
position: absolute;
left: 0;
right: 0;
width: 100vw;
height: calc(100vh - 30px);
bottom: 0;
top: 30px;
border: 0 none;
margin: 0;
padding: 0;
}
#select {
position: absolute;
top: 0;
right: 100px;
left: 10px;
height: 30px;
width: calc(100vw - 120px);
background: black;
color: white;
border: 0 none;
border-radius: 0;
padding: 10px;
box-sizing: border-box;
}
#open {
position: absolute;
top: 0;
right: 0;
width: 100px;
height: 30px;
background: black;
color: white;
border: 0 none;
border-radius: 0;
padding: 10px;
box-sizing: border-box;
}
</style>
<script>
function handleClick() {
var value = document.getElementById("select").value;
window.location = document.location.origin + value;
}
function handleSelect() {
var value = document.getElementById("select").value;
var frame = document.getElementById("frame");
sessionStorage.clear();
localStorage.clear();
frame.setAttribute('src', value);
}
</script>
<button id="open" onclick="handleClick()">open</button>
<select id="select" onchange="handleSelect()">
${deployables.map((i) => `<option value="/${i}/">${i}</option>`).join('\n')}
</select>
<iframe id="frame" src="/${deployables[0]}/" />
`;
};
const handleExamples = async (deployables) => {
await deployables.reduce(async (acc, d) => {
await acc;
logger.log('');
logger.log(`-----------------${Array(d.length).fill('-').join('')}`);
logger.log(`▶️ building: ${d}`);
logger.log(`-----------------${Array(d.length).fill('-').join('')}`);
const out = p(['built-storybooks', d]);
const cwd = p(['examples', d]);
if (existsSync(join(cwd, 'yarn.lock'))) {
await exec(`yarn`, [`install`], { cwd });
}
await exec(`yarn`, [`build-storybook`, `--output-dir=${out}`, '--quiet'], {
cwd,
});
// If the example uses `storyStoreV7` or `buildStoriesJson`, stories.json already exists
// It can fail on local machines if puppeteer fails to install, that's not critical, it will work without it
if (!existsSync(`${out}/stories.json`)) {
await exec(`npx`, [`sb`, 'extract', out, `${out}/stories.json`], {
cwd,
}).catch(() => {});
}
logger.log('-------');
logger.log(`${d} built`);
logger.log('-------');
}, Promise.resolve());
};
const run = async () => {
const allExamples = await readdir(p(['examples']));
// if a specific example is passed, use it. Else use all
let examplesToBuild =
exampleArgs.length > 0
? exampleArgs
: allExamples.filter((example) => !example.includes('README'));
if (examplesToSkip.length > 0) {
logger.log(`⏭ Will skip the following examples: ${chalk.yellow(examplesToSkip.join(', '))}`);
examplesToBuild = examplesToBuild.filter((example) => !examplesToSkip.includes(example));
}
if (!shouldRunAllExamples && exampleArgs.length === 0) {
const { selectedExamples } = await prompts([
{
type: 'autocompleteMultiselect',
message: 'Select the examples to build',
name: 'selectedExamples',
min: 1,
hint: 'You can also run directly with example name like `yarn build-storybooks official-example`, or `yarn build-storybooks --all` for all examples!',
choices: examplesToBuild.map((exampleName) => {
return {
value: exampleName,
title: exampleName,
selected: false,
};
}),
},
]);
examplesToBuild = selectedExamples;
}
const list = getDeployables(examplesToBuild, hasBuildScript);
const deployables = filterDataForCurrentCircleCINode(list);
if (deployables.length) {
logger.log(`🏗 Will build Storybook for: ${chalk.cyan(deployables.join(', '))}`);
await handleExamples(deployables);
}
if (
deployables.length &&
(process.env.CIRCLE_NODE_INDEX === undefined ||
process.env.CIRCLE_NODE_INDEX === '0' ||
process.env.CIRCLE_NODE_INDEX === 0)
) {
const indexLocation = p(['built-storybooks', 'index.html']);
logger.log('');
logger.log(`📑 creating index at: ${indexLocation}`);
logger.log('');
await writeFile(indexLocation, createContent(deployables));
logger.log('-------');
logger.log('✅ done');
logger.log('-------');
}
};
run().catch((e) => {
logger.error(e);
process.exit(1);
});