mirror of
https://github.com/storybookjs/storybook.git
synced 2025-03-16 05:03:11 +08:00
Ensure the sandbox exits cleanly if publishing
This commit is contained in:
parent
a05bccefbf
commit
ed2c21ec65
@ -147,6 +147,7 @@
|
||||
"lint-staged": "^10.5.4",
|
||||
"lodash": "^4.17.21",
|
||||
"mocha-list-tests": "^1.0.5",
|
||||
"node-abort-controller": "^3.0.1",
|
||||
"node-cleanup": "^2.1.2",
|
||||
"node-fetch": "^2.6.1",
|
||||
"node-gyp": "^8.4.0",
|
||||
|
@ -10,6 +10,7 @@ import {
|
||||
existsSync,
|
||||
} from 'fs-extra';
|
||||
import prompts from 'prompts';
|
||||
import { AbortController } from 'node-abort-controller';
|
||||
|
||||
import { createOptions, getOptionsOrPrompt, OptionValues } from './utils/options';
|
||||
import { executeCLIStep } from './utils/cli-step';
|
||||
@ -246,6 +247,7 @@ export async function sandbox(optionValues: OptionValues<typeof options>) {
|
||||
const { template, forceDelete, forceReuse, dryRun, debug } = optionValues;
|
||||
|
||||
await ensureDir(sandboxDir);
|
||||
let publishController: AbortController;
|
||||
|
||||
const cwd = path.join(sandboxDir, template.replace('/', '-'));
|
||||
|
||||
@ -331,8 +333,15 @@ export async function sandbox(optionValues: OptionValues<typeof options>) {
|
||||
}
|
||||
|
||||
if (publish || startVerdaccio) {
|
||||
// NOTE: this is a background task and will run forever (TODO: sort out logging/exiting)
|
||||
exec('CI=true yarn local-registry --open', { cwd: codeDir }, { dryRun, debug });
|
||||
publishController = new AbortController();
|
||||
exec(
|
||||
'CI=true yarn local-registry --open',
|
||||
{ cwd: codeDir },
|
||||
{ dryRun, debug, signal: publishController.signal as AbortSignal }
|
||||
).catch((err) => {
|
||||
// If aborted, we want to make sure the rejection is handled.
|
||||
if (!err.killed) throw err;
|
||||
});
|
||||
await exec('yarn wait-on http://localhost:6000', { cwd: codeDir }, { dryRun, debug });
|
||||
}
|
||||
|
||||
@ -382,6 +391,9 @@ export async function sandbox(optionValues: OptionValues<typeof options>) {
|
||||
}
|
||||
|
||||
// TODO start dev
|
||||
|
||||
// Cleanup
|
||||
publishController.abort();
|
||||
}
|
||||
|
||||
async function main() {
|
||||
|
@ -40,34 +40,61 @@ type TaskKey = keyof typeof tasks;
|
||||
export const options = createOptions({
|
||||
task: {
|
||||
type: 'string',
|
||||
values: Object.keys(tasks) as TaskKey[],
|
||||
description: 'What task are you performing (corresponds to CI job)?',
|
||||
values: Object.keys(tasks) as TaskKey[],
|
||||
required: true,
|
||||
},
|
||||
template: {
|
||||
type: 'string',
|
||||
values: Object.keys(TEMPLATES) as TemplateKey[],
|
||||
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,
|
||||
},
|
||||
});
|
||||
|
||||
const logger = console;
|
||||
|
||||
async function runTask(taskKey: TaskKey, templateKey: TemplateKey) {
|
||||
async function runTask(
|
||||
taskKey: TaskKey,
|
||||
templateKey: TemplateKey,
|
||||
{
|
||||
mustNotBeReady,
|
||||
mustBeReady,
|
||||
before,
|
||||
}: { mustNotBeReady: boolean; mustBeReady: boolean; before: boolean }
|
||||
) {
|
||||
const task = tasks[taskKey];
|
||||
const template = TEMPLATES[templateKey];
|
||||
const templateSandboxDir = join(sandboxDir, templateKey.replace('/', '-'));
|
||||
const details = { template, sandboxDir: templateSandboxDir };
|
||||
|
||||
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);
|
||||
await runTask(beforeKey, templateKey, {
|
||||
mustNotBeReady: false,
|
||||
mustBeReady: !before,
|
||||
before,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -75,15 +102,20 @@ async function runTask(taskKey: TaskKey, templateKey: TemplateKey) {
|
||||
}
|
||||
|
||||
async function run() {
|
||||
const { task: taskKey, template: templateKey } = await getOptionsOrPrompt('yarn task', options);
|
||||
const {
|
||||
task: taskKey,
|
||||
template: templateKey,
|
||||
force,
|
||||
before,
|
||||
} = await getOptionsOrPrompt('yarn task', options);
|
||||
|
||||
return runTask(taskKey, templateKey);
|
||||
return runTask(taskKey, templateKey, { mustBeReady: force, mustNotBeReady: false, before });
|
||||
}
|
||||
|
||||
if (require.main === module) {
|
||||
run().catch((err) => {
|
||||
logger.error(`🚨 An error occurred when executing task:`);
|
||||
logger.error(err);
|
||||
logger.error();
|
||||
logger.error(err.message);
|
||||
process.exit(1);
|
||||
});
|
||||
}
|
||||
|
@ -1,5 +1,5 @@
|
||||
/* eslint-disable no-await-in-loop, no-restricted-syntax */
|
||||
import execa, { Options } from 'execa';
|
||||
import execa, { ExecaChildProcess, Options } from 'execa';
|
||||
import chalk from 'chalk';
|
||||
|
||||
const logger = console;
|
||||
@ -9,12 +9,13 @@ type StepOptions = {
|
||||
errorMessage?: string;
|
||||
dryRun?: boolean;
|
||||
debug?: boolean;
|
||||
signal?: AbortSignal;
|
||||
};
|
||||
|
||||
export const exec = async (
|
||||
command: string | string[],
|
||||
options: Options = {},
|
||||
{ startMessage, errorMessage, dryRun, debug }: StepOptions = {}
|
||||
{ startMessage, errorMessage, dryRun, debug, signal }: StepOptions = {}
|
||||
): Promise<void> => {
|
||||
logger.info();
|
||||
if (startMessage) logger.info(startMessage);
|
||||
@ -28,20 +29,32 @@ export const exec = async (
|
||||
shell: true,
|
||||
stdout: debug ? 'inherit' : 'ignore',
|
||||
};
|
||||
let currentChild: ExecaChildProcess;
|
||||
|
||||
// Newer versions of execa have explicit support for abort signals, but this works
|
||||
if (signal) {
|
||||
signal.addEventListener('abort', () => currentChild.kill());
|
||||
}
|
||||
|
||||
try {
|
||||
if (typeof command === 'string') {
|
||||
logger.debug(`> ${command}`);
|
||||
await execa.command(command, { ...defaultOptions, ...options });
|
||||
currentChild = execa.command(command, { ...defaultOptions, ...options });
|
||||
await currentChild;
|
||||
} else {
|
||||
for (const subcommand of command) {
|
||||
logger.debug(`> ${subcommand}`);
|
||||
await execa.command(subcommand, { ...defaultOptions, ...options });
|
||||
currentChild = execa.command(subcommand, { ...defaultOptions, ...options });
|
||||
await currentChild;
|
||||
}
|
||||
}
|
||||
} catch (err) {
|
||||
logger.error(chalk.red(`An error occurred while executing: \`${command}\``));
|
||||
logger.error(err);
|
||||
logger.log(errorMessage);
|
||||
if (!err.killed) {
|
||||
logger.error(chalk.red(`An error occurred while executing: \`${command}\``));
|
||||
logger.error(err);
|
||||
logger.log(errorMessage);
|
||||
}
|
||||
|
||||
throw err;
|
||||
}
|
||||
|
||||
|
@ -3334,6 +3334,7 @@ __metadata:
|
||||
lint-staged: ^10.5.4
|
||||
lodash: ^4.17.21
|
||||
mocha-list-tests: ^1.0.5
|
||||
node-abort-controller: ^3.0.1
|
||||
node-cleanup: ^2.1.2
|
||||
node-fetch: ^2.6.1
|
||||
node-gyp: ^8.4.0
|
||||
@ -14476,6 +14477,13 @@ __metadata:
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-abort-controller@npm:^3.0.1":
|
||||
version: 3.0.1
|
||||
resolution: "node-abort-controller@npm:3.0.1"
|
||||
checksum: 37f895533f7a18a2d83fa4853da1cc00fcae1e0a71553f9ffc94d3153f5fc886d6d4ef3a33bf60c38be161fab78c5b2275cbbf2359351fb12f5edad68d88d8ca
|
||||
languageName: node
|
||||
linkType: hard
|
||||
|
||||
"node-cleanup@npm:^2.1.2":
|
||||
version: 2.1.2
|
||||
resolution: "node-cleanup@npm:2.1.2"
|
||||
|
Loading…
x
Reference in New Issue
Block a user