2022-07-31 22:26:49 +10:00
|
|
|
/* eslint-disable no-restricted-syntax, no-await-in-loop */
|
2022-07-25 22:43:46 +10:00
|
|
|
import path from 'path';
|
2022-07-31 22:26:49 +10:00
|
|
|
import {
|
|
|
|
remove,
|
|
|
|
pathExists,
|
|
|
|
readJSON,
|
|
|
|
writeJSON,
|
|
|
|
ensureSymlink,
|
|
|
|
readFile,
|
|
|
|
writeFile,
|
|
|
|
mkdir,
|
|
|
|
} from 'fs-extra';
|
2022-07-27 22:09:36 +10:00
|
|
|
import prompts from 'prompts';
|
2022-07-31 22:26:49 +10:00
|
|
|
import globby from 'globby';
|
|
|
|
import { transform } from 'esbuild';
|
2022-07-25 22:43:46 +10:00
|
|
|
|
2022-07-25 11:19:26 +10:00
|
|
|
import { getOptionsOrPrompt } from './utils/options';
|
2022-07-25 22:43:46 +10:00
|
|
|
import { executeCLIStep } from './utils/cli-step';
|
2022-07-28 20:30:40 +10:00
|
|
|
import { exec } from '../code/lib/cli/src/repro-generators/scripts';
|
2022-07-31 14:27:09 +10:00
|
|
|
import type { Parameters } from '../code/lib/cli/src/repro-generators/configs';
|
|
|
|
import { getInterpretedFile } from '../code/lib/core-common';
|
|
|
|
import { readConfig, writeConfig } from '../code/lib/csf-tools';
|
|
|
|
import { babelParse } from '../code/lib/csf-tools/src/babelParse';
|
2022-07-25 11:19:26 +10:00
|
|
|
|
2022-07-28 20:15:57 +10:00
|
|
|
const frameworks = ['react', 'angular'];
|
2022-07-25 11:19:26 +10:00
|
|
|
const addons = ['a11y', 'storysource'];
|
2022-07-31 22:26:49 +10:00
|
|
|
const defaultAddons = [
|
|
|
|
'actions',
|
|
|
|
'backgrounds',
|
|
|
|
'controls',
|
|
|
|
'docs',
|
|
|
|
'highlight',
|
|
|
|
'links',
|
|
|
|
'interactions',
|
|
|
|
'measure',
|
|
|
|
'outline',
|
|
|
|
'toolbars',
|
|
|
|
'viewport',
|
|
|
|
];
|
2022-07-26 16:12:07 +10:00
|
|
|
const examplesDir = path.resolve(__dirname, '../examples');
|
2022-07-28 21:21:58 +10:00
|
|
|
const codeDir = path.resolve(__dirname, '../code');
|
2022-07-25 11:19:26 +10:00
|
|
|
|
2022-07-31 22:26:49 +10:00
|
|
|
// TODO -- how to encode this information
|
|
|
|
const renderersMap = { react: 'react', angular: 'angular' };
|
|
|
|
const isTSMap = { react: false, angular: true };
|
|
|
|
|
2022-07-25 22:43:46 +10:00
|
|
|
async function getOptions() {
|
|
|
|
return getOptionsOrPrompt('yarn example', {
|
|
|
|
framework: {
|
|
|
|
description: 'Which framework would you like to use?',
|
|
|
|
values: frameworks,
|
2022-07-31 14:27:09 +10:00
|
|
|
required: true as const,
|
2022-07-25 22:43:46 +10:00
|
|
|
},
|
|
|
|
addon: {
|
|
|
|
description: 'Which extra addons (beyond the CLI defaults) would you like installed?',
|
|
|
|
values: addons,
|
2022-07-31 14:27:09 +10:00
|
|
|
multiple: true as const,
|
2022-07-25 22:43:46 +10:00
|
|
|
},
|
|
|
|
includeStories: {
|
2022-07-28 20:15:57 +10:00
|
|
|
description: "Include Storybook's own stories?",
|
|
|
|
promptType: (_, { framework }) => framework === 'react',
|
2022-07-25 22:43:46 +10:00
|
|
|
},
|
|
|
|
create: {
|
|
|
|
description: 'Create the example from scratch (rather than degitting it)?',
|
|
|
|
},
|
2022-07-27 22:09:36 +10:00
|
|
|
forceDelete: {
|
|
|
|
description: 'Always delete an existing example, even if it has the same configuration?',
|
2022-07-28 20:15:57 +10:00
|
|
|
promptType: false,
|
2022-07-27 22:09:36 +10:00
|
|
|
},
|
|
|
|
forceReuse: {
|
2022-07-28 19:46:40 +10:00
|
|
|
description: 'Always reuse an existing example, even if it has a different configuration?',
|
2022-07-28 20:15:57 +10:00
|
|
|
promptType: false,
|
2022-07-27 22:09:36 +10:00
|
|
|
},
|
2022-07-28 19:46:40 +10:00
|
|
|
link: {
|
|
|
|
description: 'Link the storybook to the local code?',
|
|
|
|
inverse: true,
|
2022-07-25 22:43:46 +10:00
|
|
|
},
|
|
|
|
start: {
|
2022-07-28 19:46:40 +10:00
|
|
|
description: 'Start the example Storybook?',
|
2022-07-25 22:43:46 +10:00
|
|
|
inverse: true,
|
|
|
|
},
|
|
|
|
build: {
|
2022-07-28 19:46:40 +10:00
|
|
|
description: 'Build the example Storybook?',
|
2022-07-25 22:43:46 +10:00
|
|
|
},
|
|
|
|
watch: {
|
2022-07-28 19:46:40 +10:00
|
|
|
description: 'Start building used packages in watch mode as well as the example Storybook?',
|
2022-07-25 22:43:46 +10:00
|
|
|
},
|
2022-07-26 16:20:49 +10:00
|
|
|
dryRun: {
|
2022-07-28 19:46:40 +10:00
|
|
|
description: "Don't execute commands, just list them (dry run)?",
|
2022-07-26 16:20:49 +10:00
|
|
|
},
|
2022-07-25 22:43:46 +10:00
|
|
|
});
|
|
|
|
}
|
|
|
|
|
2022-07-27 22:09:36 +10:00
|
|
|
const steps = {
|
2022-07-25 22:43:46 +10:00
|
|
|
repro: {
|
|
|
|
command: 'repro',
|
|
|
|
description: 'Bootstrapping example',
|
|
|
|
icon: '👷',
|
|
|
|
hasArgument: true,
|
|
|
|
options: {
|
|
|
|
template: { values: frameworks },
|
|
|
|
e2e: {},
|
|
|
|
},
|
2022-07-25 11:19:26 +10:00
|
|
|
},
|
2022-07-25 22:43:46 +10:00
|
|
|
add: {
|
|
|
|
command: 'add',
|
|
|
|
description: 'Adding addon',
|
|
|
|
icon: '+',
|
|
|
|
hasArgument: true,
|
|
|
|
options: {},
|
2022-07-25 11:19:26 +10:00
|
|
|
},
|
2022-07-28 21:21:58 +10:00
|
|
|
link: {
|
|
|
|
command: 'link',
|
|
|
|
description: 'Linking packages',
|
|
|
|
icon: '🔗',
|
|
|
|
hasArgument: true,
|
2022-07-31 14:27:09 +10:00
|
|
|
options: { local: {}, start: { inverse: true } },
|
2022-07-28 21:21:58 +10:00
|
|
|
},
|
2022-07-25 11:19:26 +10:00
|
|
|
build: {
|
2022-07-25 22:43:46 +10:00
|
|
|
command: 'build',
|
|
|
|
description: 'Building example',
|
|
|
|
icon: '🔨',
|
|
|
|
options: {},
|
2022-07-25 11:19:26 +10:00
|
|
|
},
|
2022-07-25 22:43:46 +10:00
|
|
|
dev: {
|
|
|
|
command: 'dev',
|
|
|
|
description: 'Starting example',
|
2022-07-26 16:20:49 +10:00
|
|
|
icon: '🖥 ',
|
2022-07-25 22:43:46 +10:00
|
|
|
options: {},
|
2022-07-25 11:19:26 +10:00
|
|
|
},
|
2022-07-25 22:43:46 +10:00
|
|
|
};
|
|
|
|
|
2022-07-31 14:27:09 +10:00
|
|
|
const logger = console;
|
|
|
|
|
|
|
|
const addPackageScripts = async ({
|
|
|
|
cwd,
|
|
|
|
scripts,
|
|
|
|
}: {
|
|
|
|
cwd: string;
|
|
|
|
scripts: Record<string, string>;
|
|
|
|
}) => {
|
|
|
|
logger.info(`🔢 Adding package resolutions:`);
|
|
|
|
const packageJsonPath = path.join(cwd, 'package.json');
|
|
|
|
const packageJson = await readJSON(packageJsonPath);
|
|
|
|
packageJson.scripts = {
|
|
|
|
...packageJson.scripts,
|
|
|
|
...scripts,
|
|
|
|
};
|
|
|
|
await writeJSON(packageJsonPath, packageJson, { spaces: 2 });
|
|
|
|
};
|
|
|
|
|
2022-08-01 16:56:35 +10:00
|
|
|
async function copyStories(possibleFrom: string[], to: string) {
|
2022-07-31 22:26:49 +10:00
|
|
|
await Promise.all(
|
2022-08-01 16:56:35 +10:00
|
|
|
possibleFrom.map(async (from) => {
|
|
|
|
if (!(await pathExists(from))) return;
|
|
|
|
|
|
|
|
await ensureSymlink(from, to);
|
2022-07-31 22:26:49 +10:00
|
|
|
})
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2022-07-25 22:43:46 +10:00
|
|
|
async function main() {
|
|
|
|
const optionValues = await getOptions();
|
|
|
|
|
2022-07-28 21:21:58 +10:00
|
|
|
const { framework, forceDelete, forceReuse, link, dryRun } = optionValues;
|
2022-07-31 22:26:49 +10:00
|
|
|
const cwd = path.join(examplesDir, framework);
|
2022-07-25 22:43:46 +10:00
|
|
|
|
2022-07-27 22:09:36 +10:00
|
|
|
const exists = await pathExists(cwd);
|
2022-07-28 19:46:40 +10:00
|
|
|
let shouldDelete = exists && !forceReuse;
|
2022-07-27 22:09:36 +10:00
|
|
|
if (exists && !forceDelete && !forceReuse) {
|
2022-07-28 19:46:40 +10:00
|
|
|
const relativePath = path.relative(process.cwd(), cwd);
|
|
|
|
({ shouldDelete } = await prompts({
|
2022-07-27 22:09:36 +10:00
|
|
|
type: 'toggle',
|
2022-07-28 19:46:40 +10:00
|
|
|
message: `${relativePath} already exists, should delete it and create a new one?`,
|
|
|
|
name: 'shouldDelete',
|
|
|
|
initial: false,
|
2022-07-27 22:09:36 +10:00
|
|
|
active: 'yes',
|
|
|
|
inactive: 'no',
|
|
|
|
}));
|
|
|
|
}
|
|
|
|
|
2022-07-28 19:46:40 +10:00
|
|
|
if (exists && shouldDelete && !dryRun) await remove(cwd);
|
2022-07-27 22:09:36 +10:00
|
|
|
|
2022-07-28 19:46:40 +10:00
|
|
|
if (!exists || shouldDelete) {
|
2022-07-25 22:43:46 +10:00
|
|
|
await executeCLIStep(steps.repro, {
|
|
|
|
argument: cwd,
|
|
|
|
optionValues: { template: framework },
|
|
|
|
cwd: examplesDir,
|
2022-07-26 16:20:49 +10:00
|
|
|
dryRun,
|
2022-07-25 22:43:46 +10:00
|
|
|
});
|
|
|
|
|
2022-07-31 22:26:49 +10:00
|
|
|
// TODO -- this seems like it might be framework specific. Should we search for a folder?
|
|
|
|
// or set it in the config somewhere?
|
|
|
|
const storiesDir = path.resolve(cwd, './stories');
|
|
|
|
|
|
|
|
// TODO -- can we get the options type to return something more specific
|
|
|
|
const renderer = renderersMap[framework as 'react' | 'angular'];
|
|
|
|
const isTS = isTSMap[framework as 'react' | 'angular'];
|
2022-08-01 16:56:35 +10:00
|
|
|
const possiblePackageStoriesDir = isTS ? ['src/stories'] : ['dist/stories', 'dist/esm/stories'];
|
|
|
|
|
2022-07-31 22:26:49 +10:00
|
|
|
// Copy over renderer stories
|
2022-08-01 16:56:35 +10:00
|
|
|
const rendererDir = path.join(codeDir, 'renderers', renderer);
|
|
|
|
await copyStories(
|
|
|
|
possiblePackageStoriesDir.map((dir) => path.join(rendererDir, dir)),
|
|
|
|
path.join(storiesDir, renderer)
|
|
|
|
);
|
2022-07-31 22:26:49 +10:00
|
|
|
|
2022-07-25 22:43:46 +10:00
|
|
|
// TODO -- sb add <addon> doesn't actually work properly:
|
|
|
|
// - installs in `deps` not `devDeps`
|
|
|
|
// - does a `workspace:^` install (what does that mean?)
|
|
|
|
// - doesn't add to `main.js`
|
|
|
|
|
2022-07-31 22:26:49 +10:00
|
|
|
for (const addon of optionValues.addon) {
|
2022-07-25 22:43:46 +10:00
|
|
|
const addonName = `@storybook/addon-${addon}`;
|
2022-07-26 16:20:49 +10:00
|
|
|
await executeCLIStep(steps.add, { argument: addonName, cwd, dryRun });
|
2022-07-25 22:43:46 +10:00
|
|
|
}
|
|
|
|
|
2022-07-31 22:26:49 +10:00
|
|
|
for (const addon of [...defaultAddons, ...optionValues.addon]) {
|
2022-08-01 16:56:35 +10:00
|
|
|
const addonStoriesDir = path.join(codeDir, 'addons', addon);
|
|
|
|
|
|
|
|
await copyStories(
|
|
|
|
possiblePackageStoriesDir.map((dir) => path.join(addonStoriesDir, dir)),
|
|
|
|
path.join(storiesDir, `addon-${addon}`)
|
|
|
|
);
|
2022-07-31 22:26:49 +10:00
|
|
|
}
|
2022-07-28 21:21:58 +10:00
|
|
|
|
|
|
|
if (link) {
|
|
|
|
await executeCLIStep(steps.link, {
|
|
|
|
argument: cwd,
|
|
|
|
cwd: codeDir,
|
|
|
|
dryRun,
|
2022-07-31 14:27:09 +10:00
|
|
|
optionValues: { local: true, start: false },
|
|
|
|
});
|
|
|
|
|
|
|
|
await addPackageScripts({
|
|
|
|
cwd,
|
|
|
|
scripts: {
|
|
|
|
storybook:
|
|
|
|
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook dev -p 6006',
|
|
|
|
'build-storybook':
|
|
|
|
'NODE_OPTIONS="--preserve-symlinks --preserve-symlinks-main" storybook build',
|
|
|
|
},
|
2022-07-28 21:21:58 +10:00
|
|
|
});
|
|
|
|
}
|
2022-07-25 22:43:46 +10:00
|
|
|
}
|
|
|
|
|
|
|
|
const { start } = optionValues;
|
|
|
|
if (start) {
|
2022-07-28 20:30:40 +10:00
|
|
|
await exec(
|
|
|
|
'yarn storybook',
|
|
|
|
{ cwd },
|
|
|
|
{
|
|
|
|
dryRun,
|
|
|
|
startMessage: `⬆️ Starting Storybook`,
|
|
|
|
errorMessage: `🚨 Starting Storybook failed`,
|
|
|
|
}
|
|
|
|
);
|
2022-07-25 22:43:46 +10:00
|
|
|
} else {
|
2022-07-26 16:20:49 +10:00
|
|
|
await executeCLIStep(steps.build, { cwd, dryRun });
|
2022-07-25 22:43:46 +10:00
|
|
|
// TODO serve
|
|
|
|
}
|
|
|
|
|
|
|
|
// TODO start dev
|
|
|
|
}
|
|
|
|
|
|
|
|
main().catch((err) => console.error(err));
|