Skip to content

Commit

Permalink
Merge pull request #18800 from storybookjs/yann/sb-509-create-github-…
Browse files Browse the repository at this point in the history
…action

Add command to publish repros + GH action
  • Loading branch information
yannbf authored Aug 1, 2022
2 parents 158d3ce + ce84ddb commit 8d86f10
Show file tree
Hide file tree
Showing 16 changed files with 384 additions and 34 deletions.
33 changes: 33 additions & 0 deletions .github/workflows/generate-repros-next.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
name: Generate and push repros to the next branch

on:
schedule:
- cron: '2 2 */1 * *'
workflow_dispatch:
# To remove when the branch will be merged
push:
branches:
- yann/sb-509-create-github-action

jobs:
generate:
runs-on: ubuntu-latest
env:
YARN_ENABLE_IMMUTABLE_INSTALLS: false
steps:
- uses: actions/checkout@v2
- name: Setup git user
run: |
git config --global user.name "Storybook Bot"
git config --global user.email "[email protected]"
- name: Install dependencies
run: node ./scripts/check-dependencies.js
- name: Bootstrap Storybook libraries
run: yarn bootstrap --prep
working-directory: ./code
- name: Generate repros
run: yarn next-repro
working-directory: ./code
- name: Publish repros to GitHub
run: yarn publish-repros --remote=https://storybook-bot:${{ secrets.PAT_STORYBOOK_BOT}}@github.com/storybookjs/repro-templates-temp.git --push
working-directory: ./code
7 changes: 6 additions & 1 deletion .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,3 +1,8 @@
{
"deepscan.enable": true
"deepscan.enable": true,
"workbench.colorCustomizations": {
"activityBar.background": "#263108",
"titleBar.activeBackground": "#35450C",
"titleBar.activeForeground": "#F8FCED"
}
}
3 changes: 2 additions & 1 deletion code/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,8 @@
"lint:other": "prettier --write '**/*.{css,html,json,md,yml}'",
"lint:package": "sort-package-json",
"local-registry": "ts-node --project=../scripts/tsconfig.json ../scripts/run-registry.ts --port 6000",
"next-repro": "ts-node ../scripts/next-repro-generators/index.ts",
"next-repro": "ts-node ../scripts/next-repro-generators/generate.ts",
"publish-repros": "ts-node ../scripts/next-repro-generators/publish.ts",
"publish:debug": "npm run publish:latest -- --npm-tag=debug --no-push",
"publish:latest": "lerna publish --exact --concurrency 1 --force-publish",
"publish:next": "npm run publish:latest -- --npm-tag=next",
Expand Down
2 changes: 1 addition & 1 deletion generate-repros.sh
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
#!/usr/bin/env bash

./scripts/node_modules/.bin/ts-node ./scripts/next-repro-generators/index.ts
./scripts/node_modules/.bin/ts-node ./scripts/next-repro-generators/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,25 +6,13 @@ import type { Options as ExecaOptions } from 'execa';
import yaml from 'js-yaml';
import pLimit from 'p-limit';
import prettyTime from 'pretty-hrtime';
import { copy, emptyDir, ensureDir, readFile, rename } from 'fs-extra';
import { copy, emptyDir, ensureDir, readFile, rename, writeFile } from 'fs-extra';
// @ts-ignore
import { maxConcurrentTasks } from '../utils/concurrency';

import { localizeYarnConfigFiles, setupYarn } from './utils/yarn';

type GeneratorConfig = {
name: string;
script: string;
// expected?: {
// framework?: string;
// renderer?: string;
// builder?: string;
// };
};

type DataEntry = {
script: string;
};
import { GeneratorConfig } from './utils/types';
import { getStackblitzUrl, renderTemplate } from './utils/template';

const OUTPUT_DIRECTORY = join(__dirname, '..', '..', 'repros');
const BEFORE_DIR_NAME = 'before-storybook';
Expand All @@ -33,7 +21,7 @@ const AFTER_DIR_NAME = 'after-storybook';
const addStorybook = async (baseDir: string) => {
const beforeDir = join(baseDir, BEFORE_DIR_NAME);
const afterDir = join(baseDir, AFTER_DIR_NAME);
const tmpDir = join(baseDir, '.tmp');
const tmpDir = join(baseDir, 'tmp');

await ensureDir(tmpDir);
await emptyDir(tmpDir);
Expand Down Expand Up @@ -61,18 +49,36 @@ export const runCommand = async (script: string, options: ExecaOptions) => {
return command(script, { stdout: shouldDebug ? 'inherit' : 'ignore', ...options });
};

const runGenerators = async (generators: GeneratorConfig[]) => {
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 })[]) => {
console.log(`🤹‍♂️ Generating repros with a concurrency of ${maxConcurrentTasks}`);

const limit = pLimit(maxConcurrentTasks);

return Promise.all(
generators.map(({ name, script }) =>
generators.map(({ dirName, name, script }) =>
limit(async () => {
const time = process.hrtime();
console.log(`🧬 generating ${name}`);

const baseDir = join(OUTPUT_DIRECTORY, name);
const baseDir = join(OUTPUT_DIRECTORY, dirName);
const beforeDir = join(baseDir, BEFORE_DIR_NAME);

await emptyDir(baseDir);
Expand All @@ -86,10 +92,13 @@ const runGenerators = async (generators: GeneratorConfig[]) => {

await addStorybook(baseDir);

await addDocumentation(baseDir, { name, dirName });

console.log(
`✅ Created ${name} in ./${relative(process.cwd(), baseDir)} successfully in ${prettyTime(
process.hrtime(time)
)}`
`✅ Created ${dirName} in ./${relative(
process.cwd(),
baseDir
)} successfully in ${prettyTime(process.hrtime(time))}`
);
})
)
Expand All @@ -98,12 +107,12 @@ const runGenerators = async (generators: GeneratorConfig[]) => {

const generate = async ({ config }: { config: string }) => {
const configContents = await readFile(config, 'utf8');
const data: Record<string, DataEntry> = yaml.load(configContents);
const data: Record<string, GeneratorConfig> = yaml.load(configContents);

runGenerators(
Object.entries(data).map(([name, configuration]) => ({
name,
script: configuration.script,
Object.entries(data).map(([dirName, configuration]) => ({
dirName,
...configuration,
}))
);
};
Expand Down
96 changes: 96 additions & 0 deletions scripts/next-repro-generators/publish.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import program from 'commander';
import { join } from 'path';
import { existsSync } from 'fs';
import { command } from 'execa';
import * as tempy from 'tempy';
import { copy, remove, writeFile } from 'fs-extra';

import { getTemplatesData, renderTemplate } from './utils/template';
import { commitAllToGit } from './utils/git';

export const logger = console;

const REPROS_DIRECTORY = join(__dirname, '..', '..', 'repros');

interface PublishOptions {
remote?: string;
push?: boolean;
next?: boolean;
}

const publish = async (options: PublishOptions & { tmpFolder: string }) => {
const { next: useNextVersion, remote, push, tmpFolder } = options;

const scriptPath = __dirname;
const gitBranch = useNextVersion ? 'next' : 'main';

const templatesData = await getTemplatesData(join(scriptPath, 'repro-config.yml'));

logger.log(`👯‍♂️ Cloning the repository ${remote} in branch ${gitBranch}`);
await command(`git clone ${remote} .`, { cwd: tmpFolder });
await command(`git checkout ${gitBranch}`, { cwd: tmpFolder });

logger.log(`🚚 Moving template files into the repository`);

const templatePath = join(scriptPath, 'templates', 'root.ejs');
const templateData = { data: templatesData, version: gitBranch };

const output = await renderTemplate(templatePath, templateData);

await writeFile(join(tmpFolder, 'README.md'), output);

logger.log(`🚛 Moving all the repros into the repository`);
await copy(join(REPROS_DIRECTORY), tmpFolder);

await commitAllToGit(tmpFolder);

logger.info(`
🙌 All the examples were bootstrapped:
- in ${tmpFolder}
- using the '${gitBranch}' version of Storybook CLI
- and committed on the '${gitBranch}' branch of a local Git repository
Also all the files in the 'templates' folder were copied at the root of the Git repository.
`);

if (push) {
await command(`git push --set-upstream origin ${gitBranch}`, {
cwd: tmpFolder,
});
const remoteRepoUrl = `${remote.replace('.git', '')}/tree/${gitBranch}`;
logger.info(`🚀 Everything was pushed on ${remoteRepoUrl}`);
} else {
logger.info(`
To publish these examples you just need to:
- push the branch: 'git push --set-upstream origin ${gitBranch}
`);
}
};

program
.description('Create a reproduction from a set of possible templates')
.option('--remote <remote>', 'Choose the remote to push the contents to')
.option('--next', 'Whether to use the next version of Storybook CLI', true)
.option('--push', 'Whether to push the contents to the remote', false)
.option('--force-push', 'Whether to force push the changes into the repros repository', false);

program.parse(process.argv);

if (!existsSync(REPROS_DIRECTORY)) {
throw Error("Can't find repros directory. Did you forget to run generate-repros?");
}

const tmpFolder = tempy.directory();
logger.log(`⏱ Created tmp folder: ${tmpFolder}`);

const options = program.opts() as PublishOptions;

publish({ ...options, tmpFolder }).catch(async (e) => {
logger.error(e);

if (existsSync(tmpFolder)) {
logger.log('🚮 Removing the temporary folder..');
await remove(tmpFolder);
}
process.exit(1);
});
6 changes: 4 additions & 2 deletions scripts/next-repro-generators/repro-config.yml
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
# group-name/instance-name. Should always be two levels deep.
cra/default-js:
script: "yarn create react-app ."
name: "Create React App (Javascript)"
script: "npx create-react-app ."
expected:
framework: "@storybook/cra"
renderer: "@storybook/react"
builder: "@storybook/builder-webpack5"

cra/default-ts:
script: "yarn create react-app . --template typescript"
name: "Create React App (Typescript)"
script: "npx create-react-app . --template typescript"
expected:
framework: "@storybook/cra"
renderer: "@storybook/react"
Expand Down
4 changes: 4 additions & 0 deletions scripts/next-repro-generators/templates/.stackblitzrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"installDependencies": true,
"startCommand": "yarn storybook"
}
18 changes: 18 additions & 0 deletions scripts/next-repro-generators/templates/item.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<h1><%= name %></h1>

<p>This is project generated to serve as a reproduction starter for Storybook.</p>

<a href="<%= stackblitzUrl %>">View it in Stackblitz</a>

<h3>Testing instructions</h3>

<p>Install dependencies:</p>
<pre>
yarn
</pre>

<p>Run Storybook:</p>
<pre>
yarn storybook
</pre>

27 changes: 27 additions & 0 deletions scripts/next-repro-generators/templates/root.ejs
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<h1>Storybook Reproduction Templates</h1>

<img alt="Storybook <%= version %> Badge" src="https://img.shields.io/npm/v/@storybook/react/<%= version %>" />

<p>The following repros have been generated with the `<%= version %>` version of Storybook.</p>

<% if (typeof data !== 'undefined' && Object.keys(data).length) { %>
<p>Preview any repro live on <a href="http://stackblitz.com/">StackBlitz:</a></p>
<% for (var groupName in data) { %>
<% if (data[groupName] !== undefined) { %>
<details>
<summary><b><%- (groupName) %></b></summary>
<ul>
<% for (var exampleName in data[groupName]) { %>
<% if (data[groupName][exampleName] !== undefined) { %>
<li>
<a href="<%=data[groupName][exampleName]['stackblitzUrl']%>">
<%=data[groupName][exampleName]['name']%>
</a>
</li>
<% } %>
<% } %>
</ul>
</details>
<% } %>
<% } %>
<% } %>
25 changes: 25 additions & 0 deletions scripts/next-repro-generators/utils/git.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { command } from 'execa';
import { logger } from '../publish';

export async function commitAllToGit(cwd: string) {
try {
logger.log(`💪 Committing everything to the repository`);

await command('git add .', { cwd });

const currentCommitSHA = await command('git rev-parse HEAD');
await command(
`git commit -m "Update examples - ${new Date().toDateString()} - ${currentCommitSHA.stdout
.toString()
.slice(0, 12)}"`,
{
shell: true,
cwd,
}
);
} catch (e) {
logger.log(
`🤷 Git found no changes between previous versions so there is nothing to commit. Skipping publish!`
);
}
}
Loading

0 comments on commit 8d86f10

Please sign in to comment.