storybook/scripts/task.ts
2022-08-12 16:20:15 +10:00

188 lines
4.9 KiB
TypeScript

/* eslint-disable no-await-in-loop, no-restricted-syntax */
import { getJunitXml } from 'junit-xml';
import { outputFile } from 'fs-extra';
import { join, resolve } from 'path';
import { createOptions, getOptionsOrPrompt } from './utils/options';
import { bootstrap } from './tasks/bootstrap';
import { publish } from './tasks/publish';
import { create } from './tasks/create';
import { smokeTest } from './tasks/smoke-test';
import { build } from './tasks/build';
import { testRunner } from './tasks/test-runner';
import { chromatic } from './tasks/chromatic';
import { e2eTests } from './tasks/e2e-tests';
import TEMPLATES from '../code/lib/cli/src/repro-templates';
const sandboxDir = resolve(__dirname, '../sandbox');
const junitDir = resolve(__dirname, '../code/test-results');
export type TemplateKey = keyof typeof TEMPLATES;
export type Template = typeof TEMPLATES[TemplateKey];
export type Path = string;
export type TemplateDetails = {
template: Template;
sandboxDir: Path;
builtSandboxDir: Path;
junitFilename: Path;
};
type MaybePromise<T> = T | Promise<T>;
export type Task = {
/**
* Which tasks run before this task
*/
before?: TaskKey[];
/**
* Is this task already "ready", and potentially not required?
*/
ready: (templateKey: TemplateKey, details: TemplateDetails) => MaybePromise<boolean>;
/**
* Run the task
*/
run: (templateKey: TemplateKey, details: TemplateDetails) => MaybePromise<void>;
/**
* Does this task handle its own junit results?
*/
junit?: boolean;
};
export const tasks = {
bootstrap,
publish,
create,
'smoke-test': smokeTest,
build,
'test-runner': testRunner,
chromatic,
'e2e-tests': e2eTests,
};
type TaskKey = keyof typeof tasks;
export const options = createOptions({
task: {
type: 'string',
description: 'What task are you performing (corresponds to CI job)?',
values: Object.keys(tasks) as TaskKey[],
required: true,
},
template: {
type: 'string',
description: 'What template are you running against?',
values: Object.keys(TEMPLATES) as TemplateKey[],
required: true,
},
force: {
type: 'boolean',
description: 'The task must run, it is an error if it is already ready.',
},
before: {
type: 'boolean',
description: 'Run any required dependencies of the task?',
inverse: true,
},
junit: {
type: 'boolean',
description: 'Store results in junit format?',
},
});
const logger = console;
function getJunitFilename(taskKey: TaskKey) {
return join(junitDir, `${taskKey}.xml`);
}
async function writeJunitXml(taskKey: TaskKey, templateKey: TemplateKey, start: Date, err?: Error) {
const name = `${taskKey} - ${templateKey}`;
const time = (Date.now() - +start) / 1000;
const testCase = { name, assertions: 1, time, ...(err && { errors: [err] }) };
const suite = { name, timestamp: start, time, testCases: [testCase] };
const junitXml = getJunitXml({ time, name, suites: [suite] });
const path = getJunitFilename(taskKey);
await outputFile(path, junitXml);
logger.log(`Test results written to ${resolve(path)}`);
}
async function runTask(
taskKey: TaskKey,
templateKey: TemplateKey,
{
mustNotBeReady,
mustBeReady,
before,
junit,
}: { mustNotBeReady: boolean; mustBeReady: boolean; before: boolean; junit: boolean }
) {
const task = tasks[taskKey];
const template = TEMPLATES[templateKey];
const templateSandboxDir = join(sandboxDir, templateKey.replace('/', '-'));
const details = {
template,
sandboxDir: templateSandboxDir,
builtSandboxDir: join(templateSandboxDir, 'storybook-static'),
junitFilename: junit && getJunitFilename(taskKey),
};
if (await task.ready(templateKey, details)) {
if (mustNotBeReady) throw new Error(`${taskKey} task has already run, this is unexpected!`);
logger.debug(`${taskKey} task not required!`);
return;
}
if (mustBeReady) {
throw new Error(`${taskKey} task has not already run, this is unexpected!`);
}
if (task.before?.length > 0) {
for (const beforeKey of task.before) {
await runTask(beforeKey, templateKey, {
mustNotBeReady: false,
mustBeReady: !before,
before,
junit: false, // never store junit results for dependent tasks
});
}
}
const start = new Date();
try {
await task.run(templateKey, details);
if (junit && !task.junit) await writeJunitXml(taskKey, templateKey, start);
} catch (err) {
if (junit && !task.junit) await writeJunitXml(taskKey, templateKey, start, err);
throw err;
}
}
async function run() {
const {
task: taskKey,
template: templateKey,
force,
before,
junit,
} = await getOptionsOrPrompt('yarn task', options);
return runTask(taskKey, templateKey, {
mustBeReady: force,
mustNotBeReady: false,
before,
junit,
});
}
if (require.main === module) {
run().catch((err) => {
logger.error();
logger.error(err.message);
process.exit(1);
});
}