Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add command to publish repros + GH action #18800

Merged
merged 12 commits into from
Aug 1, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -73,7 +73,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}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can probably reuse this from above

`);
}
};

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