2022-07-26 11:41:33 +02:00
|
|
|
/* eslint-disable no-console */
|
|
|
|
import path, { join, relative } from 'path';
|
|
|
|
import program from 'commander';
|
|
|
|
import { command } from 'execa';
|
|
|
|
import type { Options as ExecaOptions } from 'execa';
|
|
|
|
import yaml from 'js-yaml';
|
|
|
|
import pLimit from 'p-limit';
|
|
|
|
import prettyTime from 'pretty-hrtime';
|
2022-07-29 17:22:12 +02:00
|
|
|
import { copy, emptyDir, ensureDir, readFile, rename, writeFile } from 'fs-extra';
|
2022-07-27 11:16:08 +02:00
|
|
|
// @ts-ignore
|
|
|
|
import { maxConcurrentTasks } from '../utils/concurrency';
|
2022-07-26 11:41:33 +02:00
|
|
|
|
2022-07-27 11:16:08 +02:00
|
|
|
import { localizeYarnConfigFiles, setupYarn } from './utils/yarn';
|
2022-07-29 16:38:03 +02:00
|
|
|
import { GeneratorConfig } from './utils/types';
|
2022-07-29 17:22:12 +02:00
|
|
|
import { getStackblitzUrl, renderTemplate } from './utils/template';
|
2022-07-26 11:41:33 +02:00
|
|
|
|
|
|
|
const OUTPUT_DIRECTORY = join(__dirname, '..', '..', 'repros');
|
2022-07-27 16:09:29 +02:00
|
|
|
const BEFORE_DIR_NAME = 'before-storybook';
|
|
|
|
const AFTER_DIR_NAME = 'after-storybook';
|
2022-07-26 11:41:33 +02:00
|
|
|
|
|
|
|
const addStorybook = async (baseDir: string) => {
|
2022-07-27 11:16:08 +02:00
|
|
|
const beforeDir = join(baseDir, BEFORE_DIR_NAME);
|
|
|
|
const afterDir = join(baseDir, AFTER_DIR_NAME);
|
2022-07-26 11:41:33 +02:00
|
|
|
const tmpDir = join(baseDir, '.tmp');
|
|
|
|
|
|
|
|
await ensureDir(tmpDir);
|
|
|
|
await emptyDir(tmpDir);
|
|
|
|
|
|
|
|
await copy(beforeDir, tmpDir);
|
|
|
|
|
|
|
|
const sbCliBinaryPath = join(__dirname, `../../code/lib/cli/bin/index.js`);
|
|
|
|
await runCommand(`${sbCliBinaryPath} init`, {
|
|
|
|
cwd: tmpDir,
|
2022-07-27 11:16:08 +02:00
|
|
|
env: {
|
|
|
|
STORYBOOK_DISABLE_TELEMETRY: 'true',
|
|
|
|
},
|
2022-07-26 11:41:33 +02:00
|
|
|
});
|
|
|
|
|
|
|
|
await rename(tmpDir, afterDir);
|
|
|
|
};
|
|
|
|
|
2022-07-27 11:16:08 +02:00
|
|
|
export const runCommand = async (script: string, options: ExecaOptions) => {
|
|
|
|
const shouldDebug = !!process.env.DEBUG;
|
2022-07-26 11:41:33 +02:00
|
|
|
|
|
|
|
if (shouldDebug) {
|
2022-07-26 13:14:22 +02:00
|
|
|
console.log(`Running command: ${script}`);
|
2022-07-26 11:41:33 +02:00
|
|
|
}
|
|
|
|
|
2022-07-26 13:14:22 +02:00
|
|
|
return command(script, { stdout: shouldDebug ? 'inherit' : 'ignore', ...options });
|
2022-07-26 11:41:33 +02:00
|
|
|
};
|
|
|
|
|
2022-07-29 17:22:12 +02:00
|
|
|
const addDocumentation = async (
|
|
|
|
baseDir: string,
|
|
|
|
{ name, dirName }: { name: string; dirName: string }
|
|
|
|
) => {
|
|
|
|
const afterDir = join(baseDir, AFTER_DIR_NAME);
|
|
|
|
const stackblitzConfigPath = join(__dirname, 'templates', '.stackblitzrc');
|
|
|
|
const readmePath = join(__dirname, 'templates', 'item.ejs');
|
|
|
|
|
|
|
|
await copy(stackblitzConfigPath, join(afterDir, '.stackblitzrc'));
|
|
|
|
|
|
|
|
const stackblitzUrl = getStackblitzUrl(dirName);
|
|
|
|
const contents = await renderTemplate(readmePath, {
|
|
|
|
name,
|
|
|
|
stackblitzUrl,
|
|
|
|
});
|
|
|
|
await writeFile(join(afterDir, 'README.md'), contents);
|
|
|
|
};
|
|
|
|
|
|
|
|
const runGenerators = async (generators: (GeneratorConfig & { dirName: string })[]) => {
|
2022-07-26 11:41:33 +02:00
|
|
|
console.log(`🤹♂️ Generating repros with a concurrency of ${maxConcurrentTasks}`);
|
|
|
|
|
|
|
|
const limit = pLimit(maxConcurrentTasks);
|
|
|
|
|
|
|
|
return Promise.all(
|
2022-07-29 17:22:12 +02:00
|
|
|
generators.map(({ dirName, name, script }) =>
|
2022-07-26 11:41:33 +02:00
|
|
|
limit(async () => {
|
|
|
|
const time = process.hrtime();
|
|
|
|
console.log(`🧬 generating ${name}`);
|
|
|
|
|
2022-07-29 17:22:12 +02:00
|
|
|
const baseDir = join(OUTPUT_DIRECTORY, dirName);
|
2022-07-27 11:16:08 +02:00
|
|
|
const beforeDir = join(baseDir, BEFORE_DIR_NAME);
|
2022-07-26 11:41:33 +02:00
|
|
|
|
2022-07-27 11:16:08 +02:00
|
|
|
await emptyDir(baseDir);
|
|
|
|
await ensureDir(beforeDir);
|
2022-07-26 11:41:33 +02:00
|
|
|
|
2022-07-27 11:16:08 +02:00
|
|
|
await setupYarn({ cwd: baseDir });
|
2022-07-26 13:14:22 +02:00
|
|
|
|
2022-07-26 11:41:33 +02:00
|
|
|
await runCommand(script, { cwd: beforeDir });
|
|
|
|
|
2022-07-27 11:16:08 +02:00
|
|
|
await localizeYarnConfigFiles(baseDir, beforeDir);
|
|
|
|
|
2022-07-26 11:41:33 +02:00
|
|
|
await addStorybook(baseDir);
|
|
|
|
|
2022-07-29 17:22:12 +02:00
|
|
|
await addDocumentation(baseDir, { name, dirName });
|
|
|
|
|
2022-07-26 11:41:33 +02:00
|
|
|
console.log(
|
2022-07-29 17:22:12 +02:00
|
|
|
`✅ Created ${dirName} in ./${relative(
|
|
|
|
process.cwd(),
|
|
|
|
baseDir
|
|
|
|
)} successfully in ${prettyTime(process.hrtime(time))}`
|
2022-07-26 11:41:33 +02:00
|
|
|
);
|
|
|
|
})
|
|
|
|
)
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
const generate = async ({ config }: { config: string }) => {
|
|
|
|
const configContents = await readFile(config, 'utf8');
|
2022-07-29 17:22:12 +02:00
|
|
|
const data: Record<string, GeneratorConfig> = yaml.load(configContents);
|
2022-07-26 11:41:33 +02:00
|
|
|
|
|
|
|
runGenerators(
|
2022-07-29 17:22:12 +02:00
|
|
|
Object.entries(data).map(([dirName, configuration]) => ({
|
|
|
|
dirName,
|
|
|
|
...configuration,
|
2022-07-26 11:41:33 +02:00
|
|
|
}))
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
|
|
|
program
|
|
|
|
.description('Create a reproduction from a set of possible templates')
|
|
|
|
.option(
|
|
|
|
'-c --config <config>',
|
|
|
|
'Choose a custom configuration file (.yml format)',
|
|
|
|
path.join(__dirname, 'repro-config.yml')
|
|
|
|
);
|
|
|
|
|
|
|
|
program.parse(process.argv);
|
|
|
|
|
|
|
|
const options = program.opts() as { config: string };
|
|
|
|
|
|
|
|
generate(options).catch((e) => {
|
2022-07-27 11:16:08 +02:00
|
|
|
console.trace(e);
|
2022-07-26 11:41:33 +02:00
|
|
|
process.exit(1);
|
|
|
|
});
|