diff --git a/.storybook/main.ts b/.storybook/main.ts
index bdd3d86..527433d 100644
--- a/.storybook/main.ts
+++ b/.storybook/main.ts
@@ -5,8 +5,9 @@ const config: StorybookConfig = {
// Seems redundant, but this forces Storybook to
// default to the jump start index page
"../src/stories/*.mdx",
- "../src/stories/**/*.mdx"
+ "../src/stories/**/*.mdx",
],
+ staticDirs: ["../src/stories/assets"],
addons: [
"@storybook/addon-onboarding",
"@storybook/addon-links",
diff --git a/README.md b/README.md
index 6d9db34..c5c9121 100644
--- a/README.md
+++ b/README.md
@@ -8,8 +8,18 @@ If you want to use jump-start, you probably want to navigate to
[jump-start-template](https://github.com/kevinschaul/jump-start-template)
instead!
+## Installation
+
This repo contains the `jump-start` command, used to convert a jump-start repo
-into a Storybook website. To view the website locally, navigate to your
+into a Storybook website.
+
+```
+npm install
+pipx install shot-scraper
+shot-scraper install
+```
+
+To view the website locally, navigate to your
jump-start repo (or [mine, for example](https://github.com/kevinschaul/jump-start)), run
`npm install` and then `npm run dev`.
diff --git a/bin/buildStorybook.ts b/bin/buildStorybook.ts
index 306a545..f77c039 100644
--- a/bin/buildStorybook.ts
+++ b/bin/buildStorybook.ts
@@ -1,12 +1,41 @@
import { join } from "node:path";
+import { spawn } from "node:child_process";
import { copyJumpStartTools, spawnWithIO, symlinkStarters } from "./util";
import updateStories from "../src/util/updateStories";
+import updateScreenshots from "../src/util/updateScreenshots";
const root = join(import.meta.dirname, "../");
type BuildStorybookOpts = {
startersDir: string;
+ updateImages: string;
};
+async function startStorybookUpdateScreenshots(
+ toolsRoot: string,
+ startersDir: string,
+ storiesDir: string,
+) {
+ return new Promise((resolve, reject) => {
+ // Start the storybook server
+ console.log("Starting dev server to update images");
+ const child = spawn("storybook", ["dev", "--ci", "-p", "6006"], {
+ cwd: toolsRoot,
+ });
+
+ // When the dev server logs that storybook has started, call
+ // updateScreenshots
+ child.stdout?.on("data", (data) => {
+ if (/Storybook .* started/.test(data.toString())) {
+ console.log("Updating images");
+ updateScreenshots(startersDir, storiesDir);
+ child.kill();
+ console.log("Images updated");
+ resolve(true);
+ }
+ });
+ });
+}
+
const buildStorybook = async (opts: BuildStorybookOpts) => {
console.log(`Using startersDir: ${opts.startersDir}`);
@@ -14,14 +43,24 @@ const buildStorybook = async (opts: BuildStorybookOpts) => {
symlinkStarters(toolsRoot, opts.startersDir);
// Rewrite stories now
- console.log('Updating stories')
+ console.log("Updating stories");
const storiesDir = join(toolsRoot, "./src/stories");
updateStories(opts.startersDir, storiesDir);
- console.log('Update stories complete.')
+ console.log("Update stories complete.");
+
+ if (opts.updateImages) {
+ await startStorybookUpdateScreenshots(
+ toolsRoot,
+ opts.startersDir,
+ storiesDir,
+ );
+ }
// Build the site
- const outDir = join(opts.startersDir, "dist")
+ const outDir = join(opts.startersDir, "dist");
console.log(`Building site to ${outDir}`);
- spawnWithIO("storybook", ["build", "--output-dir", outDir], { cwd: toolsRoot });
+ spawnWithIO("storybook", ["build", "--output-dir", outDir], {
+ cwd: toolsRoot,
+ });
};
export default buildStorybook;
diff --git a/bin/cli.ts b/bin/cli.ts
index 8db0ce5..350329b 100755
--- a/bin/cli.ts
+++ b/bin/cli.ts
@@ -22,7 +22,9 @@ Arguments passed following "--" are passed along to storybook, e.g.:
"Directory where starters are. Defaults to cwd.",
process.cwd(),
)
- .option("--no-watch", "Don't watch for file changes", process.cwd())
+ .option("--no-watch", "Don't watch for file changes")
+ .option("--update-images", "Update the preview images", false)
+ .option("--no-update-images", "Don't update the preview images", true)
.action(storybook);
program
@@ -33,6 +35,8 @@ program
"Directory where starters are. Defaults to cwd.",
process.cwd(),
)
+ .option("--update-images", "Update the preview images", true)
+ .option("--no-update-images", "Don't update the preview images", false)
.action(buildStorybook);
program
@@ -45,14 +49,4 @@ program
)
.action(updateReadme);
-// TODO
-// program
-// .command("update-screenshots")
-// .option(
-// "--starters-dir
",
-// "Directory where starters are. Defaults to cwd.",
-// process.cwd(),
-// )
-// .action(updateScreenshots);
-
program.parse();
diff --git a/bin/storybook.ts b/bin/storybook.ts
index bdea358..6d67443 100644
--- a/bin/storybook.ts
+++ b/bin/storybook.ts
@@ -3,11 +3,13 @@ import { watch } from "chokidar";
import { copyJumpStartTools, spawnWithIO, symlinkStarters } from "./util";
import updateStories from "../src/util/updateStories";
import { Command } from "commander";
+import updateScreenshots from "../src/util/updateScreenshots";
const root = join(import.meta.dirname, "../");
type StorybookOpts = {
startersDir: string;
noWatch: boolean;
+ updateImages: boolean;
};
const storybook = async (opts: StorybookOpts, command: Command) => {
@@ -17,10 +19,10 @@ const storybook = async (opts: StorybookOpts, command: Command) => {
symlinkStarters(toolsRoot, opts.startersDir);
// Rewrite stories now and any time a change is made to the starters
- console.log('Updating stories')
+ console.log("Updating stories");
const storiesDir = join(toolsRoot, "./src/stories");
updateStories(opts.startersDir, storiesDir);
- console.log('Update stories complete.')
+ console.log("Update stories complete.");
if (!opts.noWatch) {
const watcher = watch(opts.startersDir, {
@@ -35,6 +37,20 @@ const storybook = async (opts: StorybookOpts, command: Command) => {
// Start the storybook server, including any additional commands passed
// through
- spawnWithIO("storybook", ["dev", "--ci", "-p", "6006", ...command.args], { cwd: toolsRoot });
+ const child = spawnWithIO(
+ "storybook",
+ ["dev", "--ci", "-p", "6006", ...command.args],
+ { cwd: toolsRoot },
+ );
+
+ if (opts.updateImages) {
+ // When the dev server logs that storybook has started, call
+ // updateScreenshots
+ child.stdout?.on("data", (data) => {
+ if (/Storybook .* started/.test(data.toString())) {
+ updateScreenshots(opts.startersDir, storiesDir);
+ }
+ });
+ }
};
export default storybook;
diff --git a/bin/util.ts b/bin/util.ts
index 8f28040..e8327d8 100644
--- a/bin/util.ts
+++ b/bin/util.ts
@@ -1,11 +1,15 @@
import { join } from "node:path";
-import { spawn } from "node:child_process";
+import { SpawnOptions, spawn } from "node:child_process";
import { cpSync, rmSync, symlinkSync, unlinkSync } from "node:fs";
+export function getToolsRoot(startersDir: string) {
+ return join(startersDir, "./.build/jump-start-tools");
+}
+
export function copyJumpStartTools(root: string, startersDir: string) {
// Copy jump-start-tools out of node_modules to avoid compilation errors with
// storybook
- const toolsRoot = join(startersDir, "./.build/jump-start-tools");
+ const toolsRoot = getToolsRoot(startersDir);
console.log(`Copying ${root} to ${toolsRoot}`);
rmSync(toolsRoot, { recursive: true, force: true });
cpSync(root, toolsRoot, { recursive: true });
@@ -22,19 +26,21 @@ export function symlinkStarters(root: string, startersDir: string) {
symlinkSync(startersDir, symlinkPath);
}
-export function spawnWithIO(command: string, args: string[], options) {
+export function spawnWithIO(command: string, args: string[], options: SpawnOptions) {
console.log("Starting server");
const child = spawn(command, args, options);
- child.stdout.on("data", (data) => {
+ child.stdout?.on("data", (data) => {
process.stdout.write(data);
});
- child.stderr.on("data", (data) => {
+ child.stderr?.on("data", (data) => {
process.stderr.write(data);
});
child.on("close", (code) => {
console.log(`child process exited with code ${code}`);
});
+
+ return child
}
diff --git a/package.json b/package.json
index ba6e01d..1d48bb4 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,7 @@
"jump-start": "bin/cli.ts"
},
"scripts": {
- "dev": "./bin/cli.ts storybook --starters-dir ../jump-start",
+ "dev": "./bin/cli.ts storybook --starters-dir ../jump-start --update-images",
"build": "./bin/cli.ts build-storybook --starters-dir ../jump-start",
"test": "vitest"
},
diff --git a/src/util/updateScreenshots.ts b/src/util/updateScreenshots.ts
index 1b13241..04368a9 100644
--- a/src/util/updateScreenshots.ts
+++ b/src/util/updateScreenshots.ts
@@ -6,45 +6,39 @@ import "dotenv/config";
import { parseStarters } from "./parseStarters";
import { execSync } from "child_process";
-if (path.basename(import.meta.url) === "updateScreenshots.ts") {
- let startersPath = process.env["JUMP_START_STARTERS"];
-
- if (!startersPath) {
- console.log(`No env variable found: JUMP_START_STARTER`);
- console.log(`Using JUMP_START_STARTER=./ by default`);
- startersPath = "./";
- }
-
- const groups = parseStarters(startersPath);
+export default function updateScreenshots(
+ startersDir: string,
+ storiesDir: string,
+) {
+ const groups = parseStarters(startersDir);
for (const group in groups) {
for (const starter of groups[group]) {
if (starter.preview) {
- const outDir = path.join(
- startersPath,
- "jump-start-gallery",
- "public",
- "screenshots",
- starter.group,
- );
+ const outDir = path.join(storiesDir, "assets", starter.group);
const outFile = path.join(outDir, `${starter.title}.png`);
try {
mkdirSync(outDir);
} catch (e) {}
- console.log(`Taking screenshot of ${starter.group}/${starter.title}`);
+ // Note: This assumes that the storybook dev server is running, and specifically at port 6006
+ const url = `http://localhost:6006/iframe.html?viewMode=docs&id=${starter.group.toLowerCase()}-${starter.title.toLowerCase()}--docs`;
+
+ console.log(`Taking screenshot of ${starter.group}/${starter.title} at ${url}`);
execSync(
`
shot-scraper \
- http://localhost:3000/${starter.group}/${starter.title} \
+ "${url}" \
--selector '.starter-preview iframe' \
--wait-for 'document.querySelector(".starter-preview[data-has-rendered=true]")' \
--javascript 'document.head.appendChild(document.createElement("style")).innerHTML = "\
- .sp-preview-actions { display: none; } \
+ .starter-preview iframe { flex-grow: 0; } \
+ .sp-preview-actions { display: none !important; } \
";' \
+ --wait 1000 \
--width 400 \
--output ${outFile}
`,
- { timeout: 30000 },
+ { timeout: 10000 },
);
}
}
diff --git a/src/util/updateStories.ts b/src/util/updateStories.ts
index 474a834..757b5bc 100644
--- a/src/util/updateStories.ts
+++ b/src/util/updateStories.ts
@@ -12,18 +12,24 @@ export default function updateStories(startersDir: string, storiesDir: string) {
try {
fs.mkdirSync(storiesDir, { recursive: true });
+ fs.mkdirSync(path.join(storiesDir, "assets"), { recursive: true });
} catch (e) {
null;
}
- const readme = fs.readFileSync(
- path.join(startersDir, "README.md"),
- "utf-8",
- );
+ const startersWithPreviews = Object.values(groups)
+ .flat()
+ .filter((d) => d.preview);
+
+ const startersWithPreviewsMd = startersWithPreviews.map((starter) => {
+ return `![Preview of ${starter.group}/${starter.title}](${starter.group}/${starter.title}.png)`;
+ });
+
+ const readme = fs.readFileSync(path.join(startersDir, "README.md"), "utf-8");
const readmeWithoutStarters = rewriteReadmeSection(
readme,
"## Starters",
- "View available starters on the left",
+ `View available starters on the left\n\n${startersWithPreviewsMd}`,
);
// Write out an overview story